Skip to content

Commit

Permalink
feat: Update Nav component and use everywhere (#203)
Browse files Browse the repository at this point in the history
  • Loading branch information
evadecker authored Nov 23, 2024
1 parent 8b8755d commit f2a5bc2
Show file tree
Hide file tree
Showing 14 changed files with 1,208 additions and 1,234 deletions.
5 changes: 5 additions & 0 deletions .changeset/large-queens-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"namesake": minor
---

Display statuses in main navigation, improve nav display
2 changes: 0 additions & 2 deletions convex/userQuests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ export const updateQuestStatus = userMutation({
args: { questId: v.id("quests"), status: status },
returns: v.null(),
handler: async (ctx, args) => {
console.log("updateQuestStatus", args);
const quest = await ctx.db.get(args.questId);
if (quest === null) throw new Error("Quest not found");

Expand Down Expand Up @@ -371,7 +370,6 @@ export const getUserQuestsByTimeRequired = userQuery({
return acc;
}, initial);

console.log("group", group);
return group;
},
});
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
"postcss": "^8.4.49",
"pretty-bytes": "^6.1.1",
"react": "^18.3.1",
"react-aria": "^3.35.1",
"react-aria-components": "^1.4.1",
"react-aria": "^3.36.0",
"react-aria-components": "^1.5.0",
"react-dom": "^18.3.1",
"react-helmet-async": "^2.0.5",
"react-markdown": "^9.0.1",
Expand Down
2,082 changes: 1,027 additions & 1,055 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/components/AppSidebar/AppSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const AppSidebar = ({ children }: AppSidebarProps) => {
};

return (
<nav className="w-80 flex flex-col shrink-0 sticky top-0 h-screen -ml-4 overflow-y-auto border-r border-gray-dim">
<div className="w-72 flex flex-col shrink-0 sticky top-0 h-screen -ml-4 overflow-y-auto border-r border-gray-dim">
<div className="flex gap-2 items-center h-16 shrink-0 px-4 sticky top-0 bg-gray-app z-20">
<Link href={{ to: "/" }} className="p-1 -m-1">
<Logo className="h-[1.25rem]" />
Expand Down Expand Up @@ -98,6 +98,6 @@ export const AppSidebar = ({ children }: AppSidebarProps) => {
</TooltipTrigger>
</div>
</div>
</nav>
</div>
);
};
11 changes: 6 additions & 5 deletions src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import { tv } from "tailwind-variants";

export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
size?: "sm" | "lg";
size?: "xs" | "sm" | "lg";
variant?: "info" | "success" | "danger" | "warning" | "waiting";
icon?: RemixiconComponentType;
}

const badge = tv({
base: "px-1.5 py-0.5 font-medium text-center inline-flex gap-1 items-center shrink-0 bg-graya-3 dark:bg-graydarka-3 text-gray-dim",
base: "px-1.5 font-medium text-center inline-flex justify-center gap-1 items-center shrink-0 bg-graya-3 dark:bg-graydarka-3 text-gray-dim",
variants: {
size: {
sm: "text-xs rounded",
lg: "text-sm rounded-md px-2 gap-1.5",
xs: "text-[10px] rounded h-4 px-1 min-w-4 leading-none",
sm: "text-xs rounded h-5 min-w-5",
lg: "text-sm rounded-md px-1.5 h-6 min-w-6 gap-1.5",
},
variant: {
info: "bg-bluea-3 dark:bg-bluedarka-3 text-blue-normal",
Expand All @@ -30,7 +31,7 @@ const badge = tv({
});

const icon = tv({
base: "w-4 h-4",
base: "w-4 h-4 shrink-0",
variants: {
variant: {
info: "text-blue-dim",
Expand Down
74 changes: 33 additions & 41 deletions src/components/Disclosure/Disclosure.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,53 @@
import { RiArrowRightSLine } from "@remixicon/react";
import {
UNSTABLE_Disclosure as AriaDisclosure,
UNSTABLE_DisclosureGroup as AriaDisclosureGroup,
Disclosure as AriaDisclosure,
DisclosureGroup as AriaDisclosureGroup,
type DisclosureGroupProps as AriaDisclosureGroupProps,
UNSTABLE_DisclosurePanel as AriaDisclosurePanel,
type DisclosurePanelProps as AriaDisclosurePanelProps,
DisclosurePanel as AriaDisclosurePanel,
type DisclosureProps as AriaDisclosureProps,
composeRenderProps,
Header,
type Key,
} from "react-aria-components";
import { twMerge } from "tailwind-merge";
import { AnimateChangeInHeight } from "../AnimateChangeInHeight";
import { Button } from "../Button";

export interface DisclosureProps extends AriaDisclosureProps {
export interface DisclosureProps
extends Omit<AriaDisclosureProps, "children" | "id"> {
id: Key;
title: React.ReactNode;
children?: React.ReactNode;
}

export function Disclosure({
title,
children,
className,
...props
}: DisclosureProps) {
export function Disclosure({ title, children, ...props }: DisclosureProps) {
return (
<AriaDisclosure {...props}>
{composeRenderProps(children, (children, { isExpanded }) => (
<>
<Button
variant="ghost"
className="w-full justify-between"
slot="trigger"
size="small"
>
{title}
<RiArrowRightSLine
size={16}
className={twMerge(
"transition-transform opacity-70",
isExpanded && "rotate-90",
)}
/>
</Button>
<AnimateChangeInHeight className="w-full">
<DisclosurePanel className="pb-2">{children}</DisclosurePanel>
</AnimateChangeInHeight>
</>
))}
<AriaDisclosure className={twMerge("group")} {...props}>
<Header>
<Button
variant="ghost"
className="w-full justify-start"
slot="trigger"
size="small"
>
<RiArrowRightSLine
size={16}
className={twMerge(
"transition-transform opacity-60",
"group-data-[expanded]:rotate-90",
)}
/>
{title}
</Button>
</Header>
<AnimateChangeInHeight className="w-full">
<AriaDisclosurePanel className="group-data-[expanded]:pb-2">
{children}
</AriaDisclosurePanel>
</AnimateChangeInHeight>
</AriaDisclosure>
);
}

interface DisclosurePanelProps extends AriaDisclosurePanelProps {}

export function DisclosurePanel({ children, ...props }: DisclosurePanelProps) {
return <AriaDisclosurePanel {...props}>{children}</AriaDisclosurePanel>;
}

interface DisclosureGroupProps extends AriaDisclosureGroupProps {}

export function DisclosureGroup({ children, ...props }: DisclosureGroupProps) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function Link({ button, ...props }: LinkProps) {
...button,
className,
})
: linkStyles({ ...renderProps, className, variant: props.variant }),
: linkStyles({ ...renderProps, variant: props.variant, className }),
)}
/>
);
Expand Down
97 changes: 71 additions & 26 deletions src/components/Nav/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,88 @@
import type { RemixiconComponentType } from "@remixicon/react";
import { type LinkProps, useMatchRoute } from "@tanstack/react-router";
import { Header } from "react-aria-components";
import { tv } from "tailwind-variants";
import { Badge } from "../Badge";
import { Link } from "../Link";
import { focusRing } from "../utils";

interface NavProps {
routes: {
icon: RemixiconComponentType;
href: LinkProps;
label: string;
}[];
interface NavItemProps {
href: LinkProps;
icon?: RemixiconComponentType;
className?: string;
children?: React.ReactNode;
}

const styles = tv({
const navItemStyles = tv({
extend: focusRing,
base: "rounded-lg no-underline flex items-center gap-2 text-gray-dim hover:text-gray-normal py-1.5 aria-current:font-semibold aria-current:text-gray-normal",
base: "rounded-md no-underline flex items-center text-sm gap-1.5 hover:bg-gray-3 dark:hover:bg-graydark-3 h-8 px-2 -mx-2 aria-current:font-semibold aria-current:text-gray-normal",
variants: {
isActive: {
true: "bg-gray-3 dark:bg-graydark-3",
},
},
});

const iconStyles = tv({
base: "text-gray-dim",
variants: {
isActive: {
true: "text-gray-normal",
},
},
});

export const Nav = ({ routes }: NavProps) => {
export const NavItem = ({
icon: Icon,
href,
className,
children,
}: NavItemProps) => {
const matchRoute = useMatchRoute();
const current = matchRoute({ ...href, fuzzy: true });

return (
<Link
href={{ ...href }}
className={({ isFocusVisible }) =>
navItemStyles({
isFocusVisible,
isActive: !!current,
class: className,
})
}
aria-current={current ? "true" : null}
>
{Icon && (
<Icon size={20} className={iconStyles({ isActive: !!current })} />
)}
{children}
</Link>
);
};

interface NavGroupProps {
label: string;
children: React.ReactNode;
count?: number;
}

export const NavGroup = ({ label, children, count }: NavGroupProps) => {
return (
<div>
{routes.map(({ href, label, icon }) => {
const current = matchRoute({ ...href, fuzzy: true });
const Icon = icon;

return (
<Link
key={href.to}
href={{ ...href }}
className={styles()}
aria-current={current ? "true" : null}
>
<Icon />
{label}
</Link>
);
})}
<div className="flex flex-col gap-0.5 [&:not(:first-child)]:mt-4">
<Header className="text-sm h-8 font-semibold text-gray-dim border-b border-gray-4 dark:border-graydark-4 flex justify-start items-center gap-1.5">
{label}
{count && <Badge size="xs">{count}</Badge>}
</Header>
{children}
</div>
);
};

interface NavProps {
children: React.ReactNode;
}

export const Nav = ({ children }: NavProps) => {
return <nav className="flex flex-col gap-0.5">{children}</nav>;
};
22 changes: 19 additions & 3 deletions src/components/StatusSelect/StatusSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { STATUS, type Status } from "@convex/constants";
import { RiArrowDropDownFill } from "@remixicon/react";
import { useState } from "react";
import type { Selection } from "react-aria-components";
import { tv } from "tailwind-variants";
import {
Badge,
type BadgeProps,
Expand All @@ -13,16 +14,31 @@ import {

interface StatusBadgeProps extends Omit<BadgeProps, "children"> {
status: Status;
condensed?: boolean;
}

export function StatusBadge({ status, ...props }: StatusBadgeProps) {
const badgeStyles = tv({
base: "flex items-center transition-colors",
variants: {
condensed: {
true: "w-5 h-5 p-0",
},
},
});

export function StatusBadge({ status, condensed, ...props }: StatusBadgeProps) {
if (status === undefined) return null;

const { label, icon, variant } = STATUS[status];

return (
<Badge icon={icon} variant={variant} {...props}>
{label}
<Badge
icon={icon}
variant={variant}
{...props}
className={badgeStyles({ condensed })}
>
{!condensed && label}
</Badge>
);
}
Expand Down
Loading

0 comments on commit f2a5bc2

Please sign in to comment.