@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,1148 @@
1
+ "use client"
2
+ import * as React from "react"
3
+ import { cn } from "../../lib/utils"
4
+ import type { DataListViewType } from "../../lib/data-list-view"
5
+ import { DATA_LIST_VIEW_TILES, dataListViewLabel } from "../../lib/data-list-view"
6
+ import { dataListViewTilesForHub } from "../../lib/data-list-view-registry"
7
+ import type { RowHeight } from "../../lib/row-height"
8
+ import { ROW_HEIGHT_TILES } from "../../lib/row-height"
9
+ import { SelectionTileGrid } from "../ui/selection-tile-grid"
10
+ import {
11
+ DropdownMenu,
12
+ DropdownMenuContent,
13
+ DropdownMenuItem,
14
+ DropdownMenuLabel,
15
+ DropdownMenuSeparator,
16
+ DropdownMenuTrigger,
17
+ } from "../ui/dropdown-menu"
18
+ import {
19
+ Sheet,
20
+ SheetContent,
21
+ SheetTitle,
22
+ } from "../ui/sheet"
23
+ import {
24
+ Select,
25
+ SelectContent,
26
+ SelectItem,
27
+ SelectTrigger,
28
+ SelectValue,
29
+ } from "../ui/select"
30
+ import type { DataListDisplayOptions } from "../../lib/data-list-display-options"
31
+ import { Tip } from "../ui/tip"
32
+ import { ToggleSwitch } from "../ui/toggle-switch"
33
+ import { Button } from "../ui/button"
34
+ import { DrawerFilterCard } from "./filter-card"
35
+ import { DrawerSortCard } from "./sort-card"
36
+ import { ColumnRow } from "./column-row"
37
+ import { useDraggableList } from "./draggable-list"
38
+ import {
39
+ type ActiveFilter,
40
+ type SortRule,
41
+ type ConditionalRule,
42
+ type FilterFieldDef,
43
+ RULE_COLORS,
44
+ } from "../../lib/table-properties-types"
45
+
46
+ export interface TablePropertiesDrawerProps {
47
+ open: boolean
48
+ onOpenChange: (open: boolean) => void
49
+ /**
50
+ * Optional deep-link target. When set (and `open` is true), the drawer
51
+ * focuses this panel instead of "main" — e.g. the column header's
52
+ * **Add Conditional Rule** menu item passes `"conditional-rules"` to jump
53
+ * straight to the conditional-formatting panel. Drawer resets to "main"
54
+ * automatically when it closes.
55
+ */
56
+ initialPanel?: string | null
57
+ // Display
58
+ showGridlines: boolean
59
+ onShowGridlinesChange: (v: boolean) => void
60
+ rowHeight: RowHeight
61
+ onRowHeightChange: (v: RowHeight) => void
62
+ pagination: boolean
63
+ onPaginationChange: (v: boolean) => void
64
+ // Filters
65
+ activeFilters: ActiveFilter[]
66
+ onAddFilter: (fieldKey: string) => void
67
+ onUpdateFilter: (id: string, patch: Partial<ActiveFilter>) => void
68
+ onRemoveFilter: (id: string) => void
69
+ /** How the filter after `leftFilterId` combines with the one above (default "and"). */
70
+ getFilterConnector: (leftFilterId: string) => "and" | "or"
71
+ onToggleFilterConnector: (leftFilterId: string) => void
72
+ filterBarVisible: boolean
73
+ onFilterBarVisibleChange: (v: boolean) => void
74
+ drawerExpandedFilters: Set<string>
75
+ onDrawerExpandedFiltersChange: React.Dispatch<React.SetStateAction<Set<string>>>
76
+ totalRows: number
77
+ filteredRows: number
78
+ // Sort
79
+ sortRules: SortRule[]
80
+ onSortRulesChange: (rules: SortRule[]) => void
81
+ onAddSortRule: (fieldKey: string) => void
82
+ onRemoveSortRule: (id: string) => void
83
+ onToggleSortDir: (id: string) => void
84
+ // Columns
85
+ colOrder: string[]
86
+ onColOrderChange: (order: string[]) => void
87
+ hiddenCols: Set<string>
88
+ onToggleColVisibility: (key: string) => void
89
+ onMoveCol: (key: string, dir: "up" | "down") => void
90
+ // Group
91
+ groupBy: string | null
92
+ onGroupByChange: (key: string | null) => void
93
+ // Sort key for display in main panel
94
+ primarySortKey?: string
95
+ // Conditional formatting
96
+ conditionalRules: ConditionalRule[]
97
+ onAddConditionalRule: (rule: Omit<ConditionalRule, "id">) => void
98
+ onRemoveConditionalRule: (id: string) => void
99
+ onUpdateConditionalRule: (id: string, patch: Partial<ConditionalRule>) => void
100
+ /**
101
+ * Filter field defs for drawer + conditional rules. Required — every list-hub
102
+ * consumer derives this from its column definitions (`columnsToFilterFields`)
103
+ * and passes it through `drawerToolbarProps` from `HubTable`. There is no
104
+ * product-specific default in the package.
105
+ */
106
+ filterFields: FilterFieldDef[]
107
+ // View type
108
+ currentView?: DataListViewType
109
+ onViewChange?: (view: DataListViewType) => void
110
+ /**
111
+ * Subset of view types this hub actually implements; when set, the view-type tile grid is
112
+ * filtered so users cannot switch to an unsupported view from Properties. Defaults to all
113
+ * registered view types (`DATA_LIST_VIEW_TILES`).
114
+ */
115
+ supportedViewTypes?: readonly DataListViewType[]
116
+ /** Lifecycle context (e.g. tab filter) — shown in the drawer header */
117
+ lifecycleTabLabel?: string
118
+ /**
119
+ * Column metadata for the Sort / Group / Columns panels. Required — every
120
+ * list-hub consumer derives this from its column definitions
121
+ * (`columnsToFieldDefinitions`) and passes it through `drawerToolbarProps`
122
+ * from `HubTable`. The drawer never falls back to a default column list.
123
+ */
124
+ fieldDefinitions: { key: string; label: string; sortable?: boolean }[]
125
+ resolveColumnLabel?: (key: string) => string
126
+ /** Shared display options (table + board); persisted at page level. */
127
+ displayOptions: DataListDisplayOptions
128
+ onDisplayOptionsChange: (patch: Partial<DataListDisplayOptions>) => void
129
+ /**
130
+ * When the active view is Board and more than one entry is provided, shows a control to pick
131
+ * which field defines swimlane columns (`displayOptions.boardGroupByColumnKey`).
132
+ */
133
+ boardGroupByColumnOptions?: { key: string; label: string }[]
134
+ /** Optional custom option renderer for filter values (e.g. status chips). */
135
+ renderFilterOptionValue?: (fieldKey: string, value: string) => React.ReactNode
136
+ }
137
+
138
+ type SheetPanel = "main" | "table-display" | "filter" | "sort" | "group" | "columns" | "conditional-rules"
139
+
140
+ /** Properties sheet uses `z-[80]`; default portaled menus are `z-50` and sit underneath. */
141
+ const PROPERTIES_SHEET_PORTAL_Z = "z-[90]"
142
+
143
+ export function TablePropertiesDrawer({
144
+ open,
145
+ onOpenChange,
146
+ initialPanel,
147
+ showGridlines,
148
+ onShowGridlinesChange,
149
+ rowHeight,
150
+ onRowHeightChange,
151
+ pagination,
152
+ onPaginationChange,
153
+ activeFilters,
154
+ onAddFilter,
155
+ onUpdateFilter,
156
+ onRemoveFilter,
157
+ getFilterConnector,
158
+ onToggleFilterConnector,
159
+ filterBarVisible,
160
+ onFilterBarVisibleChange,
161
+ drawerExpandedFilters,
162
+ onDrawerExpandedFiltersChange,
163
+ totalRows,
164
+ filteredRows,
165
+ sortRules,
166
+ onSortRulesChange,
167
+ onAddSortRule,
168
+ onRemoveSortRule,
169
+ onToggleSortDir,
170
+ colOrder,
171
+ onColOrderChange,
172
+ hiddenCols,
173
+ onToggleColVisibility,
174
+ onMoveCol,
175
+ groupBy,
176
+ onGroupByChange,
177
+ primarySortKey,
178
+ conditionalRules,
179
+ onAddConditionalRule,
180
+ onRemoveConditionalRule,
181
+ onUpdateConditionalRule,
182
+ filterFields,
183
+ currentView,
184
+ onViewChange,
185
+ supportedViewTypes,
186
+ lifecycleTabLabel,
187
+ fieldDefinitions,
188
+ resolveColumnLabel: resolveColumnLabelProp,
189
+ displayOptions,
190
+ onDisplayOptionsChange,
191
+ boardGroupByColumnOptions,
192
+ renderFilterOptionValue,
193
+ }: TablePropertiesDrawerProps) {
194
+ const [sheetPanel, setSheetPanel] = React.useState<SheetPanel>("main")
195
+
196
+ // Sync internal sheetPanel with the deep-link request from outside.
197
+ // - Open with an initialPanel → jump to that panel.
198
+ // - Open with no initialPanel → keep whatever the user navigated to last (or "main"
199
+ // after a close → re-open cycle, since the next effect resets on close).
200
+ // - initialPanel changes while open (e.g. user triggers a second deep-link
201
+ // from the column menu while the drawer is already open) → switch panels.
202
+ // - Close → reset to "main" so the next "plain" open lands on the index.
203
+ React.useEffect(() => {
204
+ if (open && initialPanel) {
205
+ setSheetPanel(initialPanel as SheetPanel)
206
+ } else if (!open) {
207
+ setSheetPanel("main")
208
+ }
209
+ }, [open, initialPanel])
210
+
211
+ const resolveColumnLabel = React.useCallback(
212
+ (key: string) =>
213
+ resolveColumnLabelProp?.(key)
214
+ ?? fieldDefinitions.find(f => f.key === key)?.label
215
+ ?? key,
216
+ [resolveColumnLabelProp, fieldDefinitions],
217
+ )
218
+
219
+ const sortFieldList = React.useMemo(
220
+ () =>
221
+ fieldDefinitions.filter(
222
+ f => f.sortable !== false && f.key !== "select" && f.key !== "actions",
223
+ ),
224
+ [fieldDefinitions],
225
+ )
226
+
227
+ const groupFieldList = React.useMemo(
228
+ () =>
229
+ fieldDefinitions.filter(f => f.key !== "select" && f.key !== "actions"),
230
+ [fieldDefinitions],
231
+ )
232
+
233
+ const viewSurface = currentView ?? "table"
234
+ const isBoardView = viewSurface === "board"
235
+ const boardGroupByLabel =
236
+ boardGroupByColumnOptions?.find(o => o.key === displayOptions.boardGroupByColumnKey)?.label
237
+ const viewDisplayLabel = dataListViewLabel(viewSurface)
238
+ const viewDisplayDesc = (() => {
239
+ if (viewSurface === "board") {
240
+ return [
241
+ boardGroupByLabel ? `By ${boardGroupByLabel}` : null,
242
+ `${displayOptions.boardLineCount}-line`,
243
+ displayOptions.showColumnLabels ? "Column labels" : "No labels",
244
+ ]
245
+ .filter(Boolean)
246
+ .join(" · ")
247
+ }
248
+ if (viewSurface === "list") {
249
+ return [
250
+ displayOptions.showColumnLabels ? "Column labels" : "No labels",
251
+ displayOptions.showToolbarSearch ? "Toolbar search" : "No search",
252
+ ].join(" · ")
253
+ }
254
+ if (viewSurface === "dashboard") {
255
+ return "Charts · KPI metrics"
256
+ }
257
+ return [showGridlines ? "Gridlines" : null, pagination ? "Paginated" : null].filter(Boolean).join(" · ") || "Default"
258
+ })()
259
+ const viewDisplayIcon =
260
+ DATA_LIST_VIEW_TILES.find(t => t.value === viewSurface)?.icon ?? "fa-table"
261
+
262
+ // ── Sort drag-and-drop ────────────────────────────────────────────────────
263
+ const sortDrag = useDraggableList(sortRules, r => r.id, onSortRulesChange)
264
+
265
+ // ── Columns drag-and-drop ─────────────────────────────────────────────────
266
+ const orderable = colOrder.filter(k => k !== "select" && k !== "actions")
267
+ const colDrag = useDraggableList(
268
+ orderable,
269
+ k => k,
270
+ newOrder => onColOrderChange(["select", ...newOrder, "actions"]),
271
+ )
272
+
273
+ // Current primary sort label for display in main panel
274
+ const primarySortLabel = primarySortKey
275
+ ? resolveColumnLabel(primarySortKey)
276
+ : sortRules[0]?.fieldKey
277
+ ? resolveColumnLabel(sortRules[0].fieldKey)
278
+ : "—"
279
+
280
+ return (
281
+ <Sheet open={open} onOpenChange={onOpenChange} modal={false}>
282
+ <SheetContent
283
+ side="right"
284
+ showCloseButton={false}
285
+ showOverlay={false}
286
+ // w-[min(20rem,calc(100vw-1rem))]: cap to viewport width - 1rem at narrow/zoomed viewports
287
+ // so the drawer never overflows horizontally. Use 100svh so height is correct on mobile.
288
+ className="z-[80] w-[min(20rem,calc(100vw-1rem))] p-0 gap-0 flex flex-col border border-border shadow-xl rounded-xl overflow-hidden"
289
+ style={{ top: "0.5rem", bottom: "0.5rem", right: "0.5rem", height: "calc(100svh - 1rem)" }}
290
+ >
291
+
292
+ {sheetPanel === "main" ? (
293
+ <>
294
+ {/* Header */}
295
+ <div className="flex items-center justify-between gap-3 px-4 pt-5 pb-3">
296
+ <div className="min-w-0">
297
+ <SheetTitle className="text-base font-semibold leading-tight">Properties</SheetTitle>
298
+ {lifecycleTabLabel ? (
299
+ <p className="text-xs text-muted-foreground mt-0.5 truncate" title={lifecycleTabLabel}>
300
+ {lifecycleTabLabel}
301
+ </p>
302
+ ) : null}
303
+ </div>
304
+ <Tip label="Close" side="bottom">
305
+ <Button
306
+ type="button"
307
+ variant="ghost"
308
+ size="icon-sm"
309
+ aria-label="Close"
310
+ onClick={() => onOpenChange(false)}
311
+ >
312
+ <i className="fa-light fa-xmark text-[13px]" aria-hidden="true" />
313
+ </Button>
314
+ </Tip>
315
+ </div>
316
+
317
+ {/* View type switcher — card tiles like export file format. When the hub passes
318
+ `supportedViewTypes`, only its implemented views are offered so users cannot
319
+ switch to a view the hub does not render (which would show the not-configured
320
+ empty state). Default is all registered view types. */}
321
+ {onViewChange && currentView && (
322
+ <div className="px-4 pb-3">
323
+ <SelectionTileGrid<DataListViewType>
324
+ sectionLabel="View type"
325
+ options={
326
+ supportedViewTypes && supportedViewTypes.length > 0
327
+ ? dataListViewTilesForHub(supportedViewTypes).map(t => ({
328
+ value: t.type,
329
+ label: t.label,
330
+ icon: t.icon,
331
+ }))
332
+ : DATA_LIST_VIEW_TILES
333
+ }
334
+ columns={4}
335
+ value={currentView}
336
+ onValueChange={onViewChange}
337
+ interaction="button"
338
+ idPrefix="props-view"
339
+ />
340
+ </div>
341
+ )}
342
+
343
+ {/* Option list — inset rows + rounded hover (not edge-to-edge) */}
344
+ <div className="flex-1 overflow-y-auto py-2 px-3 space-y-1">
345
+ {([
346
+ {
347
+ id: "table-display" as SheetPanel,
348
+ icon: viewDisplayIcon,
349
+ label: viewDisplayLabel,
350
+ desc: viewDisplayDesc,
351
+ },
352
+ {
353
+ id: "filter" as SheetPanel,
354
+ icon: "fa-filter",
355
+ label: "Filter",
356
+ desc: activeFilters.length === 0
357
+ ? `Showing all ${filteredRows} rows.`
358
+ : `${activeFilters.length} filter${activeFilters.length !== 1 ? "s" : ""} active · ${filteredRows} rows.`,
359
+ },
360
+ {
361
+ id: "sort" as SheetPanel,
362
+ icon: "fa-arrow-up-arrow-down",
363
+ label: "Sort",
364
+ desc: `Sorted by ${primarySortLabel}.`,
365
+ },
366
+ {
367
+ id: "group" as SheetPanel,
368
+ icon: "fa-layer-group",
369
+ label: "Group",
370
+ desc: groupBy
371
+ ? `Grouped by ${resolveColumnLabel(groupBy)}.`
372
+ : "No grouping.",
373
+ },
374
+ {
375
+ id: "columns" as SheetPanel,
376
+ icon: "fa-table-columns",
377
+ label: "Columns",
378
+ desc: hiddenCols.size === 0
379
+ ? "All columns visible."
380
+ : `${hiddenCols.size} column${hiddenCols.size !== 1 ? "s" : ""} hidden.`,
381
+ },
382
+ {
383
+ id: "conditional-rules" as SheetPanel,
384
+ icon: "fa-palette",
385
+ label: "Conditional rules",
386
+ desc: conditionalRules.length === 0
387
+ ? "No rules applied."
388
+ : `${conditionalRules.length} rule${conditionalRules.length !== 1 ? "s" : ""} active.`,
389
+ },
390
+ ] as { id: SheetPanel; icon: string; label: string; desc: string }[]).map(item => (
391
+ <Button
392
+ key={item.id}
393
+ type="button"
394
+ variant="ghost"
395
+ onClick={() => setSheetPanel(item.id)}
396
+ className={cn(
397
+ "w-full h-auto justify-start gap-3 px-3 py-3 rounded-2xl font-normal border border-transparent",
398
+ "hover:bg-muted/60 hover:text-foreground",
399
+ "focus-visible:bg-muted/60 focus-visible:text-foreground",
400
+ )}
401
+ >
402
+ <span className="inline-flex items-center justify-center size-9 rounded-lg bg-secondary border border-border shrink-0">
403
+ <i className={`fa-light ${item.icon} text-[15px] text-secondary-foreground`} aria-hidden="true" />
404
+ </span>
405
+ <span className="flex-1 min-w-0 text-start">
406
+ <span className="block text-sm font-medium text-foreground">{item.label}</span>
407
+ <span className="block text-xs text-muted-foreground mt-0.5">{item.desc}</span>
408
+ </span>
409
+ <i className="fa-light fa-chevron-right text-xs text-muted-foreground shrink-0" aria-hidden="true" />
410
+ </Button>
411
+ ))}
412
+ </div>
413
+ </>
414
+ ) : (
415
+ <>
416
+ {/* Sub-panel header — back + title stack as one cluster; close aligns to row center */}
417
+ <div className="flex items-center justify-between gap-3 px-4 pt-4 pb-3">
418
+ <div className="flex items-center gap-2 min-w-0 flex-1">
419
+ <Tip label="Back to Properties" side="bottom">
420
+ <Button
421
+ type="button"
422
+ variant="ghost"
423
+ size="icon-sm"
424
+ className="shrink-0"
425
+ aria-label="Back to Properties"
426
+ onClick={() => setSheetPanel("main")}
427
+ >
428
+ <i className="fa-light fa-chevron-left text-[13px]" aria-hidden="true" />
429
+ </Button>
430
+ </Tip>
431
+ <div className="min-w-0">
432
+ <SheetTitle className="text-base font-semibold text-foreground leading-tight flex items-center gap-1.5">
433
+ {{
434
+ "table-display": viewDisplayLabel,
435
+ filter: "Filter",
436
+ sort: "Sort",
437
+ group: "Group",
438
+ columns: "Columns",
439
+ "conditional-rules": "Conditional rules",
440
+ main: "",
441
+ }[sheetPanel]}
442
+ {sheetPanel === "filter" && (
443
+ <i className="fa-light fa-circle-question text-xs text-muted-foreground" aria-hidden="true" />
444
+ )}
445
+ </SheetTitle>
446
+ {sheetPanel === "filter" && (
447
+ <p
448
+ className="text-xs text-muted-foreground mt-0.5"
449
+ aria-live="polite"
450
+ aria-atomic="true"
451
+ >
452
+ {activeFilters.length === 0
453
+ ? `Showing all ${filteredRows} rows`
454
+ : `${filteredRows} of ${totalRows} rows match · ${activeFilters.length} filter${activeFilters.length !== 1 ? "s" : ""} active`}
455
+ </p>
456
+ )}
457
+ </div>
458
+ </div>
459
+ <Tip label="Close" side="bottom">
460
+ <Button
461
+ type="button"
462
+ variant="ghost"
463
+ size="icon-sm"
464
+ className="shrink-0"
465
+ aria-label="Close panel"
466
+ onClick={() => onOpenChange(false)}
467
+ >
468
+ <i className="fa-light fa-xmark text-[13px]" aria-hidden="true" />
469
+ </Button>
470
+ </Tip>
471
+ </div>
472
+
473
+ <div className="flex-1 overflow-y-auto">
474
+
475
+ {/* ── Table / Board display ── */}
476
+ {sheetPanel === "table-display" && (
477
+ <div className="p-4 space-y-5">
478
+ {isBoardView ? (
479
+ <p className="text-xs text-muted-foreground leading-relaxed">
480
+ {dataListViewLabel("board")} groups rows into columns. Sort, filter, and column settings apply to the same dataset as other views (e.g. Table view).
481
+ </p>
482
+ ) : null}
483
+
484
+ {isBoardView && boardGroupByColumnOptions && boardGroupByColumnOptions.length > 1 ? (
485
+ <div className="flex items-center justify-between gap-3 py-2">
486
+ <div className="flex items-center gap-2.5 min-w-0 flex-1">
487
+ <span className="inline-flex items-center justify-center size-9 rounded-lg bg-secondary border border-border shrink-0">
488
+ <i className="fa-light fa-table-columns text-[15px] text-secondary-foreground" aria-hidden="true" />
489
+ </span>
490
+ <div className="min-w-0">
491
+ <p className="text-sm font-medium text-foreground leading-tight">Board columns</p>
492
+ <p className="text-xs text-muted-foreground mt-0.5">Choose which field splits the board into swimlanes.</p>
493
+ </div>
494
+ </div>
495
+ <Select
496
+ value={
497
+ boardGroupByColumnOptions.some(o => o.key === displayOptions.boardGroupByColumnKey)
498
+ ? displayOptions.boardGroupByColumnKey
499
+ : boardGroupByColumnOptions[0]!.key
500
+ }
501
+ onValueChange={v => onDisplayOptionsChange({ boardGroupByColumnKey: v })}
502
+ >
503
+ <SelectTrigger
504
+ size="sm"
505
+ className="w-[9.5rem] shrink-0"
506
+ id="board-group-by-field"
507
+ aria-label="Field for board columns"
508
+ >
509
+ <SelectValue />
510
+ </SelectTrigger>
511
+ <SelectContent align="end" className={PROPERTIES_SHEET_PORTAL_Z}>
512
+ {boardGroupByColumnOptions.map(o => (
513
+ <SelectItem key={o.key} value={o.key}>
514
+ {o.label}
515
+ </SelectItem>
516
+ ))}
517
+ </SelectContent>
518
+ </Select>
519
+ </div>
520
+ ) : null}
521
+
522
+ {viewSurface === "table" ? (
523
+ <>
524
+ <div>
525
+ <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">Appearance</p>
526
+ <div className="space-y-1">
527
+ {([
528
+ { id: "gridlines", icon: "fa-border-all", label: "Gridlines", checked: showGridlines, onChange: onShowGridlinesChange },
529
+ { id: "pagination", icon: "fa-table-list", label: "Pagination", checked: pagination, onChange: onPaginationChange },
530
+ ] as { id: string; icon: string; label: string; checked: boolean; onChange: (v: boolean) => void }[]).map(row => (
531
+ <div key={row.id} className="flex items-center justify-between py-2">
532
+ <div className="flex items-center gap-2.5 text-sm">
533
+ <i className={`fa-light ${row.icon} text-muted-foreground w-4 text-center`} aria-hidden="true" />
534
+ <label htmlFor={`toggle-${row.id}`} className="cursor-pointer select-none">{row.label}</label>
535
+ </div>
536
+ <ToggleSwitch id={`toggle-${row.id}`} checked={row.checked} onChange={row.onChange} />
537
+ </div>
538
+ ))}
539
+ </div>
540
+ </div>
541
+
542
+ <div className="border-t border-border pt-4">
543
+ <SelectionTileGrid<RowHeight>
544
+ sectionLabel="Row height"
545
+ options={ROW_HEIGHT_TILES}
546
+ columns={3}
547
+ value={rowHeight}
548
+ onValueChange={onRowHeightChange}
549
+ interaction="button"
550
+ idPrefix="row-height"
551
+ />
552
+ </div>
553
+ </>
554
+ ) : null}
555
+
556
+ <div
557
+ className={cn(
558
+ "space-y-3",
559
+ (viewSurface === "board" || viewSurface === "table") && "border-t border-border pt-4",
560
+ )}
561
+ >
562
+ <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Display options</p>
563
+ <div className="space-y-1">
564
+ {isBoardView && (
565
+ <div className="flex items-center justify-between gap-2 py-2">
566
+ <div className="flex items-center gap-2.5 min-w-0 flex-1">
567
+ <span className="inline-flex items-center justify-center size-9 rounded-lg bg-secondary border border-border shrink-0">
568
+ <i className="fa-light fa-file-lines text-[15px] text-secondary-foreground" aria-hidden="true" />
569
+ </span>
570
+ <div className="min-w-0">
571
+ <p className="text-sm font-medium text-foreground leading-tight">Line count</p>
572
+ </div>
573
+ </div>
574
+ <Select
575
+ value={String(displayOptions.boardLineCount)}
576
+ onValueChange={v =>
577
+ onDisplayOptionsChange({ boardLineCount: Number(v) as 1 | 2 | 3 })}
578
+ >
579
+ <SelectTrigger size="sm" className="w-[6.5rem] shrink-0" id="board-line-count" aria-label="Line count">
580
+ <SelectValue />
581
+ </SelectTrigger>
582
+ <SelectContent align="end" className={PROPERTIES_SHEET_PORTAL_Z}>
583
+ <SelectItem value="1">1 line</SelectItem>
584
+ <SelectItem value="2">2 lines</SelectItem>
585
+ <SelectItem value="3">3 lines</SelectItem>
586
+ </SelectContent>
587
+ </Select>
588
+ </div>
589
+ )}
590
+
591
+ {viewSurface === "table" && (
592
+ <div className="flex items-center justify-between gap-2 py-2">
593
+ <div className="flex items-center gap-2.5 min-w-0 flex-1">
594
+ <span className="inline-flex items-center justify-center size-9 rounded-lg bg-secondary border border-border shrink-0">
595
+ <i className="fa-light fa-font text-[15px] text-secondary-foreground" aria-hidden="true" />
596
+ </span>
597
+ <div className="min-w-0">
598
+ <p className="text-sm font-medium text-foreground leading-tight">Table title</p>
599
+ <p className="text-xs text-muted-foreground mt-0.5">Show the page heading and subtitle.</p>
600
+ </div>
601
+ </div>
602
+ <ToggleSwitch
603
+ id="toggle-view-title"
604
+ checked={displayOptions.showViewTitle}
605
+ onChange={v => onDisplayOptionsChange({ showViewTitle: v })}
606
+ />
607
+ </div>
608
+ )}
609
+
610
+ <div className="flex items-center justify-between gap-2 py-2">
611
+ <div className="flex items-center gap-2.5 min-w-0 flex-1">
612
+ <span className="inline-flex items-center justify-center size-9 rounded-lg bg-secondary border border-border shrink-0">
613
+ <i className="fa-light fa-table-columns text-[15px] text-secondary-foreground" aria-hidden="true" />
614
+ </span>
615
+ <div className="min-w-0">
616
+ <p className="text-sm font-medium text-foreground leading-tight">Column labels</p>
617
+ {viewSurface === "table" ? (
618
+ <p className="text-xs text-muted-foreground mt-0.5">Column headers in the table.</p>
619
+ ) : viewSurface === "list" ? (
620
+ <p className="text-xs text-muted-foreground mt-0.5">Column headers in the list.</p>
621
+ ) : null}
622
+ </div>
623
+ </div>
624
+ <ToggleSwitch
625
+ id="toggle-column-labels"
626
+ checked={displayOptions.showColumnLabels}
627
+ onChange={v => onDisplayOptionsChange({ showColumnLabels: v })}
628
+ />
629
+ </div>
630
+
631
+ {isBoardView && (
632
+ <>
633
+ <div className="flex items-center justify-between gap-2 py-2">
634
+ <div className="flex items-center gap-2.5 min-w-0 flex-1">
635
+ <span className="inline-flex items-center justify-center size-9 rounded-lg bg-secondary border border-border shrink-0">
636
+ <i className="fa-light fa-hashtag text-[15px] text-secondary-foreground" aria-hidden="true" />
637
+ </span>
638
+ <div className="min-w-0">
639
+ <p className="text-sm font-medium text-foreground leading-tight">Column counts</p>
640
+ </div>
641
+ </div>
642
+ <ToggleSwitch
643
+ id="toggle-board-counts"
644
+ checked={displayOptions.showBoardColumnCounts}
645
+ onChange={v => onDisplayOptionsChange({ showBoardColumnCounts: v })}
646
+ />
647
+ </div>
648
+
649
+ <div className="flex items-center justify-between gap-2 py-2">
650
+ <div className="flex items-center gap-2.5 min-w-0 flex-1">
651
+ <span className="inline-flex items-center justify-center size-9 rounded-lg bg-secondary border border-border shrink-0">
652
+ <i className="fa-light fa-square-plus text-[15px] text-secondary-foreground" aria-hidden="true" />
653
+ </span>
654
+ <div className="min-w-0">
655
+ <p className="text-sm font-medium text-foreground leading-tight">Above new card button</p>
656
+ </div>
657
+ </div>
658
+ <ToggleSwitch
659
+ id="toggle-new-card-above"
660
+ checked={displayOptions.boardNewCardAbove}
661
+ onChange={v => onDisplayOptionsChange({ boardNewCardAbove: v })}
662
+ />
663
+ </div>
664
+ </>
665
+ )}
666
+
667
+ {(viewSurface === "table" || viewSurface === "list") && (
668
+ <div className="flex items-center justify-between gap-2 py-2">
669
+ <div className="flex items-center gap-2.5 min-w-0 flex-1">
670
+ <span className="inline-flex items-center justify-center size-9 rounded-lg bg-secondary border border-border shrink-0">
671
+ <i className="fa-light fa-magnifying-glass text-[15px] text-secondary-foreground" aria-hidden="true" />
672
+ </span>
673
+ <div className="min-w-0">
674
+ <p className="text-sm font-medium text-foreground leading-tight">Search</p>
675
+ <p className="text-xs text-muted-foreground mt-0.5">Toolbar search for this view.</p>
676
+ </div>
677
+ </div>
678
+ <ToggleSwitch
679
+ id="toggle-toolbar-search"
680
+ checked={displayOptions.showToolbarSearch}
681
+ onChange={v => onDisplayOptionsChange({ showToolbarSearch: v })}
682
+ />
683
+ </div>
684
+ )}
685
+ </div>
686
+ </div>
687
+ </div>
688
+ )}
689
+
690
+ {/* ── Filter ── */}
691
+ {sheetPanel === "filter" && (
692
+ <div className="px-4 py-4 space-y-2">
693
+ {activeFilters.length === 0 ? (
694
+ <div className="rounded-xl border border-border bg-muted/40 p-4 space-y-3">
695
+ <div className="flex items-center gap-2">
696
+ <span className="inline-flex items-center justify-center size-7 rounded-lg bg-background border border-border shrink-0">
697
+ <i className="fa-light fa-filter text-muted-foreground text-xs" aria-hidden="true" />
698
+ </span>
699
+ <p className="text-sm font-medium text-foreground">No filters yet</p>
700
+ </div>
701
+ <p className="text-xs text-muted-foreground leading-relaxed">
702
+ Use filters to show only the rows you need. With multiple filters, use <span className="font-medium text-foreground/80">and</span> or <span className="font-medium text-foreground/80">or</span> between them to control how they combine.
703
+ </p>
704
+ <div className="space-y-1.5">
705
+ {[
706
+ { icon: "fa-circle-1", text: "Click \"Add filter\" below" },
707
+ { icon: "fa-circle-2", text: "Choose a field to filter by" },
708
+ { icon: "fa-circle-3", text: "Pick at least one value — the grid updates immediately" },
709
+ ].map(step => (
710
+ <div key={step.icon} className="flex items-center gap-2 text-xs text-muted-foreground">
711
+ <i className={`fa-light ${step.icon} text-muted-foreground text-xs shrink-0`} aria-hidden="true" />
712
+ {step.text}
713
+ </div>
714
+ ))}
715
+ </div>
716
+ </div>
717
+ ) : (
718
+ <>
719
+ {activeFilters.map((f, idx) => {
720
+ const fieldDef = filterFields.find(fd => fd.key === f.fieldKey)
721
+ if (!fieldDef) return null
722
+ const leftId = idx > 0 ? activeFilters[idx - 1]!.id : null
723
+ const connector = leftId ? getFilterConnector(leftId) : "and"
724
+ return (
725
+ <React.Fragment key={f.id}>
726
+ {idx > 0 && leftId && (
727
+ <div className="flex items-center gap-2 py-1">
728
+ <div className="flex-1 h-px bg-border" aria-hidden="true" />
729
+ <Tip label="Click to switch: AND — every filter must match; OR — any matching filter is enough." side="top">
730
+ <button
731
+ type="button"
732
+ onClick={() => onToggleFilterConnector(leftId)}
733
+ className={cn(
734
+ "shrink-0 rounded-md border px-2.5 py-0.5 text-xs font-semibold uppercase tracking-wide transition-colors",
735
+ "border-border bg-muted/40 text-muted-foreground hover:bg-interactive-hover hover:text-interactive-hover-foreground",
736
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
737
+ )}
738
+ aria-label={
739
+ connector === "and"
740
+ ? "Filters are combined with AND. Click to use OR instead."
741
+ : "Filters are combined with OR. Click to use AND instead."
742
+ }
743
+ >
744
+ {connector}
745
+ </button>
746
+ </Tip>
747
+ <div className="flex-1 h-px bg-border" aria-hidden="true" />
748
+ </div>
749
+ )}
750
+ <DrawerFilterCard
751
+ filter={f}
752
+ fieldDef={fieldDef}
753
+ expanded={drawerExpandedFilters.has(f.id)}
754
+ onToggleExpand={() => onDrawerExpandedFiltersChange(prev => {
755
+ const next = new Set(prev)
756
+ if (next.has(f.id)) next.delete(f.id)
757
+ else next.add(f.id)
758
+ return next
759
+ })}
760
+ onUpdate={onUpdateFilter}
761
+ onRemove={id => {
762
+ onRemoveFilter(id)
763
+ onDrawerExpandedFiltersChange(prev => { const next = new Set(prev); next.delete(id); return next })
764
+ }}
765
+ renderOptionLabel={value => renderFilterOptionValue?.(f.fieldKey, value)}
766
+ />
767
+ </React.Fragment>
768
+ )
769
+ })}
770
+ </>
771
+ )}
772
+
773
+ {/* Add filter + Remove all */}
774
+ <div className="flex items-center gap-2 pt-2">
775
+ <DropdownMenu modal={false}>
776
+ <DropdownMenuTrigger asChild>
777
+ <Button
778
+ type="button"
779
+ variant="outline"
780
+ className="flex-1 gap-1.5 h-8 border-dashed text-muted-foreground"
781
+ >
782
+ <i className="fa-light fa-plus text-xs" aria-hidden="true" />
783
+ Add filter
784
+ </Button>
785
+ </DropdownMenuTrigger>
786
+ <DropdownMenuContent align="start" className={PROPERTIES_SHEET_PORTAL_Z}>
787
+ <DropdownMenuLabel className="text-xs">Filter by field</DropdownMenuLabel>
788
+ <DropdownMenuSeparator />
789
+ {filterFields.map(f => (
790
+ <DropdownMenuItem key={f.key} onSelect={() => onAddFilter(f.key)}>
791
+ <i className={`fa-light ${f.icon}`} aria-hidden="true" />
792
+ {f.label}
793
+ </DropdownMenuItem>
794
+ ))}
795
+ </DropdownMenuContent>
796
+ </DropdownMenu>
797
+ {activeFilters.length > 0 && (
798
+ <Button
799
+ type="button"
800
+ variant="ghost"
801
+ size="sm"
802
+ className="shrink-0 text-destructive hover:text-destructive hover:bg-destructive/10"
803
+ onClick={() => { activeFilters.forEach(f => onRemoveFilter(f.id)); onDrawerExpandedFiltersChange(new Set()) }}
804
+ >
805
+ Remove all
806
+ </Button>
807
+ )}
808
+ </div>
809
+
810
+ {/* Enable filter bar toggle */}
811
+ <div className="flex items-start justify-between gap-3 pt-3 mt-1 border-t border-border">
812
+ <div>
813
+ <label htmlFor="toggle-filter-bar" className="text-sm font-medium text-foreground cursor-pointer">Enable filter bar</label>
814
+ <p className="text-xs text-muted-foreground mt-0.5">Show filters above the table.</p>
815
+ </div>
816
+ <ToggleSwitch id="toggle-filter-bar" checked={filterBarVisible} onChange={onFilterBarVisibleChange} />
817
+ </div>
818
+ </div>
819
+ )}
820
+
821
+ {/* ── Sort ── */}
822
+ {sheetPanel === "sort" && (
823
+ <div className="px-4 py-4 space-y-2">
824
+ {sortRules.length === 0 ? (
825
+ /* Empty state */
826
+ <div className="rounded-xl border border-dashed border-border bg-muted/30 px-4 py-6 text-center space-y-2">
827
+ <div className="inline-flex items-center justify-center size-9 rounded-lg bg-muted mb-1">
828
+ <i className="fa-light fa-arrow-up-arrow-down text-muted-foreground text-[16px]" aria-hidden="true" />
829
+ </div>
830
+ <p className="text-sm font-medium text-foreground">No sorts applied</p>
831
+ <p className="text-xs text-muted-foreground leading-relaxed">
832
+ Add a sort rule to order rows by any field. Multiple rules are applied in priority order.
833
+ </p>
834
+ <div className="space-y-1.5 text-start pt-1">
835
+ {[
836
+ { icon: "fa-circle-1", text: "Click \"Add sort\" below" },
837
+ { icon: "fa-circle-2", text: "Choose a field to sort by" },
838
+ { icon: "fa-circle-3", text: "Toggle ascending or descending" },
839
+ ].map(step => (
840
+ <div key={step.icon} className="flex items-center gap-2 text-xs text-muted-foreground">
841
+ <i className={`fa-light ${step.icon} text-muted-foreground text-xs shrink-0`} aria-hidden="true" />
842
+ {step.text}
843
+ </div>
844
+ ))}
845
+ </div>
846
+ </div>
847
+ ) : (
848
+ sortRules.map((rule, idx) => {
849
+ const dragProps = sortDrag.getItemProps(rule.id)
850
+ return (
851
+ <React.Fragment key={rule.id}>
852
+ {idx > 0 && (
853
+ <div className="flex items-center gap-2 py-0.5">
854
+ <div className="flex-1 h-px bg-border" />
855
+ <span className="text-xs font-medium text-muted-foreground px-1">then by</span>
856
+ <div className="flex-1 h-px bg-border" />
857
+ </div>
858
+ )}
859
+ <div
860
+ {...dragProps}
861
+ className={cn(
862
+ "transition-all",
863
+ dragProps["data-dragging"] && "opacity-40",
864
+ dragProps["data-over"] && "ring-2 ring-ring bg-accent/30 rounded-lg",
865
+ )}
866
+ >
867
+ <DrawerSortCard
868
+ rule={rule}
869
+ fieldLabel={resolveColumnLabel(rule.fieldKey)}
870
+ isPrimary={idx === 0}
871
+ onRemove={() => onRemoveSortRule(rule.id)}
872
+ onToggleDir={() => onToggleSortDir(rule.id)}
873
+ />
874
+ </div>
875
+ </React.Fragment>
876
+ )
877
+ })
878
+ )}
879
+
880
+ {/* Add sort + Remove all */}
881
+ <div className="flex items-center gap-2 pt-2">
882
+ <DropdownMenu modal={false}>
883
+ <DropdownMenuTrigger asChild>
884
+ <Button
885
+ type="button"
886
+ variant="outline"
887
+ className="flex-1 gap-1.5 h-8 border-dashed text-muted-foreground"
888
+ >
889
+ <i className="fa-light fa-plus text-xs" aria-hidden="true" />
890
+ Add sort
891
+ </Button>
892
+ </DropdownMenuTrigger>
893
+ <DropdownMenuContent align="start" className={PROPERTIES_SHEET_PORTAL_Z}>
894
+ <DropdownMenuLabel className="text-xs">Sort by field</DropdownMenuLabel>
895
+ <DropdownMenuSeparator />
896
+ {sortFieldList.filter(f => !sortRules.some(r => r.fieldKey === f.key)).map(col => (
897
+ <DropdownMenuItem key={col.key} onSelect={() => onAddSortRule(col.key)}>
898
+ <i className="fa-light fa-arrow-up-arrow-down text-xs" aria-hidden="true" />
899
+ {col.label}
900
+ </DropdownMenuItem>
901
+ ))}
902
+ {sortFieldList.filter(f => !sortRules.some(r => r.fieldKey === f.key)).length === 0 && (
903
+ <p className="px-2 py-1.5 text-xs text-muted-foreground">All fields added</p>
904
+ )}
905
+ </DropdownMenuContent>
906
+ </DropdownMenu>
907
+ {sortRules.length > 0 && (
908
+ <Button
909
+ type="button"
910
+ variant="ghost"
911
+ size="sm"
912
+ className="shrink-0 text-destructive hover:text-destructive hover:bg-destructive/10"
913
+ onClick={() => onSortRulesChange([])}
914
+ >
915
+ Remove all
916
+ </Button>
917
+ )}
918
+ </div>
919
+ </div>
920
+ )}
921
+
922
+ {/* ── Group ── */}
923
+ {sheetPanel === "group" && (
924
+ <div className="p-4 space-y-2">
925
+ <p className="text-xs text-muted-foreground mb-3">
926
+ {groupBy ? `Grouped by ${resolveColumnLabel(groupBy)}.` : "No grouping applied."}
927
+ </p>
928
+ <Button
929
+ type="button"
930
+ variant="ghost"
931
+ onClick={() => onGroupByChange(null)}
932
+ className={cn("w-full justify-start gap-2 px-3 py-2 h-auto text-sm font-normal",
933
+ !groupBy ? "bg-accent text-accent-foreground font-medium" : "text-muted-foreground",
934
+ )}
935
+ >
936
+ <i className="fa-light fa-ban text-xs" aria-hidden="true" />
937
+ None
938
+ </Button>
939
+ {groupFieldList.map(col => (
940
+ <Button
941
+ key={col.key}
942
+ type="button"
943
+ variant="ghost"
944
+ onClick={() => onGroupByChange(groupBy === col.key ? null : col.key)}
945
+ className={cn("w-full justify-start gap-2 px-3 py-2 h-auto text-sm font-normal",
946
+ groupBy === col.key ? "bg-accent text-accent-foreground font-medium" : "",
947
+ )}
948
+ >
949
+ <i className="fa-light fa-layer-group text-xs text-muted-foreground" aria-hidden="true" />
950
+ {col.label}
951
+ {groupBy === col.key && <i className="fa-solid fa-check text-accent-foreground text-xs ms-auto" aria-hidden="true" />}
952
+ </Button>
953
+ ))}
954
+ </div>
955
+ )}
956
+
957
+ {/* ── Columns ── */}
958
+ {sheetPanel === "columns" && (
959
+ <div className="px-4 py-4">
960
+ {isBoardView ? (
961
+ <p className="text-xs text-muted-foreground mb-3">
962
+ Column visibility and order apply when you use Table view. They are saved with this tab.
963
+ </p>
964
+ ) : null}
965
+ <p className="text-xs text-muted-foreground mb-3">
966
+ {hiddenCols.size === 0
967
+ ? "All columns visible. Drag to reorder."
968
+ : `${hiddenCols.size} column${hiddenCols.size !== 1 ? "s" : ""} hidden. Drag handle to reorder.`}
969
+ </p>
970
+ <div className="space-y-0.5" role="list" aria-label="Column order and visibility">
971
+ {orderable.map((key, idx, arr) => {
972
+ const dragProps = colDrag.getItemProps(key)
973
+ return (
974
+ <ColumnRow
975
+ key={key}
976
+ label={resolveColumnLabel(key)}
977
+ isFirst={idx === 0}
978
+ isLast={idx === arr.length - 1}
979
+ visible={!hiddenCols.has(key)}
980
+ onToggleVisible={() => onToggleColVisibility(key)}
981
+ onMoveUp={() => onMoveCol(key, "up")}
982
+ onMoveDown={() => onMoveCol(key, "down")}
983
+ draggable={dragProps.draggable}
984
+ onDragStart={dragProps.onDragStart}
985
+ onDragOver={dragProps.onDragOver}
986
+ onDrop={dragProps.onDrop}
987
+ onDragEnd={dragProps.onDragEnd}
988
+ isDragging={dragProps["data-dragging"]}
989
+ isOver={dragProps["data-over"]}
990
+ />
991
+ )
992
+ })}
993
+ </div>
994
+ </div>
995
+ )}
996
+
997
+ {/* ── Conditional rules ── */}
998
+ {sheetPanel === "conditional-rules" && (
999
+ <ConditionalRulesPanel
1000
+ filterFields={filterFields}
1001
+ rules={conditionalRules}
1002
+ onAdd={onAddConditionalRule}
1003
+ onRemove={onRemoveConditionalRule}
1004
+ onUpdate={onUpdateConditionalRule}
1005
+ renderFilterOptionValue={renderFilterOptionValue}
1006
+ />
1007
+ )}
1008
+
1009
+ </div>
1010
+ </>
1011
+ )}
1012
+
1013
+ </SheetContent>
1014
+ </Sheet>
1015
+ )
1016
+ }
1017
+
1018
+ // ─────────────────────────────────────────────────────────────────────────────
1019
+ // ConditionalRulesPanel — same DrawerFilterCard as filters (incl. operator cycle);
1020
+ // highlight color lives inside the card. Adding a rule expands only that card (like
1021
+ // add filter from drawer). No And/Or connectors.
1022
+ // ─────────────────────────────────────────────────────────────────────────────
1023
+
1024
+ function ConditionalRulesPanel({
1025
+ filterFields,
1026
+ rules,
1027
+ onAdd,
1028
+ onRemove,
1029
+ onUpdate,
1030
+ renderFilterOptionValue,
1031
+ }: {
1032
+ filterFields: FilterFieldDef[]
1033
+ rules: ConditionalRule[]
1034
+ onAdd: (rule: Omit<ConditionalRule, "id">) => void
1035
+ onRemove: (id: string) => void
1036
+ onUpdate: (id: string, patch: Partial<ConditionalRule>) => void
1037
+ renderFilterOptionValue?: (fieldKey: string, value: string) => React.ReactNode
1038
+ }) {
1039
+ const [expandedIds, setExpandedIds] = React.useState<Set<string>>(() => new Set())
1040
+
1041
+ const prevLenRef = React.useRef(rules.length)
1042
+ React.useEffect(() => {
1043
+ if (rules.length > prevLenRef.current && rules.length > 0) {
1044
+ const last = rules[rules.length - 1]
1045
+ setExpandedIds(new Set([last.id]))
1046
+ }
1047
+ prevLenRef.current = rules.length
1048
+ }, [rules])
1049
+
1050
+ function toggleExpanded(id: string) {
1051
+ setExpandedIds(prev => {
1052
+ const next = new Set(prev)
1053
+ if (next.has(id)) next.delete(id)
1054
+ else next.add(id)
1055
+ return next
1056
+ })
1057
+ }
1058
+
1059
+ return (
1060
+ <div className="px-4 py-4 space-y-2">
1061
+ {rules.length === 0 ? (
1062
+ <div className="rounded-xl border border-dashed border-border bg-muted/30 px-4 py-6 text-center space-y-2">
1063
+ <div className="inline-flex items-center justify-center size-9 rounded-lg bg-muted mb-1">
1064
+ <i className="fa-light fa-palette text-muted-foreground text-[16px]" aria-hidden="true" />
1065
+ </div>
1066
+ <p className="text-sm font-medium text-foreground">No rules yet</p>
1067
+ <p className="text-xs text-muted-foreground leading-relaxed">
1068
+ Highlight cells with a background color based on their value.
1069
+ </p>
1070
+ </div>
1071
+ ) : (
1072
+ <div className="space-y-2">
1073
+ {rules.map(rule => {
1074
+ const fd = filterFields.find(f => f.key === rule.fieldKey)
1075
+ if (!fd) return null
1076
+ return (
1077
+ <DrawerFilterCard
1078
+ key={rule.id}
1079
+ variant="conditional"
1080
+ filter={rule}
1081
+ fieldDef={fd}
1082
+ expanded={expandedIds.has(rule.id)}
1083
+ onToggleExpand={() => toggleExpanded(rule.id)}
1084
+ onUpdate={onUpdate}
1085
+ onRemove={id => {
1086
+ onRemove(id)
1087
+ setExpandedIds(prev => {
1088
+ const next = new Set(prev)
1089
+ next.delete(id)
1090
+ return next
1091
+ })
1092
+ }}
1093
+ renderOptionLabel={value => renderFilterOptionValue?.(rule.fieldKey, value)}
1094
+ />
1095
+ )
1096
+ })}
1097
+ </div>
1098
+ )}
1099
+
1100
+ <div className="flex items-center gap-2 pt-2">
1101
+ <DropdownMenu modal={false}>
1102
+ <DropdownMenuTrigger asChild>
1103
+ <Button
1104
+ type="button"
1105
+ variant="outline"
1106
+ className="flex-1 gap-1.5 h-8 border-dashed text-muted-foreground"
1107
+ >
1108
+ <i className="fa-light fa-plus text-xs" aria-hidden="true" />
1109
+ Add rule
1110
+ </Button>
1111
+ </DropdownMenuTrigger>
1112
+ <DropdownMenuContent align="start" className={PROPERTIES_SHEET_PORTAL_Z}>
1113
+ <DropdownMenuLabel className="text-xs">Rule for column</DropdownMenuLabel>
1114
+ <DropdownMenuSeparator />
1115
+ {filterFields.map(f => (
1116
+ <DropdownMenuItem
1117
+ key={f.key}
1118
+ onSelect={() => onAdd({
1119
+ fieldKey: f.key,
1120
+ operator: f.operators[0],
1121
+ values: [],
1122
+ bgColor: RULE_COLORS[0].bg,
1123
+ })}
1124
+ >
1125
+ <i className={`fa-light ${f.icon}`} aria-hidden="true" />
1126
+ {f.label}
1127
+ </DropdownMenuItem>
1128
+ ))}
1129
+ </DropdownMenuContent>
1130
+ </DropdownMenu>
1131
+ {rules.length > 0 && (
1132
+ <Button
1133
+ type="button"
1134
+ variant="ghost"
1135
+ size="sm"
1136
+ className="shrink-0 text-destructive hover:text-destructive hover:bg-destructive/10"
1137
+ onClick={() => {
1138
+ rules.forEach(r => onRemove(r.id))
1139
+ setExpandedIds(new Set())
1140
+ }}
1141
+ >
1142
+ Remove all
1143
+ </Button>
1144
+ )}
1145
+ </div>
1146
+ </div>
1147
+ )
1148
+ }