diff --git a/web/src/shell/AppShell.tsx b/web/src/shell/AppShell.tsx index 69173c7..6ecc789 100644 --- a/web/src/shell/AppShell.tsx +++ b/web/src/shell/AppShell.tsx @@ -1,13 +1,19 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Link, Outlet, useNavigate } from "@tanstack/react-router"; +import * as stylex from "@stylexjs/stylex"; import { useAuth } from "@/auth/store"; import InlineError from "@/lib/InlineError"; import { versionQuery } from "@/lib/queries"; import PauseWidget from "../features/pause/PauseWidget"; import ReadOnlyConfigBanner from "../features/settings/ReadOnlyConfigBanner"; import RestartBanner from "../features/settings/RestartBanner"; -import { buttonClass, focusRing } from "@/ui/classes"; +import { styles as shared } from "@/ui/styles"; +import { colors } from "@/ui/tokens.stylex"; + +/** The one breakpoint the shell has: below it the sidebar becomes a drawer. */ +const WIDE = "@media (min-width: 768px)"; +const DARK = "@media (prefers-color-scheme: dark)"; const NAV_ITEMS = [ { to: "/", label: "Dashboard" }, @@ -23,9 +29,115 @@ const NAV_ITEMS = [ { to: "/settings", label: "Settings" }, ] as const; +const styles = stylex.create({ + navList: { + display: "flex", + flexDirection: "column", + gap: "0.25rem", + }, + navLink: { + display: "block", + borderRadius: "0.25rem", + paddingInline: "0.75rem", + paddingBlock: "0.375rem", + textDecorationLine: "none", + }, + /** The current page reads as a filled chip, heavier than the hover fill. */ + navActive: { + backgroundColor: { default: "oklch(92% 0.004 286.32)", [DARK]: "oklch(27.4% 0.006 286.033)" }, + color: colors.text, + fontWeight: 500, + }, + navIdle: { + backgroundColor: { default: "transparent", ":hover": colors.surfaceHover }, + color: { default: colors.textMuted, ":hover": colors.text }, + }, + versionFooter: { + paddingInline: "1rem", + paddingBlock: "0.75rem", + fontSize: "0.75rem", + lineHeight: "1rem", + color: colors.textMuted, + }, + logoutRow: { + display: "flex", + alignItems: "center", + gap: "0.5rem", + }, + shell: { + minHeight: "100dvh", + backgroundColor: colors.surface, + color: colors.text, + display: { default: "block", [WIDE]: "grid" }, + gridTemplateColumns: { default: null, [WIDE]: "14rem 1fr" }, + }, + sidebar: { + display: { default: "none", [WIDE]: "flex" }, + flexDirection: { default: null, [WIDE]: "column" }, + borderRightWidth: 1, + borderRightStyle: "solid", + borderRightColor: colors.border, + }, + brand: { + paddingInline: "1rem", + paddingBlock: "1rem", + fontSize: "1.125rem", + lineHeight: "1.75rem", + fontWeight: 600, + }, + sidebarNav: { + flex: 1, + paddingInline: "0.5rem", + }, + column: { + display: "flex", + minHeight: "100dvh", + flexDirection: "column", + }, + header: { + display: "flex", + alignItems: "center", + gap: "0.75rem", + borderBottomWidth: 1, + borderBottomStyle: "solid", + borderBottomColor: colors.border, + paddingInline: "1rem", + paddingBlock: "0.5rem", + }, + narrowOnly: { + display: { default: null, [WIDE]: "none" }, + }, + narrowBrand: { + fontSize: "1.125rem", + lineHeight: "1.75rem", + fontWeight: 600, + display: { default: null, [WIDE]: "none" }, + }, + headerRight: { + marginLeft: "auto", + display: "flex", + alignItems: "center", + gap: "0.75rem", + }, + drawer: { + borderBottomWidth: 1, + borderBottomStyle: "solid", + borderBottomColor: colors.border, + display: { default: null, [WIDE]: "none" }, + }, + drawerNav: { + paddingInline: "0.5rem", + paddingBlock: "0.5rem", + }, + main: { + flex: 1, + padding: "1rem", + }, +}); + function NavLinks({ onNavigate }: { onNavigate?: () => void }) { return ( -