@marktiderman/genesis-ui 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/EXTENDING.md +40 -1
  2. package/SKILL.md +144 -9
  3. package/dist/{AppShell-CX0M-dfc.d.ts → AppShell-LXQykEqe.d.ts} +47 -5
  4. package/dist/{chunk-M62CHO3N.js → chunk-4C4ESW6O.js} +9 -4
  5. package/dist/chunk-4C4ESW6O.js.map +1 -0
  6. package/dist/{chunk-IXGAEECT.js → chunk-DWCAC2E7.js} +6 -3
  7. package/dist/chunk-DWCAC2E7.js.map +1 -0
  8. package/dist/chunk-FUVWKTPL.js +19 -0
  9. package/dist/chunk-FUVWKTPL.js.map +1 -0
  10. package/dist/{chunk-PLIYF6KI.js → chunk-MPI33I4K.js} +2 -2
  11. package/dist/{chunk-PLIYF6KI.js.map → chunk-MPI33I4K.js.map} +1 -1
  12. package/dist/{chunk-JA3ZKULS.js → chunk-OITWCIG2.js} +3 -3
  13. package/dist/chunk-OITWCIG2.js.map +1 -0
  14. package/dist/chunk-PKFDGGZV.js +65 -0
  15. package/dist/chunk-PKFDGGZV.js.map +1 -0
  16. package/dist/{chunk-TXOZMKNW.js → chunk-S7NXD37O.js} +27 -26
  17. package/dist/chunk-S7NXD37O.js.map +1 -0
  18. package/dist/{chunk-ZPPLUGLA.js → chunk-UYI3GLFJ.js} +162 -19
  19. package/dist/chunk-UYI3GLFJ.js.map +1 -0
  20. package/dist/{chunk-CDYZRZ6U.js → chunk-WGMEVX3Y.js} +2 -2
  21. package/dist/chunk-WGMEVX3Y.js.map +1 -0
  22. package/dist/chunk-ZDHWNQTR.js +552 -0
  23. package/dist/chunk-ZDHWNQTR.js.map +1 -0
  24. package/dist/components/data/index.d.ts +239 -15
  25. package/dist/components/data/index.js +522 -107
  26. package/dist/components/data/index.js.map +1 -1
  27. package/dist/components/layout/form-page.d.ts +1 -1
  28. package/dist/components/layout/form-page.js +1 -1
  29. package/dist/components/layout/index.d.ts +2 -2
  30. package/dist/components/layout/index.js +8 -8
  31. package/dist/components/patterns/index.d.ts +1 -1
  32. package/dist/components/patterns/index.js +5 -5
  33. package/dist/components/patterns/sortable-list.js +1 -1
  34. package/dist/components/rich-text/rich-text-editor.d.ts +194 -0
  35. package/dist/components/rich-text/rich-text-editor.js +833 -0
  36. package/dist/components/rich-text/rich-text-editor.js.map +1 -0
  37. package/dist/components/rich-text/rich-text.d.ts +51 -0
  38. package/dist/components/rich-text/rich-text.js +4 -0
  39. package/dist/components/rich-text/rich-text.js.map +1 -0
  40. package/dist/components/ui/alert-dialog.d.ts +12 -1
  41. package/dist/components/ui/alert-dialog.js +1 -1
  42. package/dist/hooks/index.d.ts +3 -3
  43. package/dist/hooks/index.js +4 -4
  44. package/dist/hooks/use-data-filters.d.ts +5 -3
  45. package/dist/hooks/use-data-filters.js +2 -2
  46. package/dist/hooks/use-resource-page.d.ts +2 -1
  47. package/dist/hooks/use-resource-page.js +3 -2
  48. package/dist/hooks/use-view-preference.d.ts +17 -2
  49. package/dist/hooks/use-view-preference.js +2 -1
  50. package/dist/index.d.ts +2 -2
  51. package/dist/index.js +30 -30
  52. package/dist/{navigation-DBwcZ6vb.d.ts → navigation-Bha0R3Fu.d.ts} +6 -3
  53. package/dist/rich-text-doc-C54fME2C.d.ts +180 -0
  54. package/dist/router/react-router.d.ts +47 -0
  55. package/dist/router/react-router.js +53 -0
  56. package/dist/router/react-router.js.map +1 -0
  57. package/dist/router/tanstack-router.d.ts +47 -0
  58. package/dist/router/tanstack-router.js +105 -0
  59. package/dist/router/tanstack-router.js.map +1 -0
  60. package/dist/{use-resource-page-Dd2VUv30.d.ts → use-resource-page-D4byuwAs.d.ts} +20 -3
  61. package/package.json +61 -10
  62. package/dist/chunk-CDYZRZ6U.js.map +0 -1
  63. package/dist/chunk-FTTNGGLA.js +0 -20
  64. package/dist/chunk-FTTNGGLA.js.map +0 -1
  65. package/dist/chunk-IXGAEECT.js.map +0 -1
  66. package/dist/chunk-JA3ZKULS.js.map +0 -1
  67. package/dist/chunk-M62CHO3N.js.map +0 -1
  68. package/dist/chunk-TXOZMKNW.js.map +0 -1
  69. package/dist/chunk-ZPPLUGLA.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/navigation.tsx"],"names":["trail"],"mappings":"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","file":"chunk-CDYZRZ6U.js","sourcesContent":["\"use client\";\n\n/**\n * Router-agnostic navigation primitives.\n *\n * genesis-ui does NOT depend on any router. Components that need to render a\n * link or navigate imperatively accept an injectable abstraction defined here,\n * and default to plain browser behavior (`<a>` / `window.location`). Consumers\n * on React Router or Next.js inject a thin adapter that maps these props to\n * their own `<Link>` / router.\n */\nimport {\n useCallback,\n useEffect,\n useState,\n type AnchorHTMLAttributes,\n type ComponentType,\n type ReactNode,\n} from \"react\";\n\n// ---------------------------------------------------------------------------\n// Link abstraction\n// ---------------------------------------------------------------------------\n\n/**\n * Props passed to an injectable link component. The contract is a plain anchor\n * (`href`), so the default works with NO router. A React Router consumer maps\n * `href` → `to`; a Next.js consumer passes `next/link` directly (it already\n * accepts `href`, `className`, `children`).\n *\n * @stability Beta\n */\nexport interface GenesisLinkProps {\n /** Destination path or URL. */\n href: string;\n className?: string;\n onClick?: () => void;\n children?: ReactNode;\n /** Set to \"page\" when the link points at the active route. */\n \"aria-current\"?: AnchorHTMLAttributes<HTMLAnchorElement>[\"aria-current\"];\n /** Accessible name — useful when the visible label is hidden (icon rail). */\n \"aria-label\"?: string;\n /** Native tooltip / title attribute. */\n title?: string;\n /**\n * Stable test/automation selector. {@link DefaultLink} renders it as\n * `data-testid`; injected router adapters (`next/link`, RR `Link`) forward it\n * to the underlying `<a>` too.\n */\n testID?: string;\n}\n\n/**\n * A component that renders a navigable link from {@link GenesisLinkProps}.\n *\n * @stability Beta\n */\nexport type LinkComponent = ComponentType<GenesisLinkProps>;\n\n/**\n * Default link — a plain `<a>`. No router required.\n *\n * @stability Beta\n */\nexport const DefaultLink: LinkComponent = ({\n href,\n children,\n testID,\n ...rest\n}) => (\n <a href={href} data-testid={testID} {...rest}>\n {children}\n </a>\n);\n\n/**\n * Split a href into its path (trailing slash dropped, except for `/`), query\n * (no `?`) and hash (no `#`).\n */\nfunction splitHref(href: string): { path: string; search: string; hash: string } {\n const hashAt = href.indexOf(\"#\");\n const hash = hashAt === -1 ? \"\" : href.slice(hashAt + 1);\n const beforeHash = hashAt === -1 ? href : href.slice(0, hashAt);\n const searchAt = beforeHash.indexOf(\"?\");\n const rawPath = searchAt === -1 ? beforeHash : beforeHash.slice(0, searchAt);\n return {\n path: rawPath.length > 1 ? rawPath.replace(/\\/+$/, \"\") || \"/\" : rawPath,\n search: searchAt === -1 ? \"\" : beforeHash.slice(searchAt + 1),\n hash,\n };\n}\n\n/**\n * Options for {@link isPathActive}.\n *\n * @stability Beta\n */\nexport interface IsPathActiveOptions {\n /** Require an exact path match instead of a prefix match (like `NavLink end`). */\n end?: boolean;\n}\n\n/**\n * Default active-path matcher, mirroring React Router's `NavLink` (prefix\n * match, with `/` treated as exact). Consumers can override via an `isActive`\n * prop where components accept one.\n *\n * `activePath` may carry a query and hash (`pathname + search + hash`). An\n * item whose `to` has neither compares against the path part only, exactly as\n * before. An item that opts in by putting a query or hash in its `to`\n * (`/admin?tab=users`, `/settings#profile`) is active only when the path\n * matches exactly AND every query param it names has the same value in\n * `activePath` (extra params such as `&page=2` are ignored) AND, if it names a\n * hash, the hash is equal.\n *\n * @stability Beta\n */\nexport function isPathActive(\n to: string,\n activePath?: string,\n options?: IsPathActiveOptions,\n): boolean {\n if (activePath == null) return false;\n if (to === activePath) return true;\n const target = splitHref(to);\n const current = splitHref(activePath);\n\n if (!target.search && !target.hash) {\n if (target.path === current.path) return true;\n if (options?.end) return false;\n return target.path !== \"/\" && current.path.startsWith(target.path + \"/\");\n }\n\n if (target.path !== current.path) return false;\n if (target.hash && target.hash !== current.hash) return false;\n if (target.search) {\n const want = new URLSearchParams(target.search);\n const have = new URLSearchParams(current.search);\n for (const key of new Set(want.keys())) {\n const wanted = want.getAll(key);\n const had = have.getAll(key);\n if (!wanted.every((value) => had.includes(value))) return false;\n }\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Hierarchical navigation resolver\n// ---------------------------------------------------------------------------\n\n/**\n * One node of a logical navigation hierarchy. `path` is a route pattern whose\n * segments are either static (`clients`) or named params (`$clientId`). A\n * node's place in the tree is its LOGICAL parent, which need not share its URL\n * prefix — `/clients/$clientId` can sit under `/dashboard`.\n *\n * @stability Beta\n */\nexport interface NavigationNode<T> {\n id: string;\n path: string;\n label: string;\n /** Contextual nav items to show while this node is current. */\n items: T[];\n children?: NavigationNode<T>[];\n}\n\n/**\n * Result of {@link resolveNavigation}. `current`, `parent` and every `trail`\n * entry are shallow copies of their tree nodes with `path` interpolated from\n * `params` (a param the URL did not supply stays as `$name`).\n *\n * @stability Beta\n */\nexport interface ResolvedNavigation<T> {\n /** The best-matching node — the root when nothing else matches. */\n current: NavigationNode<T>;\n /** `current`'s logical parent, for an \"up\" / back-to-parent link. */\n parent?: NavigationNode<T>;\n /** `current.items`. */\n items: T[];\n /** Root → `current`, inclusive. */\n trail: NavigationNode<T>[];\n /** Params captured by `current.path`, URL-decoded. */\n params: Record<string, string>;\n}\n\nfunction pathSegments(path: string): string[] {\n return splitHref(path).path.split(\"/\").filter(Boolean);\n}\n\nfunction decodeSegment(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\n/**\n * Resolve where `pathname` sits in a logical navigation tree. Pure: it never\n * reads browser history or `window`.\n *\n * A node matches when its pattern matches a leading run of WHOLE path\n * segments (`/my` matches `/my/tasks`, never `/my-other`). Among matches the\n * winner is decided by positional specificity: at the first position where\n * one pattern is static and the other a `$param`, the static one wins (so\n * `/clients/new` beats `/clients/$clientId` whatever the sibling order, and\n * `/my` beats `/$space/tasks` for `/my/tasks`). Otherwise the pattern matching\n * more segments wins, then the deeper tree node.\n * Non-matching nodes are still searched, so a match keeps its logical\n * ancestors even when their URLs differ.\n *\n * @stability Beta\n */\nexport function resolveNavigation<T>(\n tree: NavigationNode<T>,\n pathname: string,\n): ResolvedNavigation<T> {\n // Compare decoded forms on both sides, so `/caf%C3%A9` matches a `/café`\n // node (and an encoded pattern matches a decoded pathname).\n const segments = pathSegments(pathname).map(decodeSegment);\n\n type Match = { trail: NavigationNode<T>[]; pattern: string[] };\n const matches = (pattern: string[]) =>\n pattern.length <= segments.length &&\n pattern.every(\n (part, i) => part.startsWith(\"$\") || decodeSegment(part) === segments[i],\n );\n\n // > 0 when `a` should win over `b`.\n // Positional specificity, as TanStack Router and React Router rank routes:\n // walking left to right, the first position where one pattern is static and\n // the other a `$param` decides. Only then does the longer match win.\n const compare = (a: Match, b: Match): number => {\n const shared = Math.min(a.pattern.length, b.pattern.length);\n for (let i = 0; i < shared; i++) {\n const aParam = a.pattern[i]!.startsWith(\"$\");\n const bParam = b.pattern[i]!.startsWith(\"$\");\n if (aParam !== bParam) return aParam ? -1 : 1;\n }\n if (a.pattern.length !== b.pattern.length) {\n return a.pattern.length - b.pattern.length;\n }\n return a.trail.length - b.trail.length;\n };\n\n let best: Match | undefined;\n const visit = (node: NavigationNode<T>, ancestors: NavigationNode<T>[]) => {\n const trail = [...ancestors, node];\n const pattern = pathSegments(node.path);\n if (matches(pattern)) {\n const candidate = { trail, pattern };\n if (!best || compare(candidate, best) > 0) best = candidate;\n }\n for (const child of node.children ?? []) visit(child, trail);\n };\n visit(tree, []);\n\n const winner: Match = best ?? { trail: [tree], pattern: [] };\n const params: Record<string, string> = {};\n winner.pattern.forEach((part, i) => {\n if (part.startsWith(\"$\")) params[part.slice(1)] = segments[i]!;\n });\n\n const interpolate = (node: NavigationNode<T>): NavigationNode<T> => ({\n ...node,\n path: node.path\n .split(\"/\")\n .map((part) => {\n if (!part.startsWith(\"$\")) return part;\n const value = params[part.slice(1)];\n return value == null ? part : encodeURIComponent(value);\n })\n .join(\"/\"),\n });\n\n const trail = winner.trail.map(interpolate);\n const current = trail[trail.length - 1]!;\n return {\n current,\n parent: trail.length > 1 ? trail[trail.length - 2] : undefined,\n items: current.items,\n trail,\n params,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Imperative navigation abstraction\n// ---------------------------------------------------------------------------\n\n/**\n * Injectable imperative navigation callback.\n *\n * @stability Beta\n */\nexport type NavigateFn = (path: string) => void;\n\n/**\n * Default navigate — a full-page navigation. No router required.\n *\n * @stability Beta\n */\nexport const defaultNavigate: NavigateFn = (path) => {\n if (typeof window !== \"undefined\") window.location.assign(path);\n};\n\n// ---------------------------------------------------------------------------\n// Search-params abstraction (for URL-synced hooks)\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal, router-agnostic replacement for React Router's `useSearchParams`\n * return shape. Consumers may inject their router's implementation for full\n * SPA integration; otherwise the browser History API is used.\n *\n * @stability Beta\n */\nexport interface SearchParamsAdapter {\n searchParams: URLSearchParams;\n setSearchParams: (\n next: URLSearchParams,\n options?: { replace?: boolean },\n ) => void;\n}\n\n/**\n * Browser-native {@link SearchParamsAdapter} built on the History API. Reads\n * the current query string and writes updates via `pushState`/`replaceState`,\n * staying reactive to `popstate` events. No router required.\n *\n * @stability Beta\n */\nexport function useBrowserSearchParams(): SearchParamsAdapter {\n const read = () =>\n new URLSearchParams(\n typeof window !== \"undefined\" ? window.location.search : \"\",\n );\n\n const [searchParams, setLocal] = useState<URLSearchParams>(read);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const onPop = () => setLocal(read());\n window.addEventListener(\"popstate\", onPop);\n return () => window.removeEventListener(\"popstate\", onPop);\n }, []);\n\n const setSearchParams = useCallback<SearchParamsAdapter[\"setSearchParams\"]>(\n (next, options) => {\n if (typeof window === \"undefined\") return;\n const qs = next.toString();\n const url =\n window.location.pathname + (qs ? `?${qs}` : \"\") + window.location.hash;\n if (options?.replace) {\n window.history.replaceState(window.history.state, \"\", url);\n } else {\n window.history.pushState(window.history.state, \"\", url);\n }\n setLocal(new URLSearchParams(next));\n },\n [],\n );\n\n return { searchParams, setSearchParams };\n}\n"]}
@@ -1,20 +0,0 @@
1
- "use client";
2
- import { useState, useEffect } from 'react';
3
- import { useStorage } from '@marktiderman/genesis-core';
4
-
5
- // src/hooks/use-view-preference.ts
6
- function useViewPreference(key, defaultMode = "grid") {
7
- const storage = useStorage();
8
- const storageKey = `genesis-view-${key}`;
9
- const [mode, setMode] = useState(() => {
10
- return storage.getItem(storageKey) || defaultMode;
11
- });
12
- useEffect(() => {
13
- storage.setItem(storageKey, mode);
14
- }, [mode, storageKey, storage]);
15
- return [mode, setMode];
16
- }
17
-
18
- export { useViewPreference };
19
- //# sourceMappingURL=chunk-FTTNGGLA.js.map
20
- //# sourceMappingURL=chunk-FTTNGGLA.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/use-view-preference.ts"],"names":[],"mappings":";;;;AAMO,SAAS,iBAAA,CACd,GAAA,EACA,WAAA,GAAwB,MAAA,EACc;AACtC,EAAA,MAAM,UAAU,UAAA,EAAW;AAC3B,EAAA,MAAM,UAAA,GAAa,gBAAgB,GAAG,CAAA,CAAA;AACtC,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAmB,MAAM;AAC/C,IAAA,OAAQ,OAAA,CAAQ,OAAA,CAAQ,UAAU,CAAA,IAAkB,WAAA;AAAA,EACtD,CAAC,CAAA;AAED,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,OAAA,CAAQ,OAAA,CAAQ,YAAY,IAAI,CAAA;AAAA,EAClC,CAAA,EAAG,CAAC,IAAA,EAAM,UAAA,EAAY,OAAO,CAAC,CAAA;AAE9B,EAAA,OAAO,CAAC,MAAM,OAAO,CAAA;AACvB","file":"chunk-FTTNGGLA.js","sourcesContent":["\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { useStorage } from \"@marktiderman/genesis-core\";\nimport type { ViewMode } from \"../components/patterns/view-toggle\";\n\nexport function useViewPreference(\n key: string,\n defaultMode: ViewMode = \"grid\",\n): [ViewMode, (mode: ViewMode) => void] {\n const storage = useStorage();\n const storageKey = `genesis-view-${key}`;\n const [mode, setMode] = useState<ViewMode>(() => {\n return (storage.getItem(storageKey) as ViewMode) || defaultMode;\n });\n\n useEffect(() => {\n storage.setItem(storageKey, mode);\n }, [mode, storageKey, storage]);\n\n return [mode, setMode];\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/use-resource-page.ts"],"names":[],"mappings":"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","file":"chunk-IXGAEECT.js","sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useCallback, useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { DataTableColumn } from \"../components/data/DataTable\";\nimport type { DataFilterConfig } from \"../components/data/DataFilters\";\nimport type { ViewMode } from \"../components/patterns/view-toggle\";\nimport type { Identifier, FilterParam, SortParam } from \"@marktiderman/genesis-core/provider\";\nimport { useViewPreference } from \"./use-view-preference\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Actions available inside column render functions. */\nexport interface ResourceActions<T> {\n update: (id: Identifier, data: Partial<T>) => Promise<T>;\n remove: (id: Identifier) => Promise<void>;\n refetch: () => void;\n}\n\n/** Column definition — full config form. */\nexport interface ResourceColumnDef<T> {\n key: string;\n header?: string;\n sortable?: boolean;\n hideBelow?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n render?: (\n item: T,\n value: unknown,\n actions: ResourceActions<T>,\n ) => ReactNode;\n /** In-place cell editing, passed straight through to `DataTable`. */\n editable?: DataTableColumn<T>[\"editable\"];\n /**\n * The cell's value, when it isn't `item[key]`. Passed straight through to\n * `DataTable`, which also starts an edit from `String(accessor(item))`.\n */\n accessor?: (item: T) => unknown;\n}\n\nexport interface UseResourcePageProps<T> {\n /** Resource name — used for view-preference storage key. */\n resource?: string;\n /** The data to display (pre-fetched or from useResource). */\n data: T[];\n /** Total count of records (before client-side filtering). */\n total: number;\n /** Column definitions. Strings are expanded to `{ key, header, sortable }`. Omit to auto-generate from data keys. */\n columns?: Array<string | ResourceColumnDef<T>>;\n /** Status filter config. String = field name (options auto-derived). Object = explicit field + options. */\n statusFilter?: string | { field: string; options?: string[] };\n /** Fields to search across. Defaults to all string-valued fields in data. */\n searchFields?: string[];\n /** Initial filter params applied on mount. */\n initialFilters?: FilterParam[];\n /** Callback when filters change. */\n onFiltersChange?: (filters: FilterParam[]) => void;\n /** Default view mode. */\n defaultView?: ViewMode;\n /** Detail interaction mode. */\n detail?: \"panel\" | \"modal\" | \"route\" | \"none\";\n /** Field used for the detail title. Defaults to first column key. */\n titleField?: string;\n /** Field used for the detail subtitle. Defaults to second column key. */\n subtitleField?: string;\n /**\n * Server-controlled mode. The `data` handed in has ALREADY been searched,\n * sorted and paginated by the server, so this hook must not do any of it a\n * second time — `data` is returned untouched and `total` is the server's\n * `total` rather than the length of a client-filtered slice.\n *\n * Search / sort / status state is still tracked (the filter UI stays\n * controlled and `onFiltersChange` still fires) — only the client-side\n * transform of `data` is skipped. Defaults to `false`, so every existing\n * caller keeps its current client-side behavior.\n */\n controlled?: boolean;\n}\n\nexport interface UseResourcePageReturn<T> {\n /** Filtered and sorted data for display. */\n data: T[];\n /** Total count (of the filtered set; the server's `total` when `controlled`). */\n total: number;\n /** Resolved column definitions. */\n columns: ResourceColumnDef<T>[];\n /** Columns mapped to DataTable format. */\n tableColumns: DataTableColumn<T>[];\n /** Ready-to-spread filter config for DataFilters. */\n filterConfig: {\n search: string;\n onSearchChange: (value: string) => void;\n sort: string;\n onSortChange: (sort: string) => void;\n sortOptions: Array<{ value: string; label: string }>;\n statusChips?: {\n options: string[];\n selected: string[];\n onChange: (values: string[]) => void;\n };\n };\n viewMode: ViewMode;\n setViewMode: (mode: ViewMode) => void;\n search: string;\n setSearch: (value: string) => void;\n sort: SortParam | null;\n setSort: (sort: SortParam | null) => void;\n statusValue: string[];\n setStatusValue: (values: string[]) => void;\n selectedItem: T | null;\n setSelectedItem: (item: T | null) => void;\n detailOpen: boolean;\n setDetailOpen: (open: boolean) => void;\n handleRowClick: (item: T) => void;\n titleField: string;\n subtitleField: string;\n statusOptions: string[];\n hasActiveFilters: boolean;\n clearFilters: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a key string to title case.\n * \"created_at\" → \"Created At\"\n * \"firstName\" → \"First Name\"\n */\nexport function titleCase(str: string): string {\n // Split on underscores, hyphens, and camelCase boundaries\n const words = str\n .replace(/([a-z])([A-Z])/g, \"$1 $2\") // camelCase → camel Case\n .replace(/[_-]/g, \" \") // snake_case / kebab-case → spaces\n .split(/\\s+/)\n .filter(Boolean);\n\n return words\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())\n .join(\" \");\n}\n\n/**\n * Resolve column shorthand into full ResourceColumnDef objects.\n * - undefined + data present → auto-generate from Object.keys(data[0]), skip \"id\"\n * - undefined + empty data → empty array\n * - string items → { key, header: titleCase(key), sortable: true }\n * - object items → defaults applied\n */\nfunction resolveColumns<T>(\n columns: Array<string | ResourceColumnDef<T>> | undefined,\n data: T[],\n): ResourceColumnDef<T>[] {\n if (columns === undefined) {\n if (data.length === 0) return [];\n const keys = Object.keys(data[0] as Record<string, unknown>).filter(\n (k) => k !== \"id\",\n );\n return keys.map((key) => ({\n key,\n header: titleCase(key),\n sortable: true,\n }));\n }\n\n return columns.map((col) => {\n if (typeof col === \"string\") {\n return { key: col, header: titleCase(col), sortable: true };\n }\n return {\n ...col,\n header: col.header ?? titleCase(col.key),\n sortable: col.sortable ?? true,\n };\n });\n}\n\n/**\n * Derive the list of string fields in a data set (for default search).\n */\nfunction deriveStringFields<T>(data: T[]): string[] {\n if (data.length === 0) return [];\n const first = data[0] as Record<string, unknown>;\n return Object.keys(first).filter((k) => typeof first[k] === \"string\");\n}\n\n/**\n * Derive unique status options from a data array.\n */\nfunction deriveStatusOptions<T>(data: T[], field: string): string[] {\n const values = new Set<string>();\n for (const item of data) {\n const val = (item as Record<string, unknown>)[field];\n if (val != null) values.add(String(val));\n }\n return Array.from(values).sort();\n}\n\n/**\n * Client-side text search across multiple fields.\n */\nfunction matchesSearch<T>(\n item: T,\n query: string,\n fields: string[],\n): boolean {\n if (!query) return true;\n const lower = query.toLowerCase();\n const record = item as Record<string, unknown>;\n return fields.some((f) => {\n const val = record[f];\n return val != null && String(val).toLowerCase().includes(lower);\n });\n}\n\n/**\n * Client-side sort comparator.\n */\nfunction compareItems<T>(a: T, b: T, sort: SortParam): number {\n const ra = a as Record<string, unknown>;\n const rb = b as Record<string, unknown>;\n const va = ra[sort.field];\n const vb = rb[sort.field];\n const sa = va == null ? \"\" : String(va);\n const sb = vb == null ? \"\" : String(vb);\n const cmp = sa.localeCompare(sb, undefined, {\n numeric: true,\n sensitivity: \"base\",\n });\n return sort.order === \"asc\" ? cmp : -cmp;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useResourcePage<T = Record<string, unknown>>(\n props: UseResourcePageProps<T>,\n): UseResourcePageReturn<T> {\n const {\n resource,\n data: rawData,\n total: rawTotal,\n columns: columnsProp,\n statusFilter: statusFilterProp,\n searchFields: searchFieldsProp,\n defaultView = \"table\",\n detail = \"panel\",\n titleField: titleFieldProp,\n subtitleField: subtitleFieldProp,\n initialFilters,\n onFiltersChange,\n controlled = false,\n } = props;\n\n // -- View mode --\n const [viewMode, setViewMode] = useViewPreference(\n resource ?? \"resource-page\",\n defaultView,\n );\n\n // -- Derive initial values from initialFilters --\n const initialSearch = useMemo(() => {\n if (!initialFilters) return \"\";\n const searchFilter = initialFilters.find(\n (f) => f.operator === \"contains\" && f.field === \"__search__\",\n );\n return searchFilter && \"value\" in searchFilter ? String(searchFilter.value ?? \"\") : \"\";\n }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount\n\n const initialSort = useMemo(() => {\n if (!initialFilters) return null;\n const sortFilter = initialFilters.find(\n (f) => f.field === \"__sort__\",\n );\n if (!sortFilter || !(\"value\" in sortFilter) || typeof sortFilter.value !== \"string\") return null;\n const [field, order] = sortFilter.value.split(\":\");\n if (!field || (order !== \"asc\" && order !== \"desc\")) return null;\n return { field, order } as SortParam;\n }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount\n\n const initialStatus = useMemo(() => {\n if (!initialFilters) return [];\n const statusFilter = initialFilters.find(\n (f) => f.operator === \"in\" && f.field === \"__status__\",\n );\n if (!statusFilter || statusFilter.operator !== \"in\") return [];\n return statusFilter.value.map((v) => String(v));\n }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount\n\n // -- Search --\n const [search, setSearch] = useState(initialSearch);\n\n // -- Sort --\n const [sort, setSort] = useState<SortParam | null>(initialSort);\n\n // -- Status filter --\n const [statusValue, setStatusValue] = useState<string[]>(initialStatus);\n\n // -- Detail panel --\n const [selectedItem, setSelectedItem] = useState<T | null>(null);\n const [detailOpen, setDetailOpen] = useState(false);\n\n // -- Resolve columns --\n const resolvedColumns = useMemo(\n () => resolveColumns(columnsProp, rawData),\n [columnsProp, rawData],\n );\n\n // -- Title / subtitle fields --\n const titleField =\n titleFieldProp ?? resolvedColumns[0]?.key ?? \"name\";\n const subtitleField =\n subtitleFieldProp ?? resolvedColumns[1]?.key ?? \"\";\n\n // -- Status filter config --\n const statusFieldName = useMemo(() => {\n if (!statusFilterProp) return null;\n return typeof statusFilterProp === \"string\"\n ? statusFilterProp\n : statusFilterProp.field;\n }, [statusFilterProp]);\n\n const statusOptions = useMemo(() => {\n if (!statusFilterProp) return [];\n if (typeof statusFilterProp === \"object\" && statusFilterProp.options) {\n return statusFilterProp.options;\n }\n if (!statusFieldName) return [];\n return deriveStatusOptions(rawData, statusFieldName);\n }, [statusFilterProp, statusFieldName, rawData]);\n\n // -- Search fields --\n const searchFields = useMemo(\n () => searchFieldsProp ?? deriveStringFields(rawData),\n [searchFieldsProp, rawData],\n );\n\n // -- Client-side filtering + sorting --\n // Skipped entirely in controlled mode: the server already applied the\n // search, the sort and the page window, so re-running them here would\n // filter the current page against a query the server has already honoured\n // (and re-sort a slice of rows that is not the full result set).\n const filteredData = useMemo(() => {\n if (controlled) return rawData;\n\n let result = rawData;\n\n // Text search\n if (search) {\n result = result.filter((item) =>\n matchesSearch(item, search, searchFields),\n );\n }\n\n // Status filter\n if (statusFieldName && statusValue.length > 0) {\n result = result.filter((item) => {\n const val = (item as Record<string, unknown>)[statusFieldName];\n return val != null && statusValue.includes(String(val));\n });\n }\n\n // Sort\n if (sort) {\n result = [...result].sort((a, b) => compareItems(a, b, sort));\n }\n\n return result;\n }, [controlled, rawData, search, searchFields, statusFieldName, statusValue, sort]);\n\n // -- Sort options (built from resolved columns) --\n const sortOptions = useMemo(() => {\n const opts: Array<{ value: string; label: string }> = [];\n for (const col of resolvedColumns) {\n if (col.sortable !== false) {\n opts.push({\n value: `${col.key}:asc`,\n label: `${col.header ?? titleCase(col.key)} (A-Z)`,\n });\n opts.push({\n value: `${col.key}:desc`,\n label: `${col.header ?? titleCase(col.key)} (Z-A)`,\n });\n }\n }\n return opts;\n }, [resolvedColumns]);\n\n // -- Sort string for DataFilters select --\n const sortString = sort ? `${sort.field}:${sort.order}` : \"\";\n\n const handleSortChange = useCallback((value: string) => {\n if (!value) {\n setSort(null);\n return;\n }\n const [field, order] = value.split(\":\");\n setSort({ field, order: order as \"asc\" | \"desc\" });\n }, []);\n\n // -- Map to DataTableColumn --\n const tableColumns: DataTableColumn<T>[] = useMemo(\n () =>\n resolvedColumns.map((col) => ({\n key: col.key,\n header: col.header ?? titleCase(col.key),\n sortable: col.sortable ?? true,\n hideBelow: col.hideBelow,\n editable: col.editable,\n accessor: col.accessor,\n render: col.render\n ? (item: T, value: unknown) =>\n col.render!(item, value, {\n update: async () => {\n throw new Error(\n \"ResourceActions.update not available in server-data mode\",\n );\n },\n remove: async () => {\n throw new Error(\n \"ResourceActions.remove not available in server-data mode\",\n );\n },\n refetch: () => {\n /* no-op in server-data mode */\n },\n })\n : undefined,\n })),\n [resolvedColumns],\n );\n\n // -- Active filters check --\n const hasActiveFilters =\n search !== \"\" || statusValue.length > 0 || sort !== null;\n\n const clearFilters = useCallback(() => {\n setSearch(\"\");\n setStatusValue([]);\n setSort(null);\n }, []);\n\n // -- Notify parent of filter changes --\n const onFiltersChangeRef = useRef(onFiltersChange);\n onFiltersChangeRef.current = onFiltersChange;\n\n useEffect(() => {\n if (!onFiltersChangeRef.current) return;\n const filters: FilterParam[] = [];\n if (search) {\n filters.push({ field: \"__search__\", operator: \"contains\", value: search });\n }\n if (sort) {\n filters.push({ field: \"__sort__\", operator: \"eq\", value: `${sort.field}:${sort.order}` });\n }\n if (statusValue.length > 0) {\n filters.push({ field: \"__status__\", operator: \"in\", value: statusValue });\n }\n onFiltersChangeRef.current(filters);\n }, [search, sort, statusValue]);\n\n // -- Row click --\n const handleRowClick = useCallback(\n (item: T) => {\n if (detail === \"none\") return;\n setSelectedItem(item);\n setDetailOpen(true);\n },\n [detail],\n );\n\n // -- Filter config (ready to spread into DataFilters) --\n const filterConfig = useMemo(\n () => ({\n search,\n onSearchChange: setSearch,\n sort: sortString,\n onSortChange: handleSortChange,\n sortOptions,\n ...(statusFieldName && statusOptions.length > 0\n ? {\n statusChips: {\n options: statusOptions,\n selected: statusValue,\n onChange: setStatusValue,\n },\n }\n : {}),\n }),\n [\n search,\n sortString,\n handleSortChange,\n sortOptions,\n statusFieldName,\n statusOptions,\n statusValue,\n ],\n );\n\n return {\n data: filteredData,\n // Controlled mode: `filteredData` is one server page, so its length is\n // not the record count — the server's total is.\n total: controlled ? rawTotal : filteredData.length,\n columns: resolvedColumns,\n tableColumns,\n filterConfig,\n viewMode,\n setViewMode,\n search,\n setSearch,\n sort,\n setSort,\n statusValue,\n setStatusValue,\n selectedItem,\n setSelectedItem,\n detailOpen,\n setDetailOpen,\n handleRowClick,\n titleField,\n subtitleField,\n statusOptions,\n hasActiveFilters,\n clearFilters,\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/use-data-filters.ts"],"names":[],"mappings":"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","file":"chunk-JA3ZKULS.js","sourcesContent":["\"use client\";\n\nimport { useState, useCallback, useEffect, useRef } from \"react\";\nimport { useStorage } from \"@marktiderman/genesis-core\";\nimport {\n useBrowserSearchParams,\n type SearchParamsAdapter,\n} from \"../navigation\";\n\ntype FilterType = \"string\" | \"multi\";\n\nexport interface FilterDef {\n type: FilterType;\n default: string | string[];\n}\n\nexport type FilterDefs = Record<string, FilterDef>;\n\nexport type FilterValues<T extends FilterDefs> = {\n [K in keyof T]: T[K][\"type\"] extends \"multi\" ? string[] : string;\n};\n\nfunction parseArray(val: string | null): string[] {\n if (!val) return [];\n return val.split(\",\").filter(Boolean);\n}\n\n/**\n * @stability Beta\n */\nexport interface UseDataFiltersOptions {\n /**\n * Injectable URL search-params adapter. Defaults to a browser History-API\n * implementation (no router). Router consumers may pass their own — e.g.\n * React Router's `useSearchParams()` result — for full SPA integration.\n */\n searchParams?: SearchParamsAdapter;\n}\n\nexport function useDataFilters<T extends FilterDefs>(\n storageKey: string,\n defs: T,\n options?: UseDataFiltersOptions\n) {\n // Always call the default hook (rules-of-hooks); use the override if provided.\n const browserAdapter = useBrowserSearchParams();\n const { searchParams, setSearchParams } =\n options?.searchParams ?? browserAdapter;\n const storage = useStorage();\n const initialized = useRef(false);\n\n // Build initial state from URL -> storage -> defaults\n const getInitial = useCallback((): FilterValues<T> => {\n const result = {} as Record<string, unknown>;\n const localKey = `${storageKey}-filters`;\n let stored: Record<string, unknown> = {};\n\n try {\n const raw = storage.getItem(localKey);\n if (raw) stored = JSON.parse(raw);\n } catch {\n /* ignore */\n }\n\n // Check if URL has any of our params\n const hasUrlParams = Object.keys(defs).some((k) => searchParams.has(k));\n\n for (const [key, def] of Object.entries(defs)) {\n if (def.type === \"multi\") {\n const urlVal = hasUrlParams ? searchParams.get(key) : null;\n if (urlVal !== null) {\n result[key] = parseArray(urlVal);\n } else if (Array.isArray(stored[key])) {\n result[key] = stored[key];\n } else {\n result[key] = def.default;\n }\n } else {\n const urlVal = hasUrlParams ? searchParams.get(key) : null;\n if (urlVal !== null) {\n result[key] = urlVal;\n } else if (typeof stored[key] === \"string\") {\n result[key] = stored[key];\n } else {\n result[key] = def.default;\n }\n }\n }\n\n return result as FilterValues<T>;\n }, [defs, searchParams, storageKey, storage]);\n\n const [filters, setFilters] = useState<FilterValues<T>>(() => getInitial());\n\n // Mark as initialized after first render\n useEffect(() => {\n if (!initialized.current) {\n initialized.current = true;\n }\n }, []);\n\n // Persist to storage + URL whenever filters change\n useEffect(() => {\n if (!initialized.current) return;\n\n const localKey = `${storageKey}-filters`;\n storage.setItem(localKey, JSON.stringify(filters));\n\n // Update URL params. When using the DEFAULT browser adapter, seed from the\n // LIVE URL (not the adapter's possibly stale snapshot) so a second\n // useDataFilters instance on the same page can't merge against an old query\n // string and drop params another instance just wrote. When a caller\n // explicitly injects a SearchParamsAdapter (custom router / test double\n // whose URL state may not be synced to the DOM), trust ITS snapshot instead\n // of reading the real DOM location. Also falls back to the adapter snapshot\n // in non-browser envs.\n const newParams = new URLSearchParams(\n !options?.searchParams && typeof window !== \"undefined\"\n ? window.location.search\n : searchParams.toString(),\n );\n for (const [key, def] of Object.entries(defs)) {\n const val = (filters as Record<string, unknown>)[key];\n const isDefault =\n def.type === \"multi\"\n ? (val as string[]).length === 0\n : val === def.default;\n\n if (isDefault) {\n newParams.delete(key);\n } else if (def.type === \"multi\") {\n newParams.set(key, (val as string[]).join(\",\"));\n } else {\n newParams.set(key, val as string);\n }\n }\n setSearchParams(newParams, { replace: true });\n }, [filters, defs, storageKey, storage]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const setFilter = useCallback(\n <K extends keyof T>(\n key: K,\n value: T[K][\"type\"] extends \"multi\" ? string[] : string\n ) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n },\n []\n );\n\n const resetFilters = useCallback(() => {\n const result = {} as Record<string, unknown>;\n for (const [key, def] of Object.entries(defs)) {\n result[key] = def.default;\n }\n setFilters(result as FilterValues<T>);\n }, [defs]);\n\n const loadView = useCallback(\n (viewFilters: Record<string, unknown>) => {\n const result = {} as Record<string, unknown>;\n for (const [key, def] of Object.entries(defs)) {\n const v = viewFilters[key];\n if (def.type === \"multi\" && Array.isArray(v)) {\n result[key] = v;\n } else if (def.type === \"string\" && typeof v === \"string\") {\n result[key] = v;\n } else {\n result[key] = def.default;\n }\n }\n setFilters(result as FilterValues<T>);\n },\n [defs]\n );\n\n const hasActiveFilters = Object.entries(defs).some(([key, def]) => {\n const val = (filters as Record<string, unknown>)[key];\n if (def.type === \"multi\") return (val as string[]).length > 0;\n return val !== def.default;\n });\n\n return {\n filters,\n setFilter,\n resetFilters,\n loadView,\n hasActiveFilters,\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/ui/alert-dialog.tsx"],"names":["AlertDialogPrimitive"],"mappings":"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","file":"chunk-M62CHO3N.js","sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\";\nimport { cn } from \"../../utils\";\n\nconst AlertDialog = AlertDialogPrimitive.Root;\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger;\nconst AlertDialogPortal = AlertDialogPrimitive.Portal;\n\nconst AlertDialogOverlay = React.forwardRef<\n React.ComponentRef<typeof AlertDialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Overlay\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n className\n )}\n {...props}\n ref={ref}\n />\n));\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;\n\nconst AlertDialogContent = React.forwardRef<\n React.ComponentRef<typeof AlertDialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogPrimitive.Content\n ref={ref}\n className={cn(\n \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-card p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg\",\n className\n )}\n {...props}\n />\n </AlertDialogPortal>\n));\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;\n\nconst AlertDialogHeader = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn(\"flex flex-col space-y-2 text-center sm:text-left\", className)} {...props} />\n);\n\nconst AlertDialogFooter = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn(\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\", className)} {...props} />\n);\n\nconst AlertDialogTitle = React.forwardRef<\n React.ComponentRef<typeof AlertDialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Title\n ref={ref}\n className={cn(\"text-lg font-semibold\", className)}\n {...props}\n />\n));\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;\n\nconst AlertDialogDescription = React.forwardRef<\n React.ComponentRef<typeof AlertDialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Description\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nAlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;\n\nconst AlertDialogAction = React.forwardRef<\n React.ComponentRef<typeof AlertDialogPrimitive.Action>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Action\n ref={ref}\n className={cn(\n \"inline-flex h-10 items-center justify-center rounded-lg bg-destructive px-4 py-2 text-sm font-semibold text-destructive-foreground transition-colors hover:bg-destructive/90 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n className\n )}\n {...props}\n />\n));\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;\n\nconst AlertDialogCancel = React.forwardRef<\n React.ComponentRef<typeof AlertDialogPrimitive.Cancel>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Cancel\n ref={ref}\n className={cn(\n \"mt-2 inline-flex h-10 items-center justify-center rounded-lg border border-border bg-card px-4 py-2 text-sm font-medium transition-colors hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 sm:mt-0\",\n className\n )}\n {...props}\n />\n));\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;\n\nexport {\n AlertDialog,\n AlertDialogPortal,\n AlertDialogOverlay,\n AlertDialogTrigger,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogFooter,\n AlertDialogTitle,\n AlertDialogDescription,\n AlertDialogAction,\n AlertDialogCancel,\n};\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/patterns/sortable-list.tsx"],"names":[],"mappings":"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","file":"chunk-TXOZMKNW.js","sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { GripVertical, MoreVertical } from \"lucide-react\";\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from \"../ui/dropdown-menu\";\nimport { cn } from \"../../utils\";\n\nexport interface SortableListItemContext {\n /** The item's position as shown, from 0. */\n index: number;\n /**\n * The drag handle: drag it with a pointer, or focus it and press Space or\n * Enter to pick the item up, the arrow keys to move it and Space or Enter\n * again to drop it. Render it somewhere in the row.\n */\n handle: React.ReactNode;\n /** True while this item is being dragged with a pointer. */\n isDragging: boolean;\n /** True while this item is picked up from the keyboard. */\n isGrabbed: boolean;\n /** Move this item without dragging, for a row menu of your own. */\n moveUp: () => void;\n moveDown: () => void;\n moveTo: (index: number) => void;\n}\n\nexport interface SortableListReorder {\n /** Key of the item that moved. */\n key: string;\n}\n\nexport interface SortableListProps<T> {\n items: readonly T[];\n /** A stable key per item. Required: dragging finds the item again by key. */\n getKey: (item: T) => string;\n /** A name for an item, for the handle's label and announcements (\"Week 1\"). */\n getItemLabel: (item: T) => string;\n renderItem: (item: T, context: SortableListItemContext) => React.ReactNode;\n /**\n * Move the item at `from` to `to` (both from 0, `to` counted after the item\n * is taken out). Each finished move calls this once: a keyboard pick-up\n * that wanders and comes back calls nothing.\n *\n * The list shows the new order straight away and keeps showing it until the\n * returned promise settles; by then `items` must be in the new order. A\n * synchronous handler must update `items` in the same tick. A rejected\n * promise puts the old order back and announces that the move failed. Other\n * moves wait until this one settles.\n */\n onReorder: (from: number, to: number, move: SortableListReorder) => void | Promise<void>;\n /** Accessible name of the list (\"Course steps\"). */\n label: string;\n /** Turns off dragging and every kind of move. */\n disabled?: boolean | undefined;\n /**\n * A \"Move\" menu on each row (up, down, to top, to bottom), on by default so\n * touch and screen-reader users always have a way to move an item. Turn it\n * off only when the row offers `moveUp` / `moveDown` / `moveTo` itself.\n */\n moveMenu?: boolean | undefined;\n className?: string | undefined;\n itemClassName?: string | undefined;\n}\n\ntype DropAt = { key: string; edge: \"before\" | \"after\" };\n\nconst DRAG_TYPE = \"application/x-genesis-sortable-item\";\n\n// useLayoutEffect warns during server rendering; nothing needs refocusing there.\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? React.useEffect : React.useLayoutEffect;\n\nfunction moved(keys: readonly string[], from: number, to: number) {\n const next = keys.slice();\n const [key] = next.splice(from, 1);\n next.splice(to, 0, key!);\n return next;\n}\n\n/**\n * A vertical list the user can rearrange: course steps, lesson blocks, form\n * fields.\n *\n * Every way to move an item is equivalent, so dragging is never the only one\n * (the same stance as KanbanBoard's \"Move to…\" menu):\n * - drag the handle with a pointer;\n * - focus the handle, Space or Enter to pick up, ↑/↓ and Home/End to move,\n * Space or Enter to drop, Escape to put it back (tabbing away also puts it\n * back); Alt+↑/↓ moves one step without picking up;\n * - the row's \"Move\" menu, or `moveUp` / `moveDown` / `moveTo` from the render\n * context.\n *\n * Moves are announced in a polite live region once they succeed. Native drag\n * and drop needs no dependency and Radix has none to offer (docs/FRAMEWORK.md\n * rule 3).\n *\n * @stability Beta\n */\nexport function SortableList<T>({\n items,\n getKey,\n getItemLabel,\n renderItem,\n onReorder,\n label,\n disabled,\n moveMenu = true,\n className,\n itemClassName,\n}: SortableListProps<T>) {\n const [armed, setArmed] = React.useState<string | null>(null);\n const [dragging, setDragging] = React.useState<string | null>(null);\n const [dropAt, setDropAt] = React.useState<DropAt | null>(null);\n // While a key is grabbed, `preview` is the order on screen; nothing is\n // written until the drop.\n const [grabbed, setGrabbed] = React.useState<{ key: string; from: number } | null>(null);\n // The order to show instead of `items`: during a keyboard pick-up, and from\n // a committed move until its promise settles.\n const [preview, setPreview] = React.useState<string[] | null>(null);\n const [pending, setPending] = React.useState(false);\n const [announcement, setAnnouncement] = React.useState(\"\");\n const handles = React.useRef(new Map<string, HTMLButtonElement>());\n const refocus = React.useRef<string | null>(null);\n const instructionsId = React.useId();\n\n const byKey = new Map(items.map((item) => [getKey(item), item] as const));\n const itemKeys = items.map(getKey);\n const keys = preview && preview.length === itemKeys.length && preview.every((k) => byKey.has(k)) ? preview : itemKeys;\n const total = keys.length;\n const nameOf = (key: string) => {\n const item = byKey.get(key);\n return item === undefined ? \"Item\" : getItemLabel(item);\n };\n\n // A live region only speaks when its text changes, so the same message\n // twice in a row is told apart by a trailing no-break space.\n const announce = (message: string) =>\n setAnnouncement((previous) => (previous === message ? `${message} ` : message));\n\n // Only follow the moved item with focus when its handle had focus (or the\n // DOM move dropped focus to the body); a row menu keeps its own focus.\n const keepFocus = (key: string) => {\n const active = typeof document === \"undefined\" ? null : document.activeElement;\n refocus.current = active === handles.current.get(key) || active === document.body || active === null ? key : null;\n };\n const keysSignature = keys.join(\"\\u0000\");\n useIsomorphicLayoutEffect(() => {\n const key = refocus.current;\n refocus.current = null;\n if (!key) return;\n const handle = handles.current.get(key);\n const active = document.activeElement;\n if (handle && (active === document.body || active === null || active === handle)) handle.focus();\n }, [keysSignature]);\n\n // An item that disappears mid-gesture, or the list turning disabled, ends\n // the gesture.\n React.useEffect(() => {\n if (disabled && (grabbed || armed || dragging)) {\n setGrabbed(null);\n setArmed(null);\n setDragging(null);\n setDropAt(null);\n if (!pending) setPreview(null);\n return;\n }\n if (dragging && !byKey.has(dragging)) {\n setDragging(null);\n setDropAt(null);\n setArmed(null);\n }\n if (grabbed && !byKey.has(grabbed.key)) {\n setGrabbed(null);\n setPreview(null);\n }\n });\n\n const commit = (key: string, from: number, to: number, message: string) => {\n if (from === to) return;\n setPending(true);\n setPreview(moved(itemKeys, from, to));\n // Called synchronously, so a synchronous handler updates `items` in the\n // same tick as the preview.\n let result: void | Promise<void>;\n try {\n result = onReorder(from, to, { key });\n } catch (error) {\n result = Promise.reject(error);\n }\n Promise.resolve(result)\n .then(\n () => announce(message),\n () => announce(`Couldn’t move ${nameOf(key)}.`),\n )\n .finally(() => {\n setPending(false);\n setPreview(null);\n });\n };\n\n const move = (key: string, to: number) => {\n if (disabled || pending || grabbed) return;\n if (!Number.isFinite(to)) return;\n const from = itemKeys.indexOf(key);\n const target = Math.max(0, Math.min(total - 1, Math.round(to)));\n if (from < 0 || target === from) return;\n keepFocus(key);\n commit(key, from, target, `${nameOf(key)} moved to position ${target + 1} of ${total}.`);\n };\n\n const pickUp = (key: string) => {\n if (disabled || pending) return;\n const from = itemKeys.indexOf(key);\n if (from < 0) return;\n setGrabbed({ key, from });\n setPreview(itemKeys);\n announce(\n `${nameOf(key)} picked up, position ${from + 1} of ${total}. Use the up and down arrow keys to move, Space to drop, Escape to cancel.`,\n );\n };\n\n const drop = (key: string) => {\n if (!grabbed) return;\n const to = keys.indexOf(key);\n const from = grabbed.from;\n setGrabbed(null);\n if (to === from) {\n setPreview(null);\n announce(`${nameOf(key)} dropped at position ${to + 1} of ${total}.`);\n return;\n }\n keepFocus(key);\n commit(key, from, to, `${nameOf(key)} dropped at position ${to + 1} of ${total}.`);\n };\n\n const cancel = (key: string) => {\n if (!grabbed) return;\n const from = grabbed.from;\n keepFocus(key);\n setGrabbed(null);\n setPreview(null);\n announce(`Move cancelled. ${nameOf(key)} is back at position ${from + 1} of ${total}.`);\n };\n\n const step = (key: string, to: number) => {\n const index = keys.indexOf(key);\n const target = Math.max(0, Math.min(total - 1, to));\n if (index < 0 || target === index) return;\n keepFocus(key);\n setPreview(moved(keys, index, target));\n announce(`${nameOf(key)} moved to position ${target + 1} of ${total}.`);\n };\n\n // Escape while an item is picked up must not also close a surrounding\n // Dialog or Sheet, which listen before the handle does.\n React.useEffect(() => {\n if (!grabbed) return;\n const onKey = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n event.stopPropagation();\n cancel(grabbed.key);\n };\n window.addEventListener(\"keydown\", onKey, true);\n return () => window.removeEventListener(\"keydown\", onKey, true);\n });\n\n // A press on the handle arms its row for native dragging; any release or\n // cancel, wherever it lands, disarms it.\n React.useEffect(() => {\n if (!armed || dragging) return;\n const disarm = () => setArmed(null);\n window.addEventListener(\"pointerup\", disarm, { once: true });\n window.addEventListener(\"pointercancel\", disarm, { once: true });\n return () => {\n window.removeEventListener(\"pointerup\", disarm);\n window.removeEventListener(\"pointercancel\", disarm);\n };\n }, [armed, dragging]);\n\n const onHandleKeyDown = (event: React.KeyboardEvent, key: string) => {\n if (disabled) return;\n const isGrabbed = grabbed?.key === key;\n const index = keys.indexOf(key);\n const delta = event.key === \"ArrowUp\" ? -1 : event.key === \"ArrowDown\" ? 1 : 0;\n if (isGrabbed) {\n if (delta) {\n event.preventDefault();\n step(key, index + delta);\n } else if (event.key === \"Home\" || event.key === \"End\") {\n event.preventDefault();\n step(key, event.key === \"Home\" ? 0 : total - 1);\n }\n return;\n }\n if (delta && event.altKey) {\n event.preventDefault();\n move(key, index + delta);\n }\n };\n\n // Space and Enter reach the handle as a click with `detail === 0`, as do\n // screen readers' activations; a pointer click is a press for dragging.\n const onHandleClick = (event: React.MouseEvent, key: string) => {\n if (disabled || event.detail !== 0) return;\n if (grabbed?.key === key) drop(key);\n else if (!grabbed) pickUp(key);\n };\n\n const onDragOver = (event: React.DragEvent<HTMLLIElement>, key: string) => {\n if (!dragging) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = \"move\";\n const rect = event.currentTarget.getBoundingClientRect();\n const edge = event.clientY < rect.top + rect.height / 2 ? \"before\" : \"after\";\n if (dropAt?.key !== key || dropAt.edge !== edge) setDropAt({ key, edge });\n };\n\n const endDrag = () => {\n setDragging(null);\n setDropAt(null);\n setArmed(null);\n };\n\n const onDrop = (event: React.DragEvent) => {\n event.preventDefault();\n const key = event.dataTransfer.getData(DRAG_TYPE) || dragging;\n const target = dropAt;\n endDrag();\n if (!key || !target || pending || grabbed) return;\n const from = itemKeys.indexOf(key);\n const over = itemKeys.indexOf(target.key);\n if (from < 0 || over < 0) return;\n // `to` counts positions after the dragged item is taken out.\n let to = target.edge === \"before\" ? over : over + 1;\n if (from < to) to -= 1;\n commit(key, from, to, `${nameOf(key)} dropped at position ${to + 1} of ${total}.`);\n };\n\n return (\n <>\n <ul aria-label={label} aria-busy={pending || undefined} className={cn(\"flex flex-col gap-1\", className)}>\n {keys.map((key, index) => {\n const item = byKey.get(key)!;\n const itemLabel = getItemLabel(item);\n const isGrabbed = grabbed?.key === key;\n const isDragging = dragging === key;\n const handle = disabled ? null : (\n <button\n type=\"button\"\n ref={(node) => {\n if (node) handles.current.set(key, node);\n else handles.current.delete(key);\n }}\n aria-label={`Reorder ${itemLabel}`}\n aria-describedby={instructionsId}\n data-grabbed={isGrabbed || undefined}\n className={cn(\n \"inline-flex h-7 w-5 shrink-0 cursor-grab items-center justify-center rounded text-muted-foreground\",\n \"hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n isGrabbed && \"bg-accent text-foreground\",\n )}\n onPointerDown={() => {\n if (!pending && !grabbed) setArmed(key);\n }}\n onPointerCancel={() => setArmed(null)}\n onClick={(event) => onHandleClick(event, key)}\n onKeyDown={(event) => onHandleKeyDown(event, key)}\n onBlur={() => {\n // A DOM move during the pick-up blurs the handle for a moment;\n // that is not the user leaving.\n if (isGrabbed && refocus.current !== key) cancel(key);\n }}\n >\n <GripVertical className=\"h-4 w-4\" aria-hidden />\n </button>\n );\n return (\n <li\n key={key}\n data-sortable-key={key}\n draggable={(!disabled && armed === key) || undefined}\n className={cn(\n \"relative rounded-md\",\n moveMenu && !disabled && \"pr-8\",\n isDragging && \"opacity-50\",\n isGrabbed && \"ring-2 ring-primary/50\",\n itemClassName,\n )}\n onDragStart={(event) => {\n if (disabled || armed !== key) return;\n event.dataTransfer.setData(DRAG_TYPE, key);\n event.dataTransfer.effectAllowed = \"move\";\n setDragging(key);\n }}\n onDragEnd={endDrag}\n onDragOver={(event) => onDragOver(event, key)}\n onDrop={onDrop}\n >\n {dropAt?.key === key && dragging !== key && (\n <span\n aria-hidden\n className={cn(\n \"pointer-events-none absolute inset-x-0 h-0.5 rounded bg-primary\",\n dropAt.edge === \"before\" ? \"-top-0.5\" : \"-bottom-0.5\",\n )}\n />\n )}\n {renderItem(item, {\n index,\n handle,\n isDragging,\n isGrabbed,\n moveUp: () => move(key, index - 1),\n moveDown: () => move(key, index + 1),\n moveTo: (to) => move(key, to),\n })}\n {moveMenu && !disabled && total > 1 && (\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label={`Move ${itemLabel}`}\n className=\"absolute right-1 top-1 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MoreVertical className=\"h-4 w-4\" aria-hidden />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem disabled={index === 0} onSelect={() => move(key, index - 1)}>\n Move up\n </DropdownMenuItem>\n <DropdownMenuItem disabled={index === total - 1} onSelect={() => move(key, index + 1)}>\n Move down\n </DropdownMenuItem>\n <DropdownMenuItem disabled={index === 0} onSelect={() => move(key, 0)}>\n Move to top\n </DropdownMenuItem>\n <DropdownMenuItem disabled={index === total - 1} onSelect={() => move(key, total - 1)}>\n Move to bottom\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </li>\n );\n })}\n </ul>\n <p id={instructionsId} hidden>\n Space or Enter picks the item up. Up and down arrows, Home and End move it. Space or Enter drops it, Escape puts\n it back. Alt with an arrow moves it one step.\n </p>\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {announcement}\n </div>\n </>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/option-storage.ts","../src/components/layout/PageHeader.tsx","../src/components/layout/AppShell.tsx"],"names":["readStoredOptions","OPTION_DEFAULTS","useState","useEffect","jsx","jsxs","Fragment","useMemo","useCallback","SlidersHorizontal"],"mappings":"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","file":"chunk-ZPPLUGLA.js","sourcesContent":["/**\n * Generic localStorage-backed persistence for \"customize chrome\" option\n * objects (AppShell, PageHeader, …). SSR-safe (no-ops without `window`) and\n * swallows quota / private-mode errors so a storage failure never breaks\n * rendering.\n *\n * Callers own their own shape validation — this module only handles the\n * JSON <-> localStorage plumbing shared across chrome components.\n */\nexport function readStoredOptions<T extends object>(key: string): Partial<T> {\n if (typeof window === \"undefined\") return {};\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return {};\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== \"object\") return {};\n return parsed as Partial<T>;\n } catch {\n return {};\n }\n}\n\nexport function writeStoredOptions<T extends object>(\n key: string,\n value: Partial<T>,\n): void {\n if (typeof window === \"undefined\") return;\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n // quota / private mode\n }\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useId, useMemo, useState, type ReactNode } from \"react\";\nimport type { LucideIcon } from \"lucide-react\";\nimport { ListFilter, MoreHorizontal, SlidersHorizontal } from \"lucide-react\";\nimport { Badge } from \"../ui/badge\";\nimport { Button } from \"../ui/button\";\nimport { Collapsible, CollapsibleContent } from \"../ui/collapsible\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../ui/popover\";\nimport { Switch } from \"../ui/switch\";\nimport { Label } from \"../ui/label\";\nimport { ViewToggle, type ViewMode } from \"../patterns/view-toggle\";\nimport { ViewSettings } from \"../patterns/view-settings\";\nimport type { Density } from \"../../hooks/use-view-settings\";\nimport { cn } from \"../../utils\";\nimport {\n readStoredOptions as readStoredOptionsRaw,\n writeStoredOptions,\n} from \"../../option-storage\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"../ui/dropdown-menu\";\n\n/**\n * Declarative chrome for PageHeader — developer defaults + optional\n * in-header user toggles (Notion/Linear “customize view” pattern).\n *\n * @stability Beta\n */\nexport interface PageHeaderOptions {\n showIcon?: boolean;\n showSubtitle?: boolean;\n showCount?: boolean;\n showViewToggle?: boolean;\n showViewSettings?: boolean;\n showCreate?: boolean;\n /**\n * Stat cards row under the title block. Off by default — a header that\n * receives `stats` still hides them until the developer or the user turns\n * this on, so an existing page gains no new chrome by upgrading.\n */\n showStats?: boolean;\n /** Ghost ListFilter control in the actions cluster */\n showDetailsToggle?: boolean;\n /** Quiet hint beside actions while details are collapsed */\n showDetailsHint?: boolean;\n /** Move subtitle into the details panel (default true when details exist) */\n subtitleInDetails?: boolean;\n defaultDetailsOpen?: boolean;\n detailsLabel?: string;\n /**\n * Show the in-header Header options popover so users can customize chrome.\n * Default true when the header has more than one chrome affordance.\n */\n configurable?: boolean;\n}\n\n/** @stability Beta */\nexport type PageHeaderOptionKey = keyof PageHeaderOptions;\n\n/** Naive English pluralization for the count badge — singular at exactly 1. */\nfunction pluralizeEntity(entity: string, count: number): string {\n if (count === 1) return entity;\n return entity.endsWith(\"s\") ? entity : `${entity}s`;\n}\n\nconst OPTION_DEFAULTS: Required<\n Omit<PageHeaderOptions, \"detailsLabel\" | \"defaultDetailsOpen\" | \"subtitleInDetails\" | \"configurable\">\n> &\n Pick<PageHeaderOptions, \"detailsLabel\" | \"defaultDetailsOpen\" | \"subtitleInDetails\" | \"configurable\"> = {\n showIcon: true,\n showSubtitle: true,\n showCount: true,\n showViewToggle: true,\n showViewSettings: true,\n showCreate: true,\n showStats: false,\n showDetailsToggle: true,\n showDetailsHint: true,\n subtitleInDetails: undefined,\n defaultDetailsOpen: false,\n detailsLabel: \"Details\",\n configurable: undefined,\n};\n\nconst BOOLEAN_OPTION_KEYS: PageHeaderOptionKey[] = [\n \"showIcon\",\n \"showSubtitle\",\n \"showCount\",\n \"showViewToggle\",\n \"showViewSettings\",\n \"showCreate\",\n \"showStats\",\n \"showDetailsToggle\",\n \"showDetailsHint\",\n \"subtitleInDetails\",\n \"defaultDetailsOpen\",\n \"configurable\",\n];\nconst STRING_OPTION_KEYS: PageHeaderOptionKey[] = [\"detailsLabel\"];\n\n/**\n * Reads persisted options and keeps only known keys whose stored value has\n * the expected primitive type — a malformed or hand-edited localStorage\n * value can't smuggle in an unexpected key or wrong-typed override.\n */\nfunction readStoredOptions(key: string): Partial<PageHeaderOptions> {\n const raw = readStoredOptionsRaw<PageHeaderOptions>(key);\n const sanitized: Partial<PageHeaderOptions> = {};\n for (const optKey of BOOLEAN_OPTION_KEYS) {\n const value = raw[optKey];\n if (typeof value === \"boolean\") {\n (sanitized as Record<string, boolean>)[optKey] = value;\n }\n }\n for (const optKey of STRING_OPTION_KEYS) {\n const value = raw[optKey];\n if (typeof value === \"string\") {\n (sanitized as Record<string, string>)[optKey] = value;\n }\n }\n return sanitized;\n}\n\n/** One entry in {@link PageHeaderProps.secondaryActions}. */\nexport interface PageHeaderAction {\n label: string;\n onClick: () => void;\n icon?: LucideIcon;\n /** `data-testid` on the menu item, so e2e can drive it. */\n testId?: string;\n disabled?: boolean;\n /** Omits the action entirely — for one gated on data. */\n hidden?: boolean;\n}\n\nexport interface PageHeaderProps {\n title: string;\n subtitle?: string;\n icon?: LucideIcon;\n /**\n * Header chrome config (developer defaults). Merged under user picks from\n * the in-header options menu when `options.configurable` / storage is on.\n */\n options?: PageHeaderOptions;\n /** Persist user header-option overrides (localStorage) */\n optionsStorageKey?: string;\n /** Extra trailing actions (after create); prefer first-class slots below */\n actions?: ReactNode;\n /**\n * Stat cards row, full width, directly under the title block and above\n * `details`. Layout-only: pass `<StatCard>`s (or anything else) and the\n * header positions them. Rendered only when `options.showStats` resolves\n * true — see {@link PageHeaderOptions.showStats}.\n */\n stats?: ReactNode;\n /** Collapsible details region below the title row */\n details?: ReactNode;\n detailsOpen?: boolean;\n onDetailsOpenChange?: (open: boolean) => void;\n detailsActive?: boolean;\n detailsHint?: ReactNode;\n /** Count badge */\n count?: number;\n totalCount?: number;\n /** Singular entity for “N rows” copy when unfiltered */\n entityName?: string;\n countActive?: boolean;\n viewMode?: ViewMode;\n onViewModeChange?: (mode: ViewMode) => void;\n pageSize?: number;\n onPageSizeChange?: (size: number) => void;\n density?: Density;\n onDensityChange?: (density: Density) => void;\n createAction?: ReactNode;\n /**\n * Additional actions, rendered in ONE overflow menu — never as more\n * side-by-side buttons.\n *\n * The budget is the point. A header with three top-level buttons squeezes\n * its own title to an ellipsis at real viewport widths, so a surface that\n * genuinely has four actions needs somewhere to put the other three. Before\n * this existed the only place was a fork of the header, which is exactly\n * what one consumer did.\n *\n * Pass `ReactNode` via `actions` instead when you need arbitrary chrome;\n * this prop is for the ordinary case, and it keeps the budget for you.\n */\n secondaryActions?: PageHeaderAction[];\n}\n\ntype ToggleDef = {\n key: keyof PageHeaderOptions;\n label: string;\n available: boolean;\n};\n\nexport function PageHeader({\n title,\n subtitle,\n icon: Icon,\n options: optionsProp,\n optionsStorageKey,\n actions,\n stats,\n details,\n detailsOpen,\n onDetailsOpenChange,\n detailsActive = false,\n detailsHint,\n count,\n totalCount,\n entityName,\n countActive = false,\n viewMode,\n onViewModeChange,\n pageSize,\n onPageSizeChange,\n density,\n onDensityChange,\n createAction,\n secondaryActions,\n}: PageHeaderProps) {\n const detailsRegionId = useId();\n const hasDetails = details != null;\n const hasStats = stats != null;\n const hasCount = count !== undefined && totalCount !== undefined;\n const hasViewToggle = Boolean(viewMode && onViewModeChange);\n const hasViewSettings = Boolean(onPageSizeChange && onDensityChange);\n const hasCreate = createAction != null;\n // `hidden` is filtered here rather than at the call site: an action gated on\n // data (\"Review drafts\", only when drafts exist) is the common case, and a\n // conditional array at every call site is what makes those call sites drift.\n const visibleSecondary = (secondaryActions ?? []).filter((a) => !a.hidden);\n\n const [userOverrides, setUserOverrides] = useState<Partial<PageHeaderOptions>>(\n {},\n );\n // Tracks which storage key the current overrides were hydrated from, so a\n // key change (routing between pages with distinct keys while this header\n // stays mounted) can't flush the previous key's overrides into the new key\n // before the read effect re-hydrates.\n const [hydratedKey, setHydratedKey] = useState<string | undefined>(\n optionsStorageKey ? undefined : \"\",\n );\n\n useEffect(() => {\n if (!optionsStorageKey) {\n setUserOverrides({});\n setHydratedKey(\"\");\n return;\n }\n setUserOverrides(readStoredOptions(optionsStorageKey));\n setHydratedKey(optionsStorageKey);\n }, [optionsStorageKey]);\n\n useEffect(() => {\n // Persist only once overrides belong to the current key (see hydratedKey).\n if (!optionsStorageKey || hydratedKey !== optionsStorageKey) return;\n writeStoredOptions(optionsStorageKey, userOverrides);\n }, [optionsStorageKey, userOverrides, hydratedKey]);\n\n const resolved = useMemo(() => {\n const merged: PageHeaderOptions = {\n ...OPTION_DEFAULTS,\n ...optionsProp,\n ...userOverrides,\n };\n if (merged.subtitleInDetails === undefined) {\n merged.subtitleInDetails = hasDetails;\n }\n if (merged.configurable === undefined) {\n merged.configurable =\n hasDetails || hasStats || hasCount || hasViewSettings || hasViewToggle;\n }\n return merged;\n }, [\n optionsProp,\n userOverrides,\n hasDetails,\n hasStats,\n hasCount,\n hasViewSettings,\n hasViewToggle,\n ]);\n\n const patchOption = useCallback((key: keyof PageHeaderOptions, value: boolean) => {\n setUserOverrides((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const moveSubtitle = Boolean(resolved.subtitleInDetails) && hasDetails;\n const showTitleSubtitle =\n Boolean(subtitle) &&\n Boolean(resolved.showSubtitle) &&\n !moveSubtitle;\n\n const [uncontrolledOpen, setUncontrolledOpen] = useState(\n () => optionsProp?.defaultDetailsOpen ?? OPTION_DEFAULTS.defaultDetailsOpen!,\n );\n const isControlled = detailsOpen !== undefined;\n const open = isControlled ? detailsOpen! : uncontrolledOpen;\n\n const setOpen = (next: boolean) => {\n if (!isControlled) setUncontrolledOpen(next);\n onDetailsOpenChange?.(next);\n };\n\n const detailsLabel = resolved.detailsLabel ?? \"Details\";\n const entity = entityName || \"item\";\n\n const toggles: ToggleDef[] = (\n [\n { key: \"showIcon\", label: \"Icon\", available: Boolean(Icon) },\n {\n key: \"showSubtitle\",\n label: \"Subtitle\",\n available: Boolean(subtitle) && !moveSubtitle,\n },\n { key: \"showCount\", label: \"Count\", available: hasCount },\n { key: \"showViewToggle\", label: \"View toggle\", available: hasViewToggle },\n {\n key: \"showViewSettings\",\n label: \"Display settings\",\n available: hasViewSettings,\n },\n { key: \"showCreate\", label: \"Create action\", available: hasCreate },\n { key: \"showStats\", label: \"Stat cards\", available: hasStats },\n {\n key: \"showDetailsToggle\",\n label: \"Filters / details\",\n available: hasDetails,\n },\n {\n key: \"showDetailsHint\",\n label: \"Collapsed hint\",\n available: hasDetails && detailsHint != null,\n },\n ] as const satisfies readonly ToggleDef[]\n ).filter((t) => t.available);\n\n const showDetailsToggle =\n hasDetails && Boolean(resolved.showDetailsToggle);\n const showStats = hasStats && Boolean(resolved.showStats);\n const showHint =\n hasDetails &&\n !open &&\n Boolean(resolved.showDetailsHint) &&\n detailsHint != null;\n\n const actionsCluster = (\n <div className=\"flex items-center gap-1.5 shrink-0\">\n {showHint ? (\n <span className=\"hidden max-w-[14rem] truncate text-xs text-muted-foreground sm:inline\">\n {detailsHint}\n </span>\n ) : null}\n\n {hasCount && resolved.showCount !== false ? (\n <Badge variant=\"secondary\" className=\"text-xs\">\n {countActive\n ? `${count} of ${totalCount}`\n : `${totalCount} ${pluralizeEntity(entity, totalCount!)}`}\n </Badge>\n ) : null}\n\n {hasViewToggle && resolved.showViewToggle !== false ? (\n <ViewToggle viewMode={viewMode!} onViewModeChange={onViewModeChange!} />\n ) : null}\n\n {showDetailsToggle ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\n \"relative h-8 w-8\",\n (open || detailsActive) && \"bg-accent text-accent-foreground\",\n )}\n onClick={() => setOpen(!open)}\n data-testid=\"page-header-details-toggle\"\n title={open ? `Hide ${detailsLabel}` : `Show ${detailsLabel}`}\n aria-label={open ? `Hide ${detailsLabel}` : `Show ${detailsLabel}`}\n aria-expanded={open}\n aria-controls={detailsRegionId}\n testID=\"page-header-details-toggle\"\n >\n <ListFilter className=\"h-4 w-4\" />\n {detailsActive ? (\n <span\n className=\"absolute right-1.5 top-1.5 size-1.5 rounded-full bg-primary\"\n aria-hidden\n />\n ) : null}\n </Button>\n ) : null}\n\n {hasViewSettings && resolved.showViewSettings !== false ? (\n <ViewSettings\n pageSize={pageSize ?? 25}\n onPageSizeChange={onPageSizeChange!}\n density={density ?? \"comfortable\"}\n onDensityChange={onDensityChange!}\n />\n ) : null}\n\n {hasCreate && resolved.showCreate !== false ? createAction : null}\n\n {visibleSecondary.length > 0 ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"More actions\"\n data-testid=\"page-header-more-actions\"\n >\n <MoreHorizontal className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {visibleSecondary.map((action) => {\n const ActionIcon = action.icon;\n return (\n <DropdownMenuItem\n key={action.label}\n onSelect={action.onClick}\n disabled={action.disabled}\n data-testid={action.testId}\n >\n {ActionIcon ? (\n <ActionIcon className=\"mr-2 h-4 w-4\" aria-hidden=\"true\" />\n ) : null}\n {action.label}\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n ) : null}\n\n {actions}\n\n {resolved.configurable && toggles.length > 0 ? (\n <Popover>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n title=\"Header options\"\n aria-label=\"Header options\"\n data-testid=\"page-header-options\"\n testID=\"page-header-options\"\n >\n <SlidersHorizontal className=\"h-4 w-4\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-64 space-y-3\">\n <div>\n <h4 className=\"text-sm font-semibold\">Header options</h4>\n <p className=\"text-xs text-muted-foreground\">\n Customize this page header. Saved on this device when a storage\n key is set.\n </p>\n </div>\n <ul className=\"space-y-3\">\n {toggles.map((t) => {\n const checked = Boolean(resolved[t.key] ?? true);\n return (\n <li\n key={t.key}\n className=\"flex items-center justify-between gap-3\"\n >\n <Label\n htmlFor={`page-header-opt-${t.key}`}\n className=\"text-sm font-normal\"\n >\n {t.label}\n </Label>\n <Switch\n id={`page-header-opt-${t.key}`}\n checked={checked}\n onCheckedChange={(v) => patchOption(t.key, v)}\n />\n </li>\n );\n })}\n </ul>\n {optionsStorageKey && Object.keys(userOverrides).length > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-full text-xs\"\n onClick={() => setUserOverrides({})}\n data-testid=\"page-header-options-reset\"\n aria-label=\"Reset header options\"\n testID=\"page-header-options-reset\"\n >\n Reset to defaults\n </Button>\n ) : null}\n </PopoverContent>\n </Popover>\n ) : null}\n </div>\n );\n\n const titleRow = (\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3 min-w-0\">\n {Icon && resolved.showIcon !== false ? (\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-primary/10\">\n <Icon className=\"h-5 w-5 text-primary\" />\n </div>\n ) : null}\n <div className=\"min-w-0\">\n <h1\n data-testid=\"page-title\"\n className=\"truncate font-display text-xl font-bold tracking-tight sm:text-2xl\"\n >\n {title}\n </h1>\n {showTitleSubtitle ? (\n <p className=\"truncate text-sm text-muted-foreground\">{subtitle}</p>\n ) : null}\n </div>\n </div>\n {actionsCluster}\n </div>\n );\n\n const statsRow = showStats ? (\n <div\n data-testid=\"page-header-stats\"\n className=\"mt-3 grid gap-3 sm:grid-cols-2 lg:grid-cols-4\"\n >\n {stats}\n </div>\n ) : null;\n\n if (!hasDetails) {\n return (\n <>\n {titleRow}\n {statsRow}\n </>\n );\n }\n\n return (\n <Collapsible open={open} onOpenChange={setOpen}>\n {titleRow}\n {statsRow}\n <CollapsibleContent id={detailsRegionId} className=\"mt-3\">\n <div className=\"flex flex-col gap-3 rounded-lg border border-border bg-card/40 p-3 sm:p-4\">\n {moveSubtitle && subtitle && resolved.showSubtitle !== false ? (\n <p className=\"text-sm text-muted-foreground\">{subtitle}</p>\n ) : null}\n {details}\n </div>\n </CollapsibleContent>\n </Collapsible>\n );\n}\n","\"use client\";\n\nimport {\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\nimport {\n Menu,\n X,\n Sun,\n Moon,\n SlidersHorizontal,\n PanelLeftClose,\n PanelLeft,\n ChevronDown,\n} from \"lucide-react\";\nimport type { LucideIcon } from \"lucide-react\";\nimport { Button } from \"../ui/button\";\nimport { Badge } from \"../ui/badge\";\nimport { Label } from \"../ui/label\";\nimport { Switch } from \"../ui/switch\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../ui/popover\";\nimport { Collapsible, CollapsibleContent } from \"../ui/collapsible\";\nimport { cn } from \"../../utils\";\nimport { readStoredOptions, writeStoredOptions } from \"../../option-storage\";\nimport {\n DefaultLink,\n isPathActive,\n type LinkComponent,\n} from \"../../navigation\";\n\nexport interface NavItem {\n to: string;\n icon: LucideIcon;\n label: string;\n /** Nested items — rendered as a collapsible group when the rail is expanded */\n children?: NavItem[];\n badge?: string | number;\n disabled?: boolean;\n /**\n * Exact-match active state (e.g. overview `/`). Exact on the path only, so\n * an `activePath` carrying `?search#hash` still matches.\n */\n end?: boolean;\n}\n\n/**\n * Declarative chrome for AppShell — developer defaults + optional\n * in-sidebar user toggles (Notion/Linear “customize chrome” pattern).\n *\n * @stability Beta\n */\nexport interface AppShellOptions {\n showLogo?: boolean;\n showAppName?: boolean;\n showThemeToggle?: boolean;\n /** Desktop rail collapse control */\n showCollapse?: boolean;\n showUserSlot?: boolean;\n showFooter?: boolean;\n showSecondaryNav?: boolean;\n /** Mobile fixed bottom tab bar */\n showMobileBottomNav?: boolean;\n defaultCollapsed?: boolean;\n /**\n * Show the in-sidebar Sidebar options popover so users can customize chrome.\n * Default true when collapse / secondary / theme / slots are available.\n */\n configurable?: boolean;\n /**\n * Toggle keys the developer has decided on and users may not override —\n * e.g. hiding `showThemeToggle` for a brand that manages theme elsewhere.\n * Locked keys are dropped from the options popover and any stored user\n * override for them is ignored on resolve.\n */\n lockedOptions?: (keyof AppShellOptions)[];\n /** Comfortable (default) vs compact row padding */\n density?: \"comfortable\" | \"compact\";\n /** Expanded rail width in rem-ish tokens: narrow ≈ 13rem, wide ≈ 16rem */\n railWidth?: \"narrow\" | \"wide\";\n /**\n * Where page content scrolls.\n * - `container` (default): main panel scrolls — classic app-shell chrome.\n * - `window`: document/window scrolls with a sticky rail so React Router\n * `ScrollRestoration` keeps working (dashboard dogfood).\n */\n scrollRoot?: \"container\" | \"window\";\n}\n\nconst OPTION_DEFAULTS: Required<\n Omit<AppShellOptions, \"configurable\" | \"defaultCollapsed\" | \"lockedOptions\">\n> &\n Pick<AppShellOptions, \"configurable\" | \"defaultCollapsed\" | \"lockedOptions\"> = {\n showLogo: true,\n showAppName: true,\n showThemeToggle: true,\n showCollapse: true,\n showUserSlot: true,\n showFooter: true,\n showSecondaryNav: true,\n showMobileBottomNav: true,\n defaultCollapsed: false,\n configurable: undefined,\n density: \"comfortable\",\n railWidth: \"narrow\",\n scrollRoot: \"container\",\n};\n\nfunction readStoredCollapsed(key: string): boolean | undefined {\n if (typeof window === \"undefined\") return undefined;\n try {\n const raw = localStorage.getItem(`${key}:collapsed`);\n if (raw === \"1\") return true;\n if (raw === \"0\") return false;\n return undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeStoredCollapsed(key: string, collapsed: boolean) {\n if (typeof window === \"undefined\") return;\n try {\n localStorage.setItem(`${key}:collapsed`, collapsed ? \"1\" : \"0\");\n } catch {\n // quota / private mode\n }\n}\n\n/**\n * @stability Beta\n */\nexport interface AppShellProps {\n /** App name shown in sidebar header */\n appName: string;\n /** App logo letter or short text (ignored when `logo` is set) */\n appLogo?: string;\n /** Custom logo node — preferred over `appLogo` letter tile */\n logo?: ReactNode;\n /** Primary navigation items */\n navItems: NavItem[];\n /** Secondary nav (e.g. “More”) — rendered below a divider when shown */\n secondaryNavItems?: NavItem[];\n /** Label above secondary nav (default “More”) */\n secondaryNavLabel?: string;\n /**\n * Items for the mobile bottom tab bar. Defaults to top-level `navItems`\n * (no children). Pass a short list for apps with large sidebars; pass `[]`\n * and set `options.showMobileBottomNav: false` to hide.\n */\n mobileNavItems?: NavItem[];\n /** Slot below the logo row (product switcher, workspace picker, …) */\n headerSlot?: ReactNode;\n /**\n * Compact slot shown in the **collapsed** desktop rail, where `headerSlot`\n * is hidden for space. Use it to keep a core affordance reachable without\n * expanding — e.g. an icon-only product switcher. Omit to hide the header\n * region entirely while collapsed.\n */\n collapsedHeaderSlot?: ReactNode;\n /** Footer region above the user slot (version, links) */\n footer?: ReactNode;\n /**\n * User / account block at the bottom of the sidebar. When the rail is\n * collapsed, AppShell hides any descendant carrying the\n * `data-shell-user-detail` attribute (e.g. name/email text) so only a\n * compact element (avatar, initial) remains — tag the detail wrapper you\n * want hidden in the collapsed rail with that attribute.\n */\n userSlot?: ReactNode;\n /** Page content */\n children: ReactNode;\n /**\n * @deprecated Prefer `options.showThemeToggle`. Kept for sample-app\n * back-compat; merges into resolved options when `options` omits it.\n */\n showThemeToggle?: boolean;\n /** Chrome config (developer defaults + optional in-sidebar menu) */\n options?: AppShellOptions;\n /** Persist user sidebar-option overrides + collapse state (localStorage) */\n optionsStorageKey?: string;\n /** Controlled collapse (desktop rail) */\n collapsed?: boolean;\n onCollapsedChange?: (collapsed: boolean) => void;\n /**\n * Injectable link component. Defaults to a plain `<a>` (no router). Pass a\n * router adapter — e.g. React Router `({ href, ...p }) => <Link to={href} {...p} />`\n * or Next.js `next/link` — to get client-side navigation.\n */\n linkComponent?: LinkComponent;\n /**\n * Current location used to highlight the active nav item. Pass the full\n * location, `pathname + search + hash` (React Router: `useLocation()`), so\n * items whose `to` names a query or hash (`/admin?tab=users`) can become\n * active. A pathname alone still works for path-only items, `end` included.\n * Omit to disable active highlighting.\n */\n activePath?: string;\n /**\n * Optional custom active-state matcher. Defaults to a prefix match (with `/`\n * treated as exact), mirroring React Router `NavLink`.\n */\n isActive?: (to: string, activePath?: string) => boolean;\n}\n\nfunction ThemeToggle({ testId }: { testId: string }) {\n const [dark, setDark] = useState(false);\n\n useEffect(() => {\n const stored = localStorage.getItem(\"theme\");\n const prefersDark = window.matchMedia(\n \"(prefers-color-scheme: dark)\",\n ).matches;\n const isDark = stored === \"dark\" || (!stored && prefersDark);\n setDark(isDark);\n document.documentElement.classList.toggle(\"dark\", isDark);\n }, []);\n\n function toggle() {\n const next = !dark;\n setDark(next);\n document.documentElement.classList.toggle(\"dark\", next);\n localStorage.setItem(\"theme\", next ? \"dark\" : \"light\");\n }\n\n return (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={toggle}\n className=\"h-8 w-8\"\n title=\"Toggle theme\"\n aria-label=\"Toggle theme\"\n data-testid={testId}\n testID={testId}\n >\n {dark ? <Sun className=\"h-4 w-4\" /> : <Moon className=\"h-4 w-4\" />}\n </Button>\n );\n}\n\nfunction itemIsActive(\n item: NavItem,\n activePath: string | undefined,\n isActive: (to: string, activePath?: string) => boolean,\n): boolean {\n // `end` is exact on the path but ignores an incidental query/hash, so an\n // `end` item stays active on `/dashboard?page=2`.\n if (item.end) {\n return isPathActive(item.to, activePath, { end: true });\n }\n if (isActive(item.to, activePath)) return true;\n return Boolean(\n item.children?.some((child) => itemIsActive(child, activePath, isActive)),\n );\n}\n\nfunction NavLinkRow({\n item,\n collapsed,\n density,\n onNavClick,\n linkComponent: LinkComp,\n activePath,\n isActive,\n}: {\n item: NavItem;\n collapsed: boolean;\n density: \"comfortable\" | \"compact\";\n onNavClick?: () => void;\n linkComponent: LinkComponent;\n activePath?: string;\n isActive: (to: string, activePath?: string) => boolean;\n}) {\n const active = itemIsActive(item, activePath, isActive);\n const pad = density === \"compact\" ? \"px-2.5 py-1.5\" : \"px-3 py-2\";\n\n if (item.disabled) {\n return (\n <span\n title={item.label}\n className={cn(\n \"flex items-center gap-3 rounded-lg text-sm font-medium opacity-40\",\n pad,\n collapsed && \"justify-center px-2\",\n )}\n aria-disabled\n >\n <item.icon className=\"h-4 w-4 shrink-0\" />\n {!collapsed ? <span className=\"truncate\">{item.label}</span> : null}\n </span>\n );\n }\n\n return (\n <LinkComp\n href={item.to}\n onClick={onNavClick}\n title={collapsed ? item.label : undefined}\n aria-label={collapsed ? item.label : undefined}\n aria-current={active ? \"page\" : undefined}\n className={cn(\n \"flex items-center gap-3 rounded-lg text-sm font-medium transition-colors\",\n pad,\n collapsed && \"justify-center px-2\",\n active\n ? \"bg-sidebar-accent text-sidebar-primary\"\n : \"text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n )}\n >\n <item.icon className=\"h-4 w-4 shrink-0\" />\n {!collapsed ? (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.badge != null ? (\n <Badge variant=\"secondary\" className=\"h-5 shrink-0 px-1.5 text-[10px]\">\n {item.badge}\n </Badge>\n ) : null}\n </>\n ) : null}\n </LinkComp>\n );\n}\n\nfunction NavGroup({\n item,\n collapsed,\n density,\n onNavClick,\n linkComponent,\n activePath,\n isActive,\n}: {\n item: NavItem;\n collapsed: boolean;\n density: \"comfortable\" | \"compact\";\n onNavClick?: () => void;\n linkComponent: LinkComponent;\n activePath?: string;\n isActive: (to: string, activePath?: string) => boolean;\n}) {\n const children = item.children ?? [];\n const childActive = children.some((c) =>\n itemIsActive(c, activePath, isActive),\n );\n const [open, setOpen] = useState(childActive);\n\n useEffect(() => {\n if (childActive) setOpen(true);\n }, [childActive]);\n\n if (children.length === 0) {\n return (\n <NavLinkRow\n item={item}\n collapsed={collapsed}\n density={density}\n onNavClick={onNavClick}\n linkComponent={linkComponent}\n activePath={activePath}\n isActive={isActive}\n />\n );\n }\n\n // Collapsed rail: only the parent link (or first child if parent is a group stub)\n if (collapsed) {\n const target: NavItem = item.to\n ? { ...item, children: undefined }\n : { ...children[0]!, children: undefined };\n return (\n <NavLinkRow\n item={target}\n collapsed\n density={density}\n onNavClick={onNavClick}\n linkComponent={linkComponent}\n activePath={activePath}\n isActive={isActive}\n />\n );\n }\n\n // Parent may be its own route (`item.to`) — keep a real link and a separate\n // expand control so groups under an existing page stay navigable + highlightable.\n // Visual highlight may use prefix match; aria-current is exact-only so a child\n // route does not leave two \"page\" currents in the tree (CMT-322-008). Exact\n // means the same path; a query/hash on `activePath` alone doesn't break it.\n const selfExact = Boolean(\n item.to && isPathActive(item.to, activePath, { end: true }),\n );\n const selfActive = item.to\n ? item.end\n ? selfExact\n : isActive(item.to, activePath)\n : false;\n const groupHighlight = selfActive || childActive;\n const pad = density === \"compact\" ? \"px-2.5 py-1.5\" : \"px-3 py-2\";\n const LinkComp = linkComponent;\n const parentClass = cn(\n \"flex min-w-0 flex-1 items-center gap-3 rounded-lg text-sm font-medium transition-colors\",\n pad,\n groupHighlight\n ? \"bg-sidebar-accent/60 text-sidebar-primary\"\n : \"text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n );\n\n return (\n <Collapsible open={open} onOpenChange={setOpen}>\n <div className=\"space-y-0.5\">\n <div className=\"flex items-center gap-0.5\" data-testid={`nav-group-${item.label}`}>\n {item.to && !item.disabled ? (\n <LinkComp\n href={item.to}\n onClick={onNavClick}\n aria-current={selfExact ? \"page\" : undefined}\n className={parentClass}\n >\n <item.icon className=\"h-4 w-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate text-left\">{item.label}</span>\n </LinkComp>\n ) : (\n <button\n type=\"button\"\n className={parentClass}\n onClick={() => setOpen((o) => !o)}\n aria-expanded={open}\n aria-disabled={item.disabled || undefined}\n disabled={item.disabled}\n >\n <item.icon className=\"h-4 w-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate text-left\">{item.label}</span>\n </button>\n )}\n <button\n type=\"button\"\n className={cn(\n \"shrink-0 rounded-lg p-1.5 text-sidebar-foreground transition-colors hover:bg-sidebar-accent\",\n groupHighlight && \"text-sidebar-primary\",\n item.disabled && \"pointer-events-none opacity-40\",\n )}\n onClick={() => setOpen((o) => !o)}\n aria-expanded={open}\n aria-label={open ? `Collapse ${item.label}` : `Expand ${item.label}`}\n aria-disabled={item.disabled || undefined}\n disabled={item.disabled}\n data-testid={`nav-group-toggle-${item.label}`}\n >\n <ChevronDown\n className={cn(\n \"h-3.5 w-3.5 opacity-60 transition-transform\",\n open && \"rotate-180\",\n )}\n />\n </button>\n </div>\n <CollapsibleContent>\n <div className=\"ml-3 space-y-0.5 border-l border-sidebar-border pl-2\">\n {children.map((child) => (\n <NavLinkRow\n key={child.to + child.label}\n item={child}\n collapsed={false}\n density={density}\n onNavClick={onNavClick}\n linkComponent={linkComponent}\n activePath={activePath}\n isActive={isActive}\n />\n ))}\n </div>\n </CollapsibleContent>\n </div>\n </Collapsible>\n );\n}\n\nfunction SidebarNav({\n navItems,\n secondaryNavItems,\n secondaryNavLabel,\n showSecondary,\n collapsed,\n density,\n onNavClick,\n linkComponent,\n activePath,\n isActive,\n}: {\n navItems: NavItem[];\n secondaryNavItems?: NavItem[];\n secondaryNavLabel: string;\n showSecondary: boolean;\n collapsed: boolean;\n density: \"comfortable\" | \"compact\";\n onNavClick?: () => void;\n linkComponent: LinkComponent;\n activePath?: string;\n isActive: (to: string, activePath?: string) => boolean;\n}) {\n return (\n <nav\n className={cn(\n \"flex-1 space-y-1 overflow-y-auto\",\n collapsed ? \"p-2\" : \"p-3\",\n )}\n aria-label=\"Main\"\n >\n {navItems.map((item) => (\n <NavGroup\n key={item.to + item.label}\n item={item}\n collapsed={collapsed}\n density={density}\n onNavClick={onNavClick}\n linkComponent={linkComponent}\n activePath={activePath}\n isActive={isActive}\n />\n ))}\n\n {showSecondary && secondaryNavItems && secondaryNavItems.length > 0 ? (\n <div\n className={cn(\n \"mt-4 space-y-1 border-t border-sidebar-border pt-4\",\n collapsed && \"mt-2 pt-2\",\n )}\n >\n {!collapsed ? (\n <p className=\"mb-2 px-3 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/40\">\n {secondaryNavLabel}\n </p>\n ) : null}\n {secondaryNavItems.map((item) => (\n <NavGroup\n key={item.to + item.label}\n item={item}\n collapsed={collapsed}\n density={density}\n onNavClick={onNavClick}\n linkComponent={linkComponent}\n activePath={activePath}\n isActive={isActive}\n />\n ))}\n </div>\n ) : null}\n </nav>\n );\n}\n\nfunction SidebarBrand({\n appName,\n appLogo,\n logo,\n showLogo,\n showAppName,\n collapsed,\n}: {\n appName: string;\n appLogo?: string;\n logo?: ReactNode;\n showLogo: boolean;\n showAppName: boolean;\n collapsed: boolean;\n}) {\n if (!showLogo && !showAppName) return null;\n\n return (\n <div\n className={cn(\n \"flex min-w-0 items-center gap-2\",\n collapsed && \"justify-center\",\n )}\n >\n {showLogo ? (\n logo ? (\n <div className=\"flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden\">\n {logo}\n </div>\n ) : appLogo ? (\n <div className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-primary\">\n <span className=\"text-sm font-bold text-primary-foreground\">\n {appLogo}\n </span>\n </div>\n ) : null\n ) : null}\n {showAppName && !collapsed ? (\n <span className=\"truncate font-semibold text-sidebar-foreground\">\n {appName}\n </span>\n ) : null}\n </div>\n );\n}\n\ntype ToggleDef = {\n key: keyof AppShellOptions;\n label: string;\n available: boolean;\n};\n\nexport function AppShell({\n appName,\n appLogo,\n logo,\n navItems,\n secondaryNavItems,\n secondaryNavLabel = \"More\",\n mobileNavItems,\n headerSlot,\n collapsedHeaderSlot,\n footer,\n userSlot,\n children,\n showThemeToggle: showThemeToggleProp,\n options: optionsProp,\n optionsStorageKey,\n collapsed: collapsedProp,\n onCollapsedChange,\n linkComponent = DefaultLink,\n activePath,\n isActive = isPathActive,\n}: AppShellProps) {\n const [mobileOpen, setMobileOpen] = useState(false);\n const [userOverrides, setUserOverrides] = useState<Partial<AppShellOptions>>(\n {},\n );\n // Tracks which storage key the current overrides were hydrated from, so a\n // key change can't flush the previous key's overrides into the new key\n // before the read effect re-hydrates.\n const [hydratedKey, setHydratedKey] = useState<string | undefined>(\n optionsStorageKey ? undefined : \"\",\n );\n\n // Close the drawer on any path change (product switcher, deep links, …).\n useEffect(() => {\n setMobileOpen(false);\n }, [activePath]);\n\n const hasSecondary = Boolean(secondaryNavItems && secondaryNavItems.length);\n const hasUser = userSlot != null;\n const hasFooter = footer != null;\n const hasLogo = Boolean(logo || appLogo);\n\n useEffect(() => {\n if (!optionsStorageKey) {\n setUserOverrides({});\n setHydratedKey(\"\");\n return;\n }\n setUserOverrides(readStoredOptions<AppShellOptions>(optionsStorageKey));\n setHydratedKey(optionsStorageKey);\n }, [optionsStorageKey]);\n\n useEffect(() => {\n // Persist only once overrides belong to the current key (see hydratedKey).\n if (!optionsStorageKey || hydratedKey !== optionsStorageKey) return;\n writeStoredOptions<AppShellOptions>(optionsStorageKey, userOverrides);\n }, [optionsStorageKey, userOverrides, hydratedKey]);\n\n const lockedOptions = optionsProp?.lockedOptions;\n\n const resolved = useMemo(() => {\n const merged: AppShellOptions = {\n ...OPTION_DEFAULTS,\n ...(showThemeToggleProp !== undefined\n ? { showThemeToggle: showThemeToggleProp }\n : {}),\n ...optionsProp,\n ...userOverrides,\n };\n if (merged.configurable === undefined) {\n merged.configurable =\n hasSecondary ||\n hasUser ||\n hasFooter ||\n Boolean(merged.showCollapse) ||\n Boolean(merged.showThemeToggle);\n }\n // Locked keys always resolve to the developer default, never a stored\n // or in-session user override.\n if (lockedOptions?.length) {\n const defaults = OPTION_DEFAULTS as Record<string, unknown>;\n const devOptions = optionsProp as Record<string, unknown> | undefined;\n const target = merged as Record<string, unknown>;\n for (const key of lockedOptions) {\n target[key] = devOptions?.[key] ?? defaults[key];\n }\n }\n return merged;\n }, [\n optionsProp,\n userOverrides,\n showThemeToggleProp,\n hasSecondary,\n hasUser,\n hasFooter,\n lockedOptions,\n ]);\n\n const patchOption = useCallback(\n (key: keyof AppShellOptions, value: boolean) => {\n if (lockedOptions?.includes(key)) return;\n setUserOverrides((prev) => ({ ...prev, [key]: value }));\n },\n [lockedOptions],\n );\n\n const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(\n () =>\n optionsProp?.defaultCollapsed ?? OPTION_DEFAULTS.defaultCollapsed!,\n );\n\n useEffect(() => {\n if (!optionsStorageKey || collapsedProp !== undefined) return;\n const stored = readStoredCollapsed(optionsStorageKey);\n if (stored !== undefined) setUncontrolledCollapsed(stored);\n }, [optionsStorageKey, collapsedProp]);\n\n const isCollapseControlled = collapsedProp !== undefined;\n const collapsed = isCollapseControlled\n ? collapsedProp!\n : uncontrolledCollapsed;\n\n const setCollapsed = (next: boolean) => {\n if (!isCollapseControlled) {\n setUncontrolledCollapsed(next);\n if (optionsStorageKey) writeStoredCollapsed(optionsStorageKey, next);\n }\n onCollapsedChange?.(next);\n };\n\n const density = resolved.density ?? \"comfortable\";\n const scrollWindow = resolved.scrollRoot === \"window\";\n const railExpanded =\n resolved.railWidth === \"wide\" ? \"md:w-64\" : \"md:w-52\";\n const railClass = collapsed ? \"md:w-14\" : railExpanded;\n\n const bottomItems = (\n mobileNavItems ??\n navItems.filter((i) => !i.children || i.children.length === 0)\n ).filter((i) => !i.disabled);\n\n const toggles: ToggleDef[] = (\n [\n { key: \"showLogo\", label: \"Logo\", available: hasLogo },\n { key: \"showAppName\", label: \"App name\", available: true },\n {\n key: \"showThemeToggle\",\n label: \"Theme toggle\",\n available: true,\n },\n { key: \"showCollapse\", label: \"Collapse control\", available: true },\n {\n key: \"showSecondaryNav\",\n label: \"Secondary nav\",\n available: hasSecondary,\n },\n { key: \"showFooter\", label: \"Footer\", available: hasFooter },\n { key: \"showUserSlot\", label: \"User block\", available: hasUser },\n {\n key: \"showMobileBottomNav\",\n label: \"Mobile bottom nav\",\n available: bottomItems.length > 0,\n },\n ] as const satisfies readonly ToggleDef[]\n ).filter((t) => t.available && !lockedOptions?.includes(t.key));\n\n const showTheme = resolved.showThemeToggle !== false;\n const showCollapseCtrl = resolved.showCollapse !== false;\n const showSecondary = hasSecondary && resolved.showSecondaryNav !== false;\n const showFooterSlot = hasFooter && resolved.showFooter !== false;\n const showUser = hasUser && resolved.showUserSlot !== false;\n const showMobileBottom =\n bottomItems.length > 0 && resolved.showMobileBottomNav !== false;\n\n const optionsControl =\n resolved.configurable && toggles.length > 0 ? (\n <Popover>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n title=\"Sidebar options\"\n aria-label=\"Sidebar options\"\n data-testid=\"app-shell-options\"\n testID=\"app-shell-options\"\n >\n <SlidersHorizontal className=\"h-4 w-4\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" side=\"top\" className=\"w-64 space-y-3\">\n <div>\n <h4 className=\"text-sm font-semibold\">Sidebar options</h4>\n <p className=\"text-xs text-muted-foreground\">\n Customize this sidebar. Saved on this device when a storage key is\n set.\n </p>\n </div>\n <ul className=\"space-y-3\">\n {toggles.map((t) => {\n const checked = Boolean(resolved[t.key] ?? true);\n return (\n <li\n key={t.key}\n className=\"flex items-center justify-between gap-3\"\n >\n <Label\n htmlFor={`app-shell-opt-${t.key}`}\n className=\"text-sm font-normal\"\n >\n {t.label}\n </Label>\n <Switch\n id={`app-shell-opt-${t.key}`}\n checked={checked}\n onCheckedChange={(v) => patchOption(t.key, v)}\n />\n </li>\n );\n })}\n </ul>\n {optionsStorageKey && Object.keys(userOverrides).length > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-full text-xs\"\n onClick={() => setUserOverrides({})}\n data-testid=\"app-shell-options-reset\"\n aria-label=\"Reset sidebar options\"\n testID=\"app-shell-options-reset\"\n >\n Reset to defaults\n </Button>\n ) : null}\n </PopoverContent>\n </Popover>\n ) : null;\n\n const sidebarChrome = (opts: { mobile?: boolean }) => (\n <>\n <div\n className={cn(\n \"flex h-14 shrink-0 items-center gap-2 border-b border-sidebar-border\",\n collapsed && !opts.mobile ? \"justify-center px-2\" : \"px-3\",\n )}\n >\n <div className=\"min-w-0 flex-1\">\n <SidebarBrand\n appName={appName}\n appLogo={appLogo}\n logo={logo}\n showLogo={resolved.showLogo !== false}\n showAppName={resolved.showAppName !== false}\n collapsed={collapsed && !opts.mobile}\n />\n </div>\n {opts.mobile ? (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setMobileOpen(false)}\n aria-label=\"Close navigation\"\n data-testid=\"app-shell-mobile-close\"\n testID=\"app-shell-mobile-close\"\n >\n <X className=\"h-5 w-5\" />\n </Button>\n ) : (\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {showCollapseCtrl ? (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => setCollapsed(!collapsed)}\n title={collapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n aria-label={collapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n aria-pressed={collapsed}\n data-testid=\"app-shell-collapse\"\n testID=\"app-shell-collapse\"\n >\n {collapsed ? (\n <PanelLeft className=\"h-4 w-4\" />\n ) : (\n <PanelLeftClose className=\"h-4 w-4\" />\n )}\n </Button>\n ) : null}\n {!collapsed ? optionsControl : null}\n </div>\n )}\n </div>\n\n {collapsed && !opts.mobile ? (\n collapsedHeaderSlot ? (\n <div className=\"shrink-0 border-b border-sidebar-border p-2\">\n {collapsedHeaderSlot}\n </div>\n ) : null\n ) : headerSlot ? (\n <div className=\"shrink-0 border-b border-sidebar-border\">\n {headerSlot}\n </div>\n ) : null}\n\n <SidebarNav\n navItems={navItems}\n secondaryNavItems={secondaryNavItems}\n secondaryNavLabel={secondaryNavLabel}\n showSecondary={showSecondary}\n collapsed={collapsed && !opts.mobile}\n density={density}\n onNavClick={opts.mobile ? () => setMobileOpen(false) : undefined}\n linkComponent={linkComponent}\n activePath={activePath}\n isActive={isActive}\n />\n\n {(showFooterSlot || showUser || showTheme || (collapsed && optionsControl)) &&\n !opts.mobile ? (\n <div className=\"mt-auto shrink-0 space-y-2 border-t border-sidebar-border p-2\">\n {showFooterSlot && !collapsed ? footer : null}\n {showUser ? (\n <div\n className={cn(\n collapsed &&\n \"flex justify-center [&_[data-shell-user-detail]]:hidden\",\n )}\n >\n {userSlot}\n </div>\n ) : null}\n <div\n className={cn(\n \"flex items-center gap-1\",\n collapsed ? \"flex-col\" : \"justify-end\",\n )}\n >\n {collapsed ? optionsControl : null}\n {showTheme ? (\n <ThemeToggle testId=\"app-shell-theme-toggle-desktop\" />\n ) : null}\n </div>\n </div>\n ) : null}\n\n {opts.mobile && (showFooterSlot || showUser || showTheme) ? (\n <div className=\"mt-auto shrink-0 space-y-2 border-t border-sidebar-border p-3\">\n {showFooterSlot ? footer : null}\n {showUser ? userSlot : null}\n {showTheme ? (\n <div className=\"flex justify-end\">\n <ThemeToggle testId=\"app-shell-theme-toggle-mobile-drawer\" />\n </div>\n ) : null}\n </div>\n ) : null}\n </>\n );\n\n return (\n <div\n className={cn(\n \"flex\",\n scrollWindow ? \"min-h-screen\" : \"h-screen overflow-hidden\",\n )}\n >\n {/* Desktop sidebar */}\n <aside\n className={cn(\n \"hidden flex-col border-r border-sidebar-border bg-sidebar transition-[width] duration-200 md:flex\",\n scrollWindow && \"sticky top-0 h-screen\",\n railClass,\n )}\n data-collapsed={collapsed || undefined}\n data-testid=\"app-shell-sidebar\"\n >\n {sidebarChrome({})}\n </aside>\n\n {/* Mobile overlay */}\n {mobileOpen ? (\n <div\n className=\"fixed inset-0 z-40 bg-black/50 md:hidden\"\n onClick={() => setMobileOpen(false)}\n aria-hidden\n />\n ) : null}\n\n {/* Mobile sidebar — inert when closed so off-screen links leave tab order */}\n <aside\n className={cn(\n \"fixed inset-y-0 left-0 z-50 flex w-60 flex-col border-r border-sidebar-border bg-sidebar transition-transform md:hidden\",\n mobileOpen ? \"translate-x-0\" : \"-translate-x-full pointer-events-none\",\n )}\n id=\"app-shell-mobile-nav\"\n aria-hidden={!mobileOpen}\n inert={!mobileOpen || undefined}\n >\n {sidebarChrome({ mobile: true })}\n </aside>\n\n {/* Main content */}\n <div\n className={cn(\n \"flex min-w-0 flex-1 flex-col\",\n !scrollWindow && \"overflow-hidden\",\n )}\n >\n <header\n className={cn(\n \"flex h-14 items-center gap-3 border-b px-4 md:hidden\",\n scrollWindow && \"sticky top-0 z-20 bg-background\",\n )}\n >\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setMobileOpen(true)}\n aria-label=\"Open navigation\"\n aria-expanded={mobileOpen}\n aria-controls=\"app-shell-mobile-nav\"\n data-testid=\"app-shell-mobile-open\"\n testID=\"app-shell-mobile-open\"\n >\n <Menu className=\"h-5 w-5\" />\n </Button>\n <span className=\"flex-1 truncate font-semibold\">{appName}</span>\n {showTheme ? (\n <ThemeToggle testId=\"app-shell-theme-toggle-mobile-header\" />\n ) : null}\n </header>\n\n <main\n className={cn(\n \"flex-1\",\n !scrollWindow && \"overflow-y-auto\",\n showMobileBottom ? \"pb-16 md:pb-0\" : \"pb-0\",\n )}\n >\n <div className=\"min-w-0 p-4 sm:p-6 lg:p-8\">{children}</div>\n </main>\n\n {showMobileBottom ? (\n <nav\n className=\"fixed bottom-0 left-0 right-0 z-30 flex border-t bg-background md:hidden\"\n aria-label=\"Primary\"\n >\n {bottomItems.map((item) => {\n const LinkComp = linkComponent;\n const active = itemIsActive(item, activePath, isActive);\n return (\n <LinkComp\n key={item.to + item.label}\n href={item.to}\n title={item.label}\n aria-label={item.label}\n aria-current={active ? \"page\" : undefined}\n className={cn(\n \"flex flex-1 flex-col items-center gap-1 py-2 text-xs font-medium transition-colors\",\n active ? \"text-primary\" : \"text-muted-foreground\",\n )}\n >\n <item.icon className=\"h-5 w-5\" />\n <span className=\"max-w-full truncate px-0.5\">\n {item.label}\n </span>\n </LinkComp>\n );\n })}\n </nav>\n ) : null}\n </div>\n </div>\n );\n}\n"]}