@exxatdesignux/ui 0.2.19 → 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 (688) hide show
  1. package/CHANGELOG.md +60 -7
  2. package/bin/sync-extras.mjs +116 -29
  3. package/consumer-extras/README.md +42 -7
  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 +4 -15
  36. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +13 -28
  37. package/consumer-extras/cursor-skills/exxat-ds-skill/references/data-table-pattern.md +1 -1
  38. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +2 -4
  39. package/consumer-extras/handbook/HANDBOOK.md +185 -0
  40. package/consumer-extras/handbook/glossary.md +57 -0
  41. package/consumer-extras/handbook/reference-implementations.md +126 -0
  42. package/consumer-extras/handbook/voice-and-tone.md +262 -0
  43. package/consumer-extras/patterns/command-menu-pattern.md +1 -1
  44. package/consumer-extras/patterns/consumer-upgrade-checklist.md +0 -20
  45. package/consumer-extras/patterns/data-views-pattern.md +17 -54
  46. package/consumer-extras/patterns/shell-surface-elevation-pattern.md +3 -5
  47. package/dist/components/data-table/filter-date-calendar.d.ts +10 -0
  48. package/dist/components/data-table/filter-date-calendar.js +280 -0
  49. package/dist/components/data-table/filter-date-calendar.js.map +1 -0
  50. package/dist/components/data-table/filter-text-value-input.d.ts +15 -0
  51. package/dist/components/data-table/filter-text-value-input.js +561 -0
  52. package/dist/components/data-table/filter-text-value-input.js.map +1 -0
  53. package/dist/components/data-table/index.d.ts +45 -0
  54. package/dist/components/data-table/index.js +3085 -0
  55. package/dist/components/data-table/index.js.map +1 -0
  56. package/dist/components/data-table/pagination.d.ts +28 -0
  57. package/dist/components/data-table/pagination.js +3264 -0
  58. package/dist/components/data-table/pagination.js.map +1 -0
  59. package/dist/components/data-table/types.d.ts +84 -0
  60. package/dist/components/data-table/types.js +3 -0
  61. package/dist/components/data-table/types.js.map +1 -0
  62. package/dist/components/data-table/use-table-state.d.ts +116 -0
  63. package/dist/components/data-table/use-table-state.js +670 -0
  64. package/dist/components/data-table/use-table-state.js.map +1 -0
  65. package/dist/components/data-views/board-card-primitives.d.ts +22 -0
  66. package/dist/components/data-views/board-card-primitives.js +84 -0
  67. package/dist/components/data-views/board-card-primitives.js.map +1 -0
  68. package/dist/components/data-views/data-row-list.d.ts +33 -0
  69. package/dist/components/data-views/data-row-list.js +106 -0
  70. package/dist/components/data-views/data-row-list.js.map +1 -0
  71. package/dist/components/data-views/finder-panel-view.d.ts +54 -0
  72. package/dist/components/data-views/finder-panel-view.js +388 -0
  73. package/dist/components/data-views/finder-panel-view.js.map +1 -0
  74. package/dist/components/data-views/folder-grid-view.d.ts +22 -0
  75. package/dist/components/data-views/folder-grid-view.js +58 -0
  76. package/dist/components/data-views/folder-grid-view.js.map +1 -0
  77. package/dist/components/data-views/hub-table.d.ts +167 -0
  78. package/dist/components/data-views/hub-table.js +5561 -0
  79. package/dist/components/data-views/hub-table.js.map +1 -0
  80. package/dist/components/data-views/index.d.ts +27 -0
  81. package/dist/components/data-views/index.js +6575 -0
  82. package/dist/components/data-views/index.js.map +1 -0
  83. package/dist/components/data-views/list-page-board-card.d.ts +72 -0
  84. package/dist/components/data-views/list-page-board-card.js +264 -0
  85. package/dist/components/data-views/list-page-board-card.js.map +1 -0
  86. package/dist/components/data-views/list-page-board-template.d.ts +24 -0
  87. package/dist/components/data-views/list-page-board-template.js +137 -0
  88. package/dist/components/data-views/list-page-board-template.js.map +1 -0
  89. package/dist/components/data-views/list-page-connected-view-body.d.ts +19 -0
  90. package/dist/components/data-views/list-page-connected-view-body.js +116 -0
  91. package/dist/components/data-views/list-page-connected-view-body.js.map +1 -0
  92. package/dist/components/data-views/list-page-split-details-placeholder.d.ts +14 -0
  93. package/dist/components/data-views/list-page-split-details-placeholder.js +38 -0
  94. package/dist/components/data-views/list-page-split-details-placeholder.js.map +1 -0
  95. package/dist/components/data-views/list-page-split-hub-chrome.d.ts +17 -0
  96. package/dist/components/data-views/list-page-split-hub-chrome.js +54 -0
  97. package/dist/components/data-views/list-page-split-hub-chrome.js.map +1 -0
  98. package/dist/components/data-views/list-page-split-hub-tokens.d.ts +12 -0
  99. package/dist/components/data-views/list-page-split-hub-tokens.js +8 -0
  100. package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -0
  101. package/dist/components/data-views/list-page-tree-column-header.d.ts +15 -0
  102. package/dist/components/data-views/list-page-tree-column-header.js +22 -0
  103. package/dist/components/data-views/list-page-tree-column-header.js.map +1 -0
  104. package/dist/components/data-views/list-page-tree-panel-shell.d.ts +25 -0
  105. package/dist/components/data-views/list-page-tree-panel-shell.js +146 -0
  106. package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -0
  107. package/dist/components/data-views/os-folder-glyph.d.ts +35 -0
  108. package/dist/components/data-views/os-folder-glyph.js +104 -0
  109. package/dist/components/data-views/os-folder-glyph.js.map +1 -0
  110. package/dist/components/data-views/outline-tree-menu.d.ts +36 -0
  111. package/dist/components/data-views/outline-tree-menu.js +131 -0
  112. package/dist/components/data-views/outline-tree-menu.js.map +1 -0
  113. package/dist/components/table-properties/column-row.d.ts +22 -0
  114. package/dist/components/table-properties/column-row.js +153 -0
  115. package/dist/components/table-properties/column-row.js.map +1 -0
  116. package/dist/components/table-properties/draggable-list.d.ts +24 -0
  117. package/dist/components/table-properties/draggable-list.js +53 -0
  118. package/dist/components/table-properties/draggable-list.js.map +1 -0
  119. package/dist/components/table-properties/drawer-button.d.ts +110 -0
  120. package/dist/components/table-properties/drawer-button.js +2748 -0
  121. package/dist/components/table-properties/drawer-button.js.map +1 -0
  122. package/dist/components/table-properties/drawer.d.ts +100 -0
  123. package/dist/components/table-properties/drawer.js +2595 -0
  124. package/dist/components/table-properties/drawer.js.map +1 -0
  125. package/dist/components/table-properties/filter-card.d.ts +24 -0
  126. package/dist/components/table-properties/filter-card.js +854 -0
  127. package/dist/components/table-properties/filter-card.js.map +1 -0
  128. package/dist/components/table-properties/index.d.ts +14 -0
  129. package/dist/components/table-properties/index.js +2768 -0
  130. package/dist/components/table-properties/index.js.map +1 -0
  131. package/dist/components/table-properties/sort-card.d.ts +20 -0
  132. package/dist/components/table-properties/sort-card.js +102 -0
  133. package/dist/components/table-properties/sort-card.js.map +1 -0
  134. package/dist/components/templates/dedicated-search-landing-template.d.ts +21 -0
  135. package/dist/components/templates/dedicated-search-landing-template.js +254 -0
  136. package/dist/components/templates/dedicated-search-landing-template.js.map +1 -0
  137. package/dist/components/templates/dedicated-search-results-template.d.ts +15 -0
  138. package/dist/components/templates/dedicated-search-results-template.js +16 -0
  139. package/dist/components/templates/dedicated-search-results-template.js.map +1 -0
  140. package/dist/components/templates/index.d.ts +9 -0
  141. package/dist/components/templates/index.js +2720 -0
  142. package/dist/components/templates/index.js.map +1 -0
  143. package/dist/components/templates/list-page.d.ts +83 -0
  144. package/dist/components/templates/list-page.js +2433 -0
  145. package/dist/components/templates/list-page.js.map +1 -0
  146. package/dist/components/templates/nested-secondary-panel-shell.d.ts +20 -0
  147. package/dist/components/templates/nested-secondary-panel-shell.js +54 -0
  148. package/dist/components/templates/nested-secondary-panel-shell.js.map +1 -0
  149. package/dist/components/ui/accordion.d.ts +10 -0
  150. package/dist/components/ui/accordion.js +74 -0
  151. package/dist/components/ui/accordion.js.map +1 -0
  152. package/dist/components/ui/alert-dialog.d.ts +37 -0
  153. package/dist/components/ui/alert-dialog.js +201 -0
  154. package/dist/components/ui/alert-dialog.js.map +1 -0
  155. package/dist/components/ui/avatar.d.ts +84 -0
  156. package/dist/components/ui/avatar.js +328 -0
  157. package/dist/components/ui/avatar.js.map +1 -0
  158. package/dist/components/ui/badge.d.ts +13 -0
  159. package/dist/components/ui/badge.js +49 -0
  160. package/dist/components/ui/badge.js.map +1 -0
  161. package/dist/components/ui/banner.d.ts +62 -0
  162. package/dist/components/ui/banner.js +364 -0
  163. package/dist/components/ui/banner.js.map +1 -0
  164. package/dist/components/ui/breadcrumb.d.ts +14 -0
  165. package/dist/components/ui/breadcrumb.js +114 -0
  166. package/dist/components/ui/breadcrumb.js.map +1 -0
  167. package/dist/components/ui/button.d.ts +16 -0
  168. package/dist/components/ui/button.js +59 -0
  169. package/dist/components/ui/button.js.map +1 -0
  170. package/dist/components/ui/calendar.d.ts +13 -0
  171. package/dist/components/ui/calendar.js +238 -0
  172. package/dist/components/ui/calendar.js.map +1 -0
  173. package/dist/components/ui/card.d.ts +14 -0
  174. package/dist/components/ui/card.js +102 -0
  175. package/dist/components/ui/card.js.map +1 -0
  176. package/dist/components/ui/chart.d.ts +58 -0
  177. package/dist/components/ui/chart.js +292 -0
  178. package/dist/components/ui/chart.js.map +1 -0
  179. package/dist/components/ui/checkbox.d.ts +23 -0
  180. package/dist/components/ui/checkbox.js +155 -0
  181. package/dist/components/ui/checkbox.js.map +1 -0
  182. package/dist/components/ui/coach-mark.d.ts +27 -0
  183. package/dist/components/ui/coach-mark.js +306 -0
  184. package/dist/components/ui/coach-mark.js.map +1 -0
  185. package/dist/components/ui/collapsible.d.ts +8 -0
  186. package/dist/components/ui/collapsible.js +35 -0
  187. package/dist/components/ui/collapsible.js.map +1 -0
  188. package/dist/components/ui/command.d.ts +36 -0
  189. package/dist/components/ui/command.js +274 -0
  190. package/dist/components/ui/command.js.map +1 -0
  191. package/dist/components/ui/context-menu.d.ts +32 -0
  192. package/dist/components/ui/context-menu.js +245 -0
  193. package/dist/components/ui/context-menu.js.map +1 -0
  194. package/dist/components/ui/date-picker-field.d.ts +38 -0
  195. package/dist/components/ui/date-picker-field.js +550 -0
  196. package/dist/components/ui/date-picker-field.js.map +1 -0
  197. package/dist/components/ui/dialog.d.ts +22 -0
  198. package/dist/components/ui/dialog.js +200 -0
  199. package/dist/components/ui/dialog.js.map +1 -0
  200. package/dist/components/ui/dot-pattern.d.ts +21 -0
  201. package/dist/components/ui/dot-pattern.js +139 -0
  202. package/dist/components/ui/dot-pattern.js.map +1 -0
  203. package/dist/components/ui/drag-handle-grip.d.ts +10 -0
  204. package/dist/components/ui/drag-handle-grip.js +15 -0
  205. package/dist/components/ui/drag-handle-grip.js.map +1 -0
  206. package/dist/components/ui/drawer.d.ts +16 -0
  207. package/dist/components/ui/drawer.js +125 -0
  208. package/dist/components/ui/drawer.js.map +1 -0
  209. package/dist/components/ui/dropdown-menu.d.ts +45 -0
  210. package/dist/components/ui/dropdown-menu.js +353 -0
  211. package/dist/components/ui/dropdown-menu.js.map +1 -0
  212. package/dist/components/ui/export-drawer.d.ts +11 -0
  213. package/dist/components/ui/export-drawer.js +1658 -0
  214. package/dist/components/ui/export-drawer.js.map +1 -0
  215. package/dist/components/ui/field.d.ts +30 -0
  216. package/dist/components/ui/field.js +249 -0
  217. package/dist/components/ui/field.js.map +1 -0
  218. package/dist/components/ui/form.d.ts +28 -0
  219. package/dist/components/ui/form.js +110 -0
  220. package/dist/components/ui/form.js.map +1 -0
  221. package/dist/components/ui/hover-card.d.ts +9 -0
  222. package/dist/components/ui/hover-card.js +43 -0
  223. package/dist/components/ui/hover-card.js.map +1 -0
  224. package/dist/components/ui/input-group.d.ts +20 -0
  225. package/dist/components/ui/input-group.js +219 -0
  226. package/dist/components/ui/input-group.js.map +1 -0
  227. package/dist/components/ui/input-mask.d.ts +39 -0
  228. package/dist/components/ui/input-mask.js +118 -0
  229. package/dist/components/ui/input-mask.js.map +1 -0
  230. package/dist/components/ui/input.d.ts +5 -0
  231. package/dist/components/ui/input.js +30 -0
  232. package/dist/components/ui/input.js.map +1 -0
  233. package/dist/components/ui/kbd.d.ts +20 -0
  234. package/dist/components/ui/kbd.js +45 -0
  235. package/dist/components/ui/kbd.js.map +1 -0
  236. package/dist/components/ui/key-metrics-context.d.ts +19 -0
  237. package/dist/components/ui/key-metrics-context.js +26 -0
  238. package/dist/components/ui/key-metrics-context.js.map +1 -0
  239. package/dist/components/ui/key-metrics.d.ts +131 -0
  240. package/dist/components/ui/key-metrics.js +1015 -0
  241. package/dist/components/ui/key-metrics.js.map +1 -0
  242. package/dist/components/ui/label.d.ts +6 -0
  243. package/dist/components/ui/label.js +28 -0
  244. package/dist/components/ui/label.js.map +1 -0
  245. package/dist/components/ui/list-page-view-frame.d.ts +22 -0
  246. package/dist/components/ui/list-page-view-frame.js +24 -0
  247. package/dist/components/ui/list-page-view-frame.js.map +1 -0
  248. package/dist/components/ui/page-header.d.ts +51 -0
  249. package/dist/components/ui/page-header.js +372 -0
  250. package/dist/components/ui/page-header.js.map +1 -0
  251. package/dist/components/ui/payment-card-fields.d.ts +10 -0
  252. package/dist/components/ui/payment-card-fields.js +80 -0
  253. package/dist/components/ui/payment-card-fields.js.map +1 -0
  254. package/dist/components/ui/popover.d.ts +10 -0
  255. package/dist/components/ui/popover.js +47 -0
  256. package/dist/components/ui/popover.js.map +1 -0
  257. package/dist/components/ui/radio-group.d.ts +29 -0
  258. package/dist/components/ui/radio-group.js +190 -0
  259. package/dist/components/ui/radio-group.js.map +1 -0
  260. package/dist/components/ui/resizable.d.ts +16 -0
  261. package/dist/components/ui/resizable.js +51 -0
  262. package/dist/components/ui/resizable.js.map +1 -0
  263. package/dist/components/ui/scroll-area.d.ts +8 -0
  264. package/dist/components/ui/scroll-area.js +66 -0
  265. package/dist/components/ui/scroll-area.js.map +1 -0
  266. package/dist/components/ui/select.d.ts +18 -0
  267. package/dist/components/ui/select.js +186 -0
  268. package/dist/components/ui/select.js.map +1 -0
  269. package/dist/components/ui/selection-tile-grid.d.ts +52 -0
  270. package/dist/components/ui/selection-tile-grid.js +347 -0
  271. package/dist/components/ui/selection-tile-grid.js.map +1 -0
  272. package/dist/components/ui/separator.d.ts +7 -0
  273. package/dist/components/ui/separator.js +33 -0
  274. package/dist/components/ui/separator.js.map +1 -0
  275. package/dist/components/ui/sheet.d.ts +18 -0
  276. package/dist/components/ui/sheet.js +181 -0
  277. package/dist/components/ui/sheet.js.map +1 -0
  278. package/dist/components/ui/sidebar.d.ts +94 -0
  279. package/dist/components/ui/sidebar.js +805 -0
  280. package/dist/components/ui/sidebar.js.map +1 -0
  281. package/dist/components/ui/skeleton.d.ts +5 -0
  282. package/dist/components/ui/skeleton.js +22 -0
  283. package/dist/components/ui/skeleton.js.map +1 -0
  284. package/dist/components/ui/slider.d.ts +7 -0
  285. package/dist/components/ui/slider.js +66 -0
  286. package/dist/components/ui/slider.js.map +1 -0
  287. package/dist/components/ui/sonner.d.ts +6 -0
  288. package/dist/components/ui/sonner.js +38 -0
  289. package/dist/components/ui/sonner.js.map +1 -0
  290. package/dist/components/ui/status-badge.d.ts +38 -0
  291. package/dist/components/ui/status-badge.js +77 -0
  292. package/dist/components/ui/status-badge.js.map +1 -0
  293. package/dist/components/ui/table.d.ts +13 -0
  294. package/dist/components/ui/table.js +115 -0
  295. package/dist/components/ui/table.js.map +1 -0
  296. package/dist/components/ui/tabs.d.ts +15 -0
  297. package/dist/components/ui/tabs.js +93 -0
  298. package/dist/components/ui/tabs.js.map +1 -0
  299. package/dist/components/ui/textarea.d.ts +6 -0
  300. package/dist/components/ui/textarea.js +25 -0
  301. package/dist/components/ui/textarea.js.map +1 -0
  302. package/dist/components/ui/tip.d.ts +12 -0
  303. package/dist/components/ui/tip.js +61 -0
  304. package/dist/components/ui/tip.js.map +1 -0
  305. package/dist/components/ui/toggle-group.d.ts +14 -0
  306. package/dist/components/ui/toggle-group.js +104 -0
  307. package/dist/components/ui/toggle-group.js.map +1 -0
  308. package/dist/components/ui/toggle-switch.d.ts +10 -0
  309. package/dist/components/ui/toggle-switch.js +33 -0
  310. package/dist/components/ui/toggle-switch.js.map +1 -0
  311. package/dist/components/ui/toggle.d.ts +13 -0
  312. package/dist/components/ui/toggle.js +51 -0
  313. package/dist/components/ui/toggle.js.map +1 -0
  314. package/dist/components/ui/tooltip.d.ts +10 -0
  315. package/dist/components/ui/tooltip.js +68 -0
  316. package/dist/components/ui/tooltip.js.map +1 -0
  317. package/dist/components/ui/view-segmented-control.d.ts +31 -0
  318. package/dist/components/ui/view-segmented-control.js +167 -0
  319. package/dist/components/ui/view-segmented-control.js.map +1 -0
  320. package/dist/data-list-view-registry-CyBoBML4.d.ts +73 -0
  321. package/dist/hooks/use-app-theme.d.ts +24 -0
  322. package/dist/hooks/use-app-theme.js +286 -0
  323. package/dist/hooks/use-app-theme.js.map +1 -0
  324. package/dist/hooks/use-coach-mark.d.ts +86 -0
  325. package/dist/hooks/use-coach-mark.js +218 -0
  326. package/dist/hooks/use-coach-mark.js.map +1 -0
  327. package/dist/hooks/use-mobile.d.ts +3 -0
  328. package/dist/hooks/use-mobile.js +29 -0
  329. package/dist/hooks/use-mobile.js.map +1 -0
  330. package/dist/hooks/use-mod-key-label.d.ts +6 -0
  331. package/dist/hooks/use-mod-key-label.js +25 -0
  332. package/dist/hooks/use-mod-key-label.js.map +1 -0
  333. package/dist/index.d.ts +120 -0
  334. package/dist/index.js +13324 -0
  335. package/dist/index.js.map +1 -0
  336. package/dist/lib/compose-refs.d.ts +6 -0
  337. package/dist/lib/compose-refs.js +17 -0
  338. package/dist/lib/compose-refs.js.map +1 -0
  339. package/dist/lib/conditional-rule-match.d.ts +30 -0
  340. package/dist/lib/conditional-rule-match.js +66 -0
  341. package/dist/lib/conditional-rule-match.js.map +1 -0
  342. package/dist/lib/data-list-display-options.d.ts +26 -0
  343. package/dist/lib/data-list-display-options.js +14 -0
  344. package/dist/lib/data-list-display-options.js.map +1 -0
  345. package/dist/lib/data-list-view-registry.d.ts +2 -0
  346. package/dist/lib/data-list-view-registry.js +102 -0
  347. package/dist/lib/data-list-view-registry.js.map +1 -0
  348. package/dist/lib/data-list-view-surface.d.ts +2 -0
  349. package/dist/lib/data-list-view-surface.js +80 -0
  350. package/dist/lib/data-list-view-surface.js.map +1 -0
  351. package/dist/lib/data-list-view.d.ts +21 -0
  352. package/dist/lib/data-list-view.js +25 -0
  353. package/dist/lib/data-list-view.js.map +1 -0
  354. package/dist/lib/date-filter.d.ts +22 -0
  355. package/dist/lib/date-filter.js +61 -0
  356. package/dist/lib/date-filter.js.map +1 -0
  357. package/dist/lib/dev-log.d.ts +8 -0
  358. package/dist/lib/dev-log.js +10 -0
  359. package/dist/lib/dev-log.js.map +1 -0
  360. package/dist/lib/dropdown-menu-surface.d.ts +14 -0
  361. package/dist/lib/dropdown-menu-surface.js +6 -0
  362. package/dist/lib/dropdown-menu-surface.js.map +1 -0
  363. package/dist/lib/editable-target.d.ts +12 -0
  364. package/dist/lib/editable-target.js +12 -0
  365. package/dist/lib/editable-target.js.map +1 -0
  366. package/dist/lib/list-page-table-properties.d.ts +35 -0
  367. package/dist/lib/list-page-table-properties.js +81 -0
  368. package/dist/lib/list-page-table-properties.js.map +1 -0
  369. package/dist/lib/raf-throttle.d.ts +23 -0
  370. package/dist/lib/raf-throttle.js +27 -0
  371. package/dist/lib/raf-throttle.js.map +1 -0
  372. package/dist/lib/row-height.d.ts +16 -0
  373. package/dist/lib/row-height.js +10 -0
  374. package/dist/lib/row-height.js.map +1 -0
  375. package/dist/lib/table-properties-types.d.ts +83 -0
  376. package/dist/lib/table-properties-types.js +19 -0
  377. package/dist/lib/table-properties-types.js.map +1 -0
  378. package/dist/lib/utils.d.ts +5 -0
  379. package/dist/lib/utils.js +11 -0
  380. package/dist/lib/utils.js.map +1 -0
  381. package/package.json +83 -19
  382. package/src/components/data-table/filter-date-calendar.tsx +38 -0
  383. package/src/components/data-table/filter-text-value-input.tsx +77 -0
  384. package/src/components/data-table/index.tsx +1678 -0
  385. package/src/components/data-table/pagination.tsx +255 -0
  386. package/src/components/data-table/types.ts +96 -0
  387. package/src/components/data-table/use-table-state.ts +767 -0
  388. package/src/components/data-views/board-card-primitives.tsx +93 -0
  389. package/src/components/data-views/data-row-list.tsx +183 -0
  390. package/src/components/data-views/finder-panel-view.tsx +405 -0
  391. package/src/components/data-views/folder-grid-view.tsx +86 -0
  392. package/src/components/data-views/hub-table.tsx +498 -0
  393. package/src/components/data-views/index.ts +28 -0
  394. package/src/components/data-views/list-page-board-card.tsx +192 -0
  395. package/src/components/data-views/list-page-board-template.tsx +122 -0
  396. package/src/components/data-views/list-page-connected-view-body.tsx +66 -0
  397. package/src/components/data-views/list-page-split-details-placeholder.tsx +39 -0
  398. package/src/components/data-views/list-page-split-hub-chrome.tsx +60 -0
  399. package/src/components/data-views/list-page-split-hub-tokens.ts +16 -0
  400. package/src/components/data-views/list-page-tree-column-header.tsx +31 -0
  401. package/src/components/data-views/list-page-tree-panel-shell.tsx +91 -0
  402. package/src/components/data-views/os-folder-glyph.tsx +141 -0
  403. package/src/components/data-views/outline-tree-menu.tsx +157 -0
  404. package/src/components/table-properties/column-row.tsx +90 -0
  405. package/src/components/table-properties/draggable-list.ts +54 -0
  406. package/src/components/table-properties/drawer-button.tsx +300 -0
  407. package/src/components/table-properties/drawer.tsx +1148 -0
  408. package/src/components/table-properties/filter-card.tsx +251 -0
  409. package/src/components/table-properties/index.ts +36 -0
  410. package/src/components/table-properties/sort-card.tsx +63 -0
  411. package/src/components/templates/dedicated-search-landing-template.tsx +124 -0
  412. package/src/components/templates/dedicated-search-results-template.tsx +19 -0
  413. package/src/components/templates/index.ts +33 -0
  414. package/src/components/templates/list-page.tsx +602 -0
  415. package/src/components/templates/nested-secondary-panel-shell.tsx +70 -0
  416. package/src/components/ui/accordion.tsx +92 -0
  417. package/src/components/ui/alert-dialog.tsx +221 -0
  418. package/src/components/ui/avatar.tsx +13 -2
  419. package/src/components/ui/banner.tsx +2 -2
  420. package/src/components/ui/button.tsx +4 -4
  421. package/src/components/ui/calendar.tsx +1 -1
  422. package/src/components/ui/coach-mark.tsx +1 -1
  423. package/src/components/ui/context-menu.tsx +291 -0
  424. package/src/components/ui/date-picker-field.tsx +2 -2
  425. package/src/components/ui/dot-pattern.tsx +183 -0
  426. package/src/components/ui/export-drawer.tsx +375 -0
  427. package/src/components/ui/hover-card.tsx +66 -0
  428. package/src/components/ui/key-metrics-context.tsx +78 -0
  429. package/src/components/ui/key-metrics.tsx +1133 -0
  430. package/src/components/ui/list-page-view-frame.tsx +64 -0
  431. package/src/components/ui/page-header.tsx +244 -0
  432. package/src/components/ui/payment-card-fields.tsx +2 -2
  433. package/src/components/ui/resizable.tsx +68 -0
  434. package/src/components/ui/scroll-area.tsx +72 -0
  435. package/src/components/ui/selection-tile-grid.tsx +9 -2
  436. package/src/components/ui/sidebar.tsx +84 -12
  437. package/src/components/ui/slider.tsx +83 -0
  438. package/src/globals.css +2201 -7
  439. package/src/globals.d.ts +20 -0
  440. package/src/index.ts +68 -1
  441. package/src/lib/conditional-rule-match.ts +119 -0
  442. package/src/lib/data-list-display-options.ts +35 -0
  443. package/src/lib/data-list-view-registry.ts +104 -0
  444. package/src/lib/data-list-view-surface.ts +83 -0
  445. package/src/lib/data-list-view.ts +47 -0
  446. package/src/lib/dev-log.ts +10 -0
  447. package/src/lib/editable-target.ts +20 -0
  448. package/src/lib/list-page-table-properties.ts +48 -0
  449. package/src/lib/raf-throttle.ts +45 -0
  450. package/src/lib/row-height.ts +19 -0
  451. package/src/lib/table-properties-types.ts +98 -0
  452. package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
  453. package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +3 -3
  454. package/template/.cursor/rules/exxat-data-tables.mdc +1 -1
  455. package/template/.cursor/rules/exxat-ds-agents.mdc +2 -2
  456. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +2 -2
  457. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
  458. package/template/AGENTS.md +104 -78
  459. package/template/app/(app)/dashboard/loading.tsx +15 -3
  460. package/template/app/(app)/dashboard/page.tsx +14 -2
  461. package/template/app/(app)/examples/page.tsx +0 -2
  462. package/template/app/(app)/layout.tsx +17 -4
  463. package/template/app/(app)/loading.tsx +18 -1
  464. package/template/app/(app)/question-bank/find/page.tsx +1 -2
  465. package/template/app/(app)/question-bank/layout.tsx +1 -1
  466. package/template/app/(app)/question-bank/library/page.tsx +1 -2
  467. package/template/app/(app)/question-bank/list/page.tsx +1 -2
  468. package/template/app/(app)/question-bank/new/page.tsx +15 -20
  469. package/template/app/(app)/question-bank/page.tsx +1 -2
  470. package/template/app/(app)/settings/page.tsx +5 -4
  471. package/template/app/globals.css +14 -16
  472. package/template/components/ask-leo-sidebar.tsx +5 -1
  473. package/template/components/brand-color-picker.tsx +2 -2
  474. package/template/components/charts-overview.tsx +1 -1
  475. package/template/components/compliance-board-view.tsx +142 -0
  476. package/template/components/compliance-client.tsx +92 -0
  477. package/template/components/compliance-page-header.tsx +89 -0
  478. package/template/components/compliance-table.tsx +468 -0
  479. package/template/components/dashboard-report-charts.tsx +1 -1
  480. package/template/components/dashboard-tabs.tsx +1 -1
  481. package/template/components/data-table/filter-date-calendar.tsx +1 -38
  482. package/template/components/data-table/filter-text-value-input.tsx +1 -77
  483. package/template/components/data-table/index.tsx +1 -1634
  484. package/template/components/data-table/pagination.tsx +1 -255
  485. package/template/components/data-table/types.ts +1 -94
  486. package/template/components/data-table/use-table-state.test.ts +420 -0
  487. package/template/components/data-table/use-table-state.ts +1 -758
  488. package/template/components/data-view-dashboard-charts-compliance.tsx +963 -0
  489. package/template/components/data-view-dashboard-charts-team.tsx +971 -0
  490. package/template/components/data-view-dashboard-charts.tsx +1503 -0
  491. package/template/components/data-views/board-card-primitives.tsx +1 -93
  492. package/template/components/data-views/data-row-list.tsx +1 -183
  493. package/template/components/data-views/finder-panel-view.tsx +1 -405
  494. package/template/components/data-views/folder-grid-view.tsx +1 -86
  495. package/template/components/data-views/hub-table.tsx +1 -0
  496. package/template/components/data-views/index.ts +50 -37
  497. package/template/components/data-views/list-page-board-card.tsx +1 -192
  498. package/template/components/data-views/list-page-board-template.tsx +1 -122
  499. package/template/components/data-views/list-page-connected-view-body.tsx +1 -66
  500. package/template/components/data-views/list-page-split-details-placeholder.tsx +1 -39
  501. package/template/components/data-views/list-page-split-hub-chrome.tsx +1 -68
  502. package/template/components/data-views/list-page-split-hub-tokens.ts +1 -16
  503. package/template/components/data-views/list-page-tree-column-header.tsx +1 -31
  504. package/template/components/data-views/list-page-tree-panel-shell.tsx +1 -91
  505. package/template/components/data-views/list-page-view-frame.tsx +5 -53
  506. package/template/components/data-views/os-folder-glyph.tsx +1 -129
  507. package/template/components/data-views/outline-tree-menu.tsx +1 -157
  508. package/template/components/export-drawer.test.tsx +71 -0
  509. package/template/components/export-drawer.tsx +1 -375
  510. package/template/components/exxat-product-logo.tsx +5 -5
  511. package/template/components/hub-tree-panel-view.tsx +2 -2
  512. package/template/components/invite-collaborators-drawer.tsx +3 -3
  513. package/template/components/key-metrics-ask-leo-bridge.tsx +40 -0
  514. package/template/components/key-metrics.tsx +1 -1063
  515. package/template/components/leo-insight-indicator.tsx +2 -2
  516. package/template/components/new-placement-back-btn.tsx +28 -0
  517. package/template/components/new-placement-form.tsx +942 -0
  518. package/template/components/new-question-composer.tsx +456 -408
  519. package/template/components/onboarding/index.ts +9 -0
  520. package/template/components/onboarding/onboarding-01.tsx +1 -1
  521. package/template/components/onboarding/onboarding-02.tsx +1 -1
  522. package/template/components/onboarding/onboarding-03.tsx +1 -1
  523. package/template/components/onboarding/onboarding-04.tsx +1 -1
  524. package/template/components/page-header.tsx +8 -226
  525. package/template/components/placement-board-card.tsx +250 -0
  526. package/template/components/placement-detail.tsx +438 -0
  527. package/template/components/placements-board-view.tsx +397 -0
  528. package/template/components/placements-client.tsx +220 -0
  529. package/template/components/placements-list-view.tsx +124 -0
  530. package/template/components/placements-page-header.tsx +166 -0
  531. package/template/components/placements-table-cells.test.tsx +22 -0
  532. package/template/components/placements-table-cells.tsx +173 -0
  533. package/template/components/placements-table-columns.tsx +210 -0
  534. package/template/components/placements-table.tsx +934 -0
  535. package/template/components/product-switcher.tsx +3 -4
  536. package/template/components/product-wordmark.tsx +2 -1
  537. package/template/components/question-bank-client.tsx +5 -5
  538. package/template/components/question-bank-hub-client.tsx +1 -1
  539. package/template/components/question-bank-new-folder-sheet.tsx +2 -2
  540. package/template/components/question-bank-secondary-nav.tsx +3 -3
  541. package/template/components/question-bank-table.tsx +541 -431
  542. package/template/components/rotations-empty-state.tsx +50 -0
  543. package/template/components/rotations-panel-activator.tsx +8 -0
  544. package/template/components/settings-appearance-card.tsx +3 -4
  545. package/template/components/settings-client.tsx +15 -59
  546. package/template/components/settings-form-row.tsx +4 -9
  547. package/template/components/{app-sidebar-dynamic.tsx → sidebar/app-sidebar-dynamic.tsx} +1 -1
  548. package/template/components/{app-sidebar.tsx → sidebar/app-sidebar.tsx} +59 -74
  549. package/template/components/sidebar/index.ts +16 -0
  550. package/template/components/{secondary-nav.tsx → sidebar/secondary-nav.tsx} +2 -2
  551. package/template/components/{secondary-panel.tsx → sidebar/secondary-panel.tsx} +50 -7
  552. package/template/components/{sidebar-auto-collapse.tsx → sidebar/sidebar-auto-collapse.tsx} +6 -2
  553. package/template/components/{sidebar-shell.tsx → sidebar/sidebar-shell.tsx} +1 -1
  554. package/template/components/site-header.tsx +1 -1
  555. package/template/components/sites-board-view.tsx +67 -0
  556. package/template/components/sites-client.tsx +154 -0
  557. package/template/components/sites-table.tsx +249 -0
  558. package/template/components/table-properties/column-row.tsx +1 -90
  559. package/template/components/table-properties/draggable-list.ts +1 -49
  560. package/template/components/table-properties/drawer-button.tsx +1 -262
  561. package/template/components/table-properties/drawer.tsx +1 -1166
  562. package/template/components/table-properties/filter-card.tsx +1 -251
  563. package/template/components/table-properties/sort-card.tsx +1 -59
  564. package/template/components/table-properties/types.ts +28 -71
  565. package/template/components/team-board-view.tsx +122 -0
  566. package/template/components/team-client.tsx +100 -0
  567. package/template/components/team-page-header.tsx +92 -0
  568. package/template/components/team-table.tsx +553 -0
  569. package/template/components/templates/dedicated-search-landing-template.tsx +1 -124
  570. package/template/components/templates/dedicated-search-results-template.tsx +1 -19
  571. package/template/components/templates/list-page.tsx +1 -608
  572. package/template/components/templates/nested-secondary-panel-shell.tsx +1 -63
  573. package/template/components/templates/new-focus-template.tsx +659 -0
  574. package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
  575. package/template/components/ui/accordion.tsx +1 -0
  576. package/template/components/ui/alert-dialog.tsx +1 -0
  577. package/template/components/ui/context-menu.tsx +1 -0
  578. package/template/components/ui/dot-pattern.tsx +1 -183
  579. package/template/components/ui/hover-card.tsx +1 -0
  580. package/template/components/ui/resizable.tsx +1 -68
  581. package/template/components/ui/scroll-area.tsx +1 -0
  582. package/template/components/ui/slider.tsx +1 -0
  583. package/template/docs/blueprints/README.md +86 -0
  584. package/template/docs/blueprints/_template.md +91 -0
  585. package/template/docs/blueprints/board-card.md +123 -0
  586. package/template/docs/blueprints/data-table.md +139 -0
  587. package/template/docs/blueprints/key-metrics.md +128 -0
  588. package/template/docs/blueprints/list-page-template.md +123 -0
  589. package/template/docs/blueprints/page-header.md +130 -0
  590. package/template/docs/command-menu-pattern.md +1 -1
  591. package/template/docs/component-selection-guide.md +224 -0
  592. package/template/docs/components-audit-2026-05.md +158 -0
  593. package/template/docs/data-views-pattern.md +17 -54
  594. package/template/docs/drawer-vs-dialog-pattern.md +1 -3
  595. package/template/docs/migrations/0001-brand-deep-alias-stabilization.md +95 -0
  596. package/template/docs/migrations/0002-exxat-token-namespace.md +154 -0
  597. package/template/docs/migrations/0003-globals-css-canonical.md +110 -0
  598. package/template/docs/migrations/README.md +100 -0
  599. package/template/docs/migrations/_template.md +64 -0
  600. package/template/docs/shell-surface-elevation-pattern.md +3 -5
  601. package/template/docs/token-taxonomy.md +416 -0
  602. package/template/eslint.config.mjs +27 -0
  603. package/template/hooks/use-secondary-panel-hub-nav.ts +1 -1
  604. package/template/lib/command-menu-config.ts +0 -1
  605. package/template/lib/command-menu-search-data.ts +27 -11
  606. package/template/lib/compliance-supported-views.ts +10 -0
  607. package/template/lib/conditional-rule-match.ts +6 -97
  608. package/template/lib/data-list-display-options.ts +1 -49
  609. package/template/lib/data-list-view-registry.ts +1 -104
  610. package/template/lib/data-list-view-surface.ts +1 -83
  611. package/template/lib/data-list-view.ts +1 -47
  612. package/template/lib/data-view-dashboard-placements-layout.ts +215 -0
  613. package/template/lib/data-view-dashboard-storage.ts +35 -38
  614. package/template/lib/dev-log.ts +1 -8
  615. package/template/lib/editable-target.ts +1 -10
  616. package/template/lib/list-page-table-properties.ts +1 -48
  617. package/template/lib/list-status-badges.ts +97 -4
  618. package/template/lib/mock/compliance-kpi.ts +61 -0
  619. package/template/lib/mock/compliance.ts +146 -0
  620. package/template/lib/mock/navigation.tsx +0 -9
  621. package/template/lib/mock/placements-kpi.ts +134 -0
  622. package/template/lib/mock/placements.ts +176 -0
  623. package/template/lib/mock/sites-directory.ts +16 -0
  624. package/template/lib/mock/sites-kpi.ts +25 -0
  625. package/template/lib/mock/team-kpi.ts +60 -0
  626. package/template/lib/mock/team.ts +118 -0
  627. package/template/lib/placement-board-card-layout.ts +79 -0
  628. package/template/lib/placements-supported-views.ts +12 -0
  629. package/template/lib/question-bank-supported-views.ts +0 -1
  630. package/template/lib/raf-throttle.ts +1 -45
  631. package/template/lib/row-height.ts +4 -10
  632. package/template/lib/sidebar-state-cookie.ts +11 -2
  633. package/template/lib/sites-supported-views.ts +10 -0
  634. package/template/lib/table-state-lifecycle.ts +2 -2
  635. package/template/lib/team-supported-views.ts +10 -0
  636. package/template/package.json +1 -0
  637. package/template/tests/setup.ts +25 -0
  638. package/consumer-extras/AGENTS.md +0 -76
  639. package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +0 -37
  640. package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +0 -57
  641. package/consumer-extras/patterns/consumer-app-pattern.md +0 -39
  642. package/consumer-extras/patterns/focused-workflow-page-pattern.md +0 -84
  643. package/src/components/ui/button-group.tsx +0 -81
  644. package/src/theme.css +0 -16
  645. package/src/tokens/README.md +0 -15
  646. package/src/tokens/base.css +0 -337
  647. package/src/tokens/high-contrast.css +0 -1195
  648. package/src/tokens/layers.css +0 -224
  649. package/src/tokens/tailwind-bridge.css +0 -118
  650. package/src/tokens/themes.css +0 -201
  651. package/template/app/(app)/data-list/layout.tsx +0 -43
  652. package/template/app/(app)/data-list/page.tsx +0 -10
  653. package/template/app/(app)/examples/focused-workflow/page.tsx +0 -5
  654. package/template/components/app-route-loading.tsx +0 -14
  655. package/template/components/dashboard-onboarding-gallery.tsx +0 -13
  656. package/template/components/dashboard-onboarding.tsx +0 -21
  657. package/template/components/data-views/list-page-calendar-view.tsx +0 -593
  658. package/template/components/data-views/list-page-folder-columns-panel.tsx +0 -345
  659. package/template/components/examples/focused-workflow-showcase.tsx +0 -183
  660. package/template/components/list-hub-board-view.tsx +0 -68
  661. package/template/components/list-hub-client.tsx +0 -186
  662. package/template/components/list-hub-list-view.tsx +0 -36
  663. package/template/components/list-hub-panel-activator.tsx +0 -8
  664. package/template/components/list-hub-secondary-nav.tsx +0 -121
  665. package/template/components/list-hub-table.tsx +0 -336
  666. package/template/components/question-bank-folder-columns-panel.tsx +0 -104
  667. package/template/components/question-bank-list-view.tsx +0 -53
  668. package/template/components/secondary-panel/nav-link-rows.tsx +0 -83
  669. package/template/components/secondary-panels/list-hub-panel.tsx +0 -39
  670. package/template/components/secondary-panels/question-bank-panel.tsx +0 -39
  671. package/template/components/secondary-panels/registry.tsx +0 -15
  672. package/template/components/section-cards.tsx +0 -106
  673. package/template/components/templates/focused-workflow-layouts.tsx +0 -448
  674. package/template/components/templates/focused-workflow-page-template.tsx +0 -69
  675. package/template/components/templates/page-loading-shell.tsx +0 -262
  676. package/template/components/ui/button-group.tsx +0 -1
  677. package/template/docs/consumer-app-pattern.md +0 -39
  678. package/template/docs/focused-workflow-page-pattern.md +0 -84
  679. package/template/lib/list-hub-nav.ts +0 -121
  680. package/template/lib/mock/list-hub-directory.ts +0 -27
  681. package/template/lib/mock/list-hub-kpi.ts +0 -27
  682. package/template/lib/page-loading-variant.ts +0 -40
  683. /package/template/components/{getting-started.tsx → onboarding/getting-started.tsx} +0 -0
  684. /package/template/components/{nav-documents.tsx → sidebar/nav-documents.tsx} +0 -0
  685. /package/template/components/{nav-main.tsx → sidebar/nav-main.tsx} +0 -0
  686. /package/template/components/{nav-secondary.tsx → sidebar/nav-secondary.tsx} +0 -0
  687. /package/template/components/{nav-user.tsx → sidebar/nav-user.tsx} +0 -0
  688. /package/template/components/{sidebar-auto-open.tsx → sidebar/sidebar-auto-open.tsx} +0 -0
@@ -1,20 +1,27 @@
1
1
  "use client"
2
2
 
3
3
  /**
4
- * Question bank — DataTable + TablePropertiesDrawer + connected views via `ListPageConnectedViewBody`.
4
+ * Question bank — thin wrapper around the centralized `<HubTable>`. Owns column defs,
5
+ * folder/panel/tree-panel custom views, the new-folder + customize-folder sheet, and
6
+ * forwards URL search via `HubTable.syncedSearchFromUrl`.
7
+ *
8
+ * Single dataset rule: `HubTable` runs one `useTableState(tableSourceItems, columns, …)`.
9
+ * Every non-table renderer (list, board, folder, panel, tree-panel, dashboard) reads
10
+ * `state.rows` — the same filtered/sorted/searched bag as the grid.
5
11
  */
6
12
 
7
13
  import * as React from "react"
8
14
  import dynamic from "next/dynamic"
9
15
  import { mailtoHref } from "@/lib/mailto"
10
- import { DataTable, DataTableToolbar } from "@/components/data-table"
11
16
  import type { DataListViewType } from "@/lib/data-list-view"
12
- import type { OpenTablePropertiesHandle } from "@/lib/list-page-table-properties"
13
- import { QUESTION_BANK_SUPPORTED_VIEWS } from "@/lib/question-bank-supported-views"
14
17
  import type { ColumnDef } from "@/components/data-table/types"
15
- import { useTableState } from "@/components/data-table/use-table-state"
16
- import { TablePropertiesDrawerButton } from "@/components/table-properties"
17
- import type { ConditionalRule, FilterFieldDef, FilterOperator } from "@/components/table-properties/types"
18
+ import {
19
+ HubTable,
20
+ type HubTableHandle,
21
+ type HubTableRenderers,
22
+ type HubTableRendererArgs,
23
+ } from "@/components/data-views"
24
+ import { QUESTION_BANK_SUPPORTED_VIEWS } from "@/lib/question-bank-supported-views"
18
25
  import { Button } from "@/components/ui/button"
19
26
  import {
20
27
  DropdownMenu,
@@ -24,13 +31,25 @@ import {
24
31
  } from "@/components/ui/dropdown-menu"
25
32
  import { Tip } from "@/components/ui/tip"
26
33
  import { Skeleton } from "@/components/ui/skeleton"
27
- import { ListPageConnectedViewBody } from "@/components/data-views/list-page-connected-view-body"
28
- import { ListPageCalendarView } from "@/components/data-views/list-page-calendar-view"
34
+ import {
35
+ ResizableHandle,
36
+ ResizablePanel,
37
+ ResizablePanelGroup,
38
+ } from "@/components/ui/resizable"
39
+ import {
40
+ Tooltip,
41
+ TooltipContent,
42
+ TooltipTrigger,
43
+ } from "@/components/ui/tooltip"
29
44
  import { ListPageSplitHubChrome } from "@/components/data-views/list-page-split-hub-chrome"
30
- import { defineHubViewRenderers } from "@/lib/hub-connected-view-renderers"
31
- import { QuestionBankFolderColumnsPanel } from "@/components/question-bank-folder-columns-panel"
45
+ import {
46
+ LIST_PAGE_SPLIT_MILLER_DETAIL_PANEL_CLASS,
47
+ LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS,
48
+ LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS,
49
+ } from "@/components/data-views/list-page-split-hub-tokens"
50
+ import { ListPageTreeColumnHeader } from "@/components/data-views/list-page-tree-column-header"
32
51
  import { QuestionBankBoardView, QUESTION_BANK_BOARD_GROUP_OPTIONS } from "@/components/question-bank-board-view"
33
- import { QuestionBankListView } from "@/components/question-bank-list-view"
52
+ import { ListPageBoardCard } from "@/components/data-views/list-page-board-card"
34
53
  import {
35
54
  QuestionBankFavoriteButton,
36
55
  QUESTION_BANK_FAVORITE_HOVER_GROUP,
@@ -49,17 +68,19 @@ import {
49
68
  type QuestionBankItem,
50
69
  type QuestionBankType,
51
70
  } from "@/lib/mock/question-bank"
52
- import type { QuestionBankFolder } from "@/lib/mock/question-bank-folders"
71
+ import {
72
+ type QuestionBankFolder,
73
+ QUESTION_BANK_FOLDER_COLOR_STYLES,
74
+ QUESTION_BANK_FOLDER_ICON_COLORS,
75
+ } from "@/lib/mock/question-bank-folders"
53
76
  import {
54
77
  toggleQuestionBankItemFavorite,
55
78
  applyQuestionBankHubDisplayFilters,
56
79
  type QuestionBankLandingFilterState,
57
80
  type QuestionBankNavState,
58
81
  } from "@/lib/question-bank-nav"
59
- import {
60
- DEFAULT_DATA_LIST_DISPLAY_OPTIONS,
61
- type DataListDisplayOptions,
62
- } from "@/lib/data-list-display-options"
82
+
83
+ // ─── Dynamic dashboard charts section ────────────────────────────────────────
63
84
 
64
85
  const QuestionBankDashboardChartsSection = dynamic(
65
86
  () =>
@@ -91,6 +112,8 @@ const QuestionBankDashboardChartsSection = dynamic(
91
112
  },
92
113
  )
93
114
 
115
+ // ─── Constants ───────────────────────────────────────────────────────────────
116
+
94
117
  const TYPE_LABEL: Record<QuestionBankType, string> = {
95
118
  multiple_choice: "Multiple choice",
96
119
  true_false: "True / false",
@@ -103,21 +126,6 @@ const DIFFICULTY_LABEL: Record<QuestionBankDifficulty, string> = {
103
126
  hard: "Hard",
104
127
  }
105
128
 
106
- function newQuestionBankItemId() {
107
- return `q-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
108
- }
109
-
110
- /** Folder id to use when adding a question from the root column (`parentId` null). */
111
- function defaultFolderIdForColumnParent(parentId: string | null, folders: QuestionBankFolder[]): string | null {
112
- if (parentId !== null) return parentId
113
- const roots = [...folders].filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name))
114
- return roots[0]?.id ?? null
115
- }
116
-
117
- function uniqueTopics(items: QuestionBankItem[]) {
118
- return [...new Set(items.map(i => i.topic))].sort().map(t => ({ value: t, label: t }))
119
- }
120
-
121
129
  const TYPE_FILTER_OPTS = (Object.keys(TYPE_LABEL) as QuestionBankType[]).map(k => ({
122
130
  value: k,
123
131
  label: TYPE_LABEL[k],
@@ -128,26 +136,18 @@ const DIFFICULTY_FILTER_OPTS = (Object.keys(DIFFICULTY_LABEL) as QuestionBankDif
128
136
  label: DIFFICULTY_LABEL[k],
129
137
  }))
130
138
 
131
- function columnToFilterFieldDef(c: ColumnDef<QuestionBankItem>): FilterFieldDef | null {
132
- if (!c.filter) return null
133
- const f = c.filter
134
- const defaultOps: FilterOperator[] =
135
- f.type === "select" || f.type === "date"
136
- ? ["is", "is_not"]
137
- : ["contains", "not_contains"]
138
- return {
139
- key: c.key,
140
- label: c.label,
141
- icon: f.icon ?? "fa-filter",
142
- type: f.type,
143
- operators: (f.operators ?? defaultOps) as FilterOperator[],
144
- options: f.options,
145
- ...(f.textMask ? { textMask: f.textMask } : {}),
146
- }
139
+ function newQuestionBankItemId() {
140
+ return `q-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
141
+ }
142
+
143
+ function defaultFolderIdForColumnParent(parentId: string | null, folders: QuestionBankFolder[]): string | null {
144
+ if (parentId !== null) return parentId
145
+ const roots = [...folders].filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name))
146
+ return roots[0]?.id ?? null
147
147
  }
148
148
 
149
- function columnsToFilterFields(cols: ColumnDef<QuestionBankItem>[]) {
150
- return cols.map(columnToFilterFieldDef).filter((x): x is FilterFieldDef => x !== null)
149
+ function uniqueTopics(items: QuestionBankItem[]) {
150
+ return [...new Set(items.map(i => i.topic))].sort().map(t => ({ value: t, label: t }))
151
151
  }
152
152
 
153
153
  function buildQuestionBankColumns(
@@ -156,18 +156,8 @@ function buildQuestionBankColumns(
156
156
  ): ColumnDef<QuestionBankItem>[] {
157
157
  const topicOpts = uniqueTopics(items)
158
158
  const { onToggleFavorite } = opts
159
-
160
- const COLUMN_SELECT: ColumnDef<QuestionBankItem> = {
161
- key: "select",
162
- label: "",
163
- width: 40,
164
- minWidth: 40,
165
- defaultPin: "left",
166
- lockPin: true,
167
- }
168
-
169
- const cols: ColumnDef<QuestionBankItem>[] = [COLUMN_SELECT]
170
- cols.push(
159
+ return [
160
+ { key: "select", label: "", width: 40, minWidth: 40, defaultPin: "left", lockPin: true },
171
161
  {
172
162
  key: "stem",
173
163
  label: "Question",
@@ -176,14 +166,10 @@ function buildQuestionBankColumns(
176
166
  sortable: true,
177
167
  sortKey: "stem",
178
168
  defaultPin: "left",
179
- filter: {
180
- type: "text",
181
- icon: "fa-file-lines",
182
- operators: ["contains", "not_contains"],
183
- },
169
+ filter: { type: "text", icon: "fa-file-lines", operators: ["contains", "not_contains"] },
184
170
  cell: row => (
185
171
  <div className={cn(QUESTION_BANK_FAVORITE_HOVER_GROUP, "flex min-w-0 items-start gap-2")}>
186
- <div className="flex min-w-0 flex-1 flex-col gap-0.5 pr-1">
172
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5 pe-1">
187
173
  <span className="line-clamp-2 text-sm font-medium text-foreground">{row.stem}</span>
188
174
  <span className="font-mono text-xs text-muted-foreground">{row.questionId}</span>
189
175
  </div>
@@ -198,12 +184,7 @@ function buildQuestionBankColumns(
198
184
  minWidth: 120,
199
185
  sortable: true,
200
186
  sortKey: "topic",
201
- filter: {
202
- type: "select",
203
- icon: "fa-layer-group",
204
- operators: ["is", "is_not"],
205
- options: topicOpts,
206
- },
187
+ filter: { type: "select", icon: "fa-layer-group", operators: ["is", "is_not"], options: topicOpts },
207
188
  cell: row => <span className="text-sm text-foreground/90">{row.topic}</span>,
208
189
  },
209
190
  {
@@ -213,12 +194,7 @@ function buildQuestionBankColumns(
213
194
  minWidth: 120,
214
195
  sortable: true,
215
196
  sortKey: "type",
216
- filter: {
217
- type: "select",
218
- icon: "fa-list-check",
219
- operators: ["is", "is_not"],
220
- options: TYPE_FILTER_OPTS,
221
- },
197
+ filter: { type: "select", icon: "fa-list-check", operators: ["is", "is_not"], options: TYPE_FILTER_OPTS },
222
198
  cell: row => <span className="text-sm text-foreground/90">{TYPE_LABEL[row.type]}</span>,
223
199
  },
224
200
  {
@@ -228,15 +204,8 @@ function buildQuestionBankColumns(
228
204
  minWidth: 96,
229
205
  sortable: true,
230
206
  sortKey: "difficulty",
231
- filter: {
232
- type: "select",
233
- icon: "fa-signal",
234
- operators: ["is", "is_not"],
235
- options: DIFFICULTY_FILTER_OPTS,
236
- },
237
- cell: row => (
238
- <span className="text-sm text-foreground/90">{DIFFICULTY_LABEL[row.difficulty]}</span>
239
- ),
207
+ filter: { type: "select", icon: "fa-signal", operators: ["is", "is_not"], options: DIFFICULTY_FILTER_OPTS },
208
+ cell: row => <span className="text-sm text-foreground/90">{DIFFICULTY_LABEL[row.difficulty]}</span>,
240
209
  },
241
210
  {
242
211
  key: "updatedAt",
@@ -257,11 +226,7 @@ function buildQuestionBankColumns(
257
226
  minWidth: 200,
258
227
  sortable: true,
259
228
  sortKey: "author",
260
- filter: {
261
- type: "text",
262
- icon: "fa-user",
263
- operators: ["contains", "not_contains"],
264
- },
229
+ filter: { type: "text", icon: "fa-user", operators: ["contains", "not_contains"] },
265
230
  cell: row => {
266
231
  const initials = initialsFromDisplayName(row.author)
267
232
  return (
@@ -312,168 +277,260 @@ function buildQuestionBankColumns(
312
277
  </div>
313
278
  ),
314
279
  },
315
- )
316
-
317
- return cols
280
+ ]
318
281
  }
319
282
 
320
- export type QuestionBankTableHandle = OpenTablePropertiesHandle
321
-
322
- export const QuestionBankTable = React.forwardRef<
323
- QuestionBankTableHandle,
324
- {
325
- items: QuestionBankItem[]
326
- /** When set, table / board / tree rows are limited to this nav scope (secondary sidebar). */
327
- navState?: QuestionBankNavState
328
- /** URL toolbar search binding (`?q=`) — omit on search landing so hub `q` does not pre-fill the grid search. */
329
- urlListSearch?: string
330
- /** When true, dedicated search shell: hub landing row filters; table toolbar search stays independent of URL `q`. */
331
- searchLanding?: boolean
332
- /** Applied with nav filters before `useTableState` when {@link searchLanding} is true. */
333
- landingFilters?: QuestionBankLandingFilterState | null
334
- view?: DataListViewType
335
- onViewChange?: (v: DataListViewType) => void
336
- /** Aligns Properties view tiles with `ListPageTemplate` `supportedViewTypes`. */
337
- supportedViewTypes?: readonly DataListViewType[]
338
- folders: QuestionBankFolder[]
339
- onFoldersChange: React.Dispatch<React.SetStateAction<QuestionBankFolder[]>>
340
- onItemsChange: React.Dispatch<React.SetStateAction<QuestionBankItem[]>>
341
- }
342
- >(function QuestionBankTable(
343
- {
344
- items,
345
- navState,
346
- urlListSearch,
347
- searchLanding,
348
- landingFilters,
349
- view = "table",
350
- onViewChange,
351
- supportedViewTypes = QUESTION_BANK_SUPPORTED_VIEWS,
352
- folders,
353
- onFoldersChange,
354
- onItemsChange,
355
- },
356
- ref,
357
- ) {
358
- const tableSourceItems = React.useMemo(() => {
359
- const nav = navState ?? { scope: "all" as const, folderId: null }
360
- const landing = searchLanding ? (landingFilters ?? null) : null
361
- return applyQuestionBankHubDisplayFilters(items, folders, nav, landing)
362
- }, [items, folders, navState, searchLanding, landingFilters])
363
-
364
- const toggleFavorite = React.useCallback(
365
- (row: QuestionBankItem) => {
366
- onItemsChange(prev => prev.map(r => (r.id === row.id ? toggleQuestionBankItemFavorite(r) : r)))
367
- },
368
- [onItemsChange],
369
- )
370
-
371
- const columns = React.useMemo(
372
- () => buildQuestionBankColumns(tableSourceItems, { onToggleFavorite: toggleFavorite }),
373
- [tableSourceItems, toggleFavorite],
374
- )
375
- const filterFields = React.useMemo(() => columnsToFilterFields(columns), [columns])
376
- const fieldDefinitionsForDrawer = React.useMemo(
377
- () =>
378
- columns
379
- .filter(c => c.key !== "select" && c.key !== "favorite" && c.key !== "actions")
380
- .map(c => ({ key: c.key, label: c.label, sortable: !!(c.sortable && (c.sortKey ?? c.key)) })),
381
- [columns],
382
- )
283
+ // ─── Folder columns panel (custom multi-column miller view) ─────────────────
383
284
 
384
- const resolveColumnLabel = React.useCallback(
385
- (key: string) => columns.find(c => c.key === key)?.label ?? key,
386
- [columns],
387
- )
285
+ interface HubFolderColumnsPanelProps {
286
+ folders: QuestionBankFolder[]
287
+ rows: QuestionBankItem[]
288
+ panelRenderDetail: (row: QuestionBankItem) => React.ReactNode
289
+ onAddFolder: (parentId: string | null) => void
290
+ onAddQuestion: (parentId: string | null) => void
291
+ onCustomizeFolder?: (folder: QuestionBankFolder) => void
292
+ }
388
293
 
389
- const [displayOptions, setDisplayOptions] = React.useState<DataListDisplayOptions>(DEFAULT_DATA_LIST_DISPLAY_OPTIONS)
390
- const patchDisplay = React.useCallback((patch: Partial<DataListDisplayOptions>) => {
391
- setDisplayOptions(prev => ({ ...prev, ...patch }))
392
- }, [])
294
+ type HierarchyItem = QuestionBankFolder | QuestionBankItem
393
295
 
394
- const [newFolderOpen, setNewFolderOpen] = React.useState(false)
395
- const [newFolderParentId, setNewFolderParentId] = React.useState<string | null>(null)
396
- const [customizingFolder, setCustomizingFolder] = React.useState<QuestionBankFolder | null>(null)
296
+ function isFolder(item: HierarchyItem): item is QuestionBankFolder {
297
+ return "parentId" in item
298
+ }
397
299
 
398
- const [conditionalRules, setConditionalRules] = React.useState<ConditionalRule[]>([])
399
- const addConditionalRule = React.useCallback((rule: Omit<ConditionalRule, "id">) => {
400
- setConditionalRules(prev => [...prev, { ...rule, id: `cr-${Date.now()}` }])
401
- }, [])
402
- const removeConditionalRule = React.useCallback((id: string) => {
403
- setConditionalRules(prev => prev.filter(r => r.id !== id))
404
- }, [])
405
- const updateConditionalRule = React.useCallback((id: string, patch: Partial<ConditionalRule>) => {
406
- setConditionalRules(prev => prev.map(r => r.id === id ? { ...r, ...patch } : r))
407
- }, [])
300
+ function isQuestion(item: HierarchyItem): item is QuestionBankItem {
301
+ return "stem" in item
302
+ }
408
303
 
409
- const tableState = useTableState(
410
- tableSourceItems,
411
- columns,
412
- { key: "updatedAt", dir: "desc" },
413
- undefined,
414
- searchLanding ? undefined : urlListSearch,
304
+ function HubFolderColumnsPanel({
305
+ folders,
306
+ rows,
307
+ panelRenderDetail,
308
+ onAddFolder,
309
+ onAddQuestion,
310
+ onCustomizeFolder,
311
+ }: HubFolderColumnsPanelProps) {
312
+ const [selectedPath, setSelectedPath] = React.useState<HierarchyItem[]>(() => {
313
+ const rootFolders = folders
314
+ .filter(f => f.parentId === null)
315
+ .sort((a, b) => a.name.localeCompare(b.name))
316
+ if (rootFolders.length > 0) return [rootFolders[0]]
317
+ return []
318
+ })
319
+
320
+ const isFirstRenderRef = React.useRef(true)
321
+
322
+ const rootFolders = React.useMemo(
323
+ () => folders.filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name)),
324
+ [folders],
415
325
  )
416
326
 
417
- const openNewFolderForColumn = React.useCallback((parentId: string | null) => {
418
- setNewFolderParentId(parentId)
419
- setCustomizingFolder(null)
420
- setNewFolderOpen(true)
421
- }, [])
327
+ const handleSelect = (item: HierarchyItem, depth: number) => {
328
+ setSelectedPath(prev => [...prev.slice(0, depth), item])
329
+ }
422
330
 
423
- const openCustomizeFolderSheet = React.useCallback((folder: QuestionBankFolder) => {
424
- setCustomizingFolder(folder)
425
- setNewFolderOpen(true)
331
+ React.useEffect(() => {
332
+ if (isFirstRenderRef.current && selectedPath.length > 0) {
333
+ const lastItem = selectedPath[selectedPath.length - 1]
334
+ if (isFolder(lastItem)) {
335
+ const folder = lastItem as QuestionBankFolder
336
+ const subfolders = folders.filter(f => f.parentId === folder.id).sort((a, b) => a.name.localeCompare(b.name))
337
+ const questionsInFolder = rows.filter(r => r.folderId === folder.id)
338
+ const items: HierarchyItem[] = [...subfolders, ...questionsInFolder]
339
+ if (items.length > 0 && !selectedPath[selectedPath.length + 1]) {
340
+ setSelectedPath(prev => [...prev, items[0]])
341
+ isFirstRenderRef.current = false
342
+ }
343
+ }
344
+ }
345
+ // eslint-disable-next-line react-hooks/exhaustive-deps
426
346
  }, [])
427
347
 
428
- const addQuestionInColumn = React.useCallback(
429
- (parentId: string | null) => {
430
- const folderId = defaultFolderIdForColumnParent(parentId, folders)
431
- if (!folderId) return
432
- const today = new Date()
433
- const y = today.getFullYear()
434
- const m = String(today.getMonth() + 1).padStart(2, "0")
435
- const d = String(today.getDate()).padStart(2, "0")
436
- onItemsChange(prev => [
437
- ...prev,
438
- {
439
- id: newQuestionBankItemId(),
440
- questionId: newQuestionBankQuestionId(),
441
- stem: "New question",
442
- topic: "General",
443
- type: "short_answer",
444
- difficulty: "medium",
445
- author: "Demo user",
446
- authorEmail: "demo.user@demo.exxat.io",
447
- updatedAt: `${y}-${m}-${d}`,
448
- folderId,
449
- },
450
- ])
451
- },
452
- [folders, onItemsChange],
453
- )
454
-
455
- const renderFilterOptionValue = React.useCallback(
456
- (fieldKey: string, value: string): React.ReactNode => {
457
- const col = columns.find(c => c.key === fieldKey)
458
- const opt = col?.filter?.options?.find(o => o.value === value)
459
- return <span className="text-foreground">{opt?.label ?? value}</span>
460
- },
461
- [columns],
462
- )
348
+ const columns: Array<{ items: HierarchyItem[]; depth: number; parentId?: string | null }> = React.useMemo(() => {
349
+ const cols: Array<{ items: HierarchyItem[]; depth: number; parentId?: string | null }> = [
350
+ { items: rootFolders, depth: 0, parentId: null },
351
+ ]
352
+ for (let i = 0; i < selectedPath.length; i++) {
353
+ const item = selectedPath[i]
354
+ if (isFolder(item)) {
355
+ const subfolders = folders.filter(f => f.parentId === item.id).sort((a, b) => a.name.localeCompare(b.name))
356
+ const questionsInFolder = rows.filter(r => r.folderId === item.id)
357
+ const items: HierarchyItem[] = [...subfolders, ...questionsInFolder]
358
+ if (items.length > 0) cols.push({ items, depth: i + 1, parentId: item.id })
359
+ }
360
+ }
361
+ return cols
362
+ }, [selectedPath, rootFolders, folders, rows])
463
363
 
464
- React.useImperativeHandle(ref, () => ({
465
- openPropertiesDrawer: () => {
466
- tableState.setSheetOpen(true)
467
- },
468
- }), [tableState])
364
+ const selectedLeaf = selectedPath.length > 0 ? selectedPath.at(-1)! : null
365
+ const selectedQuestion = selectedLeaf && isQuestion(selectedLeaf) ? (selectedLeaf as QuestionBankItem) : null
366
+ const selectedFolderLeaf = selectedLeaf && isFolder(selectedLeaf) ? (selectedLeaf as QuestionBankFolder) : null
469
367
 
470
- const questionBankBoardGroupKey = QUESTION_BANK_BOARD_GROUP_OPTIONS.some(
471
- o => o.key === displayOptions.boardGroupByColumnKey,
368
+ return (
369
+ <ResizablePanelGroup direction="horizontal" className="flex h-full min-h-0 w-full flex-1 overflow-hidden">
370
+ {columns.map(({ items, depth, parentId }, columnIdx) => (
371
+ <React.Fragment key={`col-${depth}`}>
372
+ {columnIdx > 0 && <ResizableHandle withHandle className={LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS} />}
373
+ <ResizablePanel
374
+ id={`col-${depth}`}
375
+ defaultSize={columnIdx === 0 ? 35 : columnIdx === 1 ? 35 : 30}
376
+ minSize={15}
377
+ className={LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS}
378
+ >
379
+ <ListPageTreeColumnHeader
380
+ title={
381
+ depth === 0
382
+ ? "Categories"
383
+ : selectedPath[depth - 1] && isFolder(selectedPath[depth - 1])
384
+ ? (selectedPath[depth - 1] as QuestionBankFolder).name
385
+ : "Items"
386
+ }
387
+ trailing={
388
+ <>
389
+ <span className="shrink-0 text-xs font-medium text-muted-foreground tabular-nums">{items.length}</span>
390
+ {depth < columns.length - 1 && items.length > 0 ? (
391
+ <div className="flex shrink-0 items-center gap-0.5">
392
+ <Tooltip>
393
+ <TooltipTrigger asChild>
394
+ <Button
395
+ size="icon-sm"
396
+ variant="ghost"
397
+ onClick={() => onAddFolder(parentId ?? null)}
398
+ aria-label="Add folder"
399
+ >
400
+ <i className="fa-light fa-folder-plus text-xs" aria-hidden="true" />
401
+ </Button>
402
+ </TooltipTrigger>
403
+ <TooltipContent side="top" sideOffset={4}>
404
+ Add folder
405
+ </TooltipContent>
406
+ </Tooltip>
407
+ <Tooltip>
408
+ <TooltipTrigger asChild>
409
+ <Button
410
+ size="icon-sm"
411
+ variant="ghost"
412
+ onClick={() => onAddQuestion(parentId ?? null)}
413
+ aria-label="Add question"
414
+ >
415
+ <i className="fa-light fa-plus text-xs" aria-hidden="true" />
416
+ </Button>
417
+ </TooltipTrigger>
418
+ <TooltipContent side="top" sideOffset={4}>
419
+ Add question
420
+ </TooltipContent>
421
+ </Tooltip>
422
+ </div>
423
+ ) : null}
424
+ </>
425
+ }
426
+ />
427
+ <div className="min-h-0 flex-1 overflow-y-auto py-1">
428
+ {items.map(item => {
429
+ const isSelected = selectedPath[depth]?.id === item.id
430
+ const isFolder_ = isFolder(item)
431
+ const folder = isFolder_ ? item : null
432
+ const question = isQuestion(item) ? item : null
433
+ const subfolderCount = isFolder_ ? folders.filter(f => f.parentId === item.id).length : 0
434
+ const questionCount = isFolder_ ? rows.filter(r => r.folderId === item.id).length : 0
435
+ const itemCount = subfolderCount + questionCount
436
+ return (
437
+ <div key={item.id} className="group flex items-center hover:bg-muted/50">
438
+ <button
439
+ onClick={() => handleSelect(item, depth)}
440
+ className={cn(
441
+ "flex flex-1 items-center gap-3 px-3 py-2 text-left text-sm transition-colors duration-75",
442
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
443
+ isSelected ? "bg-accent text-accent-foreground" : "text-foreground",
444
+ isFolder_ && !isSelected && folder?.colorKey && depth > 0
445
+ ? QUESTION_BANK_FOLDER_COLOR_STYLES[folder.colorKey]?.tile
446
+ : "",
447
+ )}
448
+ aria-selected={isSelected}
449
+ role="option"
450
+ >
451
+ {isFolder_ ? (
452
+ <i
453
+ className={cn(
454
+ "fa-folder text-sm shrink-0",
455
+ isSelected ? "fa-solid" : "fa-light",
456
+ folder?.colorKey && QUESTION_BANK_FOLDER_ICON_COLORS[folder.colorKey],
457
+ )}
458
+ aria-hidden="true"
459
+ />
460
+ ) : (
461
+ <i className={cn("fa-file text-sm shrink-0", isSelected ? "fa-solid" : "fa-light")} aria-hidden="true" />
462
+ )}
463
+ <span className={cn("min-w-0 flex-1 truncate leading-tight", isSelected && "font-medium")}>
464
+ {isFolder_ ? folder?.name : question?.stem}
465
+ </span>
466
+ <span
467
+ className={cn(
468
+ "shrink-0 tabular-nums text-xs ms-auto",
469
+ isSelected ? "text-accent-foreground/70" : "text-muted-foreground",
470
+ )}
471
+ >
472
+ {isFolder_
473
+ ? itemCount
474
+ : question?.type === "multiple_choice"
475
+ ? "MCQ"
476
+ : question?.difficulty?.charAt(0).toUpperCase()}
477
+ </span>
478
+ </button>
479
+ {isFolder_ && folder && (
480
+ <DropdownMenu>
481
+ <DropdownMenuTrigger asChild>
482
+ <Button
483
+ type="button"
484
+ size="icon-xs"
485
+ variant="ghost"
486
+ aria-label={`Actions for folder ${folder.name}`}
487
+ className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
488
+ >
489
+ <i className="fa-light fa-ellipsis text-xs" aria-hidden="true" />
490
+ </Button>
491
+ </DropdownMenuTrigger>
492
+ <DropdownMenuContent align="end">
493
+ <DropdownMenuItem onSelect={() => onCustomizeFolder?.(folder)}>
494
+ <i className="fa-light fa-wand-magic-sparkles text-xs" aria-hidden="true" />
495
+ Customize
496
+ </DropdownMenuItem>
497
+ </DropdownMenuContent>
498
+ </DropdownMenu>
499
+ )}
500
+ </div>
501
+ )
502
+ })}
503
+ </div>
504
+ </ResizablePanel>
505
+ </React.Fragment>
506
+ ))}
507
+ {(selectedQuestion || selectedFolderLeaf) && (
508
+ <>
509
+ <ResizableHandle withHandle className={LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS} />
510
+ <ResizablePanel id="col-detail" defaultSize={30} minSize={20} className={LIST_PAGE_SPLIT_MILLER_DETAIL_PANEL_CLASS}>
511
+ {selectedQuestion ? (
512
+ <>
513
+ <ListPageTreeColumnHeader title="Details" className="px-4" />
514
+ <div className="min-h-0 flex-1 overflow-y-auto px-4 py-4">
515
+ {panelRenderDetail(selectedQuestion)}
516
+ </div>
517
+ </>
518
+ ) : selectedFolderLeaf ? (
519
+ <div className="min-h-0 flex-1 overflow-hidden">
520
+ <FolderDetailsShell folder={selectedFolderLeaf} folders={folders} questions={rows} />
521
+ </div>
522
+ ) : null}
523
+ </ResizablePanel>
524
+ </>
525
+ )}
526
+ </ResizablePanelGroup>
472
527
  )
473
- ? displayOptions.boardGroupByColumnKey
474
- : "topic"
528
+ }
475
529
 
476
- const panelRenderDetail = (row: QuestionBankItem) => (
530
+ // ─── Detail renderer reused by panel + tree-panel ───────────────────────────
531
+
532
+ function questionBankPanelDetail(row: QuestionBankItem) {
533
+ return (
477
534
  <div className="flex min-w-0 flex-col gap-4">
478
535
  <div>
479
536
  <h3 className="text-sm font-semibold text-foreground mb-2">Question</h3>
@@ -505,14 +562,11 @@ export const QuestionBankTable = React.forwardRef<
505
562
  <span className="text-xs font-medium text-muted-foreground mt-0.5 shrink-0">
506
563
  {String.fromCharCode(65 + idx)}.
507
564
  </span>
508
- <span className={cn(
509
- "text-sm",
510
- option.isCorrect ? "text-foreground font-medium" : "text-foreground/80"
511
- )}>
565
+ <span className={cn("text-sm", option.isCorrect ? "text-foreground font-medium" : "text-foreground/80")}>
512
566
  {option.text}
513
567
  </span>
514
568
  {option.isCorrect && (
515
- <i className="fa-light fa-check text-emerald-600 text-sm ml-auto shrink-0" aria-hidden="true" />
569
+ <i className="fa-light fa-check text-emerald-600 text-sm ms-auto shrink-0" aria-hidden="true" />
516
570
  )}
517
571
  </div>
518
572
  ))}
@@ -521,209 +575,265 @@ export const QuestionBankTable = React.forwardRef<
521
575
  )}
522
576
  </div>
523
577
  )
578
+ }
524
579
 
525
- const drawerToolbarProps = {
526
- totalRows: tableSourceItems.length,
527
- filterFields,
528
- fieldDefinitions: fieldDefinitionsForDrawer,
529
- resolveColumnLabel,
530
- displayOptions,
531
- onDisplayOptionsChange: patchDisplay,
532
- conditionalRules,
533
- onAddConditionalRule: addConditionalRule,
534
- onRemoveConditionalRule: removeConditionalRule,
535
- onUpdateConditionalRule: updateConditionalRule,
536
- currentView: view,
537
- onViewChange,
538
- supportedViewTypes,
539
- lifecycleTabLabel: "Question bank",
540
- boardGroupByColumnOptions: [...QUESTION_BANK_BOARD_GROUP_OPTIONS],
541
- renderFilterOptionValue,
542
- }
580
+ // ─── Public component ───────────────────────────────────────────────────────
581
+
582
+ export type QuestionBankTableHandle = HubTableHandle
583
+
584
+ export interface QuestionBankTableProps {
585
+ items: QuestionBankItem[]
586
+ /** When set, table / board / tree rows are limited to this nav scope (secondary sidebar). */
587
+ navState?: QuestionBankNavState
588
+ /** URL toolbar search binding (`?q=`) — omit on search landing so hub `q` does not pre-fill the grid search. */
589
+ urlListSearch?: string
590
+ /** When true, dedicated search shell: hub landing row filters; table toolbar search stays independent of URL `q`. */
591
+ searchLanding?: boolean
592
+ /** Applied with nav filters before `useTableState` when {@link searchLanding} is true. */
593
+ landingFilters?: QuestionBankLandingFilterState | null
594
+ view?: DataListViewType
595
+ onViewChange?: (v: DataListViewType) => void
596
+ folders: QuestionBankFolder[]
597
+ onFoldersChange: React.Dispatch<React.SetStateAction<QuestionBankFolder[]>>
598
+ onItemsChange: React.Dispatch<React.SetStateAction<QuestionBankItem[]>>
599
+ }
543
600
 
544
- const tableProps = {
545
- data: tableSourceItems,
546
- columns,
547
- getRowId: (row: QuestionBankItem) => row.id,
548
- getRowSelectionLabel: (row: QuestionBankItem) => row.stem,
549
- selectable: true,
550
- searchable: displayOptions.showToolbarSearch,
551
- showColumnHeaders: displayOptions.showColumnLabels,
552
- groupable: true,
553
- defaultSort: { key: "updatedAt", dir: "desc" as const },
554
- emptyState: <p className="text-sm text-muted-foreground">No questions in the bank.</p>,
555
- conditionalRules,
556
- state: tableState,
557
- renderFilterOptionValue,
558
- toolbarSlot: (s: ReturnType<typeof useTableState<QuestionBankItem>>) => (
559
- <TablePropertiesDrawerButton {...drawerToolbarProps} state={s} />
560
- ),
561
- bulkActionsSlot: (selected: Set<string | number>) => {
562
- const n = selected.size
563
- if (n === 0) return null
564
- return (
565
- <>
566
- <span className="sr-only">{n} selected</span>
567
- <Tip label="Export selection (demo)">
568
- <Button size="sm" variant="outline" type="button">
569
- <i className="fa-light fa-arrow-down-to-line" aria-hidden="true" />
570
- Export
571
- </Button>
572
- </Tip>
573
- </>
574
- )
601
+ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, QuestionBankTableProps>(
602
+ function QuestionBankTable(
603
+ {
604
+ items,
605
+ navState,
606
+ urlListSearch,
607
+ searchLanding,
608
+ landingFilters,
609
+ view = "table",
610
+ onViewChange,
611
+ folders,
612
+ onFoldersChange,
613
+ onItemsChange,
575
614
  },
576
- }
577
-
578
- const sharedToolbar = (
579
- <DataTableToolbar
580
- state={tableState}
581
- columns={columns}
582
- searchable={displayOptions.showToolbarSearch}
583
- searchAriaLabel="Search questions"
584
- renderFilterOptionValue={renderFilterOptionValue}
585
- toolbarSlot={s => <TablePropertiesDrawerButton {...drawerToolbarProps} state={s} />}
586
- />
587
- )
588
-
589
- const toolbarShell = (body: React.ReactNode) => (
590
- <div className="flex min-h-0 flex-1 flex-col">
591
- {sharedToolbar}
592
- {body}
593
- </div>
594
- )
595
-
596
- const handleFolderSheetCreated = React.useCallback(
597
- (newFolder: {
598
- name: string
599
- icon: string
600
- colorKey: QuestionBankFolder["colorKey"]
601
- parentId: string | null
602
- }) => {
603
- if (customizingFolder) {
604
- onFoldersChange(prev =>
605
- prev.map(f =>
606
- f.id === customizingFolder.id
607
- ? { ...f, name: newFolder.name, icon: newFolder.icon, colorKey: newFolder.colorKey }
608
- : f,
609
- ),
610
- )
611
- setCustomizingFolder(null)
612
- } else {
613
- onFoldersChange(prev => [
615
+ ref,
616
+ ) {
617
+ const tableSourceItems = React.useMemo(() => {
618
+ const nav = navState ?? { scope: "all" as const, folderId: null }
619
+ const landing = searchLanding ? (landingFilters ?? null) : null
620
+ return applyQuestionBankHubDisplayFilters(items, folders, nav, landing)
621
+ }, [items, folders, navState, searchLanding, landingFilters])
622
+
623
+ const toggleFavorite = React.useCallback(
624
+ (row: QuestionBankItem) => {
625
+ onItemsChange(prev => prev.map(r => (r.id === row.id ? toggleQuestionBankItemFavorite(r) : r)))
626
+ },
627
+ [onItemsChange],
628
+ )
629
+
630
+ const columns = React.useMemo(
631
+ () => buildQuestionBankColumns(tableSourceItems, { onToggleFavorite: toggleFavorite }),
632
+ [tableSourceItems, toggleFavorite],
633
+ )
634
+
635
+ // New-folder / customize-folder modal state (shared by panel + tree-panel) ────
636
+ const [newFolderOpen, setNewFolderOpen] = React.useState(false)
637
+ const [newFolderParentId, setNewFolderParentId] = React.useState<string | null>(null)
638
+ const [customizingFolder, setCustomizingFolder] = React.useState<QuestionBankFolder | null>(null)
639
+
640
+ const openNewFolderForColumn = React.useCallback((parentId: string | null) => {
641
+ setNewFolderParentId(parentId)
642
+ setCustomizingFolder(null)
643
+ setNewFolderOpen(true)
644
+ }, [])
645
+
646
+ const openCustomizeFolderSheet = React.useCallback((folder: QuestionBankFolder) => {
647
+ setCustomizingFolder(folder)
648
+ setNewFolderOpen(true)
649
+ }, [])
650
+
651
+ const addQuestionInColumn = React.useCallback(
652
+ (parentId: string | null) => {
653
+ const folderId = defaultFolderIdForColumnParent(parentId, folders)
654
+ if (!folderId) return
655
+ const today = new Date()
656
+ const y = today.getFullYear()
657
+ const m = String(today.getMonth() + 1).padStart(2, "0")
658
+ const d = String(today.getDate()).padStart(2, "0")
659
+ onItemsChange(prev => [
614
660
  ...prev,
615
661
  {
616
- id: `fld-${Date.now()}`,
617
- name: newFolder.name,
618
- icon: newFolder.icon,
619
- colorKey: newFolder.colorKey,
620
- parentId: newFolder.parentId,
662
+ id: newQuestionBankItemId(),
663
+ questionId: newQuestionBankQuestionId(),
664
+ stem: "New question",
665
+ topic: "General",
666
+ type: "short_answer",
667
+ difficulty: "medium",
668
+ author: "Demo user",
669
+ authorEmail: "demo.user@demo.exxat.io",
670
+ updatedAt: `${y}-${m}-${d}`,
671
+ folderId,
621
672
  },
622
673
  ])
623
- }
624
- setNewFolderOpen(false)
625
- },
626
- [customizingFolder, onFoldersChange],
627
- )
628
-
629
- const folderSheet = (
630
- <QuestionBankNewFolderSheet
631
- open={newFolderOpen}
632
- onOpenChange={setNewFolderOpen}
633
- parentFolderId={customizingFolder?.parentId ?? newFolderParentId}
634
- customizingFolder={customizingFolder}
635
- onCreated={handleFolderSheetCreated}
636
- />
637
- )
638
-
639
- const rows = tableState.rows as QuestionBankItem[]
640
-
641
- return (
642
- <ListPageConnectedViewBody
643
- view={view}
644
- hubLabel="Question bank"
645
- renderers={defineHubViewRenderers(QUESTION_BANK_SUPPORTED_VIEWS, {
646
- "data-table": (
647
- <div className="pb-6">
648
- <DataTable<QuestionBankItem> {...tableProps} />
649
- </div>
650
- ),
651
- "list-with-toolbar": toolbarShell(
652
- <QuestionBankListView
653
- rows={rows}
654
- onToggleFavorite={toggleFavorite}
655
- onRowActivate={row => tableState.toggleRow(row.id)}
656
- />,
657
- ),
658
- "board-with-toolbar": toolbarShell(
674
+ },
675
+ [folders, onItemsChange],
676
+ )
677
+
678
+ const renderFilterOptionValue = React.useCallback(
679
+ (fieldKey: string, value: string): React.ReactNode => {
680
+ const col = columns.find(c => c.key === fieldKey)
681
+ const opt = col?.filter?.options?.find(o => o.value === value)
682
+ return <span className="text-foreground">{opt?.label ?? value}</span>
683
+ },
684
+ [columns],
685
+ )
686
+
687
+ // ─ Renderers ──────────────────────────────────────────────────────────────
688
+ const renderers: HubTableRenderers<QuestionBankItem> = {
689
+ "board-with-toolbar": ({ state, toolbarShell, displayOptions }) => {
690
+ const boardGroupKey = QUESTION_BANK_BOARD_GROUP_OPTIONS.some(
691
+ o => o.key === displayOptions.boardGroupByColumnKey,
692
+ )
693
+ ? displayOptions.boardGroupByColumnKey
694
+ : "topic"
695
+ return toolbarShell(
659
696
  <QuestionBankBoardView
660
- rows={rows}
661
- groupByColumnKey={questionBankBoardGroupKey}
697
+ rows={state.rows as QuestionBankItem[]}
698
+ groupByColumnKey={boardGroupKey}
662
699
  onToggleFavorite={toggleFavorite}
663
- onRowActivate={row => tableState.toggleRow(row.id)}
700
+ onRowActivate={row => state.toggleRow(row.id)}
664
701
  />,
665
- ),
666
- "folder-with-toolbar": toolbarShell(
702
+ )
703
+ },
704
+ "folder-with-toolbar": ({ state, toolbarShell }) =>
705
+ toolbarShell(
667
706
  <QuestionBankOsFolderView
668
707
  folders={folders}
669
708
  onFoldersChange={onFoldersChange}
670
- questions={rows}
709
+ questions={state.rows as QuestionBankItem[]}
671
710
  onQuestionsChange={onItemsChange}
672
711
  />,
673
712
  ),
674
- "panel-with-toolbar": (
675
- <>
676
- {toolbarShell(
677
- <ListPageSplitHubChrome aria-label="Question bank folder columns">
678
- <QuestionBankFolderColumnsPanel
679
- folders={folders}
680
- rows={rows}
681
- panelRenderDetail={panelRenderDetail}
682
- onAddFolder={openNewFolderForColumn}
683
- onAddQuestion={addQuestionInColumn}
684
- onCustomizeFolder={openCustomizeFolderSheet}
685
- />
686
- </ListPageSplitHubChrome>,
687
- )}
688
- {folderSheet}
689
- </>
690
- ),
691
- "tree-panel-with-toolbar": (
692
- <>
693
- {toolbarShell(
694
- <div className="flex min-h-0 flex-1 flex-col">
695
- <HubTreePanelView
696
- items={rows}
697
- folders={folders}
698
- onItemsChange={onItemsChange}
699
- onFoldersChange={onFoldersChange}
700
- />
701
- </div>,
702
- )}
703
- {folderSheet}
704
- </>
713
+ "panel-with-toolbar": ({ state, toolbarShell }) =>
714
+ toolbarShell(
715
+ <ListPageSplitHubChrome aria-label="Question bank folder columns">
716
+ <HubFolderColumnsPanel
717
+ folders={folders}
718
+ rows={state.rows as QuestionBankItem[]}
719
+ panelRenderDetail={questionBankPanelDetail}
720
+ onAddFolder={openNewFolderForColumn}
721
+ onAddQuestion={addQuestionInColumn}
722
+ onCustomizeFolder={openCustomizeFolderSheet}
723
+ />
724
+ </ListPageSplitHubChrome>,
705
725
  ),
706
- "calendar-with-toolbar": toolbarShell(
707
- <ListPageCalendarView
708
- rows={rows}
709
- getRowId={row => row.id}
710
- getEventDate={row => row.updatedAt}
711
- getEventLabel={row => row.stem}
712
- getEventMeta={row => row.topic}
713
- emptyMonthLabel="No questions on this day."
714
- ariaLabel="Question bank calendar"
715
- showSummaryPanel={displayOptions.showCalendarSummaryPanel}
716
- calendarMainView={displayOptions.calendarMainView}
717
- onCalendarMainViewChange={v => patchDisplay({ calendarMainView: v })}
718
- onEventActivate={row => tableState.toggleRow(row.id)}
719
- />,
720
- ),
721
- "dashboard-with-toolbar": toolbarShell(
722
- <QuestionBankDashboardChartsSection rows={rows} />,
726
+ "tree-panel-with-toolbar": ({ state, toolbarShell }) =>
727
+ toolbarShell(
728
+ <div className="flex min-h-0 flex-1 flex-col">
729
+ <HubTreePanelView
730
+ items={state.rows as QuestionBankItem[]}
731
+ folders={folders}
732
+ onItemsChange={onItemsChange}
733
+ onFoldersChange={onFoldersChange}
734
+ />
735
+ </div>,
723
736
  ),
724
- })}
725
- />
726
- )
727
- })
737
+ "dashboard-with-toolbar": ({ state, toolbar }) => (
738
+ <div className="flex min-h-0 flex-1 flex-col">
739
+ {toolbar}
740
+ <QuestionBankDashboardChartsSection rows={state.rows as QuestionBankItem[]} />
741
+ </div>
742
+ ),
743
+ }
744
+
745
+ return (
746
+ <>
747
+ <HubTable<QuestionBankItem>
748
+ rows={tableSourceItems}
749
+ columns={columns}
750
+ view={view}
751
+ onViewChange={onViewChange}
752
+ supportedViewTypes={QUESTION_BANK_SUPPORTED_VIEWS}
753
+ hubLabel="Question bank"
754
+ lifecycleTabLabel="Question bank"
755
+ searchAriaLabel="Search questions"
756
+ getRowId={row => row.id}
757
+ getRowSelectionLabel={row => row.stem}
758
+ defaultSort={{ key: "updatedAt", dir: "desc" }}
759
+ emptyState={<p className="text-sm text-muted-foreground">No questions in the bank.</p>}
760
+ boardGroupByColumnOptions={[...QUESTION_BANK_BOARD_GROUP_OPTIONS]}
761
+ renderFilterOptionValue={renderFilterOptionValue}
762
+ syncedSearchFromUrl={searchLanding ? undefined : urlListSearch}
763
+ listAriaLabel="Questions"
764
+ listEmptyState="No questions match your filters."
765
+ renderListRow={row => (
766
+ <ListPageBoardCard
767
+ className={QUESTION_BANK_FAVORITE_HOVER_GROUP}
768
+ layout="row"
769
+ rowContainerClassName="flex w-full flex-col gap-1 sm:flex-row sm:items-center sm:gap-4"
770
+ rowEnd={
771
+ <div className="flex shrink-0 items-center gap-1">
772
+ <QuestionBankFavoriteButton row={row} onToggleFavorite={toggleFavorite} />
773
+ <i className="fa-light fa-chevron-right text-xs text-muted-foreground" aria-hidden="true" />
774
+ </div>
775
+ }
776
+ >
777
+ <div className="space-y-0.5">
778
+ <p className="line-clamp-2 text-sm font-semibold text-foreground">{row.stem}</p>
779
+ <p className="font-mono text-xs text-muted-foreground">{row.questionId}</p>
780
+ <p className="text-xs text-muted-foreground">
781
+ {row.topic} · Updated {formatDateUS(row.updatedAt)}
782
+ </p>
783
+ <p className="text-xs text-muted-foreground">{row.author}</p>
784
+ </div>
785
+ </ListPageBoardCard>
786
+ )}
787
+ bulkActionsSlot={selected => {
788
+ if (selected.size === 0) return null
789
+ return (
790
+ <>
791
+ <span className="sr-only">{selected.size} selected</span>
792
+ <Tip label="Export selection (demo)">
793
+ <Button size="sm" variant="outline" type="button">
794
+ <i className="fa-light fa-arrow-down-to-line" aria-hidden="true" />
795
+ Export
796
+ </Button>
797
+ </Tip>
798
+ </>
799
+ )
800
+ }}
801
+ renderers={renderers}
802
+ handleRef={ref}
803
+ />
804
+ <QuestionBankNewFolderSheet
805
+ open={newFolderOpen}
806
+ onOpenChange={setNewFolderOpen}
807
+ parentFolderId={customizingFolder?.parentId ?? newFolderParentId}
808
+ customizingFolder={customizingFolder}
809
+ onCreated={(newFolder) => {
810
+ if (customizingFolder) {
811
+ onFoldersChange(prev =>
812
+ prev.map(f =>
813
+ f.id === customizingFolder.id
814
+ ? { ...f, name: newFolder.name, icon: newFolder.icon, colorKey: newFolder.colorKey }
815
+ : f,
816
+ ),
817
+ )
818
+ setCustomizingFolder(null)
819
+ } else {
820
+ onFoldersChange(prev => [
821
+ ...prev,
822
+ {
823
+ id: `fld-${Date.now()}`,
824
+ name: newFolder.name,
825
+ icon: newFolder.icon,
826
+ colorKey: newFolder.colorKey,
827
+ parentId: newFolder.parentId,
828
+ },
829
+ ])
830
+ }
831
+ setNewFolderOpen(false)
832
+ }}
833
+ />
834
+ </>
835
+ )
836
+ },
837
+ )
728
838
 
729
839
  QuestionBankTable.displayName = "QuestionBankTable"