@exxatdesignux/ui 0.2.17 → 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 (162) hide show
  1. package/CHANGELOG.md +30 -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 +22 -7
  7. package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +57 -0
  8. package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +38 -0
  9. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +10 -3
  10. package/consumer-extras/patterns/consumer-app-pattern.md +39 -0
  11. package/consumer-extras/patterns/consumer-upgrade-checklist.md +20 -0
  12. package/consumer-extras/patterns/data-views-pattern.md +42 -3
  13. package/consumer-extras/patterns/focused-workflow-page-pattern.md +84 -0
  14. package/consumer-extras/patterns/kpi-flat-band-pattern.md +57 -0
  15. package/consumer-extras/patterns/shell-surface-elevation-pattern.md +54 -0
  16. package/package.json +2 -1
  17. package/src/components/ui/button-group.tsx +81 -0
  18. package/src/components/ui/button.tsx +4 -4
  19. package/src/components/ui/sidebar.tsx +2 -2
  20. package/src/globals.css +7 -1807
  21. package/src/theme.css +10 -1126
  22. package/src/tokens/README.md +15 -0
  23. package/src/tokens/base.css +337 -0
  24. package/src/tokens/high-contrast.css +1195 -0
  25. package/src/tokens/layers.css +224 -0
  26. package/src/tokens/tailwind-bridge.css +118 -0
  27. package/src/tokens/themes.css +201 -0
  28. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -1
  29. package/template/AGENTS.md +66 -21
  30. package/template/app/(app)/dashboard/loading.tsx +3 -15
  31. package/template/app/(app)/dashboard/page.tsx +2 -14
  32. package/template/app/(app)/data-list/layout.tsx +43 -0
  33. package/template/app/(app)/data-list/page.tsx +2 -2
  34. package/template/app/(app)/error.tsx +22 -6
  35. package/template/app/(app)/examples/focused-workflow/page.tsx +5 -0
  36. package/template/app/(app)/examples/page.tsx +1 -0
  37. package/template/app/(app)/layout.tsx +13 -6
  38. package/template/app/(app)/loading.tsx +1 -18
  39. package/template/app/(app)/question-bank/find/page.tsx +2 -1
  40. package/template/app/(app)/question-bank/library/page.tsx +2 -1
  41. package/template/app/(app)/question-bank/list/page.tsx +2 -1
  42. package/template/app/(app)/question-bank/new/page.tsx +15 -23
  43. package/template/app/(app)/question-bank/page.tsx +2 -1
  44. package/template/app/(app)/settings/page.tsx +4 -5
  45. package/template/app/global-error.tsx +63 -0
  46. package/template/app/globals.css +7 -1934
  47. package/template/app/layout.tsx +2 -0
  48. package/template/components/app-route-loading.tsx +14 -0
  49. package/template/components/app-sidebar.tsx +71 -55
  50. package/template/components/data-table/index.tsx +31 -67
  51. package/template/components/data-table/use-table-state.ts +33 -6
  52. package/template/components/data-views/index.ts +37 -9
  53. package/template/components/data-views/list-page-calendar-view.tsx +593 -0
  54. package/template/components/data-views/list-page-connected-view-body.tsx +66 -0
  55. package/template/components/data-views/list-page-folder-columns-panel.tsx +345 -0
  56. package/template/components/data-views/list-page-split-hub-chrome.tsx +8 -0
  57. package/template/components/dev-chunk-load-recovery.tsx +41 -0
  58. package/template/components/examples/focused-workflow-showcase.tsx +183 -0
  59. package/template/components/exxat-product-logo.tsx +2 -6
  60. package/template/components/key-metrics.tsx +54 -22
  61. package/template/components/list-hub-board-view.tsx +68 -0
  62. package/template/components/list-hub-client.tsx +186 -0
  63. package/template/components/list-hub-list-view.tsx +36 -0
  64. package/template/components/list-hub-panel-activator.tsx +8 -0
  65. package/template/components/list-hub-secondary-nav.tsx +121 -0
  66. package/template/components/list-hub-table.tsx +336 -0
  67. package/template/components/new-question-composer.tsx +6 -24
  68. package/template/components/product-switcher.tsx +5 -5
  69. package/template/components/product-wordmark.tsx +4 -7
  70. package/template/components/question-bank-client.tsx +4 -1
  71. package/template/components/question-bank-folder-columns-panel.tsx +104 -0
  72. package/template/components/question-bank-hub-client.tsx +2 -5
  73. package/template/components/question-bank-table.tsx +155 -509
  74. package/template/components/secondary-panel/nav-link-rows.tsx +83 -0
  75. package/template/components/secondary-panel.tsx +4 -44
  76. package/template/components/secondary-panels/list-hub-panel.tsx +39 -0
  77. package/template/components/secondary-panels/question-bank-panel.tsx +39 -0
  78. package/template/components/secondary-panels/registry.tsx +15 -0
  79. package/template/components/settings-appearance-card.tsx +3 -2
  80. package/template/components/settings-client.tsx +59 -15
  81. package/template/components/settings-form-row.tsx +9 -4
  82. package/template/components/sidebar-shell.tsx +2 -1
  83. package/template/components/table-properties/drawer-button.tsx +51 -20
  84. package/template/components/table-properties/drawer.tsx +81 -17
  85. package/template/components/templates/focused-workflow-layouts.tsx +448 -0
  86. package/template/components/templates/focused-workflow-page-template.tsx +69 -0
  87. package/template/components/templates/list-page.tsx +40 -13
  88. package/template/components/templates/nested-secondary-panel-shell.tsx +3 -2
  89. package/template/components/templates/page-loading-shell.tsx +262 -0
  90. package/template/components/ui/button-group.tsx +1 -0
  91. package/template/contexts/product-context.tsx +21 -2
  92. package/template/docs/consumer-app-pattern.md +39 -0
  93. package/template/docs/data-views-pattern.md +42 -3
  94. package/template/docs/drawer-vs-dialog-pattern.md +3 -1
  95. package/template/docs/focused-workflow-page-pattern.md +84 -0
  96. package/template/docs/kpi-flat-band-pattern.md +57 -0
  97. package/template/docs/kpi-strip-max-four-pattern.md +1 -0
  98. package/template/docs/shell-surface-elevation-pattern.md +54 -0
  99. package/template/lib/chunk-load-error.ts +13 -0
  100. package/template/lib/command-menu-search-data.ts +11 -27
  101. package/template/lib/conditional-rule-match.ts +87 -22
  102. package/template/lib/data-list-display-options.ts +16 -2
  103. package/template/lib/data-list-view-registry.ts +104 -0
  104. package/template/lib/data-list-view-surface.ts +15 -1
  105. package/template/lib/data-list-view.ts +16 -1
  106. package/template/lib/data-view-dashboard-storage.ts +38 -35
  107. package/template/lib/hub-connected-view-renderers.ts +58 -0
  108. package/template/lib/list-hub-nav.ts +121 -0
  109. package/template/lib/list-hub-supported-views.ts +10 -0
  110. package/template/lib/list-page-table-properties.ts +3 -7
  111. package/template/lib/list-status-badges.ts +4 -97
  112. package/template/lib/mock/list-hub-directory.ts +27 -0
  113. package/template/lib/mock/list-hub-kpi.ts +27 -0
  114. package/template/lib/mock/navigation.tsx +1 -0
  115. package/template/lib/page-loading-variant.ts +40 -0
  116. package/template/lib/question-bank-supported-views.ts +13 -0
  117. package/template/lib/sidebar-state-cookie.ts +9 -0
  118. package/template/lib/table-state-lifecycle.ts +60 -13
  119. package/template/app/(app)/data-list/[id]/page.tsx +0 -44
  120. package/template/app/(app)/data-list/new/page.tsx +0 -34
  121. package/template/components/compliance-board-view.tsx +0 -142
  122. package/template/components/compliance-client.tsx +0 -92
  123. package/template/components/compliance-list-view.tsx +0 -54
  124. package/template/components/compliance-page-header.tsx +0 -89
  125. package/template/components/compliance-table.tsx +0 -632
  126. package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
  127. package/template/components/data-view-dashboard-charts-team.tsx +0 -971
  128. package/template/components/data-view-dashboard-charts.tsx +0 -1503
  129. package/template/components/new-placement-back-btn.tsx +0 -28
  130. package/template/components/new-placement-form.tsx +0 -1068
  131. package/template/components/placement-board-card.tsx +0 -262
  132. package/template/components/placement-detail.tsx +0 -438
  133. package/template/components/placements-board-view.tsx +0 -404
  134. package/template/components/placements-client.tsx +0 -252
  135. package/template/components/placements-list-view.tsx +0 -171
  136. package/template/components/placements-page-header.tsx +0 -166
  137. package/template/components/placements-table-cells.test.tsx +0 -22
  138. package/template/components/placements-table-cells.tsx +0 -173
  139. package/template/components/placements-table-columns.tsx +0 -640
  140. package/template/components/placements-table.tsx +0 -1675
  141. package/template/components/rotations-empty-state.tsx +0 -50
  142. package/template/components/rotations-panel-activator.tsx +0 -8
  143. package/template/components/sites-all-client.tsx +0 -154
  144. package/template/components/sites-board-view.tsx +0 -67
  145. package/template/components/sites-list-view.tsx +0 -42
  146. package/template/components/sites-table.tsx +0 -402
  147. package/template/components/team-board-view.tsx +0 -122
  148. package/template/components/team-client.tsx +0 -100
  149. package/template/components/team-list-view.tsx +0 -59
  150. package/template/components/team-page-header.tsx +0 -92
  151. package/template/components/team-table.tsx +0 -714
  152. package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
  153. package/template/lib/mock/compliance-kpi.ts +0 -61
  154. package/template/lib/mock/compliance.ts +0 -146
  155. package/template/lib/mock/placements-kpi.ts +0 -134
  156. package/template/lib/mock/placements.ts +0 -183
  157. package/template/lib/mock/sites-directory.ts +0 -16
  158. package/template/lib/mock/sites-kpi.ts +0 -25
  159. package/template/lib/mock/team-kpi.ts +0 -60
  160. package/template/lib/mock/team.ts +0 -118
  161. package/template/lib/placement-board-card-layout.ts +0 -79
  162. package/template/lib/placement-lifecycle.ts +0 -5
@@ -1,262 +0,0 @@
1
- "use client"
2
-
3
- /**
4
- * Placement-specific board card — composes shared board primitives with column defs and lifecycle layout rules.
5
- */
6
-
7
- import * as React from "react"
8
- import { cn } from "@/lib/utils"
9
- import type { Placement } from "@/lib/mock/placements"
10
- import { StatusBadge } from "@/components/placements-table-cells"
11
- import { AvatarInitials } from "@/components/ui/avatar"
12
- import { Badge } from "@/components/ui/badge"
13
- import {
14
- ListPageBoardCard,
15
- ListPageBoardCardBadgeRow,
16
- ListPageBoardCardBody,
17
- ListPageBoardCardHeader,
18
- ListPageBoardCardSecondary,
19
- ListPageBoardCardTitleRow,
20
- } from "@/components/data-views/list-page-board-card"
21
- import type { BoardLineCount } from "@/lib/data-list-display-options"
22
- import {
23
- type BoardCardLifecycleTabId,
24
- filterColumnsForBoardCard,
25
- isBoardFieldActive,
26
- remainingBodyColumns,
27
- scheduleKeysForTab,
28
- } from "@/lib/placement-board-card-layout"
29
- import { getConditionalRowBackground } from "@/lib/conditional-rule-match"
30
- import type { ConditionalRule } from "@/components/table-properties/types"
31
- import type { CellContext, ColumnDef } from "@/components/data-table/types"
32
- import {
33
- BoardCardIconRow,
34
- BoardCardTwoLineBlock,
35
- lineClampClass,
36
- } from "@/components/data-views/board-card-primitives"
37
-
38
- const BOARD_CELL_CTX: CellContext<Placement> = {
39
- rowIndex: 0,
40
- selected: false,
41
- onSelect: () => {},
42
- }
43
-
44
- function columnIconClass(col: ColumnDef<Placement>): string {
45
- if (col.filter?.icon) return col.filter.icon
46
- const fallbacks: Record<string, string> = {
47
- specialization: "fa-stethoscope",
48
- site: "fa-hospital",
49
- internship: "fa-briefcase",
50
- supervisor: "fa-user-tie",
51
- start: "fa-calendar-days",
52
- compliance: "fa-shield-check",
53
- daysUntilStart: "fa-calendar-days",
54
- readiness: "fa-flag",
55
- progressWeeksDone: "fa-chart-line",
56
- endDate: "fa-calendar-xmark",
57
- lastCheckin: "fa-calendar-clock",
58
- completionDate: "fa-calendar-check",
59
- finalStatus: "fa-circle-check",
60
- rating: "fa-star",
61
- suggestedToHire: "fa-user-check",
62
- duration: "fa-clock",
63
- program: "fa-graduation-cap",
64
- student: "fa-user",
65
- status: "fa-circle-dot",
66
- }
67
- return fallbacks[col.key] ?? "fa-tag"
68
- }
69
-
70
- function boardCellContent(row: Placement, col: ColumnDef<Placement>): React.ReactNode {
71
- if (col.key === "status") return <StatusBadge status={row.status} surface="board" />
72
- if (col.cell) return col.cell(row, BOARD_CELL_CTX)
73
- return <span className="text-foreground/90">{String(row[col.key as keyof Placement] ?? "")}</span>
74
- }
75
-
76
- function renderScheduleSection(
77
- row: Placement,
78
- tab: BoardCardLifecycleTabId,
79
- hiddenColKeys: Set<string>,
80
- boardColumns: ColumnDef<Placement>[],
81
- ): React.ReactNode {
82
- const sk = scheduleKeysForTab(tab)
83
- const anyActive = sk.some(k => isBoardFieldActive(k, tab, hiddenColKeys, boardColumns))
84
- if (!anyActive) return null
85
-
86
- switch (tab) {
87
- case "all": {
88
- const aStart = isBoardFieldActive("start", tab, hiddenColKeys, boardColumns)
89
- const aDur = isBoardFieldActive("duration", tab, hiddenColKeys, boardColumns)
90
- if (!aStart && !aDur) return null
91
- return (
92
- <BoardCardTwoLineBlock
93
- iconClass="fa-calendar-days"
94
- line1={aStart ? row.start : "—"}
95
- line2={aDur ? row.duration : "—"}
96
- />
97
- )
98
- }
99
- case "upcoming": {
100
- const aStart = isBoardFieldActive("start", tab, hiddenColKeys, boardColumns)
101
- const aDays = isBoardFieldActive("daysUntilStart", tab, hiddenColKeys, boardColumns)
102
- if (!aStart && !aDays) return null
103
- const line2 =
104
- aDays && row.daysUntilStart > 0
105
- ? `Starts in ${row.daysUntilStart} days`
106
- : aDays && row.daysUntilStart === 0
107
- ? "Starts today"
108
- : "—"
109
- return (
110
- <BoardCardTwoLineBlock
111
- iconClass="fa-calendar-days"
112
- line1={aStart ? row.start : "—"}
113
- line2={line2}
114
- />
115
- )
116
- }
117
- case "ongoing": {
118
- const aP = isBoardFieldActive("progressWeeksDone", tab, hiddenColKeys, boardColumns)
119
- const aEnd = isBoardFieldActive("endDate", tab, hiddenColKeys, boardColumns)
120
- if (!aP && !aEnd) return null
121
- return (
122
- <BoardCardTwoLineBlock
123
- iconClass="fa-calendar-days"
124
- line1={aP ? `${row.progressWeeksDone} / ${row.progressWeeksTotal} wks` : "—"}
125
- line2={aEnd ? row.endDate : "—"}
126
- />
127
- )
128
- }
129
- case "completed": {
130
- const aComp = isBoardFieldActive("completionDate", tab, hiddenColKeys, boardColumns)
131
- const aFinal = isBoardFieldActive("finalStatus", tab, hiddenColKeys, boardColumns)
132
- if (!aComp && !aFinal) return null
133
- const finalCol = boardColumns.find(c => c.key === "finalStatus")
134
- return (
135
- <BoardCardTwoLineBlock
136
- iconClass="fa-calendar-check"
137
- line1={aComp ? row.completionDate : "—"}
138
- line2={
139
- aFinal && finalCol ? (
140
- <span className="inline-flex min-w-0 max-w-full [&_span]:text-xs">
141
- {boardCellContent(row, finalCol)}
142
- </span>
143
- ) : aFinal ? (
144
- row.finalStatus
145
- ) : (
146
- "—"
147
- )
148
- }
149
- line2ClassName={aFinal && finalCol ? "text-xs" : undefined}
150
- />
151
- )
152
- }
153
- default:
154
- return null
155
- }
156
- }
157
-
158
- export function BoardPlacementCard({
159
- row,
160
- lifecycleTabId,
161
- hiddenColKeys,
162
- lineCount,
163
- conditionalRules,
164
- boardColumns,
165
- onOpen,
166
- }: {
167
- row: Placement
168
- lifecycleTabId: BoardCardLifecycleTabId
169
- hiddenColKeys: Set<string>
170
- lineCount: BoardLineCount
171
- conditionalRules: ConditionalRule[] | undefined
172
- boardColumns: ColumnDef<Placement>[]
173
- onOpen: (id: number) => void
174
- }) {
175
- const lc = lineClampClass(lineCount)
176
- const ruleBg = getConditionalRowBackground(row, conditionalRules)
177
-
178
- const visibleCols = boardColumns.filter(c => !hiddenColKeys.has(c.key))
179
- const showStudent = visibleCols.some(c => c.key === "student")
180
- const cardCols = filterColumnsForBoardCard(lifecycleTabId, visibleCols)
181
- const remainingCols = remainingBodyColumns(lifecycleTabId, cardCols)
182
-
183
- const showStatus = isBoardFieldActive("status", lifecycleTabId, hiddenColKeys, boardColumns)
184
- const showSite = isBoardFieldActive("site", lifecycleTabId, hiddenColKeys, boardColumns)
185
- const siteCol = boardColumns.find(c => c.key === "site")
186
-
187
- const cardShell = (className: string, children: React.ReactNode) => (
188
- <ListPageBoardCard
189
- className={className}
190
- style={ruleBg ? { background: ruleBg } : undefined}
191
- isNew={row.isNew}
192
- onClick={() => onOpen(row.id)}
193
- >
194
- {children}
195
- </ListPageBoardCard>
196
- )
197
-
198
- if (visibleCols.length === 0) {
199
- return cardShell(
200
- "cursor-pointer",
201
- <ListPageBoardCardHeader className="gap-1 pb-2">
202
- <ListPageBoardCardTitleRow title={`Placement #${row.id}`} />
203
- <ListPageBoardCardSecondary>
204
- Unhide columns in Properties → Columns to show card fields.
205
- </ListPageBoardCardSecondary>
206
- </ListPageBoardCardHeader>,
207
- )
208
- }
209
-
210
- const titlePrimary = showStudent ? row.student : `Placement ${row.id}`
211
- const headerBadgeRow = showStatus || row.isNew
212
-
213
- return cardShell(
214
- "cursor-pointer",
215
- <ListPageBoardCardHeader>
216
- <ListPageBoardCardTitleRow
217
- title={titlePrimary}
218
- titleClassName={lc}
219
- trailing={
220
- showStudent ? (
221
- <AvatarInitials
222
- initials={row.initials}
223
- className="size-7 shrink-0 text-xs"
224
- fallbackClassName="text-xs"
225
- />
226
- ) : undefined
227
- }
228
- />
229
-
230
- {headerBadgeRow ? (
231
- <ListPageBoardCardBadgeRow>
232
- {showStatus ? <StatusBadge status={row.status} surface="board" /> : null}
233
- {row.isNew ? (
234
- <Badge variant="secondary" className="h-6 px-2 text-xs font-medium">
235
- New
236
- </Badge>
237
- ) : null}
238
- </ListPageBoardCardBadgeRow>
239
- ) : null}
240
-
241
- <ListPageBoardCardBody>
242
- {showSite && siteCol ? (
243
- <BoardCardIconRow iconClass="fa-hospital">
244
- <div className={cn(lc, "[&_.text-sm]:text-xs")}>{boardCellContent(row, siteCol)}</div>
245
- </BoardCardIconRow>
246
- ) : null}
247
-
248
- {renderScheduleSection(row, lifecycleTabId, hiddenColKeys, boardColumns)}
249
-
250
- {remainingCols.length > 0 ? (
251
- <div className="flex flex-col gap-2">
252
- {remainingCols.map(col => (
253
- <BoardCardIconRow key={col.key} iconClass={columnIconClass(col)}>
254
- <div className={cn(lc)}>{boardCellContent(row, col)}</div>
255
- </BoardCardIconRow>
256
- ))}
257
- </div>
258
- ) : null}
259
- </ListPageBoardCardBody>
260
- </ListPageBoardCardHeader>,
261
- )
262
- }