Skip to content

Commit

Permalink
feat: Improve settings display (#210)
Browse files Browse the repository at this point in the history
  • Loading branch information
evadecker authored Nov 25, 2024
1 parent 887054f commit 1a6b588
Show file tree
Hide file tree
Showing 17 changed files with 354 additions and 182 deletions.
5 changes: 5 additions & 0 deletions .changeset/tasty-ducks-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"namesake": minor
---

Redesign settings pages
59 changes: 8 additions & 51 deletions src/components/AppSidebar/AppSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
import { useTheme } from "@/utils/useTheme";
import { useAuthActions } from "@convex-dev/auth/react";
import { api } from "@convex/_generated/api";
import { THEMES, type Theme } from "@convex/constants";
import { Authenticated, useMutation, useQuery } from "convex/react";
import {
CircleUser,
Cog,
GlobeLock,
LogOut,
MessageCircleQuestion,
Plus,
Snail,
} from "lucide-react";
import { useTheme } from "next-themes";
import { useState } from "react";
import type { Selection } from "react-aria-components";
import { Authenticated, useQuery } from "convex/react";
import { CircleUser, Cog, GlobeLock, LogOut, Plus } from "lucide-react";
import { Badge } from "../Badge";
import { Button } from "../Button";
import { Link } from "../Link";
Expand All @@ -35,26 +25,11 @@ type AppSidebarProps = {

export const AppSidebar = ({ children }: AppSidebarProps) => {
const { signOut } = useAuthActions();
const { theme, setTheme } = useTheme();
const [selectedTheme, setSelectedTheme] = useState<Selection>(
new Set([theme ?? "system"]),
);
const { theme, themeSelection, setTheme } = useTheme();

const user = useQuery(api.users.getCurrentUser);
const isAdmin = user?.role === "admin";

// Theme change
const updateTheme = useMutation(api.users.setUserTheme);

const handleUpdateTheme = (theme: Selection) => {
const newTheme = [...theme][0] as Theme;
// Update the theme in the database
updateTheme({ theme: newTheme });
// Update the theme in next-themes
setTheme(newTheme);
// Update the selected theme in the menu
setSelectedTheme(new Set([newTheme]));
};

const handleSignOut = async () => {
await signOut();
};
Expand Down Expand Up @@ -95,22 +70,13 @@ export const AppSidebar = ({ children }: AppSidebarProps) => {
{user?.name}
</Button>
<Menu placement="top start">
<MenuItem
icon={Snail}
href="https://namesake.fyi"
target="_blank"
rel="noreferrer"
>
About Namesake
</MenuItem>
<MenuSeparator />
{isAdmin && (
<MenuItem icon={GlobeLock} href={{ to: "/admin" }}>
Admin
</MenuItem>
)}
<MenuItem href={{ to: "/settings/account" }} icon={Cog}>
Settings
Settings and Help
</MenuItem>
<SubmenuTrigger>
<MenuItem icon={THEMES[theme as Theme].icon} textValue="Theme">
Expand All @@ -123,8 +89,8 @@ export const AppSidebar = ({ children }: AppSidebarProps) => {
<Menu
disallowEmptySelection
selectionMode="single"
selectedKeys={selectedTheme}
onSelectionChange={handleUpdateTheme}
selectedKeys={themeSelection}
onSelectionChange={setTheme}
>
{Object.entries(THEMES).map(([theme, details]) => (
<MenuItem key={theme} id={theme} icon={details.icon}>
Expand All @@ -135,15 +101,6 @@ export const AppSidebar = ({ children }: AppSidebarProps) => {
</Popover>
</SubmenuTrigger>
<MenuSeparator />
<MenuItem
href="https://namesake.fyi/chat"
target="_blank"
rel="noreferrer"
icon={MessageCircleQuestion}
>
Discord Community
</MenuItem>
<MenuSeparator />
<MenuItem icon={LogOut} onAction={handleSignOut}>
Sign out
</MenuItem>
Expand Down
5 changes: 3 additions & 2 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface ButtonProps extends AriaButtonProps {

export const buttonStyles = tv({
extend: focusRing,
base: "py-2 text-sm font-medium whitespace-nowrap rounded-lg flex gap-1.5 items-center justify-center border border-black/10 dark:border-white/10 cursor-pointer",
base: "py-2 text-sm font-medium whitespace-nowrap rounded-lg flex gap-1.5 items-center justify-center border border-black/10 dark:border-white/10",
variants: {
variant: {
primary: "bg-purple-solid text-white",
Expand All @@ -31,6 +31,7 @@ export const buttonStyles = tv({
medium: "h-10 px-3",
},
isDisabled: {
false: "cursor-pointer",
true: "cursor-default text-gray-dim opacity-50 forced-colors:text-[GrayText]",
},
},
Expand Down Expand Up @@ -72,7 +73,7 @@ export function Button({
}),
)}
>
{Icon && <Icon size={size === "small" ? 16 : 20} />}
{Icon && <Icon size={size === "small" ? 12 : 16} />}
{children}
</AriaButton>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export function Card({ children, className }: CardProps) {
return (
<div
className={twMerge(
className,
"p-6 text-gray-normal bg-gray-subtle border border-gray-dim rounded-xl",
className,
)}
>
{children}
Expand Down
15 changes: 13 additions & 2 deletions src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Check, ChevronRight, type LucideIcon } from "lucide-react";
import {
Check,
ChevronRight,
ExternalLink,
type LucideIcon,
} from "lucide-react";
import {
Menu as AriaMenu,
MenuItem as AriaMenuItem,
Expand Down Expand Up @@ -63,8 +68,14 @@ export function MenuItem({ className, icon: Icon, ...props }: MenuItemProps) {
<span className="flex items-center flex-1 gap-2 font-normal truncate group-selected:font-semibold">
{children}
</span>
{props.target === "_blank" && (
<ExternalLink
aria-hidden
className="size-4 ml-1 text-gray-8 dark:text-graydark-8"
/>
)}
{hasSubmenu && (
<ChevronRight aria-hidden className="w-4 h-4 ml-auto -mr-1" />
<ChevronRight aria-hidden className="size-4 ml-auto -mr-1" />
)}
</>
),
Expand Down
28 changes: 20 additions & 8 deletions src/components/Nav/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { type LinkProps, useMatchRoute } from "@tanstack/react-router";
import type { LucideIcon } from "lucide-react";
import { useMatchRoute } from "@tanstack/react-router";
import { ExternalLink, type LucideIcon } from "lucide-react";
import { Header } from "react-aria-components";
import { tv } from "tailwind-variants";
import { Badge } from "../Badge";
import { Link } from "../Link";
import { Link, type LinkProps } from "../Link";
import { focusRing } from "../utils";

interface NavItemProps {
href: LinkProps;
interface NavItemProps extends LinkProps {
icon?: LucideIcon;
className?: string;
children?: React.ReactNode;
Expand All @@ -34,16 +33,23 @@ const iconStyles = tv({

export const NavItem = ({
icon: Icon,
href,
className,
children,
...props
}: NavItemProps) => {
const matchRoute = useMatchRoute();
const current = matchRoute({ ...href, fuzzy: true });
let current: boolean | undefined;

if (typeof props.href === "string") {
// Link is external, so we can't match it
current = false;
} else {
current = Boolean(matchRoute({ ...props.href, fuzzy: true }));
}

return (
<Link
href={{ ...href }}
{...props}
className={({ isFocusVisible }) =>
navItemStyles({
isFocusVisible,
Expand All @@ -57,6 +63,12 @@ export const NavItem = ({
<Icon size={20} className={iconStyles({ isActive: !!current })} />
)}
{children}
{props.target === "_blank" && (
<ExternalLink
aria-hidden
className="size-4 ml-auto text-gray-8 dark:text-graydark-8"
/>
)}
</Link>
);
};
Expand Down
12 changes: 5 additions & 7 deletions src/components/ToggleButton/ToggleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@ import {
composeRenderProps,
} from "react-aria-components";
import { tv } from "tailwind-variants";
import { buttonStyles } from "../Button";
import { focusRing } from "../utils";

const styles = tv({
extend: focusRing,
base: "px-5 py-2 text-sm text-center transition rounded-lg border border-black/10 dark:border-white/10 forced-colors:border-[ButtonBorder] cursor-pointer forced-color-adjust-none",
base: "h-10 px-3.5 [&:has(svg:only-child)]:px-2 text-sm text-center transition rounded-lg border border-black/10 dark:border-white/10",
variants: {
isSelected: {
false:
"bg-gray-1 hover:bg-gray-2 pressed:bg-gray-3 text-gray-normal dark:bg-gray-6 dark:hover:bg-gray-5 dark:pressed:bg-gray-4 dark:text-gray-1 forced-colors:!bg-[ButtonFace] forced-colors:!text-[ButtonText]",
true: "bg-purple-9 hover:bg-purple-10 text-white dark:bg-purpledark-9 dark:hover:bg-purpledark-10 forced-colors:!bg-[Highlight] forced-colors:!text-[HighlightText]",
},
isDisabled: {
true: "bg-gray-1 dark:bg-gray-11 forced-colors:!bg-[ButtonFace] text-gray-3 dark:text-gray-6 forced-colors:!text-[GrayText] border-black/5 dark:border-white/5 forced-colors:border-[GrayText]",
false: buttonStyles.variants.variant.secondary,
true: "bg-gray-12 dark:bg-graydark-12 text-gray-1 dark:text-gray-12 shadow-sm",
},
isDisabled: buttonStyles.variants.isDisabled,
},
});

Expand Down
10 changes: 10 additions & 0 deletions src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Meta } from "@storybook/react";
import { ToggleButtonGroup } from ".";

const meta: Meta<typeof ToggleButtonGroup> = {
component: ToggleButtonGroup,
};

export default meta;

export const Example = (args: any) => <ToggleButtonGroup {...args} />;
27 changes: 27 additions & 0 deletions src/components/ToggleButtonGroup/ToggleButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
ToggleButtonGroup as AriaToggleButtonGroup,
type ToggleButtonGroupProps,
composeRenderProps,
} from "react-aria-components";
import { tv } from "tailwind-variants";

const styles = tv({
base: "border rounded-lg grid grid-flow-col auto-cols-fr border-black/10 dark:border-white/10 *:border-0",
variants: {
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
},
});

export function ToggleButtonGroup(props: ToggleButtonGroupProps) {
return (
<AriaToggleButtonGroup
{...props}
className={composeRenderProps(props.className, (className) =>
styles({ orientation: props.orientation || "horizontal", className }),
)}
/>
);
}
1 change: 1 addition & 0 deletions src/components/ToggleButtonGroup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./ToggleButtonGroup";
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ export * from "./TextArea";
export * from "./TextField";
export * from "./TimeField";
export * from "./ToggleButton";
export * from "./ToggleButtonGroup";
export * from "./Toolbar";
export * from "./Tooltip";
2 changes: 1 addition & 1 deletion src/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const focusRing = tv({
variants: {
isFocusVisible: {
false: "outline-0",
true: "outline-2 z-99",
true: "outline-2 z-9999",
},
},
});
Expand Down
2 changes: 1 addition & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ if (!rootElement.innerHTML) {
<StrictMode>
<HelmetProvider>
<ConvexAuthProvider client={convex}>
<ThemeProvider attribute="class">
<ThemeProvider attribute="class" disableTransitionOnChange>
<InnerApp />
</ThemeProvider>
</ConvexAuthProvider>
Expand Down
7 changes: 5 additions & 2 deletions src/routes/_authenticated/admin/fields/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,11 @@ function FieldsRoute() {
return (
<div>
<PageHeader title="Fields">
<Button onPress={() => setIsNewFieldModalOpen(true)} variant="primary">
<Plus />
<Button
onPress={() => setIsNewFieldModalOpen(true)}
variant="primary"
icon={Plus}
>
New Field
</Button>
</PageHeader>
Expand Down
Loading

0 comments on commit 1a6b588

Please sign in to comment.