@exxatdesignux/ui 0.3.0 → 0.4.1

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 (214) hide show
  1. package/CHANGELOG.md +701 -6
  2. package/README.md +138 -0
  3. package/bin/init.mjs +134 -31
  4. package/consumer-extras/cursor-rules/exxat-board-cards.mdc +1 -1
  5. package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +2 -2
  6. package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -1
  7. package/consumer-extras/cursor-rules/exxat-data-tables.mdc +2 -0
  8. package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +1 -1
  9. package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +3 -3
  10. package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
  11. package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +1 -1
  12. package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -1
  13. package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +6 -6
  14. package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -1
  15. package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
  16. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
  17. package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -3
  18. package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
  19. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +7 -7
  20. package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
  21. package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
  22. package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
  23. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +8 -8
  24. package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
  25. package/consumer-extras/handbook/HANDBOOK.md +2 -0
  26. package/consumer-extras/handbook/glossary.md +2 -1
  27. package/consumer-extras/handbook/reference-implementations.md +31 -4
  28. package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
  29. package/consumer-extras/patterns/data-views-pattern.md +18 -16
  30. package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
  31. package/dist/components/data-table/index.js +2 -2
  32. package/dist/components/data-table/index.js.map +1 -1
  33. package/dist/components/data-table/pagination.js +3 -3
  34. package/dist/components/data-table/pagination.js.map +1 -1
  35. package/dist/components/data-table/use-table-state.d.ts +1 -1
  36. package/dist/components/data-table/use-table-state.js.map +1 -1
  37. package/dist/components/data-views/data-row-list.js.map +1 -1
  38. package/dist/components/data-views/finder-panel-view.d.ts +1 -1
  39. package/dist/components/data-views/finder-panel-view.js.map +1 -1
  40. package/dist/components/data-views/hub-table.d.ts +9 -3
  41. package/dist/components/data-views/hub-table.js +262 -40
  42. package/dist/components/data-views/hub-table.js.map +1 -1
  43. package/dist/components/data-views/index.js +262 -40
  44. package/dist/components/data-views/index.js.map +1 -1
  45. package/dist/components/data-views/list-page-split-hub-tokens.d.ts +2 -2
  46. package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -1
  47. package/dist/components/data-views/list-page-tree-column-header.d.ts +1 -1
  48. package/dist/components/data-views/list-page-tree-column-header.js.map +1 -1
  49. package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -1
  50. package/dist/components/data-views/os-folder-glyph.d.ts +1 -1
  51. package/dist/components/data-views/os-folder-glyph.js.map +1 -1
  52. package/dist/components/ui/avatar.d.ts +1 -1
  53. package/dist/components/ui/key-metrics.js.map +1 -1
  54. package/dist/index.js +136 -39
  55. package/dist/index.js.map +1 -1
  56. package/package.json +3 -2
  57. package/src/components/data-table/index.tsx +2 -2
  58. package/src/components/data-table/pagination.tsx +5 -1
  59. package/src/components/data-table/use-table-state.ts +1 -1
  60. package/src/components/data-views/data-row-list.tsx +1 -1
  61. package/src/components/data-views/finder-panel-view.tsx +2 -2
  62. package/src/components/data-views/hub-table.tsx +149 -41
  63. package/src/components/data-views/list-page-split-hub-tokens.ts +2 -2
  64. package/src/components/data-views/list-page-tree-column-header.tsx +1 -1
  65. package/src/components/data-views/os-folder-glyph.tsx +1 -1
  66. package/src/components/ui/key-metrics.tsx +1 -1
  67. package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
  68. package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
  69. package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
  70. package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +6 -6
  71. package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
  72. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +5 -5
  73. package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
  74. package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
  75. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
  76. package/template/AGENTS.md +43 -37
  77. package/template/app/(app)/columns/page.tsx +11 -0
  78. package/template/app/(app)/library/all/page.tsx +11 -0
  79. package/template/app/(app)/library/find/page.tsx +12 -0
  80. package/template/app/(app)/{question-bank → library}/layout.tsx +16 -16
  81. package/template/app/(app)/library/list/page.tsx +12 -0
  82. package/template/app/(app)/{question-bank → library}/new/page.tsx +10 -10
  83. package/template/app/(app)/library/page.tsx +11 -0
  84. package/template/app/(app)/tokens-themes/page.tsx +11 -0
  85. package/template/components/ask-leo-composer.tsx +2 -2
  86. package/template/components/columns-client.tsx +158 -0
  87. package/template/components/columns-showcase.tsx +541 -0
  88. package/template/components/data-views/index.ts +32 -6
  89. package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
  90. package/template/components/data-views/table-cells.tsx +673 -0
  91. package/template/components/folder-details-shell.tsx +11 -11
  92. package/template/components/hub-tree-panel-view.tsx +24 -24
  93. package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
  94. package/template/components/{question-bank-client.tsx → library-client.tsx} +82 -82
  95. package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
  96. package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
  97. package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +43 -43
  98. package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +14 -14
  99. package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
  100. package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
  101. package/template/components/library-panel-activator.tsx +8 -0
  102. package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +60 -60
  103. package/template/components/{question-bank-table.tsx → library-table.tsx} +97 -97
  104. package/template/components/list-hub-status-badge.tsx +2 -2
  105. package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +37 -37
  106. package/template/components/sidebar/app-sidebar.tsx +61 -5
  107. package/template/components/sidebar/secondary-panel.tsx +109 -56
  108. package/template/components/sidebar/sidebar-auto-collapse.tsx +2 -2
  109. package/template/components/sidebar/sidebar-auto-open.tsx +2 -1
  110. package/template/components/table-properties/types.ts +1 -1
  111. package/template/components/templates/discovery-hub-template.tsx +1 -1
  112. package/template/components/templates/new-focus-template.tsx +2 -2
  113. package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
  114. package/template/components/tokens-secondary-nav.tsx +192 -0
  115. package/template/components/tokens-themes-client.tsx +476 -0
  116. package/template/components/tokens-themes-section.tsx +386 -0
  117. package/template/docs/HANDBOOK.md +187 -0
  118. package/template/docs/blueprints/README.md +1 -1
  119. package/template/docs/blueprints/board-card.md +1 -1
  120. package/template/docs/blueprints/data-table.md +2 -2
  121. package/template/docs/blueprints/list-page-template.md +3 -3
  122. package/template/docs/blueprints/page-header.md +4 -4
  123. package/template/docs/collaboration-access-pattern.md +7 -7
  124. package/template/docs/component-selection-guide.md +1 -1
  125. package/template/docs/data-views-pattern.md +18 -16
  126. package/template/docs/glossary.md +58 -0
  127. package/template/docs/kpi-flat-band-pattern.md +3 -3
  128. package/template/docs/kpi-trend-pattern.md +18 -3
  129. package/template/docs/large-dataset-strategy.md +155 -0
  130. package/template/docs/library-hub-header-pattern.md +25 -0
  131. package/template/docs/migrations/_template.md +1 -1
  132. package/template/docs/reference-implementations.md +151 -0
  133. package/template/docs/token-taxonomy.md +1 -1
  134. package/template/docs/voice-and-tone.md +262 -0
  135. package/template/eslint.config.mjs +9 -39
  136. package/template/hooks/use-secondary-panel-hub-nav.ts +10 -10
  137. package/template/lib/ask-leo-route-context.ts +6 -18
  138. package/template/lib/coach-mark-registry.ts +0 -16
  139. package/template/lib/command-menu-config.ts +5 -12
  140. package/template/lib/command-menu-search-data.ts +8 -39
  141. package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
  142. package/template/lib/library-dedicated-search.ts +19 -0
  143. package/template/lib/library-hub-search.ts +90 -0
  144. package/template/lib/library-nav.ts +477 -0
  145. package/template/lib/library-recent-searches.ts +22 -0
  146. package/template/lib/{placements-supported-views.ts → library-supported-views.ts} +2 -2
  147. package/template/lib/list-status-badges.ts +16 -104
  148. package/template/lib/mock/dashboard.ts +1 -1
  149. package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
  150. package/template/lib/mock/library-header-collaborators.ts +54 -0
  151. package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
  152. package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
  153. package/template/lib/mock/library.ts +249 -0
  154. package/template/lib/mock/navigation.tsx +32 -26
  155. package/template/lib/table-state-lifecycle.ts +1 -1
  156. package/template/next.config.mjs +7 -4
  157. package/template/package.json +0 -1
  158. package/tokens/hooks-index.json +2874 -0
  159. package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +0 -28
  160. package/template/app/(app)/examples/page.tsx +0 -41
  161. package/template/app/(app)/question-bank/find/page.tsx +0 -12
  162. package/template/app/(app)/question-bank/library/page.tsx +0 -11
  163. package/template/app/(app)/question-bank/list/page.tsx +0 -12
  164. package/template/app/(app)/question-bank/page.tsx +0 -11
  165. package/template/components/compliance-board-view.tsx +0 -142
  166. package/template/components/compliance-client.tsx +0 -92
  167. package/template/components/compliance-page-header.tsx +0 -89
  168. package/template/components/compliance-table.tsx +0 -468
  169. package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
  170. package/template/components/data-view-dashboard-charts-team.tsx +0 -971
  171. package/template/components/data-view-dashboard-charts.tsx +0 -1503
  172. package/template/components/new-placement-back-btn.tsx +0 -28
  173. package/template/components/new-placement-form.tsx +0 -942
  174. package/template/components/placement-board-card.tsx +0 -250
  175. package/template/components/placement-detail.tsx +0 -438
  176. package/template/components/placements-board-view.tsx +0 -397
  177. package/template/components/placements-client.tsx +0 -220
  178. package/template/components/placements-list-view.tsx +0 -124
  179. package/template/components/placements-page-header.tsx +0 -166
  180. package/template/components/placements-table-cells.test.tsx +0 -22
  181. package/template/components/placements-table-cells.tsx +0 -173
  182. package/template/components/placements-table-columns.tsx +0 -210
  183. package/template/components/placements-table.tsx +0 -934
  184. package/template/components/question-bank-panel-activator.tsx +0 -8
  185. package/template/components/rotations-empty-state.tsx +0 -50
  186. package/template/components/rotations-panel-activator.tsx +0 -8
  187. package/template/components/sites-board-view.tsx +0 -67
  188. package/template/components/sites-client.tsx +0 -154
  189. package/template/components/sites-table.tsx +0 -249
  190. package/template/components/team-board-view.tsx +0 -122
  191. package/template/components/team-client.tsx +0 -100
  192. package/template/components/team-page-header.tsx +0 -92
  193. package/template/components/team-table.tsx +0 -553
  194. package/template/docs/question-bank-hub-header-pattern.md +0 -25
  195. package/template/lib/compliance-supported-views.ts +0 -10
  196. package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
  197. package/template/lib/mock/compliance-kpi.ts +0 -61
  198. package/template/lib/mock/compliance.ts +0 -146
  199. package/template/lib/mock/placements-kpi.ts +0 -134
  200. package/template/lib/mock/placements.ts +0 -176
  201. package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
  202. package/template/lib/mock/question-bank.ts +0 -249
  203. package/template/lib/mock/sites-directory.ts +0 -16
  204. package/template/lib/mock/sites-kpi.ts +0 -25
  205. package/template/lib/mock/team-kpi.ts +0 -60
  206. package/template/lib/mock/team.ts +0 -118
  207. package/template/lib/placement-board-card-layout.ts +0 -79
  208. package/template/lib/question-bank-dedicated-search.ts +0 -19
  209. package/template/lib/question-bank-hub-search.ts +0 -90
  210. package/template/lib/question-bank-nav.ts +0 -477
  211. package/template/lib/question-bank-recent-searches.ts +0 -22
  212. package/template/lib/question-bank-supported-views.ts +0 -12
  213. package/template/lib/sites-supported-views.ts +0 -10
  214. package/template/lib/team-supported-views.ts +0 -10
@@ -1,28 +0,0 @@
1
- ---
2
- description: Question bank library — folder-scoped hub header More menu must expose Customize folder; sheet on hub client
3
- globs: apps/web/components/question-bank-*.tsx, packages/ui/template/components/question-bank-*.tsx
4
- alwaysApply: false
5
- ---
6
-
7
- # Exxat DS — Question bank hub header (folder scope)
8
-
9
- When the question bank library URL is **scoped to a folder** (`parseQuestionBankNav` → **`scope === "folder"`** and **`folderId`** set), users are effectively on a **“folder page”**: the hub title matches that folder, and **global** actions belong in the **`QuestionBankPageHeader`** **⋯ More** menu — not only on per-row or per-tile overflow menus inside a single view tab.
10
-
11
- **Pattern doc:** **`apps/web/docs/question-bank-hub-header-pattern.md`**. **Handbook:** **`apps/web/AGENTS.md` §4.6** (folder-scoped hub chrome).
12
-
13
- ## MUST
14
-
15
- 1. **`QuestionBankPageHeader`** — When **`navState.scope === "folder"`** and **`navState.folderId`** resolves to a row in **`folders`**, pass **`onCustomizeFolder`** so **⋯ More** includes **Customize folder** ( **`fa-wand-magic-sparkles`** + label **Customize folder** ), placed after **Invite people** (collaboration variant) and before **Export**.
16
- 2. **Hub client** — Mount **`QuestionBankNewFolderSheet`** on the **hub client** (e.g. **`QuestionBankClient`**) next to **`ListPageTemplate`**, driven by local **`open` / `customizingFolder`** state opened from **`onCustomizeFolder`**. **MUST NOT** rely on **`QuestionBankTable`** alone to host the sheet when some view branches (**table**, **list**, **board**, **dashboard**) do not render that sheet — users would lose **Customize folder** on those tabs.
17
- 3. **`onCreated`** — On save, **`setFolders`** (or equivalent) **maps** the scoped folder **`id`** to updated **`name`**, **`icon`**, **`colorKey`** — same contract as **`QuestionBankTable`** panel/tree customize handlers.
18
-
19
- ## MUST NOT
20
-
21
- - Omit **Customize folder** from the header **⋯** when the URL is folder-scoped, expecting users to find it only on secondary-nav tree rows or OS-folder tiles.
22
- - Mount **only** one customize sheet inside **`QuestionBankTable`** without a **client-level** sheet when the hub uses **`ListPageTemplate`** view tabs that omit that table subtree.
23
-
24
- ## See also
25
-
26
- - **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`** — URL scope + secondary panel.
27
- - **`.cursor/rules/exxat-collaboration-access.mdc`** — **`variant="collaboration"`** header + **⋯** invite pattern.
28
- - **`lib/question-bank-nav.ts`** — **`parseQuestionBankNav`**, **`QuestionBankNavState`**.
@@ -1,41 +0,0 @@
1
- import Link from "next/link"
2
- import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
3
- import { Button } from "@/components/ui/button"
4
-
5
- const LINKS = [
6
- { href: "/dashboard", label: "Dashboard", description: "Metrics, charts, and layout patterns." },
7
- { href: "/question-bank", label: "Question bank", description: "Discovery hub for browsing folders, recents, and AI-assisted create/import flows." },
8
- { href: "/question-bank/library", label: "Question library", description: "Folders, OS folder view, panel, and tree demos on mock items." },
9
- { href: "/settings", label: "Settings", description: "Appearance, tours, and shell preferences." },
10
- { href: "/help", label: "Help", description: "Support and documentation entry points." },
11
- ] as const
12
-
13
- export default function ExamplesPage() {
14
- return (
15
- <PrimaryPageTemplate
16
- siteHeader={{ title: "Patterns" }}
17
- maxWidthClassName="max-w-3xl"
18
- contentClassName="px-4 lg:px-6 py-8"
19
- bodyClassName="overflow-y-auto"
20
- >
21
- <p className="text-sm text-muted-foreground mb-6">
22
- This workspace ships neutral chrome so you can reuse layouts, data views, and tokens as a design system.
23
- </p>
24
- <ul className="flex flex-col gap-3" role="list">
25
- {LINKS.map((item) => (
26
- <li key={item.href}>
27
- <Button variant="outline" className="h-auto w-full justify-start gap-3 py-4 px-4" asChild>
28
- <Link href={item.href}>
29
- <span className="flex min-w-0 flex-1 flex-col items-start gap-0.5 text-left">
30
- <span className="font-medium text-foreground">{item.label}</span>
31
- <span className="text-xs font-normal text-muted-foreground">{item.description}</span>
32
- </span>
33
- <i className="fa-light fa-arrow-right shrink-0 text-muted-foreground" aria-hidden="true" />
34
- </Link>
35
- </Button>
36
- </li>
37
- ))}
38
- </ul>
39
- </PrimaryPageTemplate>
40
- )
41
- }
@@ -1,12 +0,0 @@
1
- import { Suspense } from "react"
2
-
3
- import { QuestionBankClient } from "@/components/question-bank-client"
4
-
5
- /** Discovery hub composer results — same hub chrome as the library, distinct from `/question-bank/list`. */
6
- export default function QuestionBankHubFindPage() {
7
- return (
8
- <Suspense fallback={null}>
9
- <QuestionBankClient />
10
- </Suspense>
11
- )
12
- }
@@ -1,11 +0,0 @@
1
- import { Suspense } from "react"
2
-
3
- import { QuestionBankClient } from "@/components/question-bank-client"
4
-
5
- export default function QuestionBankLibraryPage() {
6
- return (
7
- <Suspense fallback={null}>
8
- <QuestionBankClient />
9
- </Suspense>
10
- )
11
- }
@@ -1,12 +0,0 @@
1
- import { Suspense } from "react"
2
-
3
- import { QuestionBankClient } from "@/components/question-bank-client"
4
-
5
- /** Question bank list surface — same hub as `/question-bank/library`, optimized for `?q=` search landings. */
6
- export default function QuestionBankListPage() {
7
- return (
8
- <Suspense fallback={null}>
9
- <QuestionBankClient />
10
- </Suspense>
11
- )
12
- }
@@ -1,11 +0,0 @@
1
- import { Suspense } from "react"
2
-
3
- import { QuestionBankHubClient } from "@/components/question-bank-hub-client"
4
-
5
- export default function QuestionBankHubPage() {
6
- return (
7
- <Suspense fallback={null}>
8
- <QuestionBankHubClient />
9
- </Suspense>
10
- )
11
- }
@@ -1,142 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { initialsFromDisplayName } from "@/lib/initials-from-name"
5
- import {
6
- COMPLIANCE_STATUS_BADGE_CLASS,
7
- COMPLIANCE_STATUS_ICON,
8
- COMPLIANCE_STATUS_LABEL,
9
- } from "@/lib/list-status-badges"
10
- import type { ComplianceItem } from "@/lib/mock/compliance"
11
- import { BoardCardTwoLineBlock } from "@/components/data-views/board-card-primitives"
12
- import { ListHubStatusBadge } from "@/components/list-hub-status-badge"
13
- import {
14
- ListPageBoardCard,
15
- ListPageBoardCardAvatar,
16
- ListPageBoardCardBadgeRow,
17
- ListPageBoardCardBody,
18
- ListPageBoardCardHeader,
19
- ListPageBoardCardTitleRow,
20
- } from "@/components/data-views/list-page-board-card"
21
- import {
22
- ListPageBoardTemplate,
23
- type ListPageBoardColumnDef,
24
- } from "@/components/data-views/list-page-board-template"
25
-
26
- const NEUTRAL_COUNT_BADGE = "bg-muted/90 text-foreground"
27
-
28
- const STATUS_BOARD_COLUMNS: ListPageBoardColumnDef<ComplianceItem>[] = [
29
- {
30
- id: "compliant",
31
- label: "Compliant",
32
- description: "On track",
33
- filter: r => r.status === "compliant",
34
- },
35
- {
36
- id: "due_soon",
37
- label: "Due soon",
38
- description: "Within window",
39
- filter: r => r.status === "due_soon",
40
- },
41
- {
42
- id: "overdue",
43
- label: "Overdue",
44
- description: "Action required",
45
- filter: r => r.status === "overdue",
46
- },
47
- {
48
- id: "pending",
49
- label: "Pending",
50
- description: "Not started",
51
- filter: r => r.status === "pending",
52
- },
53
- ]
54
-
55
- function categoryBoardColumns(rows: ComplianceItem[]): {
56
- columns: ListPageBoardColumnDef<ComplianceItem>[]
57
- badgeMap: Record<string, string>
58
- } {
59
- const labels = [...new Set(rows.map(r => r.category))].sort((a, b) => a.localeCompare(b))
60
- const columns: ListPageBoardColumnDef<ComplianceItem>[] = labels.map(label => ({
61
- id: `category:${label}`,
62
- label,
63
- filter: (r: ComplianceItem) => r.category === label,
64
- }))
65
- const badgeMap = Object.fromEntries(labels.map(l => [`category:${l}`, NEUTRAL_COUNT_BADGE]))
66
- return { columns, badgeMap }
67
- }
68
-
69
- function useComplianceBoardModel(rows: ComplianceItem[], groupByColumnKey: string) {
70
- return React.useMemo(() => {
71
- if (groupByColumnKey === "category") {
72
- const { columns, badgeMap } = categoryBoardColumns(rows)
73
- return { columns, badgeMap }
74
- }
75
- return {
76
- columns: STATUS_BOARD_COLUMNS,
77
- badgeMap: COMPLIANCE_STATUS_BADGE_CLASS as Record<string, string>,
78
- }
79
- }, [rows, groupByColumnKey])
80
- }
81
-
82
- function ComplianceBoardCard({
83
- row,
84
- onRowActivate,
85
- }: {
86
- row: ComplianceItem
87
- onRowActivate?: (row: ComplianceItem) => void
88
- }) {
89
- const ownerInitials = initialsFromDisplayName(row.owner)
90
- return (
91
- <ListPageBoardCard className="w-full" onClick={onRowActivate ? () => onRowActivate(row) : undefined}>
92
- <ListPageBoardCardHeader>
93
- <ListPageBoardCardTitleRow
94
- title={row.title}
95
- titleClassName="line-clamp-2"
96
- trailing={<ListPageBoardCardAvatar initials={ownerInitials} />}
97
- />
98
- <ListPageBoardCardBadgeRow>
99
- <ListHubStatusBadge
100
- surface="board"
101
- label={COMPLIANCE_STATUS_LABEL[row.status]}
102
- tintClassName={COMPLIANCE_STATUS_BADGE_CLASS[row.status]}
103
- icon={COMPLIANCE_STATUS_ICON[row.status]}
104
- />
105
- </ListPageBoardCardBadgeRow>
106
- <ListPageBoardCardBody>
107
- <BoardCardTwoLineBlock iconClass="fa-tag" line1={row.category} line2={`Due ${row.dueDate}`} />
108
- <BoardCardTwoLineBlock iconClass="fa-user" line1={row.owner} line2="Owner" />
109
- </ListPageBoardCardBody>
110
- </ListPageBoardCardHeader>
111
- </ListPageBoardCard>
112
- )
113
- }
114
-
115
- export const COMPLIANCE_BOARD_GROUP_OPTIONS = [
116
- { key: "status", label: "Status" },
117
- { key: "category", label: "Category" },
118
- ] as const
119
-
120
- export function ComplianceBoardView({
121
- rows,
122
- groupByColumnKey,
123
- onRowActivate,
124
- }: {
125
- rows: ComplianceItem[]
126
- groupByColumnKey: string
127
- onRowActivate?: (row: ComplianceItem) => void
128
- }) {
129
- const key = groupByColumnKey === "category" ? "category" : "status"
130
- const { columns, badgeMap } = useComplianceBoardModel(rows, key)
131
-
132
- return (
133
- <ListPageBoardTemplate
134
- columns={columns}
135
- rows={rows}
136
- getRowKey={r => r.id}
137
- columnCountBadgeClassName={badgeMap}
138
- emptyColumnLabel="No items"
139
- renderCard={row => <ComplianceBoardCard row={row} onRowActivate={onRowActivate} />}
140
- />
141
- )
142
- }
@@ -1,92 +0,0 @@
1
- "use client"
2
-
3
- /**
4
- * Compliance list page — `ListPageTemplate` + `ComplianceTable`; view types from `@/components/data-views`.
5
- */
6
-
7
- import * as React from "react"
8
- import {
9
- ListPageTemplate,
10
- type ViewTab,
11
- dataListViewIcon,
12
- type DataListViewType,
13
- } from "@/components/data-views"
14
- import { CompliancePageHeader } from "@/components/compliance-page-header"
15
- import { ComplianceTable, type ComplianceTableHandle } from "@/components/compliance-table"
16
- import { KeyMetrics } from "@/components/key-metrics"
17
- import { useAskLeoPageContext } from "@/components/ask-leo-sidebar"
18
- import { COMPLIANCE_ITEMS } from "@/lib/mock/compliance"
19
- import { complianceKpiInsight, complianceKpiMetrics } from "@/lib/mock/compliance-kpi"
20
-
21
- const DEFAULT_TABS: ViewTab[] = [
22
- {
23
- id: "obligations",
24
- label: "Obligations",
25
- viewType: "table",
26
- icon: "fa-table",
27
- filterId: "all",
28
- },
29
- ]
30
-
31
- export function ComplianceClient() {
32
- const [exportOpen, setExportOpen] = React.useState(false)
33
- const [showMetrics, setShowMetrics] = React.useState(true)
34
- const tableRef = React.useRef<ComplianceTableHandle>(null)
35
- const count = COMPLIANCE_ITEMS.length
36
-
37
- const metrics = React.useMemo(() => complianceKpiMetrics(COMPLIANCE_ITEMS), [])
38
- const insight = React.useMemo(() => complianceKpiInsight(COMPLIANCE_ITEMS), [])
39
-
40
- useAskLeoPageContext(
41
- React.useMemo(
42
- () => ({
43
- title: "Compliance",
44
- description: `${count} obligations tracked on this hub.`,
45
- suggestions: [
46
- "What’s due this week?",
47
- "Summarize open items by student",
48
- ],
49
- }),
50
- [count],
51
- ),
52
- )
53
-
54
- return (
55
- <ListPageTemplate
56
- defaultTabs={DEFAULT_TABS}
57
- getTabCount={() => count}
58
- tablePropertiesRef={tableRef}
59
- header={
60
- <CompliancePageHeader
61
- itemCount={count}
62
- onAddReview={() => {}}
63
- onExport={() => setExportOpen(true)}
64
- showMetrics={showMetrics}
65
- onToggleMetrics={() => setShowMetrics(v => !v)}
66
- />
67
- }
68
- metrics={
69
- <KeyMetrics
70
- variant="flat"
71
- metrics={metrics}
72
- insight={insight}
73
- showHeader={false}
74
- metricsSingleRow
75
- />
76
- }
77
- showMetrics={showMetrics}
78
- exportOpen={exportOpen}
79
- onExportOpenChange={setExportOpen}
80
- exportTotalRows={count}
81
- renderContent={(tab, updateTab) => (
82
- <ComplianceTable
83
- key={tab.id}
84
- ref={tableRef}
85
- items={COMPLIANCE_ITEMS}
86
- view={tab.viewType}
87
- onViewChange={(v: DataListViewType) => updateTab({ viewType: v, icon: dataListViewIcon(v) })}
88
- />
89
- )}
90
- />
91
- )
92
- }
@@ -1,89 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { Button } from "@/components/ui/button"
5
- import { PageHeader } from "@/components/page-header"
6
- import {
7
- DropdownMenu,
8
- DropdownMenuContent,
9
- DropdownMenuItem,
10
- DropdownMenuSeparator,
11
- DropdownMenuTrigger,
12
- } from "@/components/ui/dropdown-menu"
13
- import { Tip } from "@/components/ui/tip"
14
-
15
- export interface CompliancePageHeaderProps {
16
- itemCount: number
17
- onAddReview: () => void
18
- onExport: () => void
19
- showMetrics: boolean
20
- onToggleMetrics: () => void
21
- showTitleBlock?: boolean
22
- }
23
-
24
- export function CompliancePageHeader({
25
- itemCount,
26
- onAddReview,
27
- onExport,
28
- showMetrics,
29
- onToggleMetrics,
30
- showTitleBlock = true,
31
- }: CompliancePageHeaderProps) {
32
- const [moreOpen, setMoreOpen] = React.useState(false)
33
- const countLine = `${itemCount} ${itemCount === 1 ? "item" : "items"} · Last updated now`
34
-
35
- return (
36
- <PageHeader
37
- title="Compliance"
38
- subtitle={countLine}
39
- showTitleBlock={showTitleBlock}
40
- actions={(
41
- <div className="flex items-center gap-2" role="group" aria-label="Compliance actions">
42
- <Tip side="bottom" label="Schedule a review (demo)">
43
- <Button type="button" size="lg" onClick={onAddReview}>
44
- <i className="fa-light fa-calendar-check" aria-hidden="true" />
45
- New review
46
- </Button>
47
- </Tip>
48
- <DropdownMenu open={moreOpen} onOpenChange={setMoreOpen}>
49
- <Tip side="bottom" label="More actions">
50
- <DropdownMenuTrigger asChild>
51
- <Button
52
- type="button"
53
- size="lg"
54
- variant="outline"
55
- className="aspect-square px-0"
56
- aria-label="More actions"
57
- >
58
- <i className="fa-light fa-ellipsis text-base" aria-hidden="true" />
59
- </Button>
60
- </DropdownMenuTrigger>
61
- </Tip>
62
- <DropdownMenuContent align="end">
63
- <DropdownMenuItem
64
- onSelect={() => {
65
- window.setTimeout(() => onExport(), 0)
66
- }}
67
- >
68
- <i className="fa-light fa-arrow-down-to-line" aria-hidden="true" />
69
- Export
70
- </DropdownMenuItem>
71
- <DropdownMenuSeparator />
72
- <DropdownMenuItem
73
- onSelect={() => {
74
- window.setTimeout(() => onToggleMetrics(), 0)
75
- }}
76
- >
77
- <i
78
- className={`fa-light ${showMetrics ? "fa-eye-slash" : "fa-eye"}`}
79
- aria-hidden="true"
80
- />
81
- {showMetrics ? "Hide metric section" : "Show metric section"}
82
- </DropdownMenuItem>
83
- </DropdownMenuContent>
84
- </DropdownMenu>
85
- </div>
86
- )}
87
- />
88
- )
89
- }