@exxatdesignux/ui 0.2.18 → 0.2.19

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 (140) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/consumer-extras/AGENTS.md +76 -0
  3. package/consumer-extras/README.md +5 -1
  4. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +14 -3
  5. package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +37 -0
  6. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +21 -6
  7. package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +57 -0
  8. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +4 -2
  9. package/consumer-extras/patterns/consumer-app-pattern.md +39 -0
  10. package/consumer-extras/patterns/consumer-upgrade-checklist.md +20 -0
  11. package/consumer-extras/patterns/data-views-pattern.md +40 -3
  12. package/consumer-extras/patterns/focused-workflow-page-pattern.md +84 -0
  13. package/consumer-extras/patterns/shell-surface-elevation-pattern.md +5 -3
  14. package/package.json +2 -1
  15. package/src/components/ui/button-group.tsx +81 -0
  16. package/src/components/ui/button.tsx +4 -4
  17. package/src/globals.css +7 -1858
  18. package/src/theme.css +10 -1126
  19. package/src/tokens/README.md +15 -0
  20. package/src/tokens/base.css +337 -0
  21. package/src/tokens/high-contrast.css +1195 -0
  22. package/src/tokens/layers.css +224 -0
  23. package/src/tokens/tailwind-bridge.css +118 -0
  24. package/src/tokens/themes.css +201 -0
  25. package/template/AGENTS.md +60 -22
  26. package/template/app/(app)/dashboard/loading.tsx +3 -15
  27. package/template/app/(app)/dashboard/page.tsx +2 -14
  28. package/template/app/(app)/data-list/layout.tsx +43 -0
  29. package/template/app/(app)/data-list/page.tsx +2 -2
  30. package/template/app/(app)/examples/focused-workflow/page.tsx +5 -0
  31. package/template/app/(app)/examples/page.tsx +1 -0
  32. package/template/app/(app)/loading.tsx +1 -18
  33. package/template/app/(app)/question-bank/find/page.tsx +2 -1
  34. package/template/app/(app)/question-bank/library/page.tsx +2 -1
  35. package/template/app/(app)/question-bank/list/page.tsx +2 -1
  36. package/template/app/(app)/question-bank/new/page.tsx +15 -23
  37. package/template/app/(app)/question-bank/page.tsx +2 -1
  38. package/template/app/(app)/settings/page.tsx +4 -5
  39. package/template/app/globals.css +7 -1964
  40. package/template/components/app-route-loading.tsx +14 -0
  41. package/template/components/app-sidebar.tsx +70 -55
  42. package/template/components/data-views/index.ts +37 -9
  43. package/template/components/data-views/list-page-calendar-view.tsx +593 -0
  44. package/template/components/data-views/list-page-connected-view-body.tsx +66 -0
  45. package/template/components/data-views/list-page-folder-columns-panel.tsx +345 -0
  46. package/template/components/data-views/list-page-split-hub-chrome.tsx +8 -0
  47. package/template/components/examples/focused-workflow-showcase.tsx +183 -0
  48. package/template/components/list-hub-board-view.tsx +68 -0
  49. package/template/components/list-hub-client.tsx +186 -0
  50. package/template/components/list-hub-list-view.tsx +36 -0
  51. package/template/components/list-hub-panel-activator.tsx +8 -0
  52. package/template/components/list-hub-secondary-nav.tsx +121 -0
  53. package/template/components/list-hub-table.tsx +336 -0
  54. package/template/components/new-question-composer.tsx +6 -24
  55. package/template/components/product-switcher.tsx +3 -2
  56. package/template/components/question-bank-client.tsx +4 -1
  57. package/template/components/question-bank-folder-columns-panel.tsx +104 -0
  58. package/template/components/question-bank-table.tsx +143 -485
  59. package/template/components/secondary-panel/nav-link-rows.tsx +83 -0
  60. package/template/components/secondary-panel.tsx +4 -44
  61. package/template/components/secondary-panels/list-hub-panel.tsx +39 -0
  62. package/template/components/secondary-panels/question-bank-panel.tsx +39 -0
  63. package/template/components/secondary-panels/registry.tsx +15 -0
  64. package/template/components/settings-appearance-card.tsx +3 -2
  65. package/template/components/settings-client.tsx +59 -15
  66. package/template/components/settings-form-row.tsx +9 -4
  67. package/template/components/table-properties/drawer-button.tsx +13 -0
  68. package/template/components/table-properties/drawer.tsx +65 -4
  69. package/template/components/templates/focused-workflow-layouts.tsx +448 -0
  70. package/template/components/templates/focused-workflow-page-template.tsx +69 -0
  71. package/template/components/templates/list-page.tsx +29 -5
  72. package/template/components/templates/nested-secondary-panel-shell.tsx +2 -1
  73. package/template/components/templates/page-loading-shell.tsx +262 -0
  74. package/template/components/ui/button-group.tsx +1 -0
  75. package/template/docs/consumer-app-pattern.md +39 -0
  76. package/template/docs/data-views-pattern.md +40 -3
  77. package/template/docs/drawer-vs-dialog-pattern.md +3 -1
  78. package/template/docs/focused-workflow-page-pattern.md +84 -0
  79. package/template/docs/shell-surface-elevation-pattern.md +5 -3
  80. package/template/lib/command-menu-search-data.ts +11 -27
  81. package/template/lib/data-list-display-options.ts +16 -2
  82. package/template/lib/data-list-view-registry.ts +104 -0
  83. package/template/lib/data-list-view-surface.ts +15 -1
  84. package/template/lib/data-list-view.ts +10 -1
  85. package/template/lib/data-view-dashboard-storage.ts +38 -35
  86. package/template/lib/hub-connected-view-renderers.ts +58 -0
  87. package/template/lib/list-hub-nav.ts +121 -0
  88. package/template/lib/list-hub-supported-views.ts +10 -0
  89. package/template/lib/list-page-table-properties.ts +3 -7
  90. package/template/lib/list-status-badges.ts +4 -97
  91. package/template/lib/mock/list-hub-directory.ts +27 -0
  92. package/template/lib/mock/list-hub-kpi.ts +27 -0
  93. package/template/lib/mock/navigation.tsx +1 -0
  94. package/template/lib/page-loading-variant.ts +40 -0
  95. package/template/lib/question-bank-supported-views.ts +13 -0
  96. package/template/lib/table-state-lifecycle.ts +2 -2
  97. package/template/app/(app)/data-list/[id]/page.tsx +0 -44
  98. package/template/app/(app)/data-list/new/page.tsx +0 -34
  99. package/template/components/compliance-board-view.tsx +0 -142
  100. package/template/components/compliance-client.tsx +0 -92
  101. package/template/components/compliance-list-view.tsx +0 -54
  102. package/template/components/compliance-page-header.tsx +0 -89
  103. package/template/components/compliance-table.tsx +0 -612
  104. package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
  105. package/template/components/data-view-dashboard-charts-team.tsx +0 -971
  106. package/template/components/data-view-dashboard-charts.tsx +0 -1503
  107. package/template/components/new-placement-back-btn.tsx +0 -28
  108. package/template/components/new-placement-form.tsx +0 -1068
  109. package/template/components/placement-board-card.tsx +0 -262
  110. package/template/components/placement-detail.tsx +0 -438
  111. package/template/components/placements-board-view.tsx +0 -404
  112. package/template/components/placements-client.tsx +0 -252
  113. package/template/components/placements-list-view.tsx +0 -171
  114. package/template/components/placements-page-header.tsx +0 -166
  115. package/template/components/placements-table-cells.test.tsx +0 -22
  116. package/template/components/placements-table-cells.tsx +0 -173
  117. package/template/components/placements-table-columns.tsx +0 -640
  118. package/template/components/placements-table.tsx +0 -1642
  119. package/template/components/rotations-empty-state.tsx +0 -50
  120. package/template/components/rotations-panel-activator.tsx +0 -8
  121. package/template/components/sites-all-client.tsx +0 -154
  122. package/template/components/sites-board-view.tsx +0 -67
  123. package/template/components/sites-list-view.tsx +0 -42
  124. package/template/components/sites-table.tsx +0 -382
  125. package/template/components/team-board-view.tsx +0 -122
  126. package/template/components/team-client.tsx +0 -100
  127. package/template/components/team-list-view.tsx +0 -59
  128. package/template/components/team-page-header.tsx +0 -92
  129. package/template/components/team-table.tsx +0 -693
  130. package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
  131. package/template/lib/mock/compliance-kpi.ts +0 -61
  132. package/template/lib/mock/compliance.ts +0 -146
  133. package/template/lib/mock/placements-kpi.ts +0 -134
  134. package/template/lib/mock/placements.ts +0 -183
  135. package/template/lib/mock/sites-directory.ts +0 -16
  136. package/template/lib/mock/sites-kpi.ts +0 -25
  137. package/template/lib/mock/team-kpi.ts +0 -60
  138. package/template/lib/mock/team.ts +0 -118
  139. package/template/lib/placement-board-card-layout.ts +0 -79
  140. package/template/lib/placement-lifecycle.ts +0 -5
@@ -0,0 +1,14 @@
1
+ "use client"
2
+
3
+ import { usePathname } from "next/navigation"
4
+
5
+ import { PageLoadingByVariant } from "@/components/templates/page-loading-shell"
6
+ import { resolvePageLoadingVariant } from "@/lib/page-loading-variant"
7
+
8
+ /**
9
+ * Route-level loading UI — keeps `SidebarInset` + header chrome; skeleton matches destination template.
10
+ */
11
+ export function AppRouteLoading() {
12
+ const pathname = usePathname()
13
+ return <PageLoadingByVariant variant={resolvePageLoadingVariant(pathname)} />
14
+ }
@@ -105,11 +105,22 @@ function normalizedLocationHash(locationHash: string): string {
105
105
  return locationHash.startsWith("#") ? locationHash.slice(1) : locationHash
106
106
  }
107
107
 
108
+ /**
109
+ * Paths where a sibling sidebar row uses `#fragment` on the same pathname
110
+ * (e.g. Settings vs Tokens & themes). The plain-path row must not stay active
111
+ * when the hash belongs to that sibling.
112
+ */
113
+ const NAV_EXCLUSIVE_HASH_BY_PATH: Readonly<Record<string, readonly string[]>> = {
114
+ "/settings": ["appearance"],
115
+ "/help": ["more"],
116
+ }
117
+
108
118
  /**
109
119
  * Whether `pathname` (+ optional `location.hash`) matches a sidebar `href`.
110
120
  * When several links share the same path (e.g. `/settings`), disambiguate with `#fragment`
111
121
  * in each `href` — those rows use the `frag !== null` branch below.
112
- * For `href` without `#…`, an in-page hash (e.g. QB view tabs) does not clear the match.
122
+ * For `href` without `#…`, an in-page hash (e.g. QB view tabs) does not clear the match,
123
+ * except when the hash is reserved for a hash-sibling row on that path.
113
124
  */
114
125
  function isNavActive(pathname: string, url: string, locationHash = ""): boolean {
115
126
  const pathOnly = navUrlPath(url)
@@ -131,7 +142,13 @@ function isNavActive(pathname: string, url: string, locationHash = ""): boolean
131
142
 
132
143
  if (pathOnly === "/") return pathname === "/" && h === ""
133
144
  /** Exact path match — ignore `location.hash` when the nav `href` has no `#…` fragment (QB view tabs use hash). */
134
- if (pathname === pathOnly) return true
145
+ if (pathname === pathOnly) {
146
+ if (h !== "") {
147
+ const exclusive = NAV_EXCLUSIVE_HASH_BY_PATH[pathOnly]
148
+ if (exclusive?.includes(h)) return false
149
+ }
150
+ return true
151
+ }
135
152
  // Design system library — active on hub and detail routes.
136
153
  if (pathOnly === "/library") {
137
154
  return pathname.startsWith("/library/")
@@ -827,60 +844,58 @@ function ProductLogoButton() {
827
844
 
828
845
  return (
829
846
  <DropdownMenu>
830
- <Tooltip>
831
- <TooltipTrigger asChild>
832
- <DropdownMenuTrigger asChild>
833
- <SidebarMenuButton
834
- size="lg"
835
- className={cn(
836
- "py-2 text-sidebar-foreground data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground",
837
- expandedOrMobile &&
838
- "h-auto min-h-12 !overflow-visible items-center [&>span:last-child]:!overflow-visible [&>span:last-child]:!whitespace-normal [&>span:last-child]:text-clip",
839
- "group-data-[collapsible=icon]:items-center group-data-[collapsible=icon]:justify-center",
840
- iconRail &&
841
- "group-data-[collapsible=icon]:!size-9 group-data-[collapsible=icon]:!min-h-9 group-data-[collapsible=icon]:!max-h-9 group-data-[collapsible=icon]:!p-0 group-data-[collapsible=icon]:overflow-visible",
842
- )}
843
- aria-label={`Current product: ${current.label}. Switch product`}
844
- suppressHydrationWarning
845
- >
846
- {iconRail ? (
847
- // Match the school selector footprint in the icon rail (32px frame,
848
- // 28px mark same visual weight as the avatar with inset padding).
849
- <span className="flex size-8 shrink-0 items-center justify-center">
850
- <ExxatProductMark product={current.id} className="size-7" />
851
- </span>
852
- ) : (
853
- <span className="flex min-h-0 min-w-0 flex-1 items-stretch gap-2">
854
- <span
855
- className="flex min-h-0 min-w-0 flex-1 items-center justify-start overflow-visible"
856
- aria-hidden="true"
857
- >
858
- <ExxatProductLogo
859
- product={current.id}
860
- variant="mutedSuffix"
861
- className="w-auto max-w-[min(100%,280px)] object-left object-contain"
862
- />
863
- </span>
864
- <span
865
- className="flex w-6 shrink-0 items-center justify-center self-stretch text-muted-foreground"
866
- aria-hidden="true"
867
- >
868
- <i
869
- className="fa-light fa-chevron-down block text-xs leading-none"
870
- aria-hidden="true"
871
- />
872
- </span>
873
- </span>
874
- )}
875
- </SidebarMenuButton>
876
- </DropdownMenuTrigger>
877
- </TooltipTrigger>
878
- <TooltipContent side="right" align="center" hidden={state !== "collapsed" || isMobile}>
879
- {current.label}
880
- </TooltipContent>
881
- </Tooltip>
847
+ <DropdownMenuTrigger asChild>
848
+ <SidebarMenuButton
849
+ size="lg"
850
+ tooltip={iconRail ? current.label : undefined}
851
+ className={cn(
852
+ "py-2 text-sidebar-foreground data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground",
853
+ expandedOrMobile &&
854
+ "h-auto min-h-12 !overflow-visible items-center [&>span:last-child]:!overflow-visible [&>span:last-child]:!whitespace-normal [&>span:last-child]:text-clip",
855
+ "group-data-[collapsible=icon]:items-center group-data-[collapsible=icon]:justify-center",
856
+ iconRail &&
857
+ "group-data-[collapsible=icon]:!size-9 group-data-[collapsible=icon]:!min-h-9 group-data-[collapsible=icon]:!max-h-9 group-data-[collapsible=icon]:!p-0 group-data-[collapsible=icon]:overflow-visible",
858
+ )}
859
+ aria-label={`Current product: ${current.label}. Switch product`}
860
+ suppressHydrationWarning
861
+ >
862
+ {iconRail ? (
863
+ // Match the school selector footprint in the icon rail (32px frame,
864
+ // 28px mark same visual weight as the avatar with inset padding).
865
+ <span className="flex size-8 shrink-0 items-center justify-center">
866
+ <ExxatProductMark product={current.id} className="size-7" />
867
+ </span>
868
+ ) : (
869
+ <span className="flex min-h-0 min-w-0 flex-1 items-stretch gap-2">
870
+ <span
871
+ className="flex min-h-0 min-w-0 flex-1 items-center justify-start overflow-visible"
872
+ aria-hidden="true"
873
+ >
874
+ <ExxatProductLogo
875
+ product={current.id}
876
+ variant="mutedSuffix"
877
+ className="w-auto max-w-[min(100%,280px)] object-left object-contain"
878
+ />
879
+ </span>
880
+ <span
881
+ className="flex w-6 shrink-0 items-center justify-center self-stretch text-muted-foreground"
882
+ aria-hidden="true"
883
+ >
884
+ <i
885
+ className="fa-light fa-chevron-down block text-xs leading-none"
886
+ aria-hidden="true"
887
+ />
888
+ </span>
889
+ </span>
890
+ )}
891
+ </SidebarMenuButton>
892
+ </DropdownMenuTrigger>
882
893
 
883
- <DropdownMenuContent align="start" side="right" sideOffset={8}>
894
+ <DropdownMenuContent
895
+ align="start"
896
+ side={iconRail ? "right" : "bottom"}
897
+ sideOffset={iconRail ? 8 : 4}
898
+ >
884
899
  <DropdownMenuLabel className="text-xs text-muted-foreground">
885
900
  Switch product
886
901
  </DropdownMenuLabel>
@@ -1,15 +1,12 @@
1
1
  /**
2
2
  * Central exports for list-page data surfaces and shared view chrome.
3
3
  *
4
- * **Pattern:** `ListPageTemplate` + `PlacementsTable` (or any hub-specific `*-table.tsx`) — one `useTableState`, one toolbar,
4
+ * **Pattern:** `ListPageTemplate` + hub `*-table.tsx` — one `useTableState`, one toolbar,
5
5
  * table | list | board | dashboard from the same component (`AGENTS.md` §4, `docs/data-views-pattern.md`).
6
6
  *
7
7
  * **View UI:** `ViewSegmentedControl` matches the template’s views toolbar (`bg-muted/60` pills).
8
8
  */
9
9
 
10
- export { PlacementsTable } from "@/components/placements-table"
11
- export type { PlacementsTableProps, PlacementsTableHandle } from "@/components/placements-table"
12
- export type { PlacementLifecycleTabId } from "@/lib/placement-lifecycle"
13
10
  export type { DataListViewType } from "@/lib/data-list-view"
14
11
  export { DATA_LIST_VIEW_TILES, dataListViewIcon, dataListViewLabel } from "@/lib/data-list-view"
15
12
 
@@ -39,6 +36,7 @@ export {
39
36
  export {
40
37
  ListPageSplitHubChrome,
41
38
  LIST_PAGE_SPLIT_HUB_HEIGHT_STYLE,
39
+ LIST_PAGE_CALENDAR_HEIGHT_STYLE,
42
40
  type ListPageSplitHubChromeProps,
43
41
  } from "@/components/data-views/list-page-split-hub-chrome"
44
42
 
@@ -63,8 +61,7 @@ export {
63
61
  type OutlineTreeSurface,
64
62
  } from "@/components/data-views/outline-tree-menu"
65
63
 
66
- export { QuestionBankFolderTreeBranch } from "@/components/data-views/question-bank-folder-tree-branch"
67
- export type { QuestionBankFolderTreeBranchProps } from "@/components/data-views/question-bank-folder-tree-branch"
64
+ /** Question-bank nav only — import from `@/components/data-views/question-bank-folder-tree-branch`. */
68
65
 
69
66
  export {
70
67
  LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS,
@@ -83,10 +80,12 @@ export {
83
80
  type FolderDetailsShellProps,
84
81
  } from "@/components/folder-details-shell"
85
82
 
83
+ /** Hub-specific tree+inspector — import from `@/components/hub-tree-panel-view` (not generic DS). */
84
+
86
85
  export {
87
- HubTreePanelView,
88
- type HubTreePanelViewProps,
89
- } from "@/components/hub-tree-panel-view"
86
+ ListPageFolderColumnsPanel,
87
+ type ListPageFolderColumnsPanelProps,
88
+ } from "@/components/data-views/list-page-folder-columns-panel"
90
89
 
91
90
  export {
92
91
  ListPageTreePanelShell,
@@ -103,6 +102,35 @@ export {
103
102
  /** Generic folder icon-grid — reusable across all list hubs. */
104
103
  export { FolderGridView, type FolderGridViewProps } from "@/components/data-views/folder-grid-view"
105
104
 
105
+ /** Month calendar — same `tableState.rows` as table / list / board. */
106
+ export {
107
+ ListPageCalendarView,
108
+ type ListPageCalendarViewProps,
109
+ } from "@/components/data-views/list-page-calendar-view"
110
+
111
+ /** Hub view router — switch on `DataListViewRenderKind`; missing renderer = explicit empty state. */
112
+ export {
113
+ ListPageConnectedViewBody,
114
+ ListPageViewNotConfigured,
115
+ type ListPageConnectedViewBodyProps,
116
+ type ListPageConnectedViewRenderers,
117
+ } from "@/components/data-views/list-page-connected-view-body"
118
+
119
+ export {
120
+ DATA_LIST_VIEW_REGISTRY,
121
+ dataListViewDefinition,
122
+ dataListViewTilesForHub,
123
+ showsListPageHubMetricsStrip,
124
+ isDataListViewTypeSupported,
125
+ } from "@/lib/data-list-view-registry"
126
+
127
+ export {
128
+ defineHubViewRenderers,
129
+ hubRenderKindsForSupported,
130
+ type HubConnectedViewRenderers,
131
+ type HubRenderKindForViews,
132
+ } from "@/lib/hub-connected-view-renderers"
133
+
106
134
  /** Generic vertical row list — used by every hub's "list" tab. Composes
107
135
  * `ListPageBoardCard layout="row"` via a `renderRow` prop. */
108
136
  export { DataRowList, type DataRowListProps } from "@/components/data-views/data-row-list"