@exxatdesignux/ui 0.2.6 → 0.2.7

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 (139) hide show
  1. package/package.json +2 -1
  2. package/template/.agents/skills/shadcn/SKILL.md +242 -0
  3. package/template/.agents/skills/shadcn/agents/openai.yml +5 -0
  4. package/template/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
  5. package/template/.agents/skills/shadcn/assets/shadcn.png +0 -0
  6. package/template/.agents/skills/shadcn/cli.md +257 -0
  7. package/template/.agents/skills/shadcn/customization.md +202 -0
  8. package/template/.agents/skills/shadcn/evals/evals.json +47 -0
  9. package/template/.agents/skills/shadcn/mcp.md +94 -0
  10. package/template/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
  11. package/template/.agents/skills/shadcn/rules/composition.md +195 -0
  12. package/template/.agents/skills/shadcn/rules/forms.md +192 -0
  13. package/template/.agents/skills/shadcn/rules/icons.md +101 -0
  14. package/template/.agents/skills/shadcn/rules/styling.md +162 -0
  15. package/template/.claude/skills/exxat-ds-skill/SKILL.md +712 -0
  16. package/template/.cursor/rules/exxat-accessibility.mdc +33 -0
  17. package/template/.cursor/rules/exxat-command-menu.mdc +23 -0
  18. package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +53 -0
  19. package/template/.cursor/rules/exxat-data-tables.mdc +31 -0
  20. package/template/.cursor/rules/exxat-ds-agents.mdc +26 -0
  21. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +100 -0
  22. package/template/.cursor/rules/exxat-list-page-connected-views.mdc +16 -0
  23. package/template/.cursor/rules/exxat-no-toast.mdc +26 -0
  24. package/template/.cursor/rules/exxat-page-vs-drawer.mdc +22 -0
  25. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +40 -0
  26. package/template/AGENTS.md +52 -11
  27. package/template/app/(app)/dashboard/page.tsx +1 -1
  28. package/template/app/(app)/data-list/[id]/page.tsx +24 -8
  29. package/template/app/(app)/data-list/new/page.tsx +7 -4
  30. package/template/app/(app)/data-list/page.tsx +1 -1
  31. package/template/app/(app)/examples/page.tsx +41 -0
  32. package/template/app/(app)/question-bank/page.tsx +3 -3
  33. package/template/app/globals.css +1 -1
  34. package/template/components/app-sidebar.tsx +52 -35
  35. package/template/components/compliance-table.tsx +79 -0
  36. package/template/components/data-list-client.tsx +36 -25
  37. package/template/components/data-list-table.tsx +797 -10
  38. package/template/components/data-views/finder-panel-view.tsx +405 -0
  39. package/template/components/data-views/folder-grid-view.tsx +86 -0
  40. package/template/components/data-views/index.ts +59 -0
  41. package/template/components/data-views/list-page-split-details-placeholder.tsx +39 -0
  42. package/template/components/data-views/list-page-split-hub-chrome.tsx +60 -0
  43. package/template/components/data-views/list-page-split-hub-tokens.ts +16 -0
  44. package/template/components/data-views/list-page-tree-column-header.tsx +31 -0
  45. package/template/components/data-views/list-page-tree-panel-shell.tsx +91 -0
  46. package/template/components/data-views/list-page-view-frame.tsx +53 -0
  47. package/template/components/data-views/os-folder-glyph.tsx +121 -0
  48. package/template/components/folder-details-shell.tsx +230 -0
  49. package/template/components/hub-tree-panel-view.tsx +672 -0
  50. package/template/components/list-hub-status-badge.tsx +17 -3
  51. package/template/components/placements-page-header.tsx +14 -8
  52. package/template/components/placements-table-columns.tsx +8 -8
  53. package/template/components/question-bank-client.tsx +157 -40
  54. package/template/components/question-bank-new-folder-sheet.tsx +248 -0
  55. package/template/components/question-bank-os-folder-view.tsx +648 -0
  56. package/template/components/question-bank-page-header.tsx +3 -3
  57. package/template/components/question-bank-panel-activator.tsx +9 -0
  58. package/template/components/question-bank-secondary-nav.tsx +226 -0
  59. package/template/components/question-bank-table.tsx +707 -22
  60. package/template/components/secondary-panel.tsx +41 -107
  61. package/template/components/sites-table.tsx +66 -0
  62. package/template/components/team-client.tsx +7 -0
  63. package/template/components/team-table.tsx +156 -1
  64. package/template/components/templates/list-page.tsx +2 -2
  65. package/template/components/ui/avatar.tsx +1 -1
  66. package/template/components/ui/badge.tsx +1 -1
  67. package/template/components/ui/banner.tsx +1 -1
  68. package/template/components/ui/breadcrumb.tsx +1 -1
  69. package/template/components/ui/button.tsx +1 -1
  70. package/template/components/ui/calendar.tsx +1 -1
  71. package/template/components/ui/card.tsx +1 -1
  72. package/template/components/ui/chart.tsx +1 -1
  73. package/template/components/ui/checkbox.tsx +1 -1
  74. package/template/components/ui/coach-mark.tsx +1 -1
  75. package/template/components/ui/collapsible.tsx +1 -1
  76. package/template/components/ui/command.tsx +1 -1
  77. package/template/components/ui/date-picker-field.tsx +1 -1
  78. package/template/components/ui/dialog.tsx +1 -1
  79. package/template/components/ui/drag-handle-grip.tsx +1 -1
  80. package/template/components/ui/drawer.tsx +1 -1
  81. package/template/components/ui/dropdown-menu.tsx +1 -1
  82. package/template/components/ui/field.tsx +1 -1
  83. package/template/components/ui/form.tsx +1 -1
  84. package/template/components/ui/input-group.tsx +1 -1
  85. package/template/components/ui/input-mask.tsx +1 -1
  86. package/template/components/ui/input.tsx +1 -1
  87. package/template/components/ui/kbd.tsx +1 -1
  88. package/template/components/ui/label.tsx +1 -1
  89. package/template/components/ui/payment-card-fields.tsx +1 -1
  90. package/template/components/ui/popover.tsx +1 -1
  91. package/template/components/ui/radio-group.tsx +1 -1
  92. package/template/components/ui/resizable.tsx +68 -0
  93. package/template/components/ui/select.tsx +1 -1
  94. package/template/components/ui/selection-tile-grid.tsx +1 -1
  95. package/template/components/ui/separator.tsx +1 -1
  96. package/template/components/ui/sheet.tsx +1 -1
  97. package/template/components/ui/sidebar.tsx +1 -1
  98. package/template/components/ui/skeleton.tsx +1 -1
  99. package/template/components/ui/sonner.tsx +1 -1
  100. package/template/components/ui/status-badge.tsx +1 -1
  101. package/template/components/ui/table.tsx +1 -1
  102. package/template/components/ui/tabs.tsx +1 -1
  103. package/template/components/ui/textarea.tsx +1 -1
  104. package/template/components/ui/tip.tsx +1 -1
  105. package/template/components/ui/toggle-group.tsx +1 -1
  106. package/template/components/ui/toggle-switch.tsx +1 -1
  107. package/template/components/ui/toggle.tsx +1 -1
  108. package/template/components/ui/tooltip.tsx +1 -1
  109. package/template/components/ui/view-segmented-control.tsx +1 -1
  110. package/template/docs/data-views-pattern.md +7 -0
  111. package/template/hooks/use-app-theme.ts +1 -1
  112. package/template/hooks/use-coach-mark.ts +1 -1
  113. package/template/hooks/use-location-hash.ts +15 -0
  114. package/template/hooks/use-mobile.ts +1 -1
  115. package/template/hooks/use-mod-key-label.ts +1 -1
  116. package/template/hooks/use-sidebar-reflow-zoom.ts +40 -0
  117. package/template/lib/ask-leo-route-context.ts +25 -57
  118. package/template/lib/coach-mark-registry.ts +13 -13
  119. package/template/lib/command-menu-config.ts +28 -23
  120. package/template/lib/command-menu-search-data.ts +10 -9
  121. package/template/lib/data-list-view-surface.ts +12 -1
  122. package/template/lib/data-list-view.ts +6 -3
  123. package/template/lib/date-filter.ts +1 -1
  124. package/template/lib/mock/dashboard.ts +11 -11
  125. package/template/lib/mock/navigation.tsx +22 -63
  126. package/template/lib/mock/placements-kpi.ts +19 -19
  127. package/template/lib/mock/question-bank-folders.ts +167 -0
  128. package/template/lib/mock/question-bank-inspector.ts +109 -0
  129. package/template/lib/mock/question-bank-kpi.ts +1 -1
  130. package/template/lib/mock/question-bank.ts +80 -0
  131. package/template/lib/question-bank-nav.ts +91 -0
  132. package/template/lib/utils.ts +1 -1
  133. package/template/next.config.mjs +8 -0
  134. package/template/package.json +1 -0
  135. package/template/public/folders/icons8-folder-windows-11.svg +1 -0
  136. package/template/app/(app)/compliance/page.tsx +0 -10
  137. package/template/app/(app)/rotations/page.tsx +0 -15
  138. package/template/app/(app)/sites/all/page.tsx +0 -13
  139. package/template/app/(app)/team/page.tsx +0 -10
@@ -9,15 +9,16 @@
9
9
  */
10
10
 
11
11
  import * as React from "react"
12
- import { cn } from "@/lib/utils"
13
- import { useSidebar } from "@/components/ui/sidebar"
14
- import { Tip } from "@/components/ui/tip"
15
- import { Button } from "@/components/ui/button"
12
+ import { cn } from "@/lib/utils"
13
+ import { useSidebar } from "@/components/ui/sidebar"
14
+ import { Tip } from "@/components/ui/tip"
15
+ import { Button } from "@/components/ui/button"
16
16
  import {
17
17
  InputGroup,
18
18
  InputGroupAddon,
19
19
  InputGroupInput,
20
20
  } from "@/components/ui/input-group"
21
+ import { QuestionBankSecondaryNav } from "@/components/question-bank-secondary-nav"
21
22
 
22
23
  // ─────────────────────────────────────────────────────────────────────────────
23
24
  // Context
@@ -43,7 +44,7 @@ export function useSecondaryPanel() {
43
44
  }
44
45
 
45
46
  export function SecondaryPanelProvider({ children }: { children: React.ReactNode }) {
46
- const [activePanel, setActivePanel] = React.useState<string | null>(null)
47
+ const [activePanel, setActivePanel] = React.useState<string | null>(null)
47
48
  const { setOpen } = useSidebar()
48
49
 
49
50
  const openPanel = React.useCallback((id: string) => {
@@ -56,9 +57,14 @@ export function SecondaryPanelProvider({ children }: { children: React.ReactNode
56
57
  setOpen(true) // expand main sidebar back
57
58
  }, [setOpen])
58
59
 
59
- const value = React.useMemo(() => ({
60
- activePanel, openPanel, closePanel,
61
- }), [activePanel, openPanel, closePanel])
60
+ const value = React.useMemo(
61
+ () => ({
62
+ activePanel,
63
+ openPanel,
64
+ closePanel,
65
+ }),
66
+ [activePanel, openPanel, closePanel],
67
+ )
62
68
 
63
69
  return (
64
70
  <SecondaryPanelContext.Provider value={value}>
@@ -68,26 +74,13 @@ export function SecondaryPanelProvider({ children }: { children: React.ReactNode
68
74
  }
69
75
 
70
76
  // ─────────────────────────────────────────────────────────────────────────────
71
- // Panel content Rotations
77
+ // SecondaryPanelthe actual rendered panel
72
78
  // ─────────────────────────────────────────────────────────────────────────────
73
79
 
74
- const ROTATION_ITEMS = [
75
- { id: "all", label: "All Rotations", icon: "fa-folder", iconActive: "fa-folder", meta: "12 active" },
76
- { id: "rotation-1", label: "Clinical Nursing — Fall 2026", icon: "fa-folder", iconActive: "fa-folder", meta: "8 students" },
77
- { id: "rotation-2", label: "PT Fieldwork — Spring 2026", icon: "fa-folder", iconActive: "fa-folder", meta: "6 students" },
78
- { id: "rotation-3", label: "OT Level II — Summer 2026", icon: "fa-folder", iconActive: "fa-folder", meta: "4 students" },
79
- ]
80
-
81
- function RotationsPanel() {
80
+ function QuestionBankPanel() {
82
81
  const { closePanel } = useSecondaryPanel()
83
- const [activeRotation, setActiveRotation] = React.useState("all")
84
82
  const [query, setQuery] = React.useState("")
85
83
 
86
- const q = query.trim().toLowerCase()
87
- const filtered = q
88
- ? ROTATION_ITEMS.filter(i => i.label.toLowerCase().includes(q))
89
- : ROTATION_ITEMS
90
-
91
84
  return (
92
85
  <>
93
86
  <div className="flex items-center justify-between gap-2 px-4 pt-4 pb-2">
@@ -95,32 +88,19 @@ function RotationsPanel() {
95
88
  className="text-xl font-semibold leading-tight text-sidebar-foreground"
96
89
  style={{ fontFamily: "var(--font-heading)" }}
97
90
  >
98
- Rotations
91
+ Question bank
99
92
  </h2>
100
- <div className="flex items-center gap-1">
101
- <Tip label="Add rotation" side="bottom">
102
- <Button
103
- type="button"
104
- size="icon"
105
- variant="ghost"
106
- onClick={() => { /* hook up create flow */ }}
107
- aria-label="Add rotation"
108
- >
109
- <i className="fa-light fa-plus" aria-hidden="true" />
110
- </Button>
111
- </Tip>
112
- <Tip label="Close panel" side="bottom">
113
- <Button
114
- type="button"
115
- size="icon"
116
- variant="ghost"
117
- onClick={closePanel}
118
- aria-label="Close panel"
119
- >
120
- <i className="fa-light fa-xmark" aria-hidden="true" />
121
- </Button>
122
- </Tip>
123
- </div>
93
+ <Tip label="Close panel" side="bottom">
94
+ <Button
95
+ type="button"
96
+ size="icon"
97
+ variant="ghost"
98
+ onClick={closePanel}
99
+ aria-label="Close panel"
100
+ >
101
+ <i className="fa-light fa-xmark" aria-hidden="true" />
102
+ </Button>
103
+ </Tip>
124
104
  </div>
125
105
  <div className="px-4 pb-2">
126
106
  <InputGroup className="h-8 min-h-8">
@@ -131,68 +111,20 @@ function RotationsPanel() {
131
111
  type="search"
132
112
  value={query}
133
113
  onChange={e => setQuery(e.target.value)}
134
- placeholder="Search rotations"
135
- aria-label="Search rotations"
114
+ placeholder="Search"
115
+ aria-label="Search"
136
116
  className="text-sm pe-3"
137
117
  />
138
118
  </InputGroup>
139
119
  </div>
140
- <ul className="flex-1 space-y-0.5 px-4 pb-4" role="listbox">
141
- {filtered.length === 0 && (
142
- <li className="py-2 text-xs text-muted-foreground">No results</li>
143
- )}
144
- {filtered.map(item => {
145
- const isActive = item.id === activeRotation
146
- return (
147
- <li key={item.id}>
148
- <Tip label={item.label} side="right">
149
- <button
150
- type="button"
151
- role="option"
152
- aria-selected={isActive}
153
- onClick={() => setActiveRotation(item.id)}
154
- className={cn(
155
- // Match primary `SidebarMenuButton`: text-sm, compact padding, sidebar tokens
156
- "flex w-full items-start gap-2 rounded-md p-2 text-left text-sm transition-colors",
157
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
158
- isActive
159
- ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
160
- : "text-sidebar-foreground hover:bg-sidebar-accent/50",
161
- )}
162
- >
163
- <i
164
- className={cn(
165
- "mt-0.5 size-4 shrink-0 text-center text-[13px] leading-none",
166
- isActive ? `fa-solid ${item.iconActive}` : `fa-light ${item.icon}`,
167
- )}
168
- aria-hidden="true"
169
- />
170
- <div className="min-w-0 flex-1">
171
- <span className="block whitespace-normal break-words leading-snug">
172
- {item.label}
173
- </span>
174
- {item.meta && (
175
- <span className="mt-0.5 block whitespace-normal break-words text-xs leading-snug text-muted-foreground">
176
- {item.meta}
177
- </span>
178
- )}
179
- </div>
180
- </button>
181
- </Tip>
182
- </li>
183
- )
184
- })}
185
- </ul>
120
+ <QuestionBankSecondaryNav query={query} />
186
121
  </>
187
122
  )
188
123
  }
189
124
 
190
- // ─────────────────────────────────────────────────────────────────────────────
191
- // SecondaryPanel — the actual rendered panel
192
- // ─────────────────────────────────────────────────────────────────────────────
193
-
125
+ /** Register panel components by id when a route opts into `secondaryPanel` in nav. */
194
126
  const PANELS: Record<string, React.FC> = {
195
- rotations: RotationsPanel,
127
+ "question-bank": QuestionBankPanel,
196
128
  }
197
129
 
198
130
  export function SecondaryPanel() {
@@ -207,18 +139,18 @@ export function SecondaryPanel() {
207
139
  "flex flex-col overflow-hidden",
208
140
  "transition-[width,margin,opacity] duration-200 ease-linear",
209
141
  activePanel
210
- ? "w-56 shrink-0 m-2 mx-2 rounded-xl ring-1 ring-sidebar-border shadow-sm relative h-[min(calc(100svh-2rem),800px)] md:sticky md:top-2 md:h-[min(calc(100svh-1rem),800px)]"
211
- : "h-0 min-h-0 shrink overflow-hidden border-0 p-0 m-0 min-w-0 w-0 max-w-0 opacity-0 pointer-events-none"
142
+ ? "w-64 shrink-0 m-2 mx-2 rounded-xl ring-1 ring-sidebar-border shadow-sm relative h-[min(calc(100svh-2rem),800px)] md:sticky md:top-2 md:h-[min(calc(100svh-1rem),800px)]"
143
+ : "h-0 min-h-0 shrink overflow-hidden border-0 p-0 m-0 min-w-0 w-0 max-w-0 opacity-0 pointer-events-none",
212
144
  )}
213
145
  style={activePanel ? { backgroundColor: "var(--secondary-panel-bg, #FAFBFF)" } : undefined}
214
146
  >
215
147
  <div
216
148
  className={cn(
217
149
  "flex flex-1 flex-col overflow-y-auto",
218
- activePanel ? "min-w-0" : "hidden min-w-0 w-0 p-0"
150
+ activePanel ? "min-w-0" : "hidden min-w-0 w-0 p-0",
219
151
  )}
220
152
  >
221
- {PanelContent && <PanelContent />}
153
+ {PanelContent ? <PanelContent /> : null}
222
154
  </div>
223
155
  </nav>
224
156
  )
@@ -233,7 +165,9 @@ export function useAutoPanel(panelId: string) {
233
165
 
234
166
  React.useEffect(() => {
235
167
  openPanel(panelId)
236
- return () => { closePanel() }
237
- // eslint-disable-next-line react-hooks/exhaustive-deps
168
+ return () => {
169
+ closePanel()
170
+ }
171
+ // eslint-disable-next-line react-hooks/exhaustive-deps
238
172
  }, [panelId])
239
173
  }
@@ -24,6 +24,8 @@ import {
24
24
  } from "@/components/ui/dropdown-menu"
25
25
  import { Tip } from "@/components/ui/tip"
26
26
  import { Avatar, AvatarFallback } from "@/components/ui/avatar"
27
+ import { FinderPanelView, type FinderGroup } from "@/components/data-views/finder-panel-view"
28
+ import { ListPageSplitHubChrome } from "@/components/data-views/list-page-split-hub-chrome"
27
29
  import { SitesCardGrid } from "@/components/sites-board-view"
28
30
  import { SitesListView } from "@/components/sites-list-view"
29
31
  import { KeyMetrics } from "@/components/key-metrics"
@@ -206,6 +208,48 @@ export const SitesTable = React.forwardRef<
206
208
  [tableState.rows.length],
207
209
  )
208
210
 
211
+ // Generic panel view rendering for sites
212
+ const panelGroupsBuilder = (rows: SiteDirectoryRow[]): FinderGroup[] => [
213
+ {
214
+ id: "all",
215
+ label: `All sites (${rows.length})`,
216
+ count: rows.length,
217
+ },
218
+ ]
219
+
220
+ const panelRenderListRow = (row: SiteDirectoryRow, _isSelected: boolean) => (
221
+ <div className="flex-1 min-w-0 flex items-center gap-2">
222
+ <Avatar size="sm" className="size-6 shrink-0">
223
+ <AvatarFallback className="bg-brand/10 p-0 text-brand text-xs">
224
+ <i className="fa-light fa-hospital text-xs" aria-hidden="true" />
225
+ </AvatarFallback>
226
+ </Avatar>
227
+ <div className="flex-1 min-w-0">
228
+ <p className="text-sm font-medium text-foreground truncate">{row.name}</p>
229
+ <p className="text-xs text-muted-foreground truncate">{row.url}</p>
230
+ </div>
231
+ </div>
232
+ )
233
+
234
+ const panelRenderDetail = (row: SiteDirectoryRow) => (
235
+ <div className="flex min-h-0 flex-1 flex-col overflow-y-auto p-4">
236
+ <div>
237
+ <h3 className="text-sm font-semibold text-foreground mb-2">Site</h3>
238
+ <p className="text-sm text-foreground">{row.name}</p>
239
+ </div>
240
+ <div className="flex flex-col gap-2">
241
+ <div>
242
+ <span className="text-xs font-medium text-muted-foreground">Key</span>
243
+ <p className="text-sm text-foreground font-mono">{row.id}</p>
244
+ </div>
245
+ <div>
246
+ <span className="text-xs font-medium text-muted-foreground">Path</span>
247
+ <p className="text-sm text-foreground break-all">{row.url}</p>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ )
252
+
209
253
  const drawerToolbarProps = {
210
254
  state: tableState,
211
255
  totalRows: sites.length,
@@ -292,6 +336,28 @@ export const SitesTable = React.forwardRef<
292
336
  )
293
337
  }
294
338
 
339
+ if (view === "panel") {
340
+ return (
341
+ <div className="flex min-h-0 flex-1 flex-col">
342
+ {sharedToolbar}
343
+ <ListPageSplitHubChrome aria-label="Sites directory panel view">
344
+ <FinderPanelView<SiteDirectoryRow>
345
+ embedded
346
+ groupsColumnTitle="Sites"
347
+ groups={panelGroupsBuilder(tableState.rows)}
348
+ rows={tableState.rows}
349
+ getRowId={(row) => row.id}
350
+ getRowGroupId={() => "all"}
351
+ autoSaveId="sites-panel-view"
352
+ renderListRow={panelRenderListRow}
353
+ renderDetail={panelRenderDetail}
354
+ emptyList={<p className="text-sm text-muted-foreground">No sites found.</p>}
355
+ />
356
+ </ListPageSplitHubChrome>
357
+ </div>
358
+ )
359
+ }
360
+
295
361
  return (
296
362
  <div className="flex min-h-0 flex-1 flex-col gap-4">
297
363
  {sharedToolbar}
@@ -27,6 +27,13 @@ const DEFAULT_TEAM_TABS: ViewTab[] = [
27
27
  icon: "fa-table",
28
28
  filterId: "all",
29
29
  },
30
+ {
31
+ id: "panel",
32
+ label: "Panel",
33
+ viewType: "panel",
34
+ icon: "fa-sidebar",
35
+ filterId: "all",
36
+ },
30
37
  ]
31
38
 
32
39
  export function TeamClient() {
@@ -1,8 +1,9 @@
1
1
  "use client"
2
2
 
3
3
  /**
4
- * Team roster — DataTable + TablePropertiesDrawer + list/board/dashboard (shared `tableState.rows`).
4
+ * Team roster — DataTable + TablePropertiesDrawer + table/list/board/panel/dashboard (shared `tableState.rows`).
5
5
  * Dashboard view uses `TeamDashboardChartsSection` (customise on canvas) + lib/mock/team-kpi.
6
+ * Panel view uses `FinderPanelView` with status-based groups (Active, Away, Invited).
6
7
  */
7
8
 
8
9
  import * as React from "react"
@@ -27,7 +28,11 @@ import { KEY_METRICS_KPI_COUNT_DEFAULT } from "@/lib/dashboard-layout-merge"
27
28
  import type { ChartType, DashboardLayout } from "@/components/data-view-dashboard-charts"
28
29
  import { TeamListView } from "@/components/team-list-view"
29
30
  import { TeamBoardView, TEAM_BOARD_GROUP_OPTIONS } from "@/components/team-board-view"
31
+ import { FinderPanelView, type FinderGroup } from "@/components/data-views/finder-panel-view"
32
+ import { ListPageSplitHubChrome } from "@/components/data-views/list-page-split-hub-chrome"
30
33
  import { teamKpiInsight, teamKpiMetrics } from "@/lib/mock/team-kpi"
34
+ import { useRouter } from "next/navigation"
35
+ import { cn } from "@/lib/utils"
31
36
  import type { DataListViewType } from "@/lib/data-list-view"
32
37
  import type { OpenTablePropertiesHandle } from "@/lib/list-page-table-properties"
33
38
  import type { ColumnDef } from "@/components/data-table/types"
@@ -67,6 +72,127 @@ const STATUS_FILTER_OPTS = [
67
72
  { value: "invited", label: TEAM_MEMBER_STATUS_LABEL.invited },
68
73
  ]
69
74
 
75
+ // ─── Team-specific panel view helpers ──────────────────────────────────────
76
+
77
+ function TeamFinderListRow({
78
+ member,
79
+ isSelected,
80
+ }: {
81
+ member: TeamMember
82
+ isSelected: boolean
83
+ }) {
84
+ return (
85
+ <div
86
+ className={`flex w-full min-w-0 items-center gap-3 transition-colors duration-75 ${
87
+ isSelected ? "bg-transparent text-accent-foreground" : "text-foreground"
88
+ }`}
89
+ >
90
+ <AvatarInitials
91
+ initials={member.initials}
92
+ className={cn(
93
+ "size-8 shrink-0 rounded-full text-[11px] font-semibold",
94
+ isSelected ? "ring-2 ring-accent-foreground/35" : "",
95
+ )}
96
+ />
97
+ <div className="min-w-0 flex-1">
98
+ <p className={cn("truncate text-[13px] font-medium leading-tight", isSelected ? "text-accent-foreground" : "text-foreground")}>
99
+ {member.name}
100
+ </p>
101
+ <p className={cn("mt-0.5 truncate text-[11px] leading-tight", isSelected ? "text-accent-foreground/80" : "text-muted-foreground")}>
102
+ {member.role}
103
+ </p>
104
+ </div>
105
+ {!isSelected && (
106
+ <ListHubStatusBadge
107
+ label={TEAM_MEMBER_STATUS_LABEL[member.status]}
108
+ tintClassName={TEAM_MEMBER_STATUS_BADGE_CLASS[member.status]}
109
+ icon={TEAM_MEMBER_STATUS_ICON[member.status]}
110
+ />
111
+ )}
112
+ </div>
113
+ )
114
+ }
115
+
116
+ function TeamFinderDetail({
117
+ member,
118
+ }: {
119
+ member: TeamMember
120
+ }) {
121
+ const router = useRouter()
122
+
123
+ return (
124
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
125
+ {/* Header */}
126
+ <div className="flex shrink-0 items-start gap-4 border-b border-border px-5 py-4">
127
+ <AvatarInitials initials={member.initials} className="size-14 shrink-0 rounded-full text-lg font-semibold" />
128
+ <div className="min-w-0 flex-1">
129
+ <h2 className="text-base font-semibold text-foreground leading-tight">{member.name}</h2>
130
+ <p className="mt-0.5 text-[13px] text-muted-foreground">{member.role}</p>
131
+ <div className="mt-2">
132
+ <ListHubStatusBadge
133
+ label={TEAM_MEMBER_STATUS_LABEL[member.status]}
134
+ tintClassName={TEAM_MEMBER_STATUS_BADGE_CLASS[member.status]}
135
+ icon={TEAM_MEMBER_STATUS_ICON[member.status]}
136
+ />
137
+ </div>
138
+ </div>
139
+ <Tip side="bottom" label="Open full profile">
140
+ <Button type="button" variant="outline" size="sm" className="shrink-0"
141
+ onClick={() => router.push(`/team/${member.id}`)}
142
+ aria-label={`Open full profile for ${member.name}`}>
143
+ <i className="fa-light fa-arrow-up-right-from-square text-[12px]" aria-hidden="true" />
144
+ Open
145
+ </Button>
146
+ </Tip>
147
+ </div>
148
+
149
+ {/* Fields */}
150
+ <div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
151
+ <dl className="grid grid-cols-1 gap-4 sm:grid-cols-2">
152
+ <div className="flex flex-col gap-0.5">
153
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
154
+ <i className="fa-light fa-envelope text-[10px]" aria-hidden="true" /> Email
155
+ </dt>
156
+ <dd className="text-[13px]">
157
+ <a href={`mailto:${member.email}`} className="text-interactive-foreground hover:underline">{member.email}</a>
158
+ </dd>
159
+ </div>
160
+ <div className="flex flex-col gap-0.5">
161
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
162
+ <i className="fa-light fa-briefcase text-[10px]" aria-hidden="true" /> Role
163
+ </dt>
164
+ <dd className="text-[13px] text-foreground">{member.role}</dd>
165
+ </div>
166
+ <div className="flex flex-col gap-0.5">
167
+ <dt className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
168
+ <i className="fa-light fa-phone text-[10px]" aria-hidden="true" /> Phone
169
+ </dt>
170
+ <dd className="text-[13px] text-foreground">{member.phone}</dd>
171
+ </div>
172
+ </dl>
173
+ </div>
174
+ </div>
175
+ )
176
+ }
177
+
178
+ // ─── Status groups for team panel view ────────────────────────────────────
179
+
180
+ const TEAM_STATUS_GROUPS: Array<{ id: string; label: string; accent: string }> = [
181
+ { id: "all", label: "All", accent: "bg-muted-foreground" },
182
+ { id: "active", label: "Active", accent: "bg-success" },
183
+ { id: "away", label: "Away", accent: "bg-warning" },
184
+ { id: "invited", label: "Invited", accent: "bg-brand" },
185
+ ]
186
+
187
+ function buildTeamStatusGroups(members: TeamMember[]): FinderGroup[] {
188
+ return TEAM_STATUS_GROUPS.map(sg => ({
189
+ id: sg.id,
190
+ label: sg.label,
191
+ accent: sg.accent,
192
+ count: sg.id === "all" ? members.length : members.filter(m => m.status === sg.id).length,
193
+ }))
194
+ }
195
+
70
196
  function columnToFilterFieldDef(c: ColumnDef<TeamMember>): FilterFieldDef | null {
71
197
  if (!c.filter) return null
72
198
  const f = c.filter
@@ -469,6 +595,35 @@ export const TeamTable = React.forwardRef<
469
595
  )
470
596
  }
471
597
 
598
+ if (view === "panel") {
599
+ const groups = React.useMemo(() => buildTeamStatusGroups(tableState.rows), [tableState.rows])
600
+ return (
601
+ <div className="flex min-h-0 flex-1 flex-col">
602
+ {sharedToolbar}
603
+ <ListPageSplitHubChrome aria-label="Team members panel view">
604
+ <FinderPanelView<TeamMember>
605
+ embedded
606
+ groupsColumnTitle="Status"
607
+ groups={groups}
608
+ rows={tableState.rows}
609
+ getRowId={r => r.id}
610
+ getRowGroupId={r => r.status}
611
+ defaultGroupId="all"
612
+ autoSaveId="team-panel-view"
613
+ ariaLabel="Team members panel view"
614
+ emptyList={<p>No team members found</p>}
615
+ renderListRow={(member, isSelected) => (
616
+ <TeamFinderListRow member={member} isSelected={isSelected} />
617
+ )}
618
+ renderDetail={member => (
619
+ <TeamFinderDetail member={member} />
620
+ )}
621
+ />
622
+ </ListPageSplitHubChrome>
623
+ </div>
624
+ )
625
+ }
626
+
472
627
  return (
473
628
  <div className="flex min-h-0 flex-1 flex-col">
474
629
  <CoachMark state={dashboardCustomizeCoach} />
@@ -478,7 +478,7 @@ export function ListPageTemplate({
478
478
  <DropdownMenuItem
479
479
  key={v.type}
480
480
  shortcut={i < 9 ? `⌘⇧${i + 1}` : undefined}
481
- onClick={() => addView(v.type)}
481
+ onSelect={() => addView(v.type)}
482
482
  >
483
483
  <i className={`fa-light ${v.icon}`} aria-hidden="true" />
484
484
  {v.label}
@@ -491,7 +491,7 @@ export function ListPageTemplate({
491
491
 
492
492
  {/* ── Content — keyed by tab so each view tab owns its height (no stale min-height). ── */}
493
493
  {activeTab ? (
494
- <div key={activeTab.id} className="flex min-h-0 flex-col">
494
+ <div key={activeTab.id} className="flex min-h-0 flex-1 flex-col">
495
495
  {renderContent(activeTab, patch => updateTab(activeTab.id, patch))}
496
496
  </div>
497
497
  ) : null}
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/avatar.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/avatar"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/badge.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/badge"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/banner.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/banner"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/breadcrumb.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/breadcrumb"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/button.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/button"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/calendar.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/calendar"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/card.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/card"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/chart.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/chart"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/checkbox.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/checkbox"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/coach-mark.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/coach-mark"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/collapsible.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/collapsible"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/command.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/command"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/date-picker-field.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/date-picker-field"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/dialog.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/dialog"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/drag-handle-grip.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/drag-handle-grip"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/drawer.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/drawer"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/dropdown-menu.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/dropdown-menu"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/field.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/field"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/form.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/form"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/input-group.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/input-group"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/input-mask.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/input-mask"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/input.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/input"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/kbd.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/kbd"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/label.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/label"
@@ -1 +1 @@
1
- export * from "@exxatdesignux/ui/components/payment-card-fields.tsx"
1
+ export * from "../../../../packages/ui/src/components/ui/payment-card-fields"