UNPKG

wcz-layout

Version:

260 lines (192 loc) 6.99 kB
--- name: layout-navigation description: > Configure LayoutProvider with theme, options, and Navigation structure. Shell: AppBar with env chip + NavigationRail (permanent sm+, modal xs). Navigation items: page (title, icon, to/href), header, divider. LayoutOptions: showShell, snackbarOrigin, publicRoutes. Dark mode via theme.applyStyles("dark", ...) with colorSchemeSelector data-mui-color-scheme. i18n with i18next, locale files auto-detected. Activate when configuring the layout shell, navigation, or theming. type: core library: wcz-layout library_version: "7.6.1" sources: - "wcz-layout:src/providers/LayoutProvider.tsx" - "wcz-layout:src/components/core/Layout.tsx" - "wcz-layout:src/models/Navigation.ts" - "wcz-layout:src/models/LayoutOptions.ts" - "wcz-layout:src/lib/theme.ts" --- # Layout & Navigation ## Setup The layout shell is configured in the root route via `LayoutProvider`: ```typescript // src/routes/__root.tsx import { LayoutProvider, rootRouteHead, RouterNotFound, RouterError } from "wcz-layout"; import { theme } from "~/lib/theme"; import { navigation } from "~/navigation"; import type { LayoutOptions } from "wcz-layout/models"; const options: LayoutOptions = { showShell: true, publicRoutes: ["/login"], }; export const Route = createRootRouteWithContext()({ head: rootRouteHead, component: RootComponent, notFoundComponent: RouterNotFound, errorComponent: RouterError, }); function RootComponent() { return ( <LayoutProvider theme={theme} navigation={navigation} options={options}> <Outlet /> </LayoutProvider> ); } ``` ## Core Patterns ### Navigation structure Navigation is an array of items with three types discriminated by the presence of `kind`: ```typescript import type { Navigation } from "wcz-layout/models"; import HomeIcon from "@mui/icons-material/Home"; import ListIcon from "@mui/icons-material/List"; import SettingsIcon from "@mui/icons-material/Settings"; export const navigation: Navigation = [ { title: "Home", icon: <HomeIcon />, to: "/" }, { kind: "divider" }, { kind: "header", title: "Management" }, { title: "Todos", icon: <ListIcon />, to: "/todos" }, { kind: "divider" }, { title: "Settings", icon: <SettingsIcon />, to: "/settings" }, ]; ``` ### Navigation item types | Type | Shape | Required fields | | --------- | --------------------------- | --------------- | | Page item | `{ title, icon, to }` | `title`, `icon` | | Divider | `{ kind: "divider" }` | `kind` | | Header | `{ kind: "header", title }` | `kind`, `title` | Page items also accept: - `to` internal route (TanStack Router link, type-safe) - `href` external URL (opens in new tab) - `params`, `search` TanStack Router link params - `children` nested navigation items (sub-menu) - `hidden` conditionally hide the item ### Nested navigation ```typescript const navigation: Navigation = [ { title: "Admin", icon: <AdminIcon />, to: "/admin", children: [ { title: "Users", icon: <PeopleIcon />, to: "/admin/users" }, { title: "Roles", icon: <SecurityIcon />, to: "/admin/roles" }, ], }, ]; ``` ### LayoutOptions ```typescript interface LayoutOptions { showShell?: boolean; // Show AppBar + NavigationRail (default: true) snackbarOrigin?: SnackbarOrigin; // Notification position publicRoutes?: string[]; // Routes that skip authentication } ``` ### Theme configuration The theme is pre-configured in the template at `src/lib/theme.ts`. Use MUI's `extendTheme` to customize: ```typescript // src/lib/theme.ts import { extendTheme } from "@mui/material/styles"; export const theme = extendTheme({ colorSchemes: { light: { palette: { primary: { main: "#1976d2" } } }, dark: { palette: { primary: { main: "#90caf9" } } }, }, colorSchemeSelector: "data-mui-color-scheme", }); ``` ### Dark mode styling Always use `theme.applyStyles("dark", ...)` for mode-specific styling: ```typescript <Box sx={(theme) => ({ backgroundColor: "#fff", ...theme.applyStyles("dark", { backgroundColor: "#121212", }), })} /> ``` ### i18n setup Locale files in `src/locales/` are auto-detected by `viteWczLayout()`: ``` src/locales/en.json # English (default) src/locales/cs.json # Czech ``` The Vite plugin generates the `virtual:wcz-layout` module with all locale resources. `LayoutProvider` initializes `i18next` with language detection (cookie-based, 1-year expiry) and syncs Zod and DayJS locales on language change. ```typescript import { useTranslation } from "react-i18next"; function MyComponent() { const { t } = useTranslation(); return <Typography>{t("welcome")}</Typography>; } ``` ### Root route head `rootRouteHead` provides meta tags and manifest link: ```typescript export const Route = createRootRouteWithContext()({ head: rootRouteHead, // optionally: head: rootRouteHead({ manifest: "/manifest.json" }), }); ``` ### Service worker `LayoutProvider` registers `/sw.js` on mount for PWA support. ## Common Mistakes ### HIGH Using theme.palette for dark mode instead of theme.applyStyles Wrong: ```typescript sx={{ color: mode === "dark" ? "white" : "black" }} ``` Correct: ```typescript sx={(theme) => ({ color: "black", ...theme.applyStyles("dark", { color: "white" }), })} ``` The app uses `colorSchemeSelector: "data-mui-color-scheme"`. Mode-specific styles must use `theme.applyStyles("dark", ...)`, not conditional palette checks or mode variables. Source: copilot-instructions.md / wcz-layout:src/lib/theme.ts Cross-skill: See also skills/ui-pages/SKILL.md § Common Mistakes ### HIGH Using href for internal routes in Navigation Wrong: ```typescript { title: "Todos", icon: <ListIcon />, href: "/todos" } ``` Correct: ```typescript { title: "Todos", icon: <ListIcon />, to: "/todos" } ``` `NavigationPageItem` supports dual destination: `to` for internal TanStack Router links, `href` for external URLs. Using `href` for internal routes causes full page reloads. Source: wcz-layout:src/models/Navigation.ts ### MEDIUM Creating NavigationPageItem without icon Wrong: ```typescript { title: "Todos", to: "/todos" } ``` Correct: ```typescript { title: "Todos", icon: <ListIcon />, to: "/todos" } ``` `NavigationPageItem` requires both `title` and `icon`. Missing `icon` renders an empty slot in the NavigationRail. Source: wcz-layout:src/models/Navigation.ts ### MEDIUM Adding locale without updating supportedLngs This is actually **not** an issue the Vite plugin auto-detects locale files in `src/locales/`. Adding a new JSON file (e.g. `de.json`) is sufficient; `supportedLngs` is derived from `Object.keys(resources)` automatically. Source: wcz-layout:src/providers/LayoutProvider.tsx --- See also: - skills/ui-pages/SKILL.md Pages render inside the layout shell. - skills/auth/SKILL.md publicRoutes bypass auth; permission guards need auth context.