@exxatdesignux/ui 0.2.8 → 0.2.10

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 (125) hide show
  1. package/consumer-extras/cursor-skills/exxat-card-vs-list-rows/SKILL.md +20 -0
  2. package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +33 -0
  3. package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +45 -0
  4. package/consumer-extras/cursor-skills/exxat-drawer-vs-dialog/SKILL.md +20 -0
  5. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +31 -5
  6. package/consumer-extras/cursor-skills/exxat-kpi-max-four/SKILL.md +19 -0
  7. package/consumer-extras/cursor-skills/exxat-kpi-trends/SKILL.md +27 -0
  8. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +1 -0
  9. package/consumer-extras/patterns/collaboration-access-pattern.md +114 -0
  10. package/consumer-extras/patterns/data-views-pattern.md +12 -4
  11. package/package.json +17 -4
  12. package/src/components/ui/banner.tsx +20 -7
  13. package/src/components/ui/date-picker-field.tsx +3 -3
  14. package/src/components/ui/dropdown-menu.tsx +17 -6
  15. package/src/components/ui/input-group.tsx +1 -1
  16. package/src/components/ui/input.tsx +1 -1
  17. package/src/components/ui/select.tsx +1 -1
  18. package/src/components/ui/separator.tsx +2 -2
  19. package/src/components/ui/sidebar.tsx +31 -3
  20. package/src/components/ui/textarea.tsx +1 -1
  21. package/src/globals.css +0 -1
  22. package/src/index.ts +1 -0
  23. package/src/lib/date-filter.ts +13 -4
  24. package/src/lib/dropdown-menu-surface.ts +13 -0
  25. package/template/.claude/skills/exxat-ds-skill/SKILL.md +27 -9
  26. package/template/.cursor/rules/exxat-data-tables.mdc +1 -0
  27. package/template/AGENTS.md +82 -27
  28. package/template/app/(app)/examples/page.tsx +2 -1
  29. package/template/app/(app)/help/page.tsx +6 -0
  30. package/template/app/(app)/layout.tsx +7 -4
  31. package/template/app/(app)/question-bank/find/page.tsx +12 -0
  32. package/template/app/(app)/question-bank/layout.tsx +46 -0
  33. package/template/app/(app)/question-bank/library/page.tsx +11 -0
  34. package/template/app/(app)/question-bank/list/page.tsx +12 -0
  35. package/template/app/(app)/question-bank/page.tsx +4 -3
  36. package/template/app/globals.css +1 -2
  37. package/template/components/app-sidebar.tsx +51 -13
  38. package/template/components/ask-leo-composer.tsx +173 -45
  39. package/template/components/ask-leo-sidebar.tsx +9 -1
  40. package/template/components/chart-area-interactive.tsx +3 -13
  41. package/template/components/charts-overview.tsx +33 -6
  42. package/template/components/collaboration-access-flow.tsx +144 -0
  43. package/template/components/compliance-page-header.tsx +1 -1
  44. package/template/components/compliance-table.tsx +2 -2
  45. package/template/components/dashboard-tabs.tsx +4 -3
  46. package/template/components/data-list-table-cells.tsx +1 -1
  47. package/template/components/data-list-table.tsx +1 -1
  48. package/template/components/data-table/index.tsx +5 -5
  49. package/template/components/data-table/use-table-state.ts +18 -2
  50. package/template/components/data-view-dashboard-charts-compliance.tsx +8 -5
  51. package/template/components/data-view-dashboard-charts-team.tsx +8 -5
  52. package/template/components/data-view-dashboard-charts.tsx +62 -227
  53. package/template/components/dedicated-search-recents.tsx +96 -0
  54. package/template/components/dedicated-search-url-composer.tsx +112 -0
  55. package/template/components/getting-started.tsx +1 -1
  56. package/template/components/hub-tree-panel-view.tsx +10 -26
  57. package/template/components/invite-collaborators-drawer.tsx +453 -0
  58. package/template/components/key-metrics.tsx +54 -8
  59. package/template/components/nav-documents.tsx +1 -1
  60. package/template/components/new-placement-form.tsx +3 -3
  61. package/template/components/page-header.tsx +76 -59
  62. package/template/components/placements-board-view.tsx +3 -3
  63. package/template/components/placements-page-header.tsx +1 -1
  64. package/template/components/placements-table-columns.tsx +3 -2
  65. package/template/components/product-switcher.tsx +0 -1
  66. package/template/components/question-bank-board-view.tsx +35 -47
  67. package/template/components/question-bank-client.tsx +293 -81
  68. package/template/components/question-bank-dashboard-charts.tsx +174 -0
  69. package/template/components/question-bank-favorite-button.tsx +46 -0
  70. package/template/components/question-bank-hub-client.tsx +436 -0
  71. package/template/components/question-bank-list-view.tsx +26 -19
  72. package/template/components/question-bank-new-folder-sheet.tsx +56 -42
  73. package/template/components/question-bank-os-folder-view.tsx +3 -14
  74. package/template/components/question-bank-page-header.tsx +85 -53
  75. package/template/components/question-bank-panel-activator.tsx +3 -4
  76. package/template/components/question-bank-secondary-nav.tsx +523 -65
  77. package/template/components/question-bank-table.tsx +125 -343
  78. package/template/components/secondary-panel.tsx +130 -63
  79. package/template/components/settings-client.tsx +3 -1
  80. package/template/components/sidebar-shell.tsx +2 -0
  81. package/template/components/sites-all-client.tsx +1 -1
  82. package/template/components/sites-table.tsx +1 -1
  83. package/template/components/system-banner-slot.tsx +2 -1
  84. package/template/components/table-properties/drawer.tsx +3 -3
  85. package/template/components/table-properties/sort-card.tsx +1 -1
  86. package/template/components/team-page-header.tsx +1 -1
  87. package/template/components/team-table.tsx +8 -4
  88. package/template/components/templates/dedicated-search-landing-template.tsx +58 -0
  89. package/template/components/templates/dedicated-search-results-template.tsx +19 -0
  90. package/template/components/templates/discovery-hub-template.tsx +273 -0
  91. package/template/components/templates/list-page.tsx +11 -4
  92. package/template/components/templates/nested-secondary-panel-shell.tsx +57 -0
  93. package/template/components/templates/secondary-panel-hub-template.tsx +54 -0
  94. package/template/docs/card-vs-rows-pattern.md +36 -0
  95. package/template/docs/collaboration-access-pattern.md +114 -0
  96. package/template/docs/data-views-pattern.md +12 -4
  97. package/template/docs/drawer-vs-dialog-pattern.md +50 -0
  98. package/template/docs/kpi-strip-max-four-pattern.md +29 -0
  99. package/template/docs/kpi-trend-pattern.md +43 -0
  100. package/template/fontawesome-subset.manifest.json +2 -2
  101. package/template/hooks/use-location-hash.ts +14 -8
  102. package/template/hooks/use-secondary-panel-hub-nav.ts +98 -0
  103. package/template/lib/ask-leo-route-context.ts +24 -0
  104. package/template/lib/collaborator-access.ts +92 -0
  105. package/template/lib/command-menu-config.ts +8 -1
  106. package/template/lib/command-menu-search-data.ts +11 -8
  107. package/template/lib/data-list-display-options.ts +1 -1
  108. package/template/lib/data-view-dashboard-placements-layout.ts +215 -0
  109. package/template/lib/date-filter.ts +1 -0
  110. package/template/lib/dedicated-search-recents.ts +76 -0
  111. package/template/lib/dedicated-search-url.ts +23 -0
  112. package/template/lib/discovery-hub.ts +15 -0
  113. package/template/lib/list-status-badges.ts +1 -21
  114. package/template/lib/mock/navigation.tsx +4 -2
  115. package/template/lib/mock/placements.ts +9 -9
  116. package/template/lib/mock/question-bank-folders.ts +7 -0
  117. package/template/lib/mock/question-bank-header-collaborators.ts +45 -5
  118. package/template/lib/mock/question-bank-inspector.ts +1 -2
  119. package/template/lib/mock/question-bank-kpi.ts +38 -26
  120. package/template/lib/mock/question-bank.ts +43 -16
  121. package/template/lib/question-bank-dedicated-search.ts +19 -0
  122. package/template/lib/question-bank-hub-search.ts +90 -0
  123. package/template/lib/question-bank-nav.ts +322 -6
  124. package/template/lib/question-bank-recent-searches.ts +22 -0
  125. package/template/package.json +0 -1
@@ -26,7 +26,7 @@ import {
26
26
  saveDashboardLayout,
27
27
  type ChartType,
28
28
  type DashboardLayout,
29
- } from "@/components/data-view-dashboard-charts"
29
+ } from "@/lib/data-view-dashboard-placements-layout"
30
30
  import { CoachMark } from "@/components/ui/coach-mark"
31
31
  import { useCoachMark } from "@/hooks/use-coach-mark"
32
32
  import { DASHBOARD_CUSTOMIZE_COACH_STEPS } from "@/lib/dashboard-customize-coach-mark"
@@ -502,7 +502,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
502
502
  Add filter
503
503
  </button>
504
504
  </DropdownMenuTrigger>
505
- <DropdownMenuContent align="start" className="w-48">
505
+ <DropdownMenuContent align="start">
506
506
  <DropdownMenuLabel className="text-xs">Filter by field</DropdownMenuLabel>
507
507
  <DropdownMenuSeparator />
508
508
  {filterableCols.map(c => (
@@ -615,7 +615,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
615
615
  <i className="fa-light fa-filter text-[13px]" aria-hidden="true" />
616
616
  </button>
617
617
  </DropdownMenuTrigger>
618
- <DropdownMenuContent align="end" className="w-48">
618
+ <DropdownMenuContent align="end">
619
619
  <DropdownMenuLabel className="text-xs">Filter by field</DropdownMenuLabel>
620
620
  <DropdownMenuSeparator />
621
621
  {filterableCols.map(c => (
@@ -1173,7 +1173,7 @@ function DataTableInner<TData extends Record<string, unknown>>({
1173
1173
  </button>
1174
1174
  </DropdownMenuTrigger>
1175
1175
  </Tip>
1176
- <DropdownMenuContent align="start" className="min-w-44">
1176
+ <DropdownMenuContent align="start">
1177
1177
 
1178
1178
  {/* Column quick-search */}
1179
1179
  <div className="px-2 pt-2 pb-1">
@@ -1234,14 +1234,14 @@ function DataTableInner<TData extends Record<string, unknown>>({
1234
1234
  const filtered = prev.filter(r => r.fieldKey !== col.key)
1235
1235
  return [{ id: `sort-${Date.now()}`, fieldKey: col.key, direction: "asc" as const }, ...filtered]
1236
1236
  })}>
1237
- <i className="fa-light fa-arrow-up-az" aria-hidden="true" />
1237
+ <i className="fa-light fa-arrow-up-a-z text-xs shrink-0" aria-hidden="true" />
1238
1238
  Sort Ascending
1239
1239
  </DropdownMenuItem>
1240
1240
  <DropdownMenuItem onClick={() => setSortRules(prev => {
1241
1241
  const filtered = prev.filter(r => r.fieldKey !== col.key)
1242
1242
  return [{ id: `sort-${Date.now()}`, fieldKey: col.key, direction: "desc" as const }, ...filtered]
1243
1243
  })}>
1244
- <i className="fa-light fa-arrow-down-az" aria-hidden="true" />
1244
+ <i className="fa-light fa-arrow-down-a-z text-xs shrink-0" aria-hidden="true" />
1245
1245
  Sort Descending
1246
1246
  </DropdownMenuItem>
1247
1247
  <DropdownMenuSeparator />
@@ -91,6 +91,11 @@ export function useTableState<TData extends Record<string, unknown>>(
91
91
  columns: ColumnDef<TData>[],
92
92
  defaultSort?: { key: string; dir: SortDir },
93
93
  paginationOverride?: { page: number; pageSize: number },
94
+ /**
95
+ * When defined (including `""`), toolbar search is synced from the URL (`?q=`).
96
+ * Use `searchParams.get("q") ?? ""` on question bank list routes; omit for other hubs.
97
+ */
98
+ syncedSearchFromUrl?: string,
94
99
  ) {
95
100
  // ── Sort ──────────────────────────────────────────────────────────────────
96
101
  const [sortRules, setSortRules] = React.useState<SortRule[]>(() => {
@@ -133,8 +138,12 @@ export function useTableState<TData extends Record<string, unknown>>(
133
138
  }, [setSortRules])
134
139
 
135
140
  // ── Filters ───────────────────────────────────────────────────────────────
136
- const [search, setSearch] = React.useState("")
137
- const [searchOpen, setSearchOpen] = React.useState(false)
141
+ const [search, setSearch] = React.useState(() =>
142
+ syncedSearchFromUrl !== undefined ? syncedSearchFromUrl.trim() : "",
143
+ )
144
+ const [searchOpen, setSearchOpen] = React.useState(() =>
145
+ syncedSearchFromUrl !== undefined && Boolean(syncedSearchFromUrl.trim()),
146
+ )
138
147
  const searchRef = React.useRef<HTMLInputElement>(null)
139
148
  const [activeFilters, setActiveFilters] = React.useState<ActiveFilter[]>([])
140
149
  const [filterConnectors, setFilterConnectors] = React.useState<Record<string, "and" | "or">>({})
@@ -142,6 +151,13 @@ export function useTableState<TData extends Record<string, unknown>>(
142
151
  const [filterBarVisible, setFilterBarVisible] = React.useState(true)
143
152
  const [drawerExpandedFilters, setDrawerExpandedFilters] = React.useState<Set<string>>(new Set())
144
153
 
154
+ React.useEffect(() => {
155
+ if (syncedSearchFromUrl === undefined) return
156
+ const next = syncedSearchFromUrl.trim()
157
+ setSearch(next)
158
+ setSearchOpen(next.length > 0)
159
+ }, [syncedSearchFromUrl])
160
+
145
161
  const toggleConnector = React.useCallback((leftId: string) => {
146
162
  setFilterConnectors(prev => ({ ...prev, [leftId]: prev[leftId] === "or" ? "and" : "or" }))
147
163
  }, [setFilterConnectors])
@@ -56,7 +56,7 @@ import {
56
56
  applyVisibleReorder,
57
57
  type ChartType,
58
58
  type DashboardLayout,
59
- } from "@/components/data-view-dashboard-charts"
59
+ } from "@/lib/data-view-dashboard-placements-layout"
60
60
  import {
61
61
  CHART_KBD_ACTIVE_BAR,
62
62
  CHART_KBD_ACTIVE_PIE_SHAPE,
@@ -74,6 +74,9 @@ const CAT_CFG: ChartConfig = {
74
74
  value: { label: "Items", color: "var(--primary)" },
75
75
  }
76
76
 
77
+ const CHART_MARGIN = { top: 8, right: 8, left: 0, bottom: 0 } as const
78
+ const CHART_MARGIN_HORIZONTAL = { top: 8, right: 8, left: 4, bottom: 0 } as const
79
+
77
80
  interface ComplianceDashboardCardDef {
78
81
  id: string
79
82
  title: string
@@ -254,7 +257,7 @@ function ComplianceByStatusChart({ rows, chartType }: { rows: ComplianceItem[];
254
257
  {(activeIndex) => (
255
258
  <>
256
259
  <ChartContainer config={STATUS_CFG} className="h-[220px] w-full">
257
- <BarChart data={byStatus} layout="vertical" margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
260
+ <BarChart data={byStatus} layout="vertical" margin={CHART_MARGIN}>
258
261
  <CartesianGrid horizontal={false} strokeDasharray="3 3" className="stroke-border" />
259
262
  <XAxis type="number" allowDecimals={false} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
260
263
  <YAxis type="category" dataKey="name" width={88} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
@@ -289,7 +292,7 @@ function ComplianceByStatusChart({ rows, chartType }: { rows: ComplianceItem[];
289
292
  {(activeIndex) => (
290
293
  <>
291
294
  <ChartContainer config={STATUS_CFG} className="h-[220px] w-full">
292
- <BarChart data={byStatus} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
295
+ <BarChart data={byStatus} margin={CHART_MARGIN}>
293
296
  <CartesianGrid vertical={false} strokeDasharray="3 3" className="stroke-border" />
294
297
  <XAxis dataKey="name" tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
295
298
  <YAxis allowDecimals={false} width={32} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
@@ -374,7 +377,7 @@ function ComplianceByCategoryChart({ rows, chartType }: { rows: ComplianceItem[]
374
377
  {(activeIndex) => (
375
378
  <>
376
379
  <ChartContainer config={CAT_CFG} className="h-[220px] w-full">
377
- <BarChart data={byCategory} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
380
+ <BarChart data={byCategory} margin={CHART_MARGIN}>
378
381
  <CartesianGrid vertical={false} strokeDasharray="3 3" className="stroke-border" />
379
382
  <XAxis dataKey="name" tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
380
383
  <YAxis allowDecimals={false} width={32} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
@@ -409,7 +412,7 @@ function ComplianceByCategoryChart({ rows, chartType }: { rows: ComplianceItem[]
409
412
  {(activeIndex) => (
410
413
  <>
411
414
  <ChartContainer config={CAT_CFG} className="h-[220px] w-full">
412
- <BarChart data={byCategory} layout="vertical" margin={{ top: 8, right: 8, left: 4, bottom: 0 }}>
415
+ <BarChart data={byCategory} layout="vertical" margin={CHART_MARGIN_HORIZONTAL}>
413
416
  <CartesianGrid horizontal={false} strokeDasharray="3 3" className="stroke-border" />
414
417
  <XAxis type="number" allowDecimals={false} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
415
418
  <YAxis type="category" dataKey="name" width={100} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
@@ -57,7 +57,7 @@ import {
57
57
  applyVisibleReorder,
58
58
  type ChartType,
59
59
  type DashboardLayout,
60
- } from "@/components/data-view-dashboard-charts"
60
+ } from "@/lib/data-view-dashboard-placements-layout"
61
61
  import {
62
62
  CHART_KBD_ACTIVE_BAR,
63
63
  CHART_KBD_ACTIVE_PIE_SHAPE,
@@ -75,6 +75,9 @@ const ROLE_CHART_CFG: ChartConfig = {
75
75
  value: { label: "Members", color: "var(--primary)" },
76
76
  }
77
77
 
78
+ const CHART_MARGIN = { top: 8, right: 8, left: 0, bottom: 0 } as const
79
+ const CHART_MARGIN_HORIZONTAL = { top: 8, right: 8, left: 4, bottom: 0 } as const
80
+
78
81
  interface TeamDashboardCardDef {
79
82
  id: string
80
83
  title: string
@@ -255,7 +258,7 @@ function TeamByStatusChart({ members, chartType }: { members: TeamMember[]; char
255
258
  {(activeIndex) => (
256
259
  <>
257
260
  <ChartContainer config={STATUS_CHART_CFG} className="h-[220px] w-full">
258
- <BarChart data={byStatus} layout="vertical" margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
261
+ <BarChart data={byStatus} layout="vertical" margin={CHART_MARGIN}>
259
262
  <CartesianGrid horizontal={false} strokeDasharray="3 3" className="stroke-border" />
260
263
  <XAxis type="number" allowDecimals={false} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
261
264
  <YAxis type="category" dataKey="name" width={72} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
@@ -290,7 +293,7 @@ function TeamByStatusChart({ members, chartType }: { members: TeamMember[]; char
290
293
  {(activeIndex) => (
291
294
  <>
292
295
  <ChartContainer config={STATUS_CHART_CFG} className="h-[220px] w-full">
293
- <BarChart data={byStatus} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
296
+ <BarChart data={byStatus} margin={CHART_MARGIN}>
294
297
  <CartesianGrid vertical={false} strokeDasharray="3 3" className="stroke-border" />
295
298
  <XAxis dataKey="name" tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
296
299
  <YAxis allowDecimals={false} width={36} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
@@ -382,7 +385,7 @@ function TeamByRoleChart({ members, chartType }: { members: TeamMember[]; chartT
382
385
  {(activeIndex) => (
383
386
  <>
384
387
  <ChartContainer config={ROLE_CHART_CFG} className="h-[220px] w-full">
385
- <BarChart data={byRole} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
388
+ <BarChart data={byRole} margin={CHART_MARGIN}>
386
389
  <CartesianGrid vertical={false} strokeDasharray="3 3" className="stroke-border" />
387
390
  <XAxis dataKey="name" tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
388
391
  <YAxis allowDecimals={false} width={32} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
@@ -417,7 +420,7 @@ function TeamByRoleChart({ members, chartType }: { members: TeamMember[]; chartT
417
420
  {(activeIndex) => (
418
421
  <>
419
422
  <ChartContainer config={ROLE_CHART_CFG} className="h-[220px] w-full">
420
- <BarChart data={byRole} layout="vertical" margin={{ top: 8, right: 8, left: 4, bottom: 0 }}>
423
+ <BarChart data={byRole} layout="vertical" margin={CHART_MARGIN_HORIZONTAL}>
421
424
  <CartesianGrid horizontal={false} strokeDasharray="3 3" className="stroke-border" />
422
425
  <XAxis type="number" allowDecimals={false} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
423
426
  <YAxis type="category" dataKey="name" width={120} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />