@exxatdesignux/ui 0.0.5 → 0.0.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 (264) hide show
  1. package/bin/init.mjs +29 -0
  2. package/package.json +7 -2
  3. package/template/.nvmrc +1 -0
  4. package/template/.prettierignore +7 -0
  5. package/template/.prettierrc +11 -0
  6. package/template/AGENTS.md +485 -0
  7. package/template/Logo/Exxat_Prism.svg +39 -0
  8. package/template/Logo/Exxat_one.svg +36 -0
  9. package/template/README.md +58 -0
  10. package/template/app/(app)/compliance/page.tsx +10 -0
  11. package/template/app/(app)/dashboard/loading.tsx +18 -0
  12. package/template/app/(app)/dashboard/page.tsx +36 -0
  13. package/template/app/(app)/data-list/[id]/page.tsx +28 -0
  14. package/template/app/(app)/data-list/new/page.tsx +31 -0
  15. package/template/app/(app)/data-list/page.tsx +10 -0
  16. package/template/app/(app)/error.tsx +43 -0
  17. package/template/app/(app)/help/page.tsx +34 -0
  18. package/template/app/(app)/layout.tsx +54 -0
  19. package/template/app/(app)/loading.tsx +18 -0
  20. package/template/app/(app)/question-bank/page.tsx +10 -0
  21. package/template/app/(app)/rotations/page.tsx +15 -0
  22. package/template/app/(app)/settings/page.tsx +17 -0
  23. package/template/app/(app)/sites/all/page.tsx +13 -0
  24. package/template/app/(app)/team/page.tsx +10 -0
  25. package/template/app/favicon.ico +0 -0
  26. package/template/app/globals.css +1811 -0
  27. package/template/app/layout.tsx +95 -0
  28. package/template/app/page.tsx +9 -0
  29. package/template/components/.gitkeep +0 -0
  30. package/template/components/app-sidebar-dynamic.tsx +15 -0
  31. package/template/components/app-sidebar.tsx +901 -0
  32. package/template/components/ask-leo-composer.tsx +216 -0
  33. package/template/components/ask-leo-sidebar.tsx +509 -0
  34. package/template/components/chart-area-interactive.tsx +293 -0
  35. package/template/components/charts-overview.tsx +2321 -0
  36. package/template/components/command-menu-01.tsx +133 -0
  37. package/template/components/command-menu-02.tsx +386 -0
  38. package/template/components/command-menu.tsx +182 -0
  39. package/template/components/compliance-board-view.tsx +134 -0
  40. package/template/components/compliance-client.tsx +92 -0
  41. package/template/components/compliance-list-view.tsx +59 -0
  42. package/template/components/compliance-page-header.tsx +89 -0
  43. package/template/components/compliance-table.tsx +525 -0
  44. package/template/components/dashboard-onboarding-gallery.tsx +13 -0
  45. package/template/components/dashboard-onboarding.tsx +21 -0
  46. package/template/components/dashboard-promo-banner.tsx +67 -0
  47. package/template/components/dashboard-quota-progress-card.tsx +369 -0
  48. package/template/components/dashboard-report-charts.tsx +69 -0
  49. package/template/components/dashboard-section-heading.tsx +68 -0
  50. package/template/components/dashboard-tabs.tsx +598 -0
  51. package/template/components/data-list-client.tsx +239 -0
  52. package/template/components/data-list-table-cells.test.tsx +22 -0
  53. package/template/components/data-list-table-cells.tsx +173 -0
  54. package/template/components/data-list-table.tsx +879 -0
  55. package/template/components/data-table/filter-date-calendar.tsx +38 -0
  56. package/template/components/data-table/filter-text-value-input.tsx +77 -0
  57. package/template/components/data-table/index.tsx +1612 -0
  58. package/template/components/data-table/pagination.tsx +256 -0
  59. package/template/components/data-table/types.ts +91 -0
  60. package/template/components/data-table/use-table-state.ts +566 -0
  61. package/template/components/data-view-dashboard-charts-compliance.tsx +960 -0
  62. package/template/components/data-view-dashboard-charts-team.tsx +968 -0
  63. package/template/components/data-view-dashboard-charts.tsx +1668 -0
  64. package/template/components/data-views/board-card-primitives.tsx +93 -0
  65. package/template/components/data-views/index.ts +41 -0
  66. package/template/components/data-views/list-page-board-card.tsx +192 -0
  67. package/template/components/data-views/list-page-board-template.tsx +122 -0
  68. package/template/components/data-views/placement-board-card.tsx +262 -0
  69. package/template/components/export-drawer.tsx +375 -0
  70. package/template/components/exxat-product-logo.tsx +453 -0
  71. package/template/components/form-layout-01.tsx +131 -0
  72. package/template/components/getting-started.tsx +625 -0
  73. package/template/components/key-metrics.tsx +920 -0
  74. package/template/components/leo-insight-indicator.tsx +364 -0
  75. package/template/components/leo-typing-dots.tsx +121 -0
  76. package/template/components/list-hub-status-badge.tsx +51 -0
  77. package/template/components/list-page-dashboard-charts.tsx +18 -0
  78. package/template/components/nav-documents.tsx +89 -0
  79. package/template/components/nav-main.tsx +58 -0
  80. package/template/components/nav-secondary.tsx +64 -0
  81. package/template/components/nav-user.tsx +190 -0
  82. package/template/components/new-placement-back-btn.tsx +28 -0
  83. package/template/components/new-placement-form.tsx +1066 -0
  84. package/template/components/onboarding/index.ts +4 -0
  85. package/template/components/onboarding/onboarding-01.tsx +7 -0
  86. package/template/components/onboarding/onboarding-02.tsx +7 -0
  87. package/template/components/onboarding/onboarding-03.tsx +7 -0
  88. package/template/components/onboarding/onboarding-04.tsx +7 -0
  89. package/template/components/page-header.tsx +57 -0
  90. package/template/components/placement-detail.tsx +438 -0
  91. package/template/components/placements-board-view.tsx +404 -0
  92. package/template/components/placements-list-view.tsx +285 -0
  93. package/template/components/placements-page-header.tsx +160 -0
  94. package/template/components/placements-table-columns.tsx +639 -0
  95. package/template/components/product-switcher.tsx +116 -0
  96. package/template/components/question-bank-board-view.tsx +205 -0
  97. package/template/components/question-bank-client.tsx +77 -0
  98. package/template/components/question-bank-list-view.tsx +59 -0
  99. package/template/components/question-bank-page-header.tsx +89 -0
  100. package/template/components/question-bank-table.tsx +586 -0
  101. package/template/components/rotations-empty-state.tsx +47 -0
  102. package/template/components/rotations-panel-activator.tsx +8 -0
  103. package/template/components/secondary-nav.tsx +394 -0
  104. package/template/components/secondary-panel.tsx +239 -0
  105. package/template/components/section-cards.tsx +106 -0
  106. package/template/components/settings-appearance-card.tsx +424 -0
  107. package/template/components/settings-client.tsx +537 -0
  108. package/template/components/settings-form-row.tsx +42 -0
  109. package/template/components/sidebar-auto-collapse.tsx +23 -0
  110. package/template/components/sidebar-auto-open.tsx +18 -0
  111. package/template/components/sidebar-shell.tsx +37 -0
  112. package/template/components/site-header.tsx +93 -0
  113. package/template/components/sites-all-client.tsx +154 -0
  114. package/template/components/sites-board-view.tsx +67 -0
  115. package/template/components/sites-list-view.tsx +47 -0
  116. package/template/components/sites-table.tsx +312 -0
  117. package/template/components/system-banner-slot.tsx +66 -0
  118. package/template/components/table-properties/column-row.tsx +90 -0
  119. package/template/components/table-properties/draggable-list.ts +49 -0
  120. package/template/components/table-properties/drawer-button.tsx +231 -0
  121. package/template/components/table-properties/drawer.tsx +1102 -0
  122. package/template/components/table-properties/filter-card.tsx +251 -0
  123. package/template/components/table-properties/index.ts +22 -0
  124. package/template/components/table-properties/sort-card.tsx +59 -0
  125. package/template/components/table-properties/types.ts +124 -0
  126. package/template/components/task-list-panel.tsx +98 -0
  127. package/template/components/task-priority-badge.tsx +28 -0
  128. package/template/components/team-board-view.tsx +114 -0
  129. package/template/components/team-client.tsx +93 -0
  130. package/template/components/team-list-view.tsx +62 -0
  131. package/template/components/team-page-header.tsx +92 -0
  132. package/template/components/team-table.tsx +525 -0
  133. package/template/components/templates/list-page.tsx +576 -0
  134. package/template/components/templates/primary-page-template.tsx +56 -0
  135. package/template/components/theme-color-sync.tsx +32 -0
  136. package/template/components/theme-provider.tsx +71 -0
  137. package/template/components/tinted-icon-disc.tsx +53 -0
  138. package/template/components/ui/ai-thinking-surface.tsx +121 -0
  139. package/template/components/ui/avatar.tsx +1 -0
  140. package/template/components/ui/badge.tsx +1 -0
  141. package/template/components/ui/banner.tsx +1 -0
  142. package/template/components/ui/breadcrumb.tsx +1 -0
  143. package/template/components/ui/button.tsx +1 -0
  144. package/template/components/ui/calendar.tsx +1 -0
  145. package/template/components/ui/card.tsx +1 -0
  146. package/template/components/ui/chart.tsx +1 -0
  147. package/template/components/ui/checkbox.tsx +1 -0
  148. package/template/components/ui/coach-mark.tsx +1 -0
  149. package/template/components/ui/collapsible.tsx +1 -0
  150. package/template/components/ui/command.tsx +1 -0
  151. package/template/components/ui/date-picker-field.tsx +1 -0
  152. package/template/components/ui/dialog.tsx +1 -0
  153. package/template/components/ui/dot-pattern.tsx +159 -0
  154. package/template/components/ui/drag-handle-grip.tsx +1 -0
  155. package/template/components/ui/drawer.tsx +1 -0
  156. package/template/components/ui/dropdown-menu.tsx +1 -0
  157. package/template/components/ui/field.tsx +1 -0
  158. package/template/components/ui/form.tsx +1 -0
  159. package/template/components/ui/input-group.tsx +1 -0
  160. package/template/components/ui/input-mask.tsx +1 -0
  161. package/template/components/ui/input.tsx +1 -0
  162. package/template/components/ui/kbd.tsx +1 -0
  163. package/template/components/ui/label.tsx +1 -0
  164. package/template/components/ui/leo-icon.tsx +726 -0
  165. package/template/components/ui/payment-card-fields.tsx +1 -0
  166. package/template/components/ui/popover.tsx +1 -0
  167. package/template/components/ui/radio-group.tsx +1 -0
  168. package/template/components/ui/select.tsx +1 -0
  169. package/template/components/ui/selection-tile-grid.tsx +1 -0
  170. package/template/components/ui/separator.tsx +1 -0
  171. package/template/components/ui/sheet.tsx +1 -0
  172. package/template/components/ui/sidebar.tsx +1 -0
  173. package/template/components/ui/skeleton.tsx +1 -0
  174. package/template/components/ui/sonner.tsx +1 -0
  175. package/template/components/ui/status-badge.tsx +1 -0
  176. package/template/components/ui/table.tsx +1 -0
  177. package/template/components/ui/tabs.tsx +1 -0
  178. package/template/components/ui/textarea.tsx +1 -0
  179. package/template/components/ui/tip.tsx +1 -0
  180. package/template/components/ui/toggle-group.tsx +1 -0
  181. package/template/components/ui/toggle-switch.tsx +1 -0
  182. package/template/components/ui/toggle.tsx +1 -0
  183. package/template/components/ui/tooltip.tsx +1 -0
  184. package/template/components/ui/view-segmented-control.tsx +1 -0
  185. package/template/components.json +27 -0
  186. package/template/contexts/chart-variant-context.tsx +35 -0
  187. package/template/contexts/command-menu-context.tsx +28 -0
  188. package/template/contexts/dashboard-view-context.tsx +35 -0
  189. package/template/contexts/product-context.tsx +38 -0
  190. package/template/contexts/system-banner-context.tsx +127 -0
  191. package/template/docs/command-menu-pattern.md +45 -0
  192. package/template/docs/data-views-pattern.md +160 -0
  193. package/template/ecosystem.config.cjs +20 -0
  194. package/template/eslint.config.mjs +18 -0
  195. package/template/fontawesome-subset.manifest.json +190 -0
  196. package/template/hooks/.gitkeep +0 -0
  197. package/template/hooks/use-app-theme.ts +1 -0
  198. package/template/hooks/use-coach-mark.ts +1 -0
  199. package/template/hooks/use-mobile.ts +1 -0
  200. package/template/hooks/use-mod-key-label.ts +1 -0
  201. package/template/lib/.gitkeep +0 -0
  202. package/template/lib/ask-leo-route-context.ts +133 -0
  203. package/template/lib/chart-keyboard-selection.test.ts +20 -0
  204. package/template/lib/chart-keyboard-selection.ts +17 -0
  205. package/template/lib/chart-line-dash.ts +16 -0
  206. package/template/lib/coach-mark-registry.ts +68 -0
  207. package/template/lib/command-menu-config.ts +127 -0
  208. package/template/lib/command-menu-search-data.ts +44 -0
  209. package/template/lib/conditional-rule-match.ts +32 -0
  210. package/template/lib/dashboard-customize-coach-mark.ts +18 -0
  211. package/template/lib/dashboard-layout-merge.ts +63 -0
  212. package/template/lib/data-list-display-options.ts +35 -0
  213. package/template/lib/data-list-persistence.ts +280 -0
  214. package/template/lib/data-list-view-surface.ts +58 -0
  215. package/template/lib/data-list-view.ts +29 -0
  216. package/template/lib/data-view-dashboard-storage.ts +101 -0
  217. package/template/lib/date-filter.ts +8 -0
  218. package/template/lib/dev-log.test.ts +28 -0
  219. package/template/lib/dev-log.ts +8 -0
  220. package/template/lib/editable-target.ts +10 -0
  221. package/template/lib/floating-sheet-panel.ts +72 -0
  222. package/template/lib/initials-from-name.ts +7 -0
  223. package/template/lib/list-page-table-properties.ts +52 -0
  224. package/template/lib/list-status-badges.ts +168 -0
  225. package/template/lib/logo-dev.ts +12 -0
  226. package/template/lib/mock/compliance-kpi.ts +61 -0
  227. package/template/lib/mock/compliance.ts +146 -0
  228. package/template/lib/mock/dashboard.ts +105 -0
  229. package/template/lib/mock/navigation.tsx +231 -0
  230. package/template/lib/mock/placements-kpi.ts +134 -0
  231. package/template/lib/mock/placements.ts +183 -0
  232. package/template/lib/mock/question-bank-kpi.ts +61 -0
  233. package/template/lib/mock/question-bank.ts +142 -0
  234. package/template/lib/mock/sites-directory.ts +16 -0
  235. package/template/lib/mock/sites-kpi.ts +25 -0
  236. package/template/lib/mock/team-kpi.ts +60 -0
  237. package/template/lib/mock/team.ts +118 -0
  238. package/template/lib/motion-ui.ts +17 -0
  239. package/template/lib/placement-board-card-layout.ts +79 -0
  240. package/template/lib/placement-lifecycle.ts +5 -0
  241. package/template/lib/row-height.ts +10 -0
  242. package/template/lib/stock-portrait.ts +11 -0
  243. package/template/lib/utils.test.ts +13 -0
  244. package/template/lib/utils.ts +1 -0
  245. package/template/next.config.mjs +15 -0
  246. package/template/package.json +83 -0
  247. package/template/postcss.config.mjs +8 -0
  248. package/template/public/.gitkeep +0 -0
  249. package/template/public/Illustration/Rotation.svg +74 -0
  250. package/template/public/avatars/user.svg +11 -0
  251. package/template/public/favicon/favicon.ico +0 -0
  252. package/template/public/favicon.ico +0 -0
  253. package/template/public/logos/exxat-one.svg +36 -0
  254. package/template/public/logos/exxat-prism.svg +39 -0
  255. package/template/public/mock-schools/emory.svg +4 -0
  256. package/template/public/mock-schools/rush.svg +4 -0
  257. package/template/scripts/fontawesome-subset-audit.mjs +190 -0
  258. package/template/scripts/pm2-startup-macos.sh +13 -0
  259. package/template/skills-lock.json +10 -0
  260. package/template/stores/app-store.ts +33 -0
  261. package/template/tests/setup.ts +1 -0
  262. package/template/tsconfig.json +35 -0
  263. package/template/types/react-payment-inputs.d.ts +19 -0
  264. package/template/vitest.config.ts +18 -0
@@ -0,0 +1,66 @@
1
+ "use client"
2
+
3
+ /**
4
+ * SystemBannerSlot — the live banner rendered at the top of the app shell.
5
+ * Reads from `SystemBannerContext` so toggling / editing from Settings
6
+ * updates the banner in real time (and cross-tab via the storage listener).
7
+ */
8
+
9
+ import * as React from "react"
10
+ import Link from "next/link"
11
+ import { SystemBanner } from "@/components/ui/banner"
12
+ import { AiThinkingOverlay } from "@/components/ui/ai-thinking-surface"
13
+ import { useSystemBanner } from "@/contexts/system-banner-context"
14
+
15
+ export function SystemBannerSlot() {
16
+ const { config, setEnabled } = useSystemBanner()
17
+ const [mounted, setMounted] = React.useState(false)
18
+ React.useEffect(() => setMounted(true), [])
19
+ if (!config.enabled) return null
20
+
21
+ // Banner surface matches SidebarInset + sidebar-gap (inset variant):
22
+ // expanded: gap = --sidebar-width, inset ms-0 → align at gap edge (no extra ps).
23
+ // collapsed: gap = icon + spacing(4), inset ms-2 → match packages/ui sidebar gap + 0.5rem.
24
+ // Below md: px-2 matches SidebarInset horizontal inset (high zoom / narrow viewports).
25
+ // md+: left padding follows the sidebar gap so the banner aligns with the main content area.
26
+ return (
27
+ <div className="shrink-0 px-2 pt-1.5 transition-[padding] duration-200 ease-linear md:ps-[var(--sidebar-width)] md:pe-2 md:pt-2 md:group-data-[state=collapsed]/sidebar-wrapper:ps-[calc(var(--sidebar-width-icon)+1rem+0.5rem)]">
28
+ <SystemBanner
29
+ variant={config.variant}
30
+ emphasis={config.emphasis}
31
+ title={config.title || undefined}
32
+ dismissible={config.dismissible}
33
+ onDismiss={() => setEnabled(false)}
34
+ decorativeOverlay={
35
+ config.variant === "promo" && mounted ? (
36
+ <AiThinkingOverlay
37
+ active
38
+ cloudCount={2}
39
+ cloudRadius={340}
40
+ gridSize={13}
41
+ dotRadius={1.15}
42
+ fillClassName="fill-brand/30 dark:fill-brand/38"
43
+ />
44
+ ) : undefined
45
+ }
46
+ action={
47
+ config.actionLabel
48
+ ? { label: config.actionLabel, href: config.actionHref || "#" }
49
+ : undefined
50
+ }
51
+ >
52
+ {/* Fall back gracefully if message was cleared — still show title-only banner. */}
53
+ {config.message || (config.title ? "" : <LinkAccent href="#">Details</LinkAccent>)}
54
+ </SystemBanner>
55
+ </div>
56
+ )
57
+ }
58
+
59
+ /** Tiny local link helper so the fallback above still uses next/link. */
60
+ function LinkAccent({ href, children }: { href: string; children: React.ReactNode }) {
61
+ return (
62
+ <Link href={href} className="underline">
63
+ {children}
64
+ </Link>
65
+ )
66
+ }
@@ -0,0 +1,90 @@
1
+ "use client"
2
+ import * as React from "react"
3
+ import { cn } from "@/lib/utils"
4
+ import { Tip } from "@/components/ui/tip"
5
+ import { DragHandleGripIcon } from "@/components/ui/drag-handle-grip"
6
+ import { ToggleSwitch } from "@/components/ui/toggle-switch"
7
+
8
+ export interface ColumnRowProps {
9
+ label: string
10
+ isFirst: boolean
11
+ isLast: boolean
12
+ visible: boolean
13
+ onToggleVisible: () => void
14
+ onMoveUp: () => void
15
+ onMoveDown: () => void
16
+ // drag-and-drop props spread from useDraggableList
17
+ draggable: true
18
+ onDragStart: React.DragEventHandler
19
+ onDragOver: React.DragEventHandler
20
+ onDrop: React.DragEventHandler
21
+ onDragEnd: React.DragEventHandler
22
+ isDragging: boolean
23
+ isOver: boolean
24
+ }
25
+
26
+ export function ColumnRow({
27
+ label,
28
+ isFirst,
29
+ isLast,
30
+ visible,
31
+ onToggleVisible,
32
+ onMoveUp,
33
+ onMoveDown,
34
+ draggable,
35
+ onDragStart,
36
+ onDragOver,
37
+ onDrop,
38
+ onDragEnd,
39
+ isDragging,
40
+ isOver,
41
+ }: ColumnRowProps) {
42
+ return (
43
+ <div
44
+ role="listitem"
45
+ draggable={draggable}
46
+ onDragStart={onDragStart}
47
+ onDragOver={onDragOver}
48
+ onDrop={onDrop}
49
+ onDragEnd={onDragEnd}
50
+ className={cn(
51
+ "flex items-center gap-2 px-2 py-2 rounded-lg group hover:bg-interactive-hover-subtle transition-colors cursor-grab active:cursor-grabbing",
52
+ isDragging && "opacity-40",
53
+ isOver && "ring-2 ring-ring bg-accent/30",
54
+ )}
55
+ >
56
+ <DragHandleGripIcon className="text-[13px] text-muted-foreground/40 transition-colors group-hover:text-muted-foreground" />
57
+ <span className="flex-1 text-sm text-foreground">{label}</span>
58
+ {/* Up / Down priority buttons */}
59
+ <div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
60
+ <Tip label={`Move ${label} up`} side="top">
61
+ <button
62
+ type="button"
63
+ aria-label={`Move ${label} up`}
64
+ disabled={isFirst}
65
+ onClick={onMoveUp}
66
+ className="inline-flex items-center justify-center size-6 rounded text-muted-foreground hover:text-interactive-hover-foreground hover:bg-interactive-hover disabled:opacity-30 disabled:pointer-events-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
67
+ >
68
+ <i className="fa-light fa-chevron-up text-xs" aria-hidden="true" />
69
+ </button>
70
+ </Tip>
71
+ <Tip label={`Move ${label} down`} side="top">
72
+ <button
73
+ type="button"
74
+ aria-label={`Move ${label} down`}
75
+ disabled={isLast}
76
+ onClick={onMoveDown}
77
+ className="inline-flex items-center justify-center size-6 rounded text-muted-foreground hover:text-interactive-hover-foreground hover:bg-interactive-hover disabled:opacity-30 disabled:pointer-events-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
78
+ >
79
+ <i className="fa-light fa-chevron-down text-xs" aria-hidden="true" />
80
+ </button>
81
+ </Tip>
82
+ </div>
83
+ {/* Visibility toggle */}
84
+ <ToggleSwitch
85
+ checked={visible}
86
+ onChange={onToggleVisible}
87
+ />
88
+ </div>
89
+ )
90
+ }
@@ -0,0 +1,49 @@
1
+ import * as React from "react"
2
+
3
+ export interface DraggableListHandle {
4
+ dragId: string | null
5
+ overId: string | null
6
+ getItemProps: (id: string) => {
7
+ draggable: true
8
+ onDragStart: (e: React.DragEvent) => void
9
+ onDragOver: (e: React.DragEvent) => void
10
+ onDrop: (e: React.DragEvent) => void
11
+ onDragEnd: () => void
12
+ "data-dragging": boolean
13
+ "data-over": boolean
14
+ }
15
+ }
16
+
17
+ export function useDraggableList<T>(
18
+ items: T[],
19
+ getId: (item: T) => string,
20
+ onReorder: (newItems: T[]) => void,
21
+ ): DraggableListHandle {
22
+ const [dragId, setDragId] = React.useState<string | null>(null)
23
+ const [overId, setOverId] = React.useState<string | null>(null)
24
+
25
+ function getItemProps(id: string) {
26
+ return {
27
+ draggable: true as const,
28
+ onDragStart: (e: React.DragEvent) => { e.dataTransfer.effectAllowed = "move"; setDragId(id) },
29
+ onDragOver: (e: React.DragEvent) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; setOverId(id) },
30
+ onDrop: (e: React.DragEvent) => {
31
+ e.preventDefault()
32
+ if (!dragId || dragId === id) { setDragId(null); setOverId(null); return }
33
+ const from = items.findIndex(i => getId(i) === dragId)
34
+ const to = items.findIndex(i => getId(i) === id)
35
+ if (from === -1 || to === -1) { setDragId(null); setOverId(null); return }
36
+ const next = [...items]
37
+ const [moved] = next.splice(from, 1)
38
+ next.splice(to, 0, moved)
39
+ onReorder(next)
40
+ setDragId(null); setOverId(null)
41
+ },
42
+ onDragEnd: () => { setDragId(null); setOverId(null) },
43
+ "data-dragging": dragId === id,
44
+ "data-over": overId === id && dragId !== id,
45
+ }
46
+ }
47
+
48
+ return { dragId, overId, getItemProps }
49
+ }
@@ -0,0 +1,231 @@
1
+ "use client"
2
+
3
+ /**
4
+ * TablePropertiesDrawerButton — reusable Properties button + drawer combo.
5
+ *
6
+ * Centralises the "sliders" icon button and the full `TablePropertiesDrawer` that was
7
+ * previously duplicated per hub (TeamDrawerToolbar, ComplianceDrawerToolbar, …).
8
+ *
9
+ * Pass any `useTableState<T>` return object as `state` — the component only reads
10
+ * display-agnostic fields so it is compatible with any row shape.
11
+ */
12
+
13
+ import * as React from "react"
14
+ import { cn } from "@/lib/utils"
15
+ import {
16
+ Tooltip,
17
+ TooltipContent,
18
+ TooltipProvider,
19
+ TooltipTrigger,
20
+ } from "@/components/ui/tooltip"
21
+ import { Button } from "@/components/ui/button"
22
+ import { TablePropertiesDrawer } from "./drawer"
23
+ import type { ActiveFilter, ConditionalRule, FilterFieldDef, SortRule } from "./types"
24
+ import type { RowHeight } from "@/lib/row-height"
25
+ import type { DataListViewType } from "@/lib/data-list-view"
26
+ import type { DataListDisplayOptions } from "@/lib/data-list-display-options"
27
+
28
+ // ─────────────────────────────────────────────────────────────────────────────
29
+ // State interface
30
+ // ─────────────────────────────────────────────────────────────────────────────
31
+
32
+ /**
33
+ * Structural interface over the fields consumed from `useTableState`.
34
+ * Any `ReturnType<typeof useTableState<T>>` is assignable to this type because:
35
+ * - All non-row fields have the same shape regardless of `T`.
36
+ * - `rows: T[]` is assignable to `rows: { length: number }` (arrays expose `.length`).
37
+ */
38
+ export interface TablePropertiesDrawerButtonState {
39
+ sheetOpen: boolean
40
+ setSheetOpen: (v: boolean) => void
41
+ showGridlines: boolean
42
+ setShowGridlines: (v: boolean) => void
43
+ rowHeight: RowHeight
44
+ setRowHeight: (v: RowHeight) => void
45
+ activeFilters: ActiveFilter[]
46
+ addFilter: (fieldKey: string, fromDrawer?: boolean) => void
47
+ updateFilter: (id: string, patch: Partial<ActiveFilter>) => void
48
+ removeFilter: (id: string) => void
49
+ getConnector: (leftFilterId: string) => "and" | "or"
50
+ toggleConnector: (leftFilterId: string) => void
51
+ filterBarVisible: boolean
52
+ setFilterBarVisible: (v: boolean) => void
53
+ drawerExpandedFilters: Set<string>
54
+ setDrawerExpandedFilters: React.Dispatch<React.SetStateAction<Set<string>>>
55
+ /** Only `.length` is read — compatible with any `T[]`. */
56
+ rows: { length: number }
57
+ sortRules: SortRule[]
58
+ setSortRules: (rules: SortRule[]) => void
59
+ addSortRule: (fieldKey: string) => void
60
+ removeSortRule: (id: string) => void
61
+ toggleSortDir: (id: string) => void
62
+ colOrder: string[]
63
+ setColOrder: (order: string[]) => void
64
+ hiddenCols: Set<string>
65
+ toggleColVisibility: (key: string) => void
66
+ moveCol: (key: string, dir: "up" | "down") => void
67
+ groupBy: string | null
68
+ setGroupBy: (key: string | null) => void
69
+ sortKey?: string
70
+ }
71
+
72
+ // ─────────────────────────────────────────────────────────────────────────────
73
+ // Props
74
+ // ─────────────────────────────────────────────────────────────────────────────
75
+
76
+ export interface TablePropertiesDrawerButtonProps {
77
+ /** Pass any `useTableState<T>` return value. */
78
+ state: TablePropertiesDrawerButtonState
79
+ totalRows: number
80
+ filterFields: FilterFieldDef[]
81
+ /** Column definitions for the drawer's Sort / Group / Columns panels. */
82
+ fieldDefinitions?: { key: string; label: string; sortable?: boolean }[]
83
+ resolveColumnLabel?: (key: string) => string
84
+ displayOptions: DataListDisplayOptions
85
+ onDisplayOptionsChange: (patch: Partial<DataListDisplayOptions>) => void
86
+ conditionalRules: ConditionalRule[]
87
+ onAddConditionalRule: (rule: Omit<ConditionalRule, "id">) => void
88
+ onRemoveConditionalRule: (id: string) => void
89
+ onUpdateConditionalRule: (id: string, patch: Partial<ConditionalRule>) => void
90
+ /**
91
+ * Whether pagination is enabled. Defaults to `false`.
92
+ * Placements passes `true` because it supports server-side pagination.
93
+ */
94
+ pagination?: boolean
95
+ onPaginationChange?: (v: boolean) => void
96
+ /** View type shown in the drawer header tile grid. */
97
+ currentView?: DataListViewType
98
+ onViewChange?: (v: DataListViewType) => void
99
+ /** Shown below the "Properties" title in the drawer header (e.g. "Team", "Compliance"). */
100
+ lifecycleTabLabel?: string
101
+ /**
102
+ * When the active view is Board and more than one entry is provided, the drawer renders
103
+ * a selector for which field splits the board into swimlane columns.
104
+ */
105
+ boardGroupByColumnOptions?: { key: string; label: string }[]
106
+ /** Extra controls rendered before the Properties button (e.g. a dashboard-edit button). */
107
+ extraActions?: React.ReactNode
108
+ /** Optional custom option renderer for filter values (e.g. status chips). */
109
+ renderFilterOptionValue?: (fieldKey: string, value: string) => React.ReactNode
110
+ }
111
+
112
+ // ─────────────────────────────────────────────────────────────────────────────
113
+ // Component
114
+ // ─────────────────────────────────────────────────────────────────────────────
115
+
116
+ export function TablePropertiesDrawerButton({
117
+ state,
118
+ totalRows,
119
+ filterFields,
120
+ fieldDefinitions,
121
+ resolveColumnLabel,
122
+ displayOptions,
123
+ onDisplayOptionsChange,
124
+ conditionalRules,
125
+ onAddConditionalRule,
126
+ onRemoveConditionalRule,
127
+ onUpdateConditionalRule,
128
+ pagination = false,
129
+ onPaginationChange,
130
+ currentView,
131
+ onViewChange,
132
+ lifecycleTabLabel,
133
+ boardGroupByColumnOptions,
134
+ extraActions,
135
+ renderFilterOptionValue,
136
+ }: TablePropertiesDrawerButtonProps) {
137
+ const {
138
+ sheetOpen, setSheetOpen,
139
+ showGridlines, setShowGridlines,
140
+ rowHeight, setRowHeight,
141
+ activeFilters, addFilter, updateFilter, removeFilter,
142
+ getConnector, toggleConnector,
143
+ filterBarVisible, setFilterBarVisible,
144
+ drawerExpandedFilters, setDrawerExpandedFilters,
145
+ rows,
146
+ sortRules, setSortRules, addSortRule, removeSortRule, toggleSortDir,
147
+ colOrder, setColOrder,
148
+ hiddenCols, toggleColVisibility,
149
+ moveCol,
150
+ groupBy, setGroupBy,
151
+ sortKey,
152
+ } = state
153
+
154
+ return (
155
+ <>
156
+ {extraActions}
157
+
158
+ <TooltipProvider>
159
+ <Tooltip>
160
+ <TooltipTrigger asChild>
161
+ <Button
162
+ type="button"
163
+ variant="ghost"
164
+ size="icon-sm"
165
+ aria-label="Properties"
166
+ onClick={() => setSheetOpen(true)}
167
+ className={cn(
168
+ sheetOpen
169
+ ? "bg-accent text-accent-foreground"
170
+ : "text-muted-foreground hover:text-interactive-hover-foreground hover:bg-interactive-hover",
171
+ )}
172
+ >
173
+ <i className="fa-light fa-sliders text-[13px]" aria-hidden="true" />
174
+ </Button>
175
+ </TooltipTrigger>
176
+ <TooltipContent side="bottom">Properties</TooltipContent>
177
+ </Tooltip>
178
+ </TooltipProvider>
179
+
180
+ <TablePropertiesDrawer
181
+ open={sheetOpen}
182
+ onOpenChange={setSheetOpen}
183
+ showGridlines={showGridlines}
184
+ onShowGridlinesChange={setShowGridlines}
185
+ rowHeight={rowHeight}
186
+ onRowHeightChange={setRowHeight}
187
+ pagination={pagination}
188
+ onPaginationChange={onPaginationChange ?? (() => {})}
189
+ activeFilters={activeFilters}
190
+ onAddFilter={fieldKey => addFilter(fieldKey, true)}
191
+ onUpdateFilter={updateFilter}
192
+ onRemoveFilter={removeFilter}
193
+ getFilterConnector={getConnector}
194
+ onToggleFilterConnector={toggleConnector}
195
+ filterBarVisible={filterBarVisible}
196
+ onFilterBarVisibleChange={setFilterBarVisible}
197
+ drawerExpandedFilters={drawerExpandedFilters}
198
+ onDrawerExpandedFiltersChange={setDrawerExpandedFilters}
199
+ totalRows={totalRows}
200
+ filteredRows={rows.length}
201
+ sortRules={sortRules}
202
+ onSortRulesChange={setSortRules}
203
+ onAddSortRule={addSortRule}
204
+ onRemoveSortRule={removeSortRule}
205
+ onToggleSortDir={toggleSortDir}
206
+ colOrder={colOrder}
207
+ onColOrderChange={setColOrder}
208
+ hiddenCols={hiddenCols}
209
+ onToggleColVisibility={toggleColVisibility}
210
+ onMoveCol={moveCol}
211
+ groupBy={groupBy}
212
+ onGroupByChange={setGroupBy}
213
+ primarySortKey={sortKey}
214
+ conditionalRules={conditionalRules}
215
+ onAddConditionalRule={onAddConditionalRule}
216
+ onRemoveConditionalRule={onRemoveConditionalRule}
217
+ onUpdateConditionalRule={onUpdateConditionalRule}
218
+ filterFields={filterFields}
219
+ lifecycleTabLabel={lifecycleTabLabel}
220
+ fieldDefinitions={fieldDefinitions}
221
+ resolveColumnLabel={resolveColumnLabel}
222
+ displayOptions={displayOptions}
223
+ onDisplayOptionsChange={onDisplayOptionsChange}
224
+ currentView={currentView}
225
+ onViewChange={onViewChange}
226
+ boardGroupByColumnOptions={boardGroupByColumnOptions}
227
+ renderFilterOptionValue={renderFilterOptionValue}
228
+ />
229
+ </>
230
+ )
231
+ }