import { lazy, type ComponentType, type ReactNode } from 'react'; import { Skeleton } from '@/components/skeleton'; import type { Messages } from './i18n'; import { OverviewPage } from './pages/overview'; /** * The pages that are not a component. * * There used to be no such list: `app.tsx` resolved the path through an if/else * chain and `chrome/ui/sidebar.tsx` hard-coded the same five links a second time, * so adding a page meant editing two files and keeping their order in step by * hand. Both now read this array, which is also the order the rail draws. * * A component page is *not* here — those come from the registry, which already * owns its own ordering and grouping. `app.tsx` falls through to `findEntry()` * when nothing here matches. */ /** Which block of the rail a route belongs to. `null` = reachable, not listed. */ export type NavGroup = 'foundations'; export interface RouteDef { /** Path without the leading slash. `''` is the site root. */ slug: string; nav: NavGroup | null; /** Rail label. Takes the catalogue rather than a key so TypeScript checks it. */ label: (m: Messages) => string; /** Breadcrumb title — several routes share one. */ title: (m: Messages) => string; Component: ComponentType; /** * What `app.tsx` shows while this route's chunk is in flight, when the plain * spinner is not enough. Only the icons page declares one: it is by far the * largest chunk, and a grid of the shape that is coming holds the scroll * position instead of collapsing the page to one line and then reflowing it. */ fallback?: ReactNode; } /** * The icons page's placeholder — the same 7rem auto-fill grid its cells sit in. * * Eager on purpose: it is three elements and it lives in the chunk that is * *waiting*, so putting it in the lazy module would mean it arrived with the * thing it was meant to stand in for. */ function IconGridSkeleton() { return (
{Array.from({ length: 24 }, (_, i) => ( ))}
); } /* * Everything but the overview is lazy. * * This is not premature: the site compiled to a single 1.6 MB chunk, and the * icons page alone accounts for most of it — it renders the whole 250-icon set, * so it defeats the tree-shaking every other importer of `@/icons` relies on. * The parity page is the other one worth splitting; it pulls the whole * `figma-spec.json` capture. * * The overview stays eager because it *is* the first paint, and it imports the * registry — which every component page needs too, so deferring it would buy a * spinner and no bytes. * * `.then(...)` unwraps a named export: these pages predate this file and export * `TokensPage` rather than a default, which is the better shape for everything * except `React.lazy`. */ const IntroductionPage = lazy(() => import('./pages/introduction').then((m) => ({ default: m.IntroductionPage })) ); const TokensPage = lazy(() => import('./pages/tokens').then((m) => ({ default: m.TokensPage }))); const IconsPage = lazy(() => import('./pages/icons').then((m) => ({ default: m.IconsPage }))); const ButtonsPage = lazy(() => import('./pages/buttons').then((m) => ({ default: m.ButtonsPage }))); const AlertsPage = lazy(() => import('./pages/alerts').then((m) => ({ default: m.AlertsPage }))); const AvatarsPage = lazy(() => import('./pages/avatars').then((m) => ({ default: m.AvatarsPage })) ); const BadgesPage = lazy(() => import('./pages/badges').then((m) => ({ default: m.BadgesPage }))); const ToastsPage = lazy(() => import('./pages/toasts').then((m) => ({ default: m.ToastsPage }))); const ParityPage = lazy(() => import('./pages/parity').then((m) => ({ default: m.ParityPage }))); export const ROUTES: readonly RouteDef[] = [ { slug: '', nav: 'foundations', label: (m) => m.nav.overview, title: (m) => m.header.components, Component: OverviewPage, }, { slug: 'introduction', nav: 'foundations', label: (m) => m.nav.introduction, title: (m) => m.header.foundations, Component: IntroductionPage, }, { slug: 'tokens', nav: 'foundations', label: (m) => m.nav.tokens, title: (m) => m.header.foundations, Component: TokensPage, }, { slug: 'icons', nav: 'foundations', label: (m) => m.nav.icons, title: (m) => m.header.foundations, Component: IconsPage, fallback: , }, { /* Plural — the registry entry for the component itself is `button`. */ slug: 'buttons', nav: 'foundations', label: (m) => m.nav.buttons, title: (m) => m.header.foundations, Component: ButtonsPage, }, { /* Plural — the registry entry for the component itself is `alert`. */ slug: 'alerts', nav: 'foundations', label: (m) => m.nav.alerts, title: (m) => m.header.foundations, Component: AlertsPage, }, { /* Plural — the registry entry for the component itself is `avatar`. */ slug: 'avatars', nav: 'foundations', label: (m) => m.nav.avatars, title: (m) => m.header.foundations, Component: AvatarsPage, }, { /* Plural — the registry entry for the component itself is `badge`. */ slug: 'badges', nav: 'foundations', label: (m) => m.nav.badges, title: (m) => m.header.foundations, Component: BadgesPage, }, { /* Plural — the registry entry for the component itself is `toast`. */ slug: 'toasts', nav: 'foundations', label: (m) => m.nav.toasts, title: (m) => m.header.foundations, Component: ToastsPage, }, { slug: 'parity', nav: 'foundations', label: (m) => m.nav.parity, title: (m) => m.header.foundations, Component: ParityPage, }, ]; export function findRoute(slug: string): RouteDef | undefined { return ROUTES.find((r) => r.slug === slug); } /** The rail's fixed block, in declaration order. */ export const NAV_ROUTES: readonly RouteDef[] = ROUTES.filter((r) => r.nav !== null);