@exxatdesignux/ui 0.2.18 → 0.3.0

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 (618) hide show
  1. package/CHANGELOG.md +69 -1
  2. package/bin/sync-extras.mjs +116 -29
  3. package/consumer-extras/README.md +43 -4
  4. package/consumer-extras/cursor-rules/exxat-accessibility.mdc +39 -0
  5. package/consumer-extras/cursor-rules/exxat-board-cards.mdc +26 -0
  6. package/consumer-extras/cursor-rules/exxat-breadcrumbs-no-back.mdc +21 -0
  7. package/consumer-extras/cursor-rules/exxat-card-vs-list-rows.mdc +21 -0
  8. package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +44 -0
  9. package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +32 -0
  10. package/consumer-extras/cursor-rules/exxat-command-menu.mdc +22 -0
  11. package/consumer-extras/cursor-rules/exxat-dashboard-view-charts.mdc +53 -0
  12. package/consumer-extras/cursor-rules/exxat-data-tables.mdc +41 -0
  13. package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +25 -0
  14. package/consumer-extras/cursor-rules/exxat-drawer-vs-dialog.mdc +22 -0
  15. package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +56 -0
  16. package/consumer-extras/cursor-rules/exxat-fontawesome-icons.mdc +31 -0
  17. package/consumer-extras/cursor-rules/exxat-kbd-shortcuts.mdc +100 -0
  18. package/consumer-extras/cursor-rules/exxat-kpi-flat-band.mdc +28 -0
  19. package/consumer-extras/cursor-rules/exxat-kpi-max-four.mdc +21 -0
  20. package/consumer-extras/cursor-rules/exxat-kpi-trends.mdc +31 -0
  21. package/consumer-extras/cursor-rules/exxat-list-page-connected-views.mdc +24 -0
  22. package/consumer-extras/cursor-rules/exxat-list-page-view-shells.mdc +31 -0
  23. package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +30 -0
  24. package/consumer-extras/cursor-rules/exxat-no-slds-leakage.mdc +78 -0
  25. package/consumer-extras/cursor-rules/exxat-no-toast.mdc +25 -0
  26. package/consumer-extras/cursor-rules/exxat-page-vs-drawer.mdc +23 -0
  27. package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +47 -0
  28. package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +52 -0
  29. package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +28 -0
  30. package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +34 -0
  31. package/consumer-extras/cursor-rules/exxat-table-properties-drawer.mdc +77 -0
  32. package/consumer-extras/cursor-rules/exxat-token-discipline.mdc +103 -0
  33. package/consumer-extras/cursor-skills/exxat-accessibility/SKILL.md +1 -1
  34. package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
  35. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
  36. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +9 -9
  37. package/consumer-extras/cursor-skills/exxat-ds-skill/references/data-table-pattern.md +1 -1
  38. package/consumer-extras/handbook/HANDBOOK.md +185 -0
  39. package/consumer-extras/handbook/glossary.md +57 -0
  40. package/consumer-extras/handbook/reference-implementations.md +126 -0
  41. package/consumer-extras/handbook/voice-and-tone.md +262 -0
  42. package/consumer-extras/patterns/command-menu-pattern.md +1 -1
  43. package/consumer-extras/patterns/data-views-pattern.md +14 -14
  44. package/dist/components/data-table/filter-date-calendar.d.ts +10 -0
  45. package/dist/components/data-table/filter-date-calendar.js +280 -0
  46. package/dist/components/data-table/filter-date-calendar.js.map +1 -0
  47. package/dist/components/data-table/filter-text-value-input.d.ts +15 -0
  48. package/dist/components/data-table/filter-text-value-input.js +561 -0
  49. package/dist/components/data-table/filter-text-value-input.js.map +1 -0
  50. package/dist/components/data-table/index.d.ts +45 -0
  51. package/dist/components/data-table/index.js +3085 -0
  52. package/dist/components/data-table/index.js.map +1 -0
  53. package/dist/components/data-table/pagination.d.ts +28 -0
  54. package/dist/components/data-table/pagination.js +3264 -0
  55. package/dist/components/data-table/pagination.js.map +1 -0
  56. package/dist/components/data-table/types.d.ts +84 -0
  57. package/dist/components/data-table/types.js +3 -0
  58. package/dist/components/data-table/types.js.map +1 -0
  59. package/dist/components/data-table/use-table-state.d.ts +116 -0
  60. package/dist/components/data-table/use-table-state.js +670 -0
  61. package/dist/components/data-table/use-table-state.js.map +1 -0
  62. package/dist/components/data-views/board-card-primitives.d.ts +22 -0
  63. package/dist/components/data-views/board-card-primitives.js +84 -0
  64. package/dist/components/data-views/board-card-primitives.js.map +1 -0
  65. package/dist/components/data-views/data-row-list.d.ts +33 -0
  66. package/dist/components/data-views/data-row-list.js +106 -0
  67. package/dist/components/data-views/data-row-list.js.map +1 -0
  68. package/dist/components/data-views/finder-panel-view.d.ts +54 -0
  69. package/dist/components/data-views/finder-panel-view.js +388 -0
  70. package/dist/components/data-views/finder-panel-view.js.map +1 -0
  71. package/dist/components/data-views/folder-grid-view.d.ts +22 -0
  72. package/dist/components/data-views/folder-grid-view.js +58 -0
  73. package/dist/components/data-views/folder-grid-view.js.map +1 -0
  74. package/dist/components/data-views/hub-table.d.ts +167 -0
  75. package/dist/components/data-views/hub-table.js +5561 -0
  76. package/dist/components/data-views/hub-table.js.map +1 -0
  77. package/dist/components/data-views/index.d.ts +27 -0
  78. package/dist/components/data-views/index.js +6575 -0
  79. package/dist/components/data-views/index.js.map +1 -0
  80. package/dist/components/data-views/list-page-board-card.d.ts +72 -0
  81. package/dist/components/data-views/list-page-board-card.js +264 -0
  82. package/dist/components/data-views/list-page-board-card.js.map +1 -0
  83. package/dist/components/data-views/list-page-board-template.d.ts +24 -0
  84. package/dist/components/data-views/list-page-board-template.js +137 -0
  85. package/dist/components/data-views/list-page-board-template.js.map +1 -0
  86. package/dist/components/data-views/list-page-connected-view-body.d.ts +19 -0
  87. package/dist/components/data-views/list-page-connected-view-body.js +116 -0
  88. package/dist/components/data-views/list-page-connected-view-body.js.map +1 -0
  89. package/dist/components/data-views/list-page-split-details-placeholder.d.ts +14 -0
  90. package/dist/components/data-views/list-page-split-details-placeholder.js +38 -0
  91. package/dist/components/data-views/list-page-split-details-placeholder.js.map +1 -0
  92. package/dist/components/data-views/list-page-split-hub-chrome.d.ts +17 -0
  93. package/dist/components/data-views/list-page-split-hub-chrome.js +54 -0
  94. package/dist/components/data-views/list-page-split-hub-chrome.js.map +1 -0
  95. package/dist/components/data-views/list-page-split-hub-tokens.d.ts +12 -0
  96. package/dist/components/data-views/list-page-split-hub-tokens.js +8 -0
  97. package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -0
  98. package/dist/components/data-views/list-page-tree-column-header.d.ts +15 -0
  99. package/dist/components/data-views/list-page-tree-column-header.js +22 -0
  100. package/dist/components/data-views/list-page-tree-column-header.js.map +1 -0
  101. package/dist/components/data-views/list-page-tree-panel-shell.d.ts +25 -0
  102. package/dist/components/data-views/list-page-tree-panel-shell.js +146 -0
  103. package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -0
  104. package/dist/components/data-views/os-folder-glyph.d.ts +35 -0
  105. package/dist/components/data-views/os-folder-glyph.js +104 -0
  106. package/dist/components/data-views/os-folder-glyph.js.map +1 -0
  107. package/dist/components/data-views/outline-tree-menu.d.ts +36 -0
  108. package/dist/components/data-views/outline-tree-menu.js +131 -0
  109. package/dist/components/data-views/outline-tree-menu.js.map +1 -0
  110. package/dist/components/table-properties/column-row.d.ts +22 -0
  111. package/dist/components/table-properties/column-row.js +153 -0
  112. package/dist/components/table-properties/column-row.js.map +1 -0
  113. package/dist/components/table-properties/draggable-list.d.ts +24 -0
  114. package/dist/components/table-properties/draggable-list.js +53 -0
  115. package/dist/components/table-properties/draggable-list.js.map +1 -0
  116. package/dist/components/table-properties/drawer-button.d.ts +110 -0
  117. package/dist/components/table-properties/drawer-button.js +2748 -0
  118. package/dist/components/table-properties/drawer-button.js.map +1 -0
  119. package/dist/components/table-properties/drawer.d.ts +100 -0
  120. package/dist/components/table-properties/drawer.js +2595 -0
  121. package/dist/components/table-properties/drawer.js.map +1 -0
  122. package/dist/components/table-properties/filter-card.d.ts +24 -0
  123. package/dist/components/table-properties/filter-card.js +854 -0
  124. package/dist/components/table-properties/filter-card.js.map +1 -0
  125. package/dist/components/table-properties/index.d.ts +14 -0
  126. package/dist/components/table-properties/index.js +2768 -0
  127. package/dist/components/table-properties/index.js.map +1 -0
  128. package/dist/components/table-properties/sort-card.d.ts +20 -0
  129. package/dist/components/table-properties/sort-card.js +102 -0
  130. package/dist/components/table-properties/sort-card.js.map +1 -0
  131. package/dist/components/templates/dedicated-search-landing-template.d.ts +21 -0
  132. package/dist/components/templates/dedicated-search-landing-template.js +254 -0
  133. package/dist/components/templates/dedicated-search-landing-template.js.map +1 -0
  134. package/dist/components/templates/dedicated-search-results-template.d.ts +15 -0
  135. package/dist/components/templates/dedicated-search-results-template.js +16 -0
  136. package/dist/components/templates/dedicated-search-results-template.js.map +1 -0
  137. package/dist/components/templates/index.d.ts +9 -0
  138. package/dist/components/templates/index.js +2720 -0
  139. package/dist/components/templates/index.js.map +1 -0
  140. package/dist/components/templates/list-page.d.ts +83 -0
  141. package/dist/components/templates/list-page.js +2433 -0
  142. package/dist/components/templates/list-page.js.map +1 -0
  143. package/dist/components/templates/nested-secondary-panel-shell.d.ts +20 -0
  144. package/dist/components/templates/nested-secondary-panel-shell.js +54 -0
  145. package/dist/components/templates/nested-secondary-panel-shell.js.map +1 -0
  146. package/dist/components/ui/accordion.d.ts +10 -0
  147. package/dist/components/ui/accordion.js +74 -0
  148. package/dist/components/ui/accordion.js.map +1 -0
  149. package/dist/components/ui/alert-dialog.d.ts +37 -0
  150. package/dist/components/ui/alert-dialog.js +201 -0
  151. package/dist/components/ui/alert-dialog.js.map +1 -0
  152. package/dist/components/ui/avatar.d.ts +84 -0
  153. package/dist/components/ui/avatar.js +328 -0
  154. package/dist/components/ui/avatar.js.map +1 -0
  155. package/dist/components/ui/badge.d.ts +13 -0
  156. package/dist/components/ui/badge.js +49 -0
  157. package/dist/components/ui/badge.js.map +1 -0
  158. package/dist/components/ui/banner.d.ts +62 -0
  159. package/dist/components/ui/banner.js +364 -0
  160. package/dist/components/ui/banner.js.map +1 -0
  161. package/dist/components/ui/breadcrumb.d.ts +14 -0
  162. package/dist/components/ui/breadcrumb.js +114 -0
  163. package/dist/components/ui/breadcrumb.js.map +1 -0
  164. package/dist/components/ui/button.d.ts +16 -0
  165. package/dist/components/ui/button.js +59 -0
  166. package/dist/components/ui/button.js.map +1 -0
  167. package/dist/components/ui/calendar.d.ts +13 -0
  168. package/dist/components/ui/calendar.js +238 -0
  169. package/dist/components/ui/calendar.js.map +1 -0
  170. package/dist/components/ui/card.d.ts +14 -0
  171. package/dist/components/ui/card.js +102 -0
  172. package/dist/components/ui/card.js.map +1 -0
  173. package/dist/components/ui/chart.d.ts +58 -0
  174. package/dist/components/ui/chart.js +292 -0
  175. package/dist/components/ui/chart.js.map +1 -0
  176. package/dist/components/ui/checkbox.d.ts +23 -0
  177. package/dist/components/ui/checkbox.js +155 -0
  178. package/dist/components/ui/checkbox.js.map +1 -0
  179. package/dist/components/ui/coach-mark.d.ts +27 -0
  180. package/dist/components/ui/coach-mark.js +306 -0
  181. package/dist/components/ui/coach-mark.js.map +1 -0
  182. package/dist/components/ui/collapsible.d.ts +8 -0
  183. package/dist/components/ui/collapsible.js +35 -0
  184. package/dist/components/ui/collapsible.js.map +1 -0
  185. package/dist/components/ui/command.d.ts +36 -0
  186. package/dist/components/ui/command.js +274 -0
  187. package/dist/components/ui/command.js.map +1 -0
  188. package/dist/components/ui/context-menu.d.ts +32 -0
  189. package/dist/components/ui/context-menu.js +245 -0
  190. package/dist/components/ui/context-menu.js.map +1 -0
  191. package/dist/components/ui/date-picker-field.d.ts +38 -0
  192. package/dist/components/ui/date-picker-field.js +550 -0
  193. package/dist/components/ui/date-picker-field.js.map +1 -0
  194. package/dist/components/ui/dialog.d.ts +22 -0
  195. package/dist/components/ui/dialog.js +200 -0
  196. package/dist/components/ui/dialog.js.map +1 -0
  197. package/dist/components/ui/dot-pattern.d.ts +21 -0
  198. package/dist/components/ui/dot-pattern.js +139 -0
  199. package/dist/components/ui/dot-pattern.js.map +1 -0
  200. package/dist/components/ui/drag-handle-grip.d.ts +10 -0
  201. package/dist/components/ui/drag-handle-grip.js +15 -0
  202. package/dist/components/ui/drag-handle-grip.js.map +1 -0
  203. package/dist/components/ui/drawer.d.ts +16 -0
  204. package/dist/components/ui/drawer.js +125 -0
  205. package/dist/components/ui/drawer.js.map +1 -0
  206. package/dist/components/ui/dropdown-menu.d.ts +45 -0
  207. package/dist/components/ui/dropdown-menu.js +353 -0
  208. package/dist/components/ui/dropdown-menu.js.map +1 -0
  209. package/dist/components/ui/export-drawer.d.ts +11 -0
  210. package/dist/components/ui/export-drawer.js +1658 -0
  211. package/dist/components/ui/export-drawer.js.map +1 -0
  212. package/dist/components/ui/field.d.ts +30 -0
  213. package/dist/components/ui/field.js +249 -0
  214. package/dist/components/ui/field.js.map +1 -0
  215. package/dist/components/ui/form.d.ts +28 -0
  216. package/dist/components/ui/form.js +110 -0
  217. package/dist/components/ui/form.js.map +1 -0
  218. package/dist/components/ui/hover-card.d.ts +9 -0
  219. package/dist/components/ui/hover-card.js +43 -0
  220. package/dist/components/ui/hover-card.js.map +1 -0
  221. package/dist/components/ui/input-group.d.ts +20 -0
  222. package/dist/components/ui/input-group.js +219 -0
  223. package/dist/components/ui/input-group.js.map +1 -0
  224. package/dist/components/ui/input-mask.d.ts +39 -0
  225. package/dist/components/ui/input-mask.js +118 -0
  226. package/dist/components/ui/input-mask.js.map +1 -0
  227. package/dist/components/ui/input.d.ts +5 -0
  228. package/dist/components/ui/input.js +30 -0
  229. package/dist/components/ui/input.js.map +1 -0
  230. package/dist/components/ui/kbd.d.ts +20 -0
  231. package/dist/components/ui/kbd.js +45 -0
  232. package/dist/components/ui/kbd.js.map +1 -0
  233. package/dist/components/ui/key-metrics-context.d.ts +19 -0
  234. package/dist/components/ui/key-metrics-context.js +26 -0
  235. package/dist/components/ui/key-metrics-context.js.map +1 -0
  236. package/dist/components/ui/key-metrics.d.ts +131 -0
  237. package/dist/components/ui/key-metrics.js +1015 -0
  238. package/dist/components/ui/key-metrics.js.map +1 -0
  239. package/dist/components/ui/label.d.ts +6 -0
  240. package/dist/components/ui/label.js +28 -0
  241. package/dist/components/ui/label.js.map +1 -0
  242. package/dist/components/ui/list-page-view-frame.d.ts +22 -0
  243. package/dist/components/ui/list-page-view-frame.js +24 -0
  244. package/dist/components/ui/list-page-view-frame.js.map +1 -0
  245. package/dist/components/ui/page-header.d.ts +51 -0
  246. package/dist/components/ui/page-header.js +372 -0
  247. package/dist/components/ui/page-header.js.map +1 -0
  248. package/dist/components/ui/payment-card-fields.d.ts +10 -0
  249. package/dist/components/ui/payment-card-fields.js +80 -0
  250. package/dist/components/ui/payment-card-fields.js.map +1 -0
  251. package/dist/components/ui/popover.d.ts +10 -0
  252. package/dist/components/ui/popover.js +47 -0
  253. package/dist/components/ui/popover.js.map +1 -0
  254. package/dist/components/ui/radio-group.d.ts +29 -0
  255. package/dist/components/ui/radio-group.js +190 -0
  256. package/dist/components/ui/radio-group.js.map +1 -0
  257. package/dist/components/ui/resizable.d.ts +16 -0
  258. package/dist/components/ui/resizable.js +51 -0
  259. package/dist/components/ui/resizable.js.map +1 -0
  260. package/dist/components/ui/scroll-area.d.ts +8 -0
  261. package/dist/components/ui/scroll-area.js +66 -0
  262. package/dist/components/ui/scroll-area.js.map +1 -0
  263. package/dist/components/ui/select.d.ts +18 -0
  264. package/dist/components/ui/select.js +186 -0
  265. package/dist/components/ui/select.js.map +1 -0
  266. package/dist/components/ui/selection-tile-grid.d.ts +52 -0
  267. package/dist/components/ui/selection-tile-grid.js +347 -0
  268. package/dist/components/ui/selection-tile-grid.js.map +1 -0
  269. package/dist/components/ui/separator.d.ts +7 -0
  270. package/dist/components/ui/separator.js +33 -0
  271. package/dist/components/ui/separator.js.map +1 -0
  272. package/dist/components/ui/sheet.d.ts +18 -0
  273. package/dist/components/ui/sheet.js +181 -0
  274. package/dist/components/ui/sheet.js.map +1 -0
  275. package/dist/components/ui/sidebar.d.ts +94 -0
  276. package/dist/components/ui/sidebar.js +805 -0
  277. package/dist/components/ui/sidebar.js.map +1 -0
  278. package/dist/components/ui/skeleton.d.ts +5 -0
  279. package/dist/components/ui/skeleton.js +22 -0
  280. package/dist/components/ui/skeleton.js.map +1 -0
  281. package/dist/components/ui/slider.d.ts +7 -0
  282. package/dist/components/ui/slider.js +66 -0
  283. package/dist/components/ui/slider.js.map +1 -0
  284. package/dist/components/ui/sonner.d.ts +6 -0
  285. package/dist/components/ui/sonner.js +38 -0
  286. package/dist/components/ui/sonner.js.map +1 -0
  287. package/dist/components/ui/status-badge.d.ts +38 -0
  288. package/dist/components/ui/status-badge.js +77 -0
  289. package/dist/components/ui/status-badge.js.map +1 -0
  290. package/dist/components/ui/table.d.ts +13 -0
  291. package/dist/components/ui/table.js +115 -0
  292. package/dist/components/ui/table.js.map +1 -0
  293. package/dist/components/ui/tabs.d.ts +15 -0
  294. package/dist/components/ui/tabs.js +93 -0
  295. package/dist/components/ui/tabs.js.map +1 -0
  296. package/dist/components/ui/textarea.d.ts +6 -0
  297. package/dist/components/ui/textarea.js +25 -0
  298. package/dist/components/ui/textarea.js.map +1 -0
  299. package/dist/components/ui/tip.d.ts +12 -0
  300. package/dist/components/ui/tip.js +61 -0
  301. package/dist/components/ui/tip.js.map +1 -0
  302. package/dist/components/ui/toggle-group.d.ts +14 -0
  303. package/dist/components/ui/toggle-group.js +104 -0
  304. package/dist/components/ui/toggle-group.js.map +1 -0
  305. package/dist/components/ui/toggle-switch.d.ts +10 -0
  306. package/dist/components/ui/toggle-switch.js +33 -0
  307. package/dist/components/ui/toggle-switch.js.map +1 -0
  308. package/dist/components/ui/toggle.d.ts +13 -0
  309. package/dist/components/ui/toggle.js +51 -0
  310. package/dist/components/ui/toggle.js.map +1 -0
  311. package/dist/components/ui/tooltip.d.ts +10 -0
  312. package/dist/components/ui/tooltip.js +68 -0
  313. package/dist/components/ui/tooltip.js.map +1 -0
  314. package/dist/components/ui/view-segmented-control.d.ts +31 -0
  315. package/dist/components/ui/view-segmented-control.js +167 -0
  316. package/dist/components/ui/view-segmented-control.js.map +1 -0
  317. package/dist/data-list-view-registry-CyBoBML4.d.ts +73 -0
  318. package/dist/hooks/use-app-theme.d.ts +24 -0
  319. package/dist/hooks/use-app-theme.js +286 -0
  320. package/dist/hooks/use-app-theme.js.map +1 -0
  321. package/dist/hooks/use-coach-mark.d.ts +86 -0
  322. package/dist/hooks/use-coach-mark.js +218 -0
  323. package/dist/hooks/use-coach-mark.js.map +1 -0
  324. package/dist/hooks/use-mobile.d.ts +3 -0
  325. package/dist/hooks/use-mobile.js +29 -0
  326. package/dist/hooks/use-mobile.js.map +1 -0
  327. package/dist/hooks/use-mod-key-label.d.ts +6 -0
  328. package/dist/hooks/use-mod-key-label.js +25 -0
  329. package/dist/hooks/use-mod-key-label.js.map +1 -0
  330. package/dist/index.d.ts +120 -0
  331. package/dist/index.js +13324 -0
  332. package/dist/index.js.map +1 -0
  333. package/dist/lib/compose-refs.d.ts +6 -0
  334. package/dist/lib/compose-refs.js +17 -0
  335. package/dist/lib/compose-refs.js.map +1 -0
  336. package/dist/lib/conditional-rule-match.d.ts +30 -0
  337. package/dist/lib/conditional-rule-match.js +66 -0
  338. package/dist/lib/conditional-rule-match.js.map +1 -0
  339. package/dist/lib/data-list-display-options.d.ts +26 -0
  340. package/dist/lib/data-list-display-options.js +14 -0
  341. package/dist/lib/data-list-display-options.js.map +1 -0
  342. package/dist/lib/data-list-view-registry.d.ts +2 -0
  343. package/dist/lib/data-list-view-registry.js +102 -0
  344. package/dist/lib/data-list-view-registry.js.map +1 -0
  345. package/dist/lib/data-list-view-surface.d.ts +2 -0
  346. package/dist/lib/data-list-view-surface.js +80 -0
  347. package/dist/lib/data-list-view-surface.js.map +1 -0
  348. package/dist/lib/data-list-view.d.ts +21 -0
  349. package/dist/lib/data-list-view.js +25 -0
  350. package/dist/lib/data-list-view.js.map +1 -0
  351. package/dist/lib/date-filter.d.ts +22 -0
  352. package/dist/lib/date-filter.js +61 -0
  353. package/dist/lib/date-filter.js.map +1 -0
  354. package/dist/lib/dev-log.d.ts +8 -0
  355. package/dist/lib/dev-log.js +10 -0
  356. package/dist/lib/dev-log.js.map +1 -0
  357. package/dist/lib/dropdown-menu-surface.d.ts +14 -0
  358. package/dist/lib/dropdown-menu-surface.js +6 -0
  359. package/dist/lib/dropdown-menu-surface.js.map +1 -0
  360. package/dist/lib/editable-target.d.ts +12 -0
  361. package/dist/lib/editable-target.js +12 -0
  362. package/dist/lib/editable-target.js.map +1 -0
  363. package/dist/lib/list-page-table-properties.d.ts +35 -0
  364. package/dist/lib/list-page-table-properties.js +81 -0
  365. package/dist/lib/list-page-table-properties.js.map +1 -0
  366. package/dist/lib/raf-throttle.d.ts +23 -0
  367. package/dist/lib/raf-throttle.js +27 -0
  368. package/dist/lib/raf-throttle.js.map +1 -0
  369. package/dist/lib/row-height.d.ts +16 -0
  370. package/dist/lib/row-height.js +10 -0
  371. package/dist/lib/row-height.js.map +1 -0
  372. package/dist/lib/table-properties-types.d.ts +83 -0
  373. package/dist/lib/table-properties-types.js +19 -0
  374. package/dist/lib/table-properties-types.js.map +1 -0
  375. package/dist/lib/utils.d.ts +5 -0
  376. package/dist/lib/utils.js +11 -0
  377. package/dist/lib/utils.js.map +1 -0
  378. package/package.json +83 -18
  379. package/src/components/data-table/filter-date-calendar.tsx +38 -0
  380. package/src/components/data-table/filter-text-value-input.tsx +77 -0
  381. package/src/components/data-table/index.tsx +1678 -0
  382. package/src/components/data-table/pagination.tsx +255 -0
  383. package/src/components/data-table/types.ts +96 -0
  384. package/src/components/data-table/use-table-state.ts +767 -0
  385. package/src/components/data-views/board-card-primitives.tsx +93 -0
  386. package/src/components/data-views/data-row-list.tsx +183 -0
  387. package/src/components/data-views/finder-panel-view.tsx +405 -0
  388. package/src/components/data-views/folder-grid-view.tsx +86 -0
  389. package/src/components/data-views/hub-table.tsx +498 -0
  390. package/src/components/data-views/index.ts +28 -0
  391. package/src/components/data-views/list-page-board-card.tsx +192 -0
  392. package/src/components/data-views/list-page-board-template.tsx +122 -0
  393. package/src/components/data-views/list-page-connected-view-body.tsx +66 -0
  394. package/src/components/data-views/list-page-split-details-placeholder.tsx +39 -0
  395. package/src/components/data-views/list-page-split-hub-chrome.tsx +60 -0
  396. package/src/components/data-views/list-page-split-hub-tokens.ts +16 -0
  397. package/src/components/data-views/list-page-tree-column-header.tsx +31 -0
  398. package/src/components/data-views/list-page-tree-panel-shell.tsx +91 -0
  399. package/src/components/data-views/os-folder-glyph.tsx +141 -0
  400. package/src/components/data-views/outline-tree-menu.tsx +157 -0
  401. package/src/components/table-properties/column-row.tsx +90 -0
  402. package/src/components/table-properties/draggable-list.ts +54 -0
  403. package/src/components/table-properties/drawer-button.tsx +300 -0
  404. package/src/components/table-properties/drawer.tsx +1148 -0
  405. package/src/components/table-properties/filter-card.tsx +251 -0
  406. package/src/components/table-properties/index.ts +36 -0
  407. package/src/components/table-properties/sort-card.tsx +63 -0
  408. package/src/components/templates/dedicated-search-landing-template.tsx +124 -0
  409. package/src/components/templates/dedicated-search-results-template.tsx +19 -0
  410. package/src/components/templates/index.ts +33 -0
  411. package/src/components/templates/list-page.tsx +602 -0
  412. package/src/components/templates/nested-secondary-panel-shell.tsx +70 -0
  413. package/src/components/ui/accordion.tsx +92 -0
  414. package/src/components/ui/alert-dialog.tsx +221 -0
  415. package/src/components/ui/avatar.tsx +13 -2
  416. package/src/components/ui/banner.tsx +2 -2
  417. package/src/components/ui/calendar.tsx +1 -1
  418. package/src/components/ui/coach-mark.tsx +1 -1
  419. package/src/components/ui/context-menu.tsx +291 -0
  420. package/src/components/ui/date-picker-field.tsx +2 -2
  421. package/src/components/ui/dot-pattern.tsx +183 -0
  422. package/src/components/ui/export-drawer.tsx +375 -0
  423. package/src/components/ui/hover-card.tsx +66 -0
  424. package/src/components/ui/key-metrics-context.tsx +78 -0
  425. package/src/components/ui/key-metrics.tsx +1133 -0
  426. package/src/components/ui/list-page-view-frame.tsx +64 -0
  427. package/src/components/ui/page-header.tsx +244 -0
  428. package/src/components/ui/payment-card-fields.tsx +2 -2
  429. package/src/components/ui/resizable.tsx +68 -0
  430. package/src/components/ui/scroll-area.tsx +72 -0
  431. package/src/components/ui/selection-tile-grid.tsx +9 -2
  432. package/src/components/ui/sidebar.tsx +84 -12
  433. package/src/components/ui/slider.tsx +83 -0
  434. package/src/globals.css +494 -151
  435. package/src/globals.d.ts +20 -0
  436. package/src/index.ts +68 -1
  437. package/src/lib/conditional-rule-match.ts +119 -0
  438. package/src/lib/data-list-display-options.ts +35 -0
  439. package/src/lib/data-list-view-registry.ts +104 -0
  440. package/src/lib/data-list-view-surface.ts +83 -0
  441. package/src/lib/data-list-view.ts +47 -0
  442. package/src/lib/dev-log.ts +10 -0
  443. package/src/lib/editable-target.ts +20 -0
  444. package/src/lib/list-page-table-properties.ts +48 -0
  445. package/src/lib/raf-throttle.ts +45 -0
  446. package/src/lib/row-height.ts +19 -0
  447. package/src/lib/table-properties-types.ts +98 -0
  448. package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
  449. package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +3 -3
  450. package/template/.cursor/rules/exxat-data-tables.mdc +1 -1
  451. package/template/.cursor/rules/exxat-ds-agents.mdc +2 -2
  452. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +2 -2
  453. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
  454. package/template/AGENTS.md +84 -20
  455. package/template/app/(app)/examples/page.tsx +0 -1
  456. package/template/app/(app)/layout.tsx +17 -4
  457. package/template/app/(app)/question-bank/layout.tsx +1 -1
  458. package/template/app/(app)/question-bank/new/page.tsx +11 -24
  459. package/template/app/globals.css +13 -1972
  460. package/template/components/ask-leo-sidebar.tsx +5 -1
  461. package/template/components/brand-color-picker.tsx +2 -2
  462. package/template/components/charts-overview.tsx +1 -1
  463. package/template/components/compliance-table.tsx +240 -384
  464. package/template/components/dashboard-report-charts.tsx +1 -1
  465. package/template/components/dashboard-tabs.tsx +1 -1
  466. package/template/components/data-table/filter-date-calendar.tsx +1 -38
  467. package/template/components/data-table/filter-text-value-input.tsx +1 -77
  468. package/template/components/data-table/index.tsx +1 -1634
  469. package/template/components/data-table/pagination.tsx +1 -255
  470. package/template/components/data-table/types.ts +1 -94
  471. package/template/components/data-table/use-table-state.test.ts +420 -0
  472. package/template/components/data-table/use-table-state.ts +1 -758
  473. package/template/components/data-view-dashboard-charts-compliance.tsx +2 -2
  474. package/template/components/data-view-dashboard-charts-team.tsx +2 -2
  475. package/template/components/data-view-dashboard-charts.tsx +2 -2
  476. package/template/components/data-views/board-card-primitives.tsx +1 -93
  477. package/template/components/data-views/data-row-list.tsx +1 -183
  478. package/template/components/data-views/finder-panel-view.tsx +1 -405
  479. package/template/components/data-views/folder-grid-view.tsx +1 -86
  480. package/template/components/data-views/hub-table.tsx +1 -0
  481. package/template/components/data-views/index.ts +42 -1
  482. package/template/components/data-views/list-page-board-card.tsx +1 -192
  483. package/template/components/data-views/list-page-board-template.tsx +1 -122
  484. package/template/components/data-views/list-page-connected-view-body.tsx +1 -0
  485. package/template/components/data-views/list-page-split-details-placeholder.tsx +1 -39
  486. package/template/components/data-views/list-page-split-hub-chrome.tsx +1 -60
  487. package/template/components/data-views/list-page-split-hub-tokens.ts +1 -16
  488. package/template/components/data-views/list-page-tree-column-header.tsx +1 -31
  489. package/template/components/data-views/list-page-tree-panel-shell.tsx +1 -91
  490. package/template/components/data-views/list-page-view-frame.tsx +5 -53
  491. package/template/components/data-views/os-folder-glyph.tsx +1 -129
  492. package/template/components/data-views/outline-tree-menu.tsx +1 -157
  493. package/template/components/export-drawer.test.tsx +71 -0
  494. package/template/components/export-drawer.tsx +1 -375
  495. package/template/components/exxat-product-logo.tsx +5 -5
  496. package/template/components/hub-tree-panel-view.tsx +2 -2
  497. package/template/components/invite-collaborators-drawer.tsx +3 -3
  498. package/template/components/key-metrics-ask-leo-bridge.tsx +40 -0
  499. package/template/components/key-metrics.tsx +1 -1063
  500. package/template/components/leo-insight-indicator.tsx +2 -2
  501. package/template/components/new-placement-back-btn.tsx +1 -1
  502. package/template/components/new-placement-form.tsx +63 -189
  503. package/template/components/new-question-composer.tsx +432 -402
  504. package/template/components/onboarding/index.ts +9 -0
  505. package/template/components/onboarding/onboarding-01.tsx +1 -1
  506. package/template/components/onboarding/onboarding-02.tsx +1 -1
  507. package/template/components/onboarding/onboarding-03.tsx +1 -1
  508. package/template/components/onboarding/onboarding-04.tsx +1 -1
  509. package/template/components/page-header.tsx +8 -226
  510. package/template/components/placement-board-card.tsx +71 -83
  511. package/template/components/placements-board-view.tsx +3 -10
  512. package/template/components/placements-client.tsx +10 -42
  513. package/template/components/placements-list-view.tsx +22 -69
  514. package/template/components/placements-table-columns.tsx +8 -438
  515. package/template/components/placements-table.tsx +588 -1296
  516. package/template/components/product-switcher.tsx +1 -1
  517. package/template/components/product-wordmark.tsx +2 -1
  518. package/template/components/question-bank-client.tsx +4 -1
  519. package/template/components/question-bank-hub-client.tsx +1 -1
  520. package/template/components/question-bank-new-folder-sheet.tsx +2 -2
  521. package/template/components/question-bank-secondary-nav.tsx +3 -3
  522. package/template/components/question-bank-table.tsx +294 -526
  523. package/template/components/rotations-empty-state.tsx +1 -1
  524. package/template/components/rotations-panel-activator.tsx +1 -1
  525. package/template/components/settings-appearance-card.tsx +1 -1
  526. package/template/components/{app-sidebar-dynamic.tsx → sidebar/app-sidebar-dynamic.tsx} +1 -1
  527. package/template/components/{app-sidebar.tsx → sidebar/app-sidebar.tsx} +4 -4
  528. package/template/components/sidebar/index.ts +16 -0
  529. package/template/components/{secondary-nav.tsx → sidebar/secondary-nav.tsx} +2 -2
  530. package/template/components/{secondary-panel.tsx → sidebar/secondary-panel.tsx} +6 -3
  531. package/template/components/{sidebar-auto-collapse.tsx → sidebar/sidebar-auto-collapse.tsx} +6 -2
  532. package/template/components/{sidebar-shell.tsx → sidebar/sidebar-shell.tsx} +1 -1
  533. package/template/components/site-header.tsx +1 -1
  534. package/template/components/{sites-all-client.tsx → sites-client.tsx} +1 -1
  535. package/template/components/sites-table.tsx +124 -257
  536. package/template/components/table-properties/column-row.tsx +1 -90
  537. package/template/components/table-properties/draggable-list.ts +1 -49
  538. package/template/components/table-properties/drawer-button.tsx +1 -249
  539. package/template/components/table-properties/drawer.tsx +1 -1105
  540. package/template/components/table-properties/filter-card.tsx +1 -251
  541. package/template/components/table-properties/sort-card.tsx +1 -59
  542. package/template/components/table-properties/types.ts +28 -71
  543. package/template/components/team-table.tsx +242 -382
  544. package/template/components/templates/dedicated-search-landing-template.tsx +1 -124
  545. package/template/components/templates/dedicated-search-results-template.tsx +1 -19
  546. package/template/components/templates/list-page.tsx +1 -584
  547. package/template/components/templates/nested-secondary-panel-shell.tsx +1 -62
  548. package/template/components/templates/new-focus-template.tsx +659 -0
  549. package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
  550. package/template/components/ui/accordion.tsx +1 -0
  551. package/template/components/ui/alert-dialog.tsx +1 -0
  552. package/template/components/ui/context-menu.tsx +1 -0
  553. package/template/components/ui/dot-pattern.tsx +1 -183
  554. package/template/components/ui/hover-card.tsx +1 -0
  555. package/template/components/ui/resizable.tsx +1 -68
  556. package/template/components/ui/scroll-area.tsx +1 -0
  557. package/template/components/ui/slider.tsx +1 -0
  558. package/template/docs/blueprints/README.md +86 -0
  559. package/template/docs/blueprints/_template.md +91 -0
  560. package/template/docs/blueprints/board-card.md +123 -0
  561. package/template/docs/blueprints/data-table.md +139 -0
  562. package/template/docs/blueprints/key-metrics.md +128 -0
  563. package/template/docs/blueprints/list-page-template.md +123 -0
  564. package/template/docs/blueprints/page-header.md +130 -0
  565. package/template/docs/command-menu-pattern.md +1 -1
  566. package/template/docs/component-selection-guide.md +224 -0
  567. package/template/docs/components-audit-2026-05.md +158 -0
  568. package/template/docs/data-views-pattern.md +14 -14
  569. package/template/docs/migrations/0001-brand-deep-alias-stabilization.md +95 -0
  570. package/template/docs/migrations/0002-exxat-token-namespace.md +154 -0
  571. package/template/docs/migrations/0003-globals-css-canonical.md +110 -0
  572. package/template/docs/migrations/README.md +100 -0
  573. package/template/docs/migrations/_template.md +64 -0
  574. package/template/docs/token-taxonomy.md +416 -0
  575. package/template/eslint.config.mjs +27 -0
  576. package/template/hooks/use-secondary-panel-hub-nav.ts +1 -1
  577. package/template/lib/command-menu-config.ts +0 -1
  578. package/template/lib/compliance-supported-views.ts +10 -0
  579. package/template/lib/conditional-rule-match.ts +6 -97
  580. package/template/lib/data-list-display-options.ts +1 -35
  581. package/template/lib/data-list-view-registry.ts +1 -0
  582. package/template/lib/data-list-view-surface.ts +1 -69
  583. package/template/lib/data-list-view.ts +1 -38
  584. package/template/lib/dev-log.ts +1 -8
  585. package/template/lib/editable-target.ts +1 -10
  586. package/template/lib/hub-connected-view-renderers.ts +58 -0
  587. package/template/lib/list-hub-supported-views.ts +10 -0
  588. package/template/lib/list-page-table-properties.ts +1 -52
  589. package/template/lib/mock/navigation.tsx +0 -8
  590. package/template/lib/mock/placements.ts +0 -7
  591. package/template/lib/placement-board-card-layout.ts +41 -41
  592. package/template/lib/placements-supported-views.ts +12 -0
  593. package/template/lib/question-bank-supported-views.ts +12 -0
  594. package/template/lib/raf-throttle.ts +1 -45
  595. package/template/lib/row-height.ts +4 -10
  596. package/template/lib/sidebar-state-cookie.ts +11 -2
  597. package/template/lib/sites-supported-views.ts +10 -0
  598. package/template/lib/team-supported-views.ts +10 -0
  599. package/template/package.json +1 -0
  600. package/template/tests/setup.ts +25 -0
  601. package/src/theme.css +0 -1132
  602. package/template/app/(app)/data-list/[id]/page.tsx +0 -44
  603. package/template/app/(app)/data-list/new/page.tsx +0 -34
  604. package/template/app/(app)/data-list/page.tsx +0 -10
  605. package/template/components/compliance-list-view.tsx +0 -54
  606. package/template/components/dashboard-onboarding-gallery.tsx +0 -13
  607. package/template/components/dashboard-onboarding.tsx +0 -21
  608. package/template/components/question-bank-list-view.tsx +0 -53
  609. package/template/components/section-cards.tsx +0 -106
  610. package/template/components/sites-list-view.tsx +0 -42
  611. package/template/components/team-list-view.tsx +0 -59
  612. package/template/lib/placement-lifecycle.ts +0 -5
  613. /package/template/components/{getting-started.tsx → onboarding/getting-started.tsx} +0 -0
  614. /package/template/components/{nav-documents.tsx → sidebar/nav-documents.tsx} +0 -0
  615. /package/template/components/{nav-main.tsx → sidebar/nav-main.tsx} +0 -0
  616. /package/template/components/{nav-secondary.tsx → sidebar/nav-secondary.tsx} +0 -0
  617. /package/template/components/{nav-user.tsx → sidebar/nav-user.tsx} +0 -0
  618. /package/template/components/{sidebar-auto-open.tsx → sidebar/sidebar-auto-open.tsx} +0 -0
@@ -0,0 +1,93 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Reusable building blocks for kanban / board cards (icon rows, two-line blocks, placeholders).
5
+ */
6
+
7
+ import * as React from "react"
8
+ import { cn } from "../../lib/utils"
9
+ import type { BoardLineCount } from "../../lib/data-list-display-options"
10
+
11
+ export function BoardCardIconRow({
12
+ iconClass,
13
+ children,
14
+ }: {
15
+ iconClass: string
16
+ children: React.ReactNode
17
+ }) {
18
+ return (
19
+ <div className="flex items-start gap-2">
20
+ <i
21
+ className={cn(
22
+ `fa-light ${iconClass} text-xs text-muted-foreground mt-0.5 w-4 shrink-0 text-center`,
23
+ )}
24
+ aria-hidden
25
+ />
26
+ <div className="min-w-0 flex-1 text-xs leading-snug [&_.text-sm]:text-xs [&_span]:text-xs [&_div]:text-xs">
27
+ {children}
28
+ </div>
29
+ </div>
30
+ )
31
+ }
32
+
33
+ export function BoardCardTwoLineBlock({
34
+ iconClass,
35
+ line1,
36
+ line2,
37
+ line2ClassName,
38
+ }: {
39
+ iconClass: string
40
+ line1: React.ReactNode
41
+ /** Omitted for a single-line row (same icon + primary alignment as Placements). */
42
+ line2?: React.ReactNode
43
+ /** Override default muted line-2 (e.g. badges / rich cells). */
44
+ line2ClassName?: string
45
+ }) {
46
+ const showLine2 = line2 !== undefined && line2 !== null
47
+ return (
48
+ <div className="flex items-start gap-2">
49
+ <i
50
+ className={cn(
51
+ `fa-light ${iconClass} text-xs text-muted-foreground mt-0.5 w-4 shrink-0 text-center`,
52
+ )}
53
+ aria-hidden
54
+ />
55
+ <div className="min-w-0 flex-1">
56
+ <div className="truncate text-xs font-medium text-foreground leading-tight">{line1}</div>
57
+ {showLine2 ? (
58
+ <div
59
+ className={cn(
60
+ "mt-0.5 min-w-0 leading-tight",
61
+ line2ClassName ?? "truncate text-xs text-muted-foreground",
62
+ )}
63
+ >
64
+ {line2}
65
+ </div>
66
+ ) : null}
67
+ </div>
68
+ </div>
69
+ )
70
+ }
71
+
72
+ export function lineClampClass(n: BoardLineCount): string {
73
+ const base = "min-w-0 overflow-hidden break-words"
74
+ if (n === 1) return cn(base, "line-clamp-1")
75
+ if (n === 2) return cn(base, "line-clamp-2")
76
+ return cn(base, "line-clamp-3")
77
+ }
78
+
79
+ export function BoardNewCardPlaceholder({ position }: { position: "above" | "below" }) {
80
+ return (
81
+ <button
82
+ type="button"
83
+ className={cn(
84
+ "flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-border bg-background/50 py-2 text-xs font-medium text-muted-foreground transition-colors",
85
+ "hover:border-input hover:bg-muted/40 hover:text-foreground",
86
+ position === "above" ? "mb-2" : "mt-2",
87
+ )}
88
+ >
89
+ <i className="fa-light fa-plus text-xs" aria-hidden="true" />
90
+ New card
91
+ </button>
92
+ )
93
+ }
@@ -0,0 +1,183 @@
1
+ "use client"
2
+
3
+ /**
4
+ * DataRowList — generic vertical-stack list view used by every hub's "list"
5
+ * tab (placements, team, compliance, sites, question-bank, …). Replaces the
6
+ * hand-rolled `<ul …flex-col gap-2 px-4 pb-8 pt-2 lg:px-6> {rows.map(<li>…)}`
7
+ * shell that was duplicated across `*-list-view.tsx` files.
8
+ *
9
+ * Composition over inheritance: callers provide a `renderRow(row)` that
10
+ * returns whatever ListPageBoardCard / link / chip-stack they need — this
11
+ * component owns the chrome (spacing, empty state, virtualization), not the
12
+ * row body.
13
+ *
14
+ * Auto-virtualises with `@tanstack/react-virtual` when the row count meets
15
+ * `virtualizeThreshold` (default 100). Disable by passing `0`.
16
+ */
17
+
18
+ import * as React from "react"
19
+ import { useWindowVirtualizer } from "@tanstack/react-virtual"
20
+ import { cn } from "../../lib/utils"
21
+
22
+ const DEFAULT_VIRTUALIZE_THRESHOLD = 100
23
+ const DEFAULT_ESTIMATED_ROW_HEIGHT = 96
24
+ const DEFAULT_OVERSCAN = 8
25
+
26
+ export interface DataRowListProps<TRow> {
27
+ /** The filtered/sorted rows from `tableState.rows` (or wherever). */
28
+ rows: readonly TRow[]
29
+ /** Stable id used as the React `key` and (for virtualizer) the v-key. */
30
+ getRowId: (row: TRow, index: number) => string | number
31
+ /** Render the body of one row. Wrap with `<ListPageBoardCard layout="row">` etc. */
32
+ renderRow: (row: TRow, index: number) => React.ReactNode
33
+ /**
34
+ * Shown when `rows.length === 0`. Strings render as muted body copy; pass
35
+ * a `ReactNode` for richer empty states (illustration, CTA, etc.).
36
+ */
37
+ emptyState?: React.ReactNode
38
+ /**
39
+ * Auto-virtualise when `rows.length >= virtualizeThreshold`. Default 100.
40
+ * Pass `0` to never virtualise (preserves predictable layout for short
41
+ * lists like dashboards / pinned tabs).
42
+ */
43
+ virtualizeThreshold?: number
44
+ /** Hint for the virtualizer; clamps to measured size after first paint. */
45
+ estimatedRowHeight?: number
46
+ /** Override the default container padding / gap if needed. */
47
+ className?: string
48
+ /** Override the per-row `<li>` className (e.g. tighter spacing). */
49
+ rowClassName?: string
50
+ /** `aria-label` for the `<ul>` (screen-reader name for the list). */
51
+ ariaLabel?: string
52
+ }
53
+
54
+ const DEFAULT_OUTER_CLASS = "flex list-none flex-col gap-2 px-4 pb-8 pt-2 lg:px-6"
55
+
56
+ export function DataRowList<TRow>(props: DataRowListProps<TRow>) {
57
+ const {
58
+ rows,
59
+ getRowId,
60
+ renderRow,
61
+ emptyState,
62
+ virtualizeThreshold = DEFAULT_VIRTUALIZE_THRESHOLD,
63
+ estimatedRowHeight = DEFAULT_ESTIMATED_ROW_HEIGHT,
64
+ className,
65
+ rowClassName,
66
+ ariaLabel,
67
+ } = props
68
+
69
+ if (rows.length === 0) {
70
+ if (emptyState == null) return null
71
+ if (typeof emptyState === "string") {
72
+ return (
73
+ <div className="px-4 py-16 text-center lg:px-6">
74
+ <p className="text-sm text-muted-foreground">{emptyState}</p>
75
+ </div>
76
+ )
77
+ }
78
+ return <div className="px-4 py-16 text-center lg:px-6">{emptyState}</div>
79
+ }
80
+
81
+ if (virtualizeThreshold > 0 && rows.length >= virtualizeThreshold) {
82
+ return (
83
+ <DataRowListVirtualized
84
+ rows={rows}
85
+ getRowId={getRowId}
86
+ renderRow={renderRow}
87
+ estimatedRowHeight={estimatedRowHeight}
88
+ className={className}
89
+ rowClassName={rowClassName}
90
+ ariaLabel={ariaLabel}
91
+ />
92
+ )
93
+ }
94
+
95
+ return (
96
+ <ul aria-label={ariaLabel} className={cn(DEFAULT_OUTER_CLASS, className)}>
97
+ {rows.map((row, i) => (
98
+ <li key={getRowId(row, i)} className={rowClassName}>
99
+ {renderRow(row, i)}
100
+ </li>
101
+ ))}
102
+ </ul>
103
+ )
104
+ }
105
+
106
+ // ─────────────────────────────────────────────────────────────────────────────
107
+ // Virtualised variant — keeps the DOM short on long lists (e.g. 1000+ rows).
108
+ // Uses `useWindowVirtualizer` so the page scroll drives row recycling; this
109
+ // is the right tool for hub-level lists (not nested-scroll containers).
110
+ // ─────────────────────────────────────────────────────────────────────────────
111
+
112
+ function DataRowListVirtualized<TRow>({
113
+ rows,
114
+ getRowId,
115
+ renderRow,
116
+ estimatedRowHeight,
117
+ className,
118
+ rowClassName,
119
+ ariaLabel,
120
+ }: {
121
+ rows: readonly TRow[]
122
+ getRowId: (row: TRow, index: number) => string | number
123
+ renderRow: (row: TRow, index: number) => React.ReactNode
124
+ estimatedRowHeight: number
125
+ className?: string
126
+ rowClassName?: string
127
+ ariaLabel?: string
128
+ }) {
129
+ const anchorRef = React.useRef<HTMLDivElement | null>(null)
130
+ // `scrollMargin` is read by the virtualizer during render, so it has to
131
+ // be state (not a ref). We measure with `useLayoutEffect` after the first
132
+ // paint and on resize so window-scroll math stays accurate when the page
133
+ // layout shifts (sidebar collapse, banner, etc.).
134
+ const [scrollMargin, setScrollMargin] = React.useState(0)
135
+
136
+ const updateScrollMargin = React.useCallback(() => {
137
+ const el = anchorRef.current
138
+ if (!el) return
139
+ setScrollMargin(el.getBoundingClientRect().top + window.scrollY)
140
+ }, [])
141
+
142
+ React.useLayoutEffect(() => {
143
+ updateScrollMargin()
144
+ window.addEventListener("resize", updateScrollMargin)
145
+ return () => window.removeEventListener("resize", updateScrollMargin)
146
+ }, [updateScrollMargin, rows.length])
147
+
148
+ const virtualizer = useWindowVirtualizer({
149
+ count: rows.length,
150
+ estimateSize: () => estimatedRowHeight,
151
+ overscan: DEFAULT_OVERSCAN,
152
+ scrollMargin,
153
+ getItemKey: i => String(getRowId(rows[i], i)),
154
+ })
155
+
156
+ const totalSize = virtualizer.getTotalSize()
157
+
158
+ return (
159
+ <div ref={anchorRef} className={cn("px-4 pb-8 pt-2 lg:px-6", className)}>
160
+ <ul
161
+ aria-label={ariaLabel}
162
+ className="relative m-0 w-full list-none p-0"
163
+ style={{ height: `${totalSize}px` }}
164
+ >
165
+ {virtualizer.getVirtualItems().map(vr => {
166
+ const row = rows[vr.index]
167
+ if (!row) return null
168
+ return (
169
+ <li
170
+ key={vr.key}
171
+ data-index={vr.index}
172
+ ref={virtualizer.measureElement}
173
+ className={cn("absolute left-0 top-0 w-full pb-2", rowClassName)}
174
+ style={{ transform: `translateY(${vr.start}px)` }}
175
+ >
176
+ {renderRow(row, vr.index)}
177
+ </li>
178
+ )
179
+ })}
180
+ </ul>
181
+ </div>
182
+ )
183
+ }
@@ -0,0 +1,405 @@
1
+ "use client"
2
+
3
+ /**
4
+ * FinderPanelView — Miller-style 3-column split for list-page hubs.
5
+ *
6
+ * Visual shell matches Question bank panel (`ListPageTreeColumnHeader`, `LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS`,
7
+ * shared resizable handles) — see `list-page-split-hub-tokens.ts`.
8
+ */
9
+
10
+ import * as React from "react"
11
+ import { cn } from "../../lib/utils"
12
+ import {
13
+ ResizableHandle,
14
+ ResizablePanel,
15
+ ResizablePanelGroup,
16
+ } from "../ui/resizable"
17
+ import { ListPageTreeColumnHeader } from "./list-page-tree-column-header"
18
+ import { ListPageSplitDetailsPlaceholder } from "./list-page-split-details-placeholder"
19
+ import {
20
+ LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS,
21
+ LIST_PAGE_SPLIT_MILLER_DETAIL_PANEL_CLASS,
22
+ LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS,
23
+ } from "./list-page-split-hub-tokens"
24
+
25
+ export interface FinderGroup {
26
+ id: string
27
+ label: string
28
+ icon?: string
29
+ accent?: string
30
+ count: number
31
+ }
32
+
33
+ export interface FinderPanelViewProps<T> {
34
+ groups: FinderGroup[]
35
+ rows: T[]
36
+ getRowId: (row: T) => string | number
37
+ getRowGroupId: (row: T) => string
38
+ renderListRow: (row: T, isSelected: boolean) => React.ReactNode
39
+ renderDetail: (row: T) => React.ReactNode
40
+ emptyDetail?: React.ReactNode
41
+ emptyList?: React.ReactNode
42
+ defaultGroupId?: string
43
+ ariaLabel?: string
44
+ autoSaveId?: string
45
+ className?: string
46
+ style?: React.CSSProperties
47
+ onAddItem?: () => void
48
+ /**
49
+ * When true, omit outer margin, border, and card fill so the grid fits inside
50
+ * `ListPageSplitHubChrome` (shared split surface across hubs).
51
+ */
52
+ embedded?: boolean
53
+ /** Left column title (Question bank: “Categories”). */
54
+ groupsColumnTitle?: string
55
+ /** Middle column title; defaults from the active group label. */
56
+ getListColumnTitle?: (activeGroup: FinderGroup | undefined) => string
57
+ }
58
+
59
+ export function GroupsColumn({
60
+ groups,
61
+ selectedGroupId,
62
+ onSelect,
63
+ columnTitle,
64
+ }: {
65
+ groups: FinderGroup[]
66
+ selectedGroupId: string
67
+ onSelect: (id: string) => void
68
+ columnTitle: string
69
+ }) {
70
+ return (
71
+ <div className="flex h-full min-h-0 flex-col overflow-hidden">
72
+ <ListPageTreeColumnHeader title={columnTitle} />
73
+ <div
74
+ className="min-h-0 flex-1 overflow-y-auto py-1"
75
+ role="listbox"
76
+ aria-label="Group navigation"
77
+ aria-multiselectable="false"
78
+ >
79
+ {groups.map(group => {
80
+ const isSelected = group.id === selectedGroupId
81
+ return (
82
+ <div key={group.id} className="group flex items-center hover:bg-muted/50">
83
+ <button
84
+ type="button"
85
+ role="option"
86
+ aria-selected={isSelected}
87
+ onClick={() => onSelect(group.id)}
88
+ className={cn(
89
+ "flex w-full flex-1 items-center gap-2 px-3 py-2 text-start text-sm transition-colors duration-75",
90
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
91
+ isSelected
92
+ ? "bg-accent font-medium text-accent-foreground"
93
+ : "text-foreground",
94
+ )}
95
+ >
96
+ {group.accent ? (
97
+ <span className={cn("size-2 shrink-0 rounded-full", group.accent)} aria-hidden="true" />
98
+ ) : group.icon ? (
99
+ <i
100
+ className={cn(
101
+ "fa-light shrink-0 text-xs",
102
+ group.icon,
103
+ isSelected ? "text-accent-foreground" : "text-muted-foreground",
104
+ )}
105
+ aria-hidden="true"
106
+ />
107
+ ) : null}
108
+ <span className="min-w-0 flex-1 truncate leading-tight">{group.label}</span>
109
+ <span
110
+ className={cn(
111
+ "shrink-0 tabular-nums text-xs",
112
+ isSelected ? "text-accent-foreground/80" : "text-muted-foreground",
113
+ )}
114
+ >
115
+ {group.count}
116
+ </span>
117
+ </button>
118
+ </div>
119
+ )
120
+ })}
121
+ </div>
122
+ </div>
123
+ )
124
+ }
125
+
126
+ /**
127
+ * Horizontal scope strip (toolbar toggles). Optional: place above a hub body when you need
128
+ * status scope without consuming a Finder column.
129
+ */
130
+ export function FinderGroupStrip({
131
+ groups,
132
+ selectedGroupId,
133
+ onSelect,
134
+ ariaLabel = "Scope",
135
+ }: {
136
+ groups: FinderGroup[]
137
+ selectedGroupId: string
138
+ onSelect: (id: string) => void
139
+ ariaLabel?: string
140
+ }) {
141
+ return (
142
+ <div
143
+ role="toolbar"
144
+ aria-label={ariaLabel}
145
+ className="flex min-h-10 flex-wrap items-center gap-1.5 border-b border-border bg-card px-2 py-2"
146
+ >
147
+ {groups.map(group => {
148
+ const isSelected = group.id === selectedGroupId
149
+ return (
150
+ <button
151
+ key={group.id}
152
+ type="button"
153
+ aria-pressed={isSelected}
154
+ onClick={() => onSelect(group.id)}
155
+ className={cn(
156
+ "inline-flex h-8 max-w-full min-w-0 shrink-0 items-center gap-1.5 rounded-lg border px-2.5 text-xs font-medium transition-colors",
157
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
158
+ isSelected
159
+ ? "border-brand/40 bg-accent text-accent-foreground"
160
+ : "border-transparent bg-background/80 text-foreground hover:bg-muted",
161
+ )}
162
+ >
163
+ {group.accent ? (
164
+ <span className={cn("size-2 shrink-0 rounded-full", group.accent)} aria-hidden="true" />
165
+ ) : group.icon ? (
166
+ <i
167
+ className={cn(
168
+ "fa-light shrink-0 text-[11px]",
169
+ group.icon,
170
+ isSelected ? "text-accent-foreground" : "text-muted-foreground",
171
+ )}
172
+ aria-hidden="true"
173
+ />
174
+ ) : null}
175
+ <span className="min-w-0 truncate">{group.label}</span>
176
+ <span
177
+ className={cn(
178
+ "shrink-0 tabular-nums text-[11px]",
179
+ isSelected ? "text-accent-foreground/85" : "text-muted-foreground",
180
+ )}
181
+ >
182
+ {group.count}
183
+ </span>
184
+ </button>
185
+ )
186
+ })}
187
+ </div>
188
+ )
189
+ }
190
+
191
+ function ListColumn<T>({
192
+ rows,
193
+ getRowId,
194
+ selectedId,
195
+ renderListRow,
196
+ onSelect,
197
+ emptyList,
198
+ groupLabel,
199
+ columnTitle,
200
+ onAddItem,
201
+ }: {
202
+ rows: T[]
203
+ getRowId: (row: T) => string | number
204
+ selectedId: string | number | null
205
+ renderListRow: (row: T, isSelected: boolean) => React.ReactNode
206
+ onSelect: (id: string | number) => void
207
+ emptyList?: React.ReactNode
208
+ groupLabel: string
209
+ columnTitle: string
210
+ onAddItem?: () => void
211
+ }) {
212
+ return (
213
+ <div className="flex h-full min-h-0 flex-col overflow-hidden">
214
+ <ListPageTreeColumnHeader title={columnTitle} />
215
+ <div className="flex min-h-0 flex-1 flex-col">
216
+ <div
217
+ className="min-h-0 flex-1 overflow-y-auto py-1"
218
+ role="listbox"
219
+ aria-label={`${groupLabel} items`}
220
+ aria-multiselectable="false"
221
+ >
222
+ {rows.length === 0 ? (
223
+ <div className="flex flex-col items-center justify-center px-4 py-12 text-center text-sm text-muted-foreground">
224
+ {emptyList ?? <p>No items in this group.</p>}
225
+ </div>
226
+ ) : (
227
+ rows.map(row => {
228
+ const id = getRowId(row)
229
+ const isSelected = id === selectedId
230
+ return (
231
+ <div key={id} className="group flex items-center hover:bg-muted/50">
232
+ <button
233
+ type="button"
234
+ role="option"
235
+ aria-selected={isSelected}
236
+ onClick={() => onSelect(id)}
237
+ className={cn(
238
+ "flex w-full flex-1 items-center gap-3 px-3 py-2 text-start text-sm transition-colors duration-75",
239
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
240
+ isSelected
241
+ ? "bg-accent font-medium text-accent-foreground"
242
+ : "text-foreground",
243
+ )}
244
+ >
245
+ {renderListRow(row, isSelected)}
246
+ </button>
247
+ </div>
248
+ )
249
+ })
250
+ )}
251
+ </div>
252
+ {onAddItem ? (
253
+ <div className="flex shrink-0 items-center justify-center border-t border-border/50 px-2 py-1.5">
254
+ <button
255
+ type="button"
256
+ onClick={onAddItem}
257
+ className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
258
+ aria-label={`Add item to ${groupLabel}`}
259
+ >
260
+ <i className="fa-light fa-plus text-xs" aria-hidden="true" />
261
+ </button>
262
+ </div>
263
+ ) : null}
264
+ </div>
265
+ </div>
266
+ )
267
+ }
268
+
269
+ function DetailColumn({ children }: { children: React.ReactNode }) {
270
+ return (
271
+ <div className={cn(LIST_PAGE_SPLIT_MILLER_DETAIL_PANEL_CLASS, "overflow-hidden")}>
272
+ <ListPageTreeColumnHeader title="Details" />
273
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden">{children}</div>
274
+ </div>
275
+ )
276
+ }
277
+
278
+ function DefaultEmptyDetail() {
279
+ return (
280
+ <ListPageSplitDetailsPlaceholder title="Nothing selected" />
281
+ )
282
+ }
283
+
284
+ export function FinderPanelView<T>({
285
+ groups,
286
+ rows,
287
+ getRowId,
288
+ getRowGroupId,
289
+ renderListRow,
290
+ renderDetail,
291
+ emptyDetail,
292
+ emptyList,
293
+ defaultGroupId,
294
+ ariaLabel = "List and details",
295
+ autoSaveId = "finder-panel-view",
296
+ className,
297
+ style,
298
+ onAddItem,
299
+ embedded = false,
300
+ groupsColumnTitle = "Categories",
301
+ getListColumnTitle = g => g?.label ?? "Items",
302
+ }: FinderPanelViewProps<T>) {
303
+ const mergedStyle = React.useMemo<React.CSSProperties>(() => {
304
+ if (embedded) {
305
+ return { height: "100%", minHeight: 0, ...style }
306
+ }
307
+ return {
308
+ height: "calc(100vh - 280px)",
309
+ minHeight: 420,
310
+ ...style,
311
+ }
312
+ }, [embedded, style])
313
+ const firstGroupId = defaultGroupId ?? groups[0]?.id ?? "all"
314
+ const [selectedGroupId, setSelectedGroupId] = React.useState(firstGroupId)
315
+ const [selectedRowId, setSelectedRowId] = React.useState<string | number | null>(null)
316
+
317
+ const visibleRows = React.useMemo(() => {
318
+ if (selectedGroupId === "all") return rows
319
+ return rows.filter(r => getRowGroupId(r) === selectedGroupId)
320
+ }, [rows, selectedGroupId, getRowGroupId])
321
+
322
+ React.useEffect(() => {
323
+ setSelectedRowId(visibleRows[0] ? getRowId(visibleRows[0]) : null)
324
+ }, [selectedGroupId, visibleRows, getRowId])
325
+
326
+ React.useEffect(() => {
327
+ if (selectedRowId !== null && !visibleRows.find(r => getRowId(r) === selectedRowId)) {
328
+ setSelectedRowId(visibleRows[0] ? getRowId(visibleRows[0]) : null)
329
+ }
330
+ }, [visibleRows, selectedRowId, getRowId])
331
+
332
+ const selectedRow =
333
+ selectedRowId !== null
334
+ ? (visibleRows.find(r => getRowId(r) === selectedRowId) ?? null)
335
+ : null
336
+
337
+ const selectedGroup = groups.find(g => g.id === selectedGroupId)
338
+ const listColumnTitle = getListColumnTitle(selectedGroup)
339
+
340
+ return (
341
+ <div
342
+ className={cn(
343
+ embedded
344
+ ? "flex h-full min-h-0 flex-col overflow-hidden"
345
+ : "mx-4 mb-6 overflow-hidden rounded-xl border border-border bg-card lg:mx-6",
346
+ className,
347
+ )}
348
+ style={mergedStyle}
349
+ aria-label={ariaLabel}
350
+ >
351
+ <ResizablePanelGroup
352
+ id={String(autoSaveId)}
353
+ direction="horizontal"
354
+ className="h-full min-h-0 w-full flex-1"
355
+ >
356
+ <ResizablePanel
357
+ id="groups"
358
+ defaultSize="22%"
359
+ minSize="16%"
360
+ maxSize="32%"
361
+ className={LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS}
362
+ >
363
+ <GroupsColumn
364
+ columnTitle={groupsColumnTitle}
365
+ groups={groups}
366
+ selectedGroupId={selectedGroupId}
367
+ onSelect={id => setSelectedGroupId(id)}
368
+ />
369
+ </ResizablePanel>
370
+
371
+ <ResizableHandle withHandle className={LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS} />
372
+
373
+ <ResizablePanel
374
+ id="list"
375
+ defaultSize="34%"
376
+ minSize="22%"
377
+ maxSize="48%"
378
+ className={LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS}
379
+ >
380
+ <ListColumn
381
+ columnTitle={listColumnTitle}
382
+ rows={visibleRows}
383
+ getRowId={getRowId}
384
+ selectedId={selectedRowId}
385
+ renderListRow={renderListRow}
386
+ onSelect={id => setSelectedRowId(id)}
387
+ emptyList={emptyList}
388
+ groupLabel={selectedGroup?.label ?? "All"}
389
+ onAddItem={onAddItem}
390
+ />
391
+ </ResizablePanel>
392
+
393
+ <ResizableHandle withHandle className={LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS} />
394
+
395
+ <ResizablePanel id="detail" defaultSize="44%" minSize="30%" className={LIST_PAGE_SPLIT_MILLER_DETAIL_PANEL_CLASS}>
396
+ <DetailColumn>
397
+ {selectedRow
398
+ ? renderDetail(selectedRow)
399
+ : (emptyDetail ?? <DefaultEmptyDetail />)}
400
+ </DetailColumn>
401
+ </ResizablePanel>
402
+ </ResizablePanelGroup>
403
+ </div>
404
+ )
405
+ }