@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
@@ -0,0 +1,386 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Tokens & themes — visualizer primitives + token index.
5
+ *
6
+ * This module exports the building blocks consumed by `tokens-themes-client.tsx`
7
+ * (which wires them into `PrimaryPageTemplate` + `ListPageTemplate`). It does
8
+ * NOT render the page shell.
9
+ *
10
+ * Each category gets a representation that matches the token's nature:
11
+ *
12
+ * | Tab | Renders tokens as… |
13
+ * |--------------|---------------------------------------------------------------|
14
+ * | Colors | 56-px swatch (`background: var(--name)`) |
15
+ * | Gradients | 96×40 fill swatch (paint-based, value usually multi-line) |
16
+ * | Radius | 64×64 muted box with `border-radius: var(--name)` |
17
+ * | Size | bar with `height: var(--name)` (scaled visually) |
18
+ * | Shadow | floating mini-card with `box-shadow: var(--name)` |
19
+ * | Typography | "Aa Sample" in `font-family: var(--name)` |
20
+ * | Motion | a dot that translates on hover using `transition: var(--name)`|
21
+ * | Aliases | `name → var(--target)` row (resolves the indirection) |
22
+ * | Other | raw text value |
23
+ *
24
+ * All tiles share click-to-copy on the `var(--name)` reference. The token
25
+ * index is the single source of truth: `packages/ui/tokens/hooks-index.json`.
26
+ */
27
+
28
+ import * as React from "react"
29
+ import { useTheme } from "next-themes"
30
+ import tokensIndex from "@exxatdesignux/ui/tokens/hooks-index.json"
31
+ import { Button } from "@/components/ui/button"
32
+ import { Badge } from "@/components/ui/badge"
33
+ import { Tip } from "@/components/ui/tip"
34
+ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
35
+ import { cn } from "@/lib/utils"
36
+
37
+ /* ── Token index types ────────────────────────────────────────────────── */
38
+
39
+ export type TokenCategory =
40
+ | "color"
41
+ | "gradient"
42
+ | "radius"
43
+ | "size"
44
+ | "shadow"
45
+ | "typography"
46
+ | "transition"
47
+ | "alias"
48
+ | "other"
49
+
50
+ export type TokenRecord = {
51
+ namespace: string
52
+ category: TokenCategory | string
53
+ values: Record<string, string>
54
+ tailwindUtilities?: string[]
55
+ deprecated?: boolean
56
+ deprecatedMessage?: string | null
57
+ }
58
+
59
+ type TokensIndex = {
60
+ version: string
61
+ tokenCount: number
62
+ namespaces: string[]
63
+ themeKeys: string[]
64
+ tokens: Record<string, TokenRecord>
65
+ }
66
+
67
+ export const TOKENS_INDEX = tokensIndex as unknown as TokensIndex
68
+
69
+ /** First available theme value — used for the "raw value" text under each tile. */
70
+ export function primaryValueText(t: TokenRecord): string {
71
+ return t.values.light ?? Object.values(t.values)[0] ?? ""
72
+ }
73
+
74
+ /* ── Category tab catalogue ────────────────────────────────────────────── */
75
+
76
+ export interface CategoryTabDef {
77
+ id: TokenCategory
78
+ label: string
79
+ icon: string
80
+ matches: (cat: string) => boolean
81
+ }
82
+
83
+ export const CATEGORY_TABS: CategoryTabDef[] = [
84
+ { id: "color", label: "Colors", icon: "fa-palette", matches: (c) => c === "color" },
85
+ { id: "gradient", label: "Gradients", icon: "fa-circle-half-stroke", matches: (c) => c === "gradient" },
86
+ { id: "radius", label: "Radius", icon: "fa-rectangle-vertical", matches: (c) => c === "radius" },
87
+ { id: "size", label: "Size", icon: "fa-ruler-horizontal", matches: (c) => c === "size" },
88
+ { id: "shadow", label: "Shadow", icon: "fa-clone", matches: (c) => c === "shadow" },
89
+ { id: "typography", label: "Typography", icon: "fa-text-size", matches: (c) => c === "typography" },
90
+ { id: "transition", label: "Motion", icon: "fa-wave-sine", matches: (c) => c === "transition" },
91
+ { id: "alias", label: "Aliases", icon: "fa-link", matches: (c) => c === "alias" },
92
+ { id: "other", label: "Other", icon: "fa-hashtag", matches: (c) => c === "other" },
93
+ ]
94
+
95
+ /** Pre-compute counts per category — same shape `getTabCount(filterId)` expects. */
96
+ export const CATEGORY_COUNTS: Record<TokenCategory, number> = CATEGORY_TABS.reduce(
97
+ (acc, tab) => { acc[tab.id] = 0; return acc },
98
+ {} as Record<TokenCategory, number>,
99
+ )
100
+ export const DEPRECATED_COUNT = (() => {
101
+ let n = 0
102
+ for (const t of Object.values(TOKENS_INDEX.tokens)) {
103
+ if (t.deprecated) n += 1
104
+ for (const tab of CATEGORY_TABS) {
105
+ if (tab.matches(t.category)) {
106
+ CATEGORY_COUNTS[tab.id] += 1
107
+ break
108
+ }
109
+ }
110
+ }
111
+ return n
112
+ })()
113
+
114
+ /* ── Theme switcher ────────────────────────────────────────────────────── */
115
+
116
+ export function TokensThemeSwitcher({ className }: { className?: string }) {
117
+ const { theme = "system", setTheme } = useTheme()
118
+ const [mounted, setMounted] = React.useState(false)
119
+ React.useEffect(() => setMounted(true), [])
120
+ const value = mounted ? theme : "system"
121
+ return (
122
+ <RadioGroup
123
+ value={value}
124
+ onValueChange={setTheme}
125
+ className={cn("inline-flex items-center gap-1 rounded-md border border-border bg-card p-1", className)}
126
+ aria-label="Theme preview"
127
+ >
128
+ {(["light", "dark", "system"] as const).map((t) => (
129
+ <label
130
+ key={t}
131
+ htmlFor={`theme-${t}`}
132
+ className={cn(
133
+ "flex cursor-pointer items-center gap-1.5 rounded px-2.5 py-1 text-xs transition-colors",
134
+ "hover:bg-foreground/[0.04]",
135
+ value === t && "bg-foreground/[0.06] text-foreground",
136
+ )}
137
+ >
138
+ <RadioGroupItem value={t} id={`theme-${t}`} className="sr-only" />
139
+ <i
140
+ className={cn(
141
+ "fa-light text-xs",
142
+ t === "light" && "fa-sun",
143
+ t === "dark" && "fa-moon",
144
+ t === "system" && "fa-desktop",
145
+ )}
146
+ aria-hidden="true"
147
+ />
148
+ <span className="capitalize">{t}</span>
149
+ </label>
150
+ ))}
151
+ </RadioGroup>
152
+ )
153
+ }
154
+
155
+ /* ── Clipboard hook ────────────────────────────────────────────────────── */
156
+
157
+ export function useTokenClipboard() {
158
+ const [copied, setCopied] = React.useState<string | null>(null)
159
+ const copy = React.useCallback((text: string) => {
160
+ if (typeof navigator === "undefined" || !navigator.clipboard) return
161
+ navigator.clipboard.writeText(text).then(() => {
162
+ setCopied(text)
163
+ window.setTimeout(() => setCopied((c) => (c === text ? null : c)), 1200)
164
+ }).catch(() => {})
165
+ }, [])
166
+ return { copied, copy }
167
+ }
168
+
169
+ /* ── Tile shell ────────────────────────────────────────────────────────── */
170
+
171
+ interface TokenTileProps {
172
+ name: string
173
+ record: TokenRecord
174
+ onCopy: (text: string) => void
175
+ preview: React.ReactNode
176
+ valueText?: string
177
+ density?: "tight" | "wide"
178
+ }
179
+
180
+ function TokenTile({ name, record, onCopy, preview, valueText, density = "wide" }: TokenTileProps) {
181
+ const cssRef = `var(${name})`
182
+ const raw = valueText ?? primaryValueText(record)
183
+ return (
184
+ <div
185
+ className={cn(
186
+ "group flex items-stretch gap-3 rounded-md border border-border bg-card p-3 transition-colors",
187
+ "hover:border-brand/40 hover:bg-interactive-hover-soft",
188
+ )}
189
+ >
190
+ <div className={cn("shrink-0", density === "tight" ? "w-14" : "w-20")}>{preview}</div>
191
+ <div className="min-w-0 flex-1">
192
+ <div className="flex items-center gap-2 min-w-0">
193
+ <code className="font-mono text-xs text-foreground truncate tabular-nums">{name}</code>
194
+ {record.deprecated && (
195
+ <Badge variant="destructive" className="text-[10px] h-4 px-1.5 shrink-0">
196
+ deprecated
197
+ </Badge>
198
+ )}
199
+ </div>
200
+ <div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-muted-foreground">
201
+ <span className="rounded-sm bg-muted/60 px-1.5 py-0.5">{record.namespace}</span>
202
+ </div>
203
+ <div className="mt-1 truncate font-mono text-[11px] text-muted-foreground" title={raw}>
204
+ {raw || "—"}
205
+ </div>
206
+ </div>
207
+ <Tip side="left" label={`Copy ${cssRef}`}>
208
+ <Button
209
+ type="button"
210
+ size="icon"
211
+ variant="ghost"
212
+ className="size-7 shrink-0 self-center opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
213
+ onClick={() => onCopy(cssRef)}
214
+ aria-label={`Copy ${cssRef}`}
215
+ >
216
+ <i className="fa-light fa-copy text-sm" aria-hidden="true" />
217
+ </Button>
218
+ </Tip>
219
+ </div>
220
+ )
221
+ }
222
+
223
+ /* ── Category-specific previews ───────────────────────────────────────── */
224
+
225
+ function ColorPreview({ name }: { name: string }) {
226
+ return (
227
+ <div
228
+ className="h-14 w-14 rounded-md border border-border"
229
+ style={{ backgroundColor: `var(${name})` }}
230
+ aria-hidden="true"
231
+ />
232
+ )
233
+ }
234
+ function GradientPreview({ name }: { name: string }) {
235
+ return (
236
+ <div
237
+ className="h-14 w-20 rounded-md border border-border"
238
+ style={{ background: `var(${name})` }}
239
+ aria-hidden="true"
240
+ />
241
+ )
242
+ }
243
+ function RadiusPreview({ name }: { name: string }) {
244
+ return (
245
+ <div
246
+ className="h-14 w-14 border border-border bg-muted/50"
247
+ style={{ borderRadius: `var(${name})` }}
248
+ aria-hidden="true"
249
+ />
250
+ )
251
+ }
252
+ function SizePreview({ name, record }: { name: string; record: TokenRecord }) {
253
+ const raw = primaryValueText(record)
254
+ return (
255
+ <div className="flex h-14 w-20 items-center justify-center" aria-hidden="true">
256
+ <div
257
+ className="w-full bg-brand rounded-sm"
258
+ style={{ height: `min(56px, var(${name}))` }}
259
+ title={raw}
260
+ />
261
+ </div>
262
+ )
263
+ }
264
+ function ShadowPreview({ name }: { name: string }) {
265
+ return (
266
+ <div
267
+ className="m-1 h-12 w-12 rounded-md bg-card"
268
+ style={{ boxShadow: `var(${name})` }}
269
+ aria-hidden="true"
270
+ />
271
+ )
272
+ }
273
+ function TypographyPreview({ name }: { name: string }) {
274
+ return (
275
+ <div
276
+ className="flex h-14 w-20 items-center justify-center rounded-md border border-border bg-muted/30"
277
+ style={{ fontFamily: `var(${name})` }}
278
+ aria-hidden="true"
279
+ >
280
+ <span className="text-2xl text-foreground">Aa</span>
281
+ </div>
282
+ )
283
+ }
284
+ function MotionPreview({ name }: { name: string }) {
285
+ return (
286
+ <div className="relative h-14 w-20 overflow-hidden rounded-md border border-border bg-muted/30" aria-hidden="true">
287
+ <div
288
+ className="absolute left-2 top-1/2 size-3 -translate-y-1/2 rounded-full bg-brand group-hover:translate-x-12"
289
+ style={{ transition: `var(${name})` }}
290
+ />
291
+ </div>
292
+ )
293
+ }
294
+ function AliasPreview({ record }: { record: TokenRecord }) {
295
+ const v = primaryValueText(record)
296
+ const targetMatch = v.match(/var\((--[a-z0-9-]+)\)/i)
297
+ const target = targetMatch?.[1]
298
+ return (
299
+ <div className="flex h-14 w-20 flex-col items-center justify-center rounded-md border border-border bg-muted/30 text-center" aria-hidden="true">
300
+ <i className="fa-light fa-link text-base text-muted-foreground" />
301
+ {target && (
302
+ <code className="mt-0.5 truncate max-w-full px-1 font-mono text-[9px] text-muted-foreground">{target}</code>
303
+ )}
304
+ </div>
305
+ )
306
+ }
307
+ function OtherPreview() {
308
+ return (
309
+ <div className="flex h-14 w-20 items-center justify-center rounded-md border border-dashed border-border text-muted-foreground" aria-hidden="true">
310
+ <i className="fa-light fa-hashtag text-base" />
311
+ </div>
312
+ )
313
+ }
314
+
315
+ /**
316
+ * Pick the right visualizer for a token. Used both by the legacy `TokensCategoryGrid`
317
+ * and by the DataTable Preview cell in `tokens-themes-client.tsx`.
318
+ */
319
+ export function categoryPreview(name: string, record: TokenRecord): React.ReactNode {
320
+ switch (record.category) {
321
+ case "color": return <ColorPreview name={name} />
322
+ case "gradient": return <GradientPreview name={name} />
323
+ case "radius": return <RadiusPreview name={name} />
324
+ case "size": return <SizePreview name={name} record={record} />
325
+ case "shadow": return <ShadowPreview name={name} />
326
+ case "typography": return <TypographyPreview name={name} />
327
+ case "transition": return <MotionPreview name={name} />
328
+ case "alias": return <AliasPreview record={record} />
329
+ default: return <OtherPreview />
330
+ }
331
+ }
332
+
333
+ /* ── Category grid (the page body for one view tab) ───────────────────── */
334
+
335
+ export interface TokensCategoryGridProps {
336
+ query: string
337
+ showDeprecated: boolean
338
+ category: CategoryTabDef
339
+ onCopy: (text: string) => void
340
+ }
341
+
342
+ export function TokensCategoryGrid({ query, showDeprecated, category, onCopy }: TokensCategoryGridProps) {
343
+ const filtered = React.useMemo(() => {
344
+ const q = query.trim().toLowerCase()
345
+ const out: Array<[string, TokenRecord]> = []
346
+ for (const [name, record] of Object.entries(TOKENS_INDEX.tokens)) {
347
+ if (!category.matches(record.category)) continue
348
+ if (!showDeprecated && record.deprecated) continue
349
+ if (q && !(name.toLowerCase().includes(q) || record.namespace.toLowerCase().includes(q))) continue
350
+ out.push([name, record])
351
+ }
352
+ return out.sort(([a], [b]) => a.localeCompare(b))
353
+ }, [query, showDeprecated, category])
354
+
355
+ if (filtered.length === 0) {
356
+ return (
357
+ <div className="rounded-md border border-dashed border-border bg-muted/20 p-8 text-center text-sm text-muted-foreground">
358
+ No {category.label.toLowerCase()} tokens match your filter.
359
+ </div>
360
+ )
361
+ }
362
+
363
+ /** Colors are dense (100+ tokens) → 3-column on wide. Other categories 1–2 col. */
364
+ const isDense = category.id === "color"
365
+ return (
366
+ <div
367
+ className={cn(
368
+ "grid gap-2",
369
+ isDense
370
+ ? "grid-cols-1 md:grid-cols-2 xl:grid-cols-3"
371
+ : "grid-cols-1 md:grid-cols-2",
372
+ )}
373
+ >
374
+ {filtered.map(([name, record]) => (
375
+ <TokenTile
376
+ key={name}
377
+ name={name}
378
+ record={record}
379
+ onCopy={onCopy}
380
+ density={isDense ? "tight" : "wide"}
381
+ preview={categoryPreview(name, record)}
382
+ />
383
+ ))}
384
+ </div>
385
+ )
386
+ }
@@ -0,0 +1,187 @@
1
+ # Exxat DS — Handbook
2
+
3
+ > **Start here.** One page. Read in 10 minutes. Links out to everything else.
4
+ >
5
+ > **Audience:** designers, engineers, contributors, AI agents — anyone shipping UI in the Exxat product.
6
+ >
7
+ > **Working with an AI assistant?** Read [`.cursor/skills/exxat-token-economy/SKILL.md`](../../.cursor/skills/exxat-token-economy/SKILL.md) **first** (or `.claude/skills/exxat-token-economy/SKILL.md` for Claude Code). It's a one-page pre-flight that cuts token usage by ~50%: a task → minimum-file-set table, the five-question rule check, and tiny scaffolds that mean the assistant never has to re-read this handbook for the common case.
8
+
9
+ ---
10
+
11
+ ## 1. Five principles
12
+
13
+ Every screen, primitive, and pattern in this design system serves one or more of these. When in doubt, the principle wins over the convenience.
14
+
15
+ 1. **Clarity over decoration.** Users see one product, one shell, one rhythm. Surprise (mystery icons, hidden states, novel layouts) is a tax on attention.
16
+ 2. **Progressive disclosure.** Beginners see what they need. Power users reach what they want. Default to the simpler surface; expose density on opt-in (Properties drawer, view tabs, secondary panel).
17
+ 3. **Same-shaped tools.** A hub looks like a hub. A drawer looks like a drawer. A KPI looks like a KPI. Pick the canonical primitive (§5 reference pages) before composing your own.
18
+ 4. **Accessibility is non-negotiable.** WCAG 2.1 AA is the **floor**, not the goal. Keyboard, screen-reader, contrast, touch-target, format hints — all enforced by rules, lints, and a checklist.
19
+ 5. **The data drives the chrome.** KPIs, status, trend polarity, descriptions — they come from the dataset (or the product) and are honest. No spin arrows, no decorative placeholders.
20
+
21
+ ---
22
+
23
+ ## 2. How to build a hub in 6 steps
24
+
25
+ This is the **happy path** for the most common task: "I have an entity (records, library items, tokens, …); ship a hub for it." Follow these in order and the page lands at "best UX/UI", not "random design".
26
+
27
+ | Step | What to do | Where it lives | Rule |
28
+ |---|---|---|---|
29
+ | 1 | Add typed mock rows in `lib/mock/<entity>.ts`. Aim for ~12 realistic records. | `apps/web/lib/mock/` | `.cursor/rules/exxat-centralized-list-dataset.mdc` |
30
+ | 2 | Write **one** KPI helper `lib/mock/<entity>-kpi.ts` returning `MetricItem[]` (≤ 4 tiles). | same | `exxat-kpi-max-four.mdc`, `exxat-kpi-trends.mdc` |
31
+ | 3 | Build the column defs (`ColumnDef[]`). Set `filter:` per column to get filter chips automatically. | `apps/web/components/<entity>-table.tsx` | `exxat-data-tables.mdc` |
32
+ | 4 | Mount **`HubTable`** (NOT raw `<DataTable>`) inside `ListPageTemplate.renderContent`. `HubTable` wires `useTableState`, toolbar (search + filter chips + sort), and the **Properties drawer** in one place. | `apps/web/components/<entity>-table.tsx` | `exxat-data-tables.mdc` |
33
+ | 5 | Compose the page client with `PrimaryPageTemplate` → `ListPageTemplate` (KPIs in `metrics`, view tabs in `defaultTabs`, the `HubTable` in `renderContent`). | `apps/web/components/<entity>-client.tsx` | `exxat-list-page-connected-views.mdc` |
34
+ | 6 | Add to nav (`lib/mock/navigation.tsx`). If the hub needs scoped sub-navigation (e.g. categories), declare `secondaryPanel: "<id>"` and register the panel. | `apps/web/lib/mock/navigation.tsx`, `apps/web/components/sidebar/secondary-panel.tsx` | `exxat-primary-nav-secondary-panel.mdc` |
35
+
36
+ **Reference pages to copy:** `apps/web/components/columns-showcase.tsx` (single-view catalog hub composing every reusable cell), `apps/web/components/tokens-themes-client.tsx` (hub with a secondary panel + URL-driven scope), or `apps/web/components/library-table.tsx` (full hub: table / board / dashboard + conditional rules).
37
+
38
+ > **Stop signs.** If you find yourself building a parallel table stack, a second metrics strip, a custom filter row, or pasting raw `<DataTable>` into `renderContent` — **stop and re-read** `.cursor/rules/exxat-reuse-before-custom.mdc`.
39
+
40
+ ---
41
+
42
+ ## 3. Where everything lives
43
+
44
+ ```
45
+ ┌─────────────────────────────────────────────────────────────────────┐
46
+ │ PRINCIPLES + HANDBOOK → docs/HANDBOOK.md (this file) │
47
+ │ │
48
+ │ FOUNDATIONS │
49
+ │ tokens → docs/token-taxonomy.md │
50
+ │ icons (Font Awesome) → .cursor/rules/exxat-fontawesome-icons │
51
+ │ typography → docs/token-taxonomy.md (font-* tokens) │
52
+ │ spacing / radius → docs/token-taxonomy.md (--exxat-*) │
53
+ │ color & themes → apps/web/components/tokens-themes-* │
54
+ │ voice & tone → docs/voice-and-tone.md │
55
+ │ glossary → docs/glossary.md │
56
+ │ reference pages → docs/reference-implementations.md │
57
+ │ │
58
+ │ DECIDING (selection guides) │
59
+ │ which component? → docs/component-selection-guide.md │
60
+ │ page vs drawer vs → docs/drawer-vs-dialog-pattern.md │
61
+ │ dialog vs route + .cursor/rules/exxat-{drawer-vs-dialog, │
62
+ │ page-vs-drawer}.mdc │
63
+ │ card vs row vs list → docs/card-vs-rows-pattern.md │
64
+ │ │
65
+ │ BLUEPRINTS (framework-agnostic specs — one per pattern) │
66
+ │ → docs/blueprints/ │
67
+ │ page-header · data-table · │
68
+ │ list-page-template · board-card · │
69
+ │ key-metrics │
70
+ │ │
71
+ │ PATTERNS (long-form narrative — the "why" + the "how") │
72
+ │ → docs/*.md (data-views-pattern, │
73
+ │ kpi-trend-pattern, drawer-vs-dialog- │
74
+ │ pattern, dedicated-search, │
75
+ │ command-menu, …) │
76
+ │ │
77
+ │ RULES (binding MUST / MUST NOT — for AI agents + reviewers) │
78
+ │ → .cursor/rules/*.mdc │
79
+ │ │
80
+ │ SKILLS (workflows + checklists — for AI agents doing a task) │
81
+ │ → .cursor/skills/ + .claude/skills/ │
82
+ │ │
83
+ │ MIGRATIONS (deprecation history, every breaking change) │
84
+ │ → docs/migrations/ │
85
+ │ │
86
+ │ AGENT HANDBOOK (authoritative §-numbered manual) │
87
+ │ → apps/web/AGENTS.md │
88
+ └─────────────────────────────────────────────────────────────────────┘
89
+ ```
90
+
91
+ ### Quick "which file should I open?" cheat-sheet
92
+
93
+ | You want to… | Open this |
94
+ |---|---|
95
+ | Pick the right component for a job | [`docs/component-selection-guide.md`](./component-selection-guide.md) |
96
+ | Know what "hub" / "view tab" / "KPI band" mean | [`docs/glossary.md`](./glossary.md) |
97
+ | Write empty-state / error / button copy | [`docs/voice-and-tone.md`](./voice-and-tone.md) |
98
+ | Find the canonical reference page to copy | [`docs/reference-implementations.md`](./reference-implementations.md) |
99
+ | Know the spec for a pattern | [`docs/blueprints/`](./blueprints/) |
100
+ | Understand the "why" of a pattern | [`docs/<pattern>-pattern.md`](.) |
101
+ | Know the binding MUST / MUST NOT | [`.cursor/rules/`](../../../.cursor/rules/) |
102
+ | Run a recurring agent workflow | [`.cursor/skills/`](../../../.cursor/skills/) or [`.claude/skills/`](../../../.claude/skills/) |
103
+ | Token name & semantics | [`docs/token-taxonomy.md`](./token-taxonomy.md) |
104
+ | Full authoritative handbook | [`apps/web/AGENTS.md`](../AGENTS.md) |
105
+
106
+ ---
107
+
108
+ ## 4. Rule precedence (when sources conflict)
109
+
110
+ If two docs say different things, **the higher row wins**:
111
+
112
+ 1. **`.cursor/rules/*.mdc`** — these are MUST / MUST NOT; they bind the AI agent and the reviewer.
113
+ 2. **`apps/web/AGENTS.md`** — authoritative §-numbered handbook. The rules above are summaries of this.
114
+ 3. **`docs/blueprints/*.md`** — framework-agnostic specs for a single pattern.
115
+ 4. **`docs/*-pattern.md`** — long-form narrative for a pattern.
116
+ 5. **Reference page in code** (`apps/web/components/<reference>.tsx`) — the working implementation.
117
+ 6. **This handbook** — orientation only. If it conflicts with rules or AGENTS, the rules/AGENTS win.
118
+
119
+ > **Found a conflict?** Open a PR that updates the *binding* layer (rule or AGENTS section) first, then propagate down. Don't fork the truth.
120
+
121
+ ---
122
+
123
+ ## 5. The canonical primitives (memorize these)
124
+
125
+ These are the ones you'll use on >90% of screens. If a screen needs something else, it almost certainly already exists — search `components/` before building.
126
+
127
+ | Need | Primitive | Lives in |
128
+ |---|---|---|
129
+ | Page chrome (breadcrumbs, site header, max-width content rail) | `PrimaryPageTemplate` | `apps/web/components/templates/primary-page-template.tsx` |
130
+ | Hub frame (header + metrics + view tabs + content) | `ListPageTemplate` | `packages/ui` |
131
+ | **Hub view body** (table + search + filters + Properties drawer + bulk-actions) | **`HubTable`** | `packages/ui` (re-exported from `@/components/data-views`) |
132
+ | Page header (title + subtitle + actions + collaborators rail) | `PageHeader` | `apps/web/components/page-header.tsx` |
133
+ | KPI strip / band | `KeyMetrics` (`variant="flat"` on hubs) | `packages/ui` |
134
+ | Status chip + icon | `ListHubStatusBadge` + `lib/list-status-badges.ts` | `apps/web/components/` |
135
+ | Board / kanban card | `ListPageBoardCard` + primitives | `packages/ui` |
136
+ | Side overlay | `Sheet` / `Drawer` (NOT toast — `exxat-no-toast.mdc`) | `packages/ui` |
137
+ | Persistent banner | `LocalBanner` / `SystemBanner` | `packages/ui` |
138
+ | Inline status / format hint | `FormDescription`, inline `<small>` | `packages/ui` |
139
+ | Tooltip | `Tip` / `Tooltip` | `packages/ui` |
140
+ | Keyboard shortcut hint | `Kbd` (`variant="bare"` inside buttons) | `packages/ui` |
141
+ | Global search | `CommandMenu` (⌘K) | `apps/web/components/command-menu.tsx` |
142
+ | AI assistant chrome | Ask Leo side panel (⌘⌥K) | `apps/web/components/` |
143
+
144
+ For a fuller decision tree see [`docs/component-selection-guide.md`](./component-selection-guide.md).
145
+
146
+ ---
147
+
148
+ ## 6. The shortest accessibility checklist
149
+
150
+ Run this on every PR. If you can't tick every box, the change isn't ready. (Full list: `.cursor/skills/exxat-accessibility/SKILL.md` and [`AGENTS.md` §8](../AGENTS.md).)
151
+
152
+ - [ ] **Keyboard.** Every interactive thing reachable via Tab + activatable via Enter / Space. Focus ring visible (≥ 3:1).
153
+ - [ ] **Touch target ≥ 24×24 CSS px** (or 24 px spacing) per WCAG 2.5.8.
154
+ - [ ] **Icons that mean something** have a text alt — either adjacent label (Case A, `aria-hidden`), or `role="img" + aria-label + Tooltip` (Case B), or `aria-label + Tooltip` on the button (Case C). No silent icons.
155
+ - [ ] **Contrast.** Text ≥ 4.5:1; UI components ≥ 3:1. Don't encode state with color alone — pair with icon or label.
156
+ - [ ] **Format hints are persistent**, never placeholder-only. Use `FormDescription`.
157
+ - [ ] **Dialogs / drawers / sheets** have a `Title` (use `sr-only` if visually hidden).
158
+ - [ ] **Tabs** use `role="tablist"` correctly (no mixed children); composite switchers use `role="toolbar"` instead.
159
+ - [ ] **No toast.** Use banners, inline status, or dialogs (`exxat-no-toast.mdc`).
160
+ - [ ] **HC modes.** Forced-colors and `data-contrast="high"` covered for any fill-only state (progress, gauge, pill).
161
+
162
+ ---
163
+
164
+ ## 7. The "you're done" definition
165
+
166
+ A hub or screen is **done** when:
167
+
168
+ 1. It uses `PrimaryPageTemplate` + `ListPageTemplate` (or another canonical template).
169
+ 2. The data surface is `HubTable` (or, for non-hubs, the right primitive from §5).
170
+ 3. KPIs use `KeyMetrics` with `delta` for counts, `description` for prose, ≤ 4 tiles, polarity set correctly.
171
+ 4. The §6 accessibility checklist is green.
172
+ 5. Copy passes [`docs/voice-and-tone.md`](./voice-and-tone.md).
173
+ 6. No new shared primitives were added without `.cursor/rules/exxat-reuse-before-custom.mdc` approval.
174
+ 7. The §13 PR-review checklist in [`AGENTS.md`](../AGENTS.md#section-13) is green.
175
+
176
+ ---
177
+
178
+ ## 8. Where to ask for help
179
+
180
+ - **Code-level questions** — open the file referenced in a rule's "See also" section.
181
+ - **Pattern-level questions** — open the matching `docs/*-pattern.md`.
182
+ - **"Is this the right approach?"** — read [`.cursor/rules/exxat-reuse-before-custom.mdc`](../../../.cursor/rules/exxat-reuse-before-custom.mdc). If still unsure, ask before building.
183
+ - **AGENTS.md is too long** — that's why this handbook exists. Bring the §-number you're stuck on; we'll split it out.
184
+
185
+ ---
186
+
187
+ *This file is intentionally short. If you want to add something, ask whether it belongs in a rule, a pattern, a blueprint, or the glossary instead.*
@@ -62,7 +62,7 @@ dive.
62
62
 
63
63
  | Blueprint | Narrative doc | Cursor rule(s) | React component(s) |
64
64
  |---|---|---|---|
65
- | [page-header](./page-header.md) | `apps/web/docs/data-views-pattern.md` (Page header section) | `exxat-collaboration-access.mdc` (variant), `exxat-mono-ids.mdc` (subtitle IDs) | `PageHeader`, `PlacementsPageHeader`, `TeamPageHeader`, `QuestionBankPageHeader` |
65
+ | [page-header](./page-header.md) | `apps/web/docs/data-views-pattern.md` (Page header section) | `exxat-collaboration-access.mdc` (variant), `exxat-mono-ids.mdc` (subtitle IDs) | `PageHeader`, `PlacementsPageHeader`, `TeamPageHeader`, `LibraryPageHeader` |
66
66
  | [data-table](./data-table.md) | `apps/web/docs/data-views-pattern.md` | `exxat-data-tables.mdc`, `exxat-list-page-connected-views.mdc`, `exxat-centralized-list-dataset.mdc`, `exxat-table-properties-drawer.mdc` | `DataTable`, `DataTablePaginated`, `useTableState`, `TablePropertiesDrawer` |
67
67
  | [list-page-template](./list-page-template.md) | `apps/web/docs/data-views-pattern.md`, `kpi-flat-band-pattern.md` | `exxat-list-page-connected-views.mdc`, `exxat-centralized-list-dataset.mdc`, `exxat-table-properties-drawer.mdc`, `exxat-list-page-view-shells.mdc` | `ListPageTemplate`, `HubTable`, `useTableState`, `TablePropertiesDrawer` |
68
68
  | [board-card](./board-card.md) | `apps/web/docs/data-views-pattern.md` (board UI section) | `exxat-board-cards.mdc`, `exxat-centralized-list-dataset.mdc`, `exxat-card-vs-list-rows.mdc` | `ListPageBoardCard`, `ListPageBoardCardTitleRow`, `ListPageBoardCardBadgeRow`, `BoardCardTwoLineBlock`, `BoardCardIconRow`, `ListHubStatusBadge` |
@@ -85,7 +85,7 @@ a bespoke `<button>` + border-class wrapper for the same pattern.
85
85
  | Variant | When to use | Differences from default |
86
86
  |---|---|---|
87
87
  | `base` | Most hubs | Title + status badge + body |
88
- | `no-status` | Hubs without lifecycle (Question bank items) | Omit `badgeRow`; body carries the differentiator |
88
+ | `no-status` | Hubs without lifecycle (Library items) | Omit `badgeRow`; body carries the differentiator |
89
89
  | `compact` | Dense boards (10+ cards visible) | Drop body; show title + status + avatar only |
90
90
  | `with-progress` | Workflow boards with completion bar | Add a `<Progress>` row in the body slot |
91
91
 
@@ -106,7 +106,7 @@ dashboard, folder, panel — reads from.
106
106
 
107
107
  | Framework | Component(s) | File |
108
108
  |---|---|---|
109
- | **React (this app)** | `DataTable`, `DataTablePaginated`, `useTableState`, `DataTableToolbar`, `TablePropertiesDrawer`, column-cell helpers in `placements-table-cells.tsx` | [`apps/web/components/data-table/`](../../components/data-table/), [`apps/web/components/placements-table-cells.tsx`](../../components/placements-table-cells.tsx), `@/components/table-properties` |
109
+ | **React (this app)** | `DataTable`, `DataTablePaginated`, `useTableState`, `DataTableToolbar`, `TablePropertiesDrawer`, **importable cell renderers** in `components/data-views/table-cells.tsx` (`ProgressCell`, `CurrencyCell`, `RatingCell`, `RowActionsCell<TRow>`, …) | [`packages/ui/src/components/data-table/`](../../../../packages/ui/src/components/data-table/), [`apps/web/components/data-views/table-cells.tsx`](../../components/data-views/table-cells.tsx), [`packages/ui/src/components/table-properties/`](../../../../packages/ui/src/components/table-properties/) |
110
110
  | Mobile | — | — |
111
111
  | Figma | — | — |
112
112
 
@@ -115,7 +115,7 @@ Reference compositions:
115
115
  - **Placements** — `PlacementsClient` + `PlacementsTable` (most complete reference)
116
116
  - **Team** — `TeamClient` + `TeamTable`
117
117
  - **Compliance** — `ComplianceClient` + `ComplianceTable`
118
- - **Question bank** — `QuestionBankClient` + `QuestionBankTable` (adds folder
118
+ - **Library** — `LibraryClient` + `LibraryTable` (adds folder
119
119
  scope from URL)
120
120
 
121
121
  ## 8. Do / Don't
@@ -5,7 +5,7 @@
5
5
  ## 1. Intent
6
6
 
7
7
  A **list page** is the canonical hub surface for browsing a homogeneous
8
- collection of records (Placements, Team, Compliance, Question bank items, …).
8
+ collection of records (Placements, Team, Compliance, Library items, …).
9
9
  It pairs a single `DataTable` with view-tab variants (table · list · board ·
10
10
  dashboard · folder · panel · tree), one shared filter / sort / column model,
11
11
  and a metric strip that consumes the same filtered row bag.
@@ -93,9 +93,9 @@ The view body is rendered by `HubTable` (or a custom non-table view) and
93
93
  | Variant | When to use | Differences from default |
94
94
  |---|---|---|
95
95
  | `base` | Most hubs | Header + metrics + tabs + view body |
96
- | `no-metrics` | Hubs where KPI summary adds noise (e.g. Question bank: too many heterogeneous folders) | Omit `metrics` prop |
96
+ | `no-metrics` | Hubs where KPI summary adds noise (e.g. Library: too many heterogeneous folders) | Omit `metrics` prop |
97
97
  | `with-banner` | Pages that need a promo or alert above the header | Use `beforeSiteHeader` slot |
98
- | `secondary-panel-hub` | Hubs scoped by a sidebar panel (Question bank) | Wrap in `SecondaryPanelHubTemplate`; same `useTableState` |
98
+ | `secondary-panel-hub` | Hubs scoped by a sidebar panel (Library) | Wrap in `SecondaryPanelHubTemplate`; same `useTableState` |
99
99
 
100
100
  ## 7. Implementation
101
101