@exxatdesignux/ui 0.2.19 → 0.4.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 (716) hide show
  1. package/CHANGELOG.md +662 -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 +43 -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-library-hub-header.mdc +28 -0
  22. package/consumer-extras/cursor-rules/exxat-list-page-connected-views.mdc +24 -0
  23. package/consumer-extras/cursor-rules/exxat-list-page-view-shells.mdc +31 -0
  24. package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +30 -0
  25. package/consumer-extras/cursor-rules/exxat-no-slds-leakage.mdc +78 -0
  26. package/consumer-extras/cursor-rules/exxat-no-toast.mdc +25 -0
  27. package/consumer-extras/cursor-rules/exxat-page-vs-drawer.mdc +23 -0
  28. package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +47 -0
  29. package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +52 -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 +3 -3
  35. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +5 -16
  36. package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -3
  37. package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
  38. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +19 -34
  39. package/consumer-extras/cursor-skills/exxat-ds-skill/references/data-table-pattern.md +1 -1
  40. package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
  41. package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
  42. package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
  43. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +10 -12
  44. package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
  45. package/consumer-extras/handbook/HANDBOOK.md +187 -0
  46. package/consumer-extras/handbook/glossary.md +58 -0
  47. package/consumer-extras/handbook/reference-implementations.md +153 -0
  48. package/consumer-extras/handbook/voice-and-tone.md +262 -0
  49. package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
  50. package/consumer-extras/patterns/command-menu-pattern.md +1 -1
  51. package/consumer-extras/patterns/consumer-upgrade-checklist.md +0 -20
  52. package/consumer-extras/patterns/data-views-pattern.md +31 -66
  53. package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
  54. package/consumer-extras/patterns/shell-surface-elevation-pattern.md +3 -5
  55. package/dist/components/data-table/filter-date-calendar.d.ts +10 -0
  56. package/dist/components/data-table/filter-date-calendar.js +280 -0
  57. package/dist/components/data-table/filter-date-calendar.js.map +1 -0
  58. package/dist/components/data-table/filter-text-value-input.d.ts +15 -0
  59. package/dist/components/data-table/filter-text-value-input.js +561 -0
  60. package/dist/components/data-table/filter-text-value-input.js.map +1 -0
  61. package/dist/components/data-table/index.d.ts +45 -0
  62. package/dist/components/data-table/index.js +3085 -0
  63. package/dist/components/data-table/index.js.map +1 -0
  64. package/dist/components/data-table/pagination.d.ts +28 -0
  65. package/dist/components/data-table/pagination.js +3264 -0
  66. package/dist/components/data-table/pagination.js.map +1 -0
  67. package/dist/components/data-table/types.d.ts +84 -0
  68. package/dist/components/data-table/types.js +3 -0
  69. package/dist/components/data-table/types.js.map +1 -0
  70. package/dist/components/data-table/use-table-state.d.ts +116 -0
  71. package/dist/components/data-table/use-table-state.js +670 -0
  72. package/dist/components/data-table/use-table-state.js.map +1 -0
  73. package/dist/components/data-views/board-card-primitives.d.ts +22 -0
  74. package/dist/components/data-views/board-card-primitives.js +84 -0
  75. package/dist/components/data-views/board-card-primitives.js.map +1 -0
  76. package/dist/components/data-views/data-row-list.d.ts +33 -0
  77. package/dist/components/data-views/data-row-list.js +106 -0
  78. package/dist/components/data-views/data-row-list.js.map +1 -0
  79. package/dist/components/data-views/finder-panel-view.d.ts +54 -0
  80. package/dist/components/data-views/finder-panel-view.js +388 -0
  81. package/dist/components/data-views/finder-panel-view.js.map +1 -0
  82. package/dist/components/data-views/folder-grid-view.d.ts +22 -0
  83. package/dist/components/data-views/folder-grid-view.js +58 -0
  84. package/dist/components/data-views/folder-grid-view.js.map +1 -0
  85. package/dist/components/data-views/hub-table.d.ts +173 -0
  86. package/dist/components/data-views/hub-table.js +5783 -0
  87. package/dist/components/data-views/hub-table.js.map +1 -0
  88. package/dist/components/data-views/index.d.ts +27 -0
  89. package/dist/components/data-views/index.js +6797 -0
  90. package/dist/components/data-views/index.js.map +1 -0
  91. package/dist/components/data-views/list-page-board-card.d.ts +72 -0
  92. package/dist/components/data-views/list-page-board-card.js +264 -0
  93. package/dist/components/data-views/list-page-board-card.js.map +1 -0
  94. package/dist/components/data-views/list-page-board-template.d.ts +24 -0
  95. package/dist/components/data-views/list-page-board-template.js +137 -0
  96. package/dist/components/data-views/list-page-board-template.js.map +1 -0
  97. package/dist/components/data-views/list-page-connected-view-body.d.ts +19 -0
  98. package/dist/components/data-views/list-page-connected-view-body.js +116 -0
  99. package/dist/components/data-views/list-page-connected-view-body.js.map +1 -0
  100. package/dist/components/data-views/list-page-split-details-placeholder.d.ts +14 -0
  101. package/dist/components/data-views/list-page-split-details-placeholder.js +38 -0
  102. package/dist/components/data-views/list-page-split-details-placeholder.js.map +1 -0
  103. package/dist/components/data-views/list-page-split-hub-chrome.d.ts +17 -0
  104. package/dist/components/data-views/list-page-split-hub-chrome.js +54 -0
  105. package/dist/components/data-views/list-page-split-hub-chrome.js.map +1 -0
  106. package/dist/components/data-views/list-page-split-hub-tokens.d.ts +12 -0
  107. package/dist/components/data-views/list-page-split-hub-tokens.js +8 -0
  108. package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -0
  109. package/dist/components/data-views/list-page-tree-column-header.d.ts +15 -0
  110. package/dist/components/data-views/list-page-tree-column-header.js +22 -0
  111. package/dist/components/data-views/list-page-tree-column-header.js.map +1 -0
  112. package/dist/components/data-views/list-page-tree-panel-shell.d.ts +25 -0
  113. package/dist/components/data-views/list-page-tree-panel-shell.js +146 -0
  114. package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -0
  115. package/dist/components/data-views/os-folder-glyph.d.ts +35 -0
  116. package/dist/components/data-views/os-folder-glyph.js +104 -0
  117. package/dist/components/data-views/os-folder-glyph.js.map +1 -0
  118. package/dist/components/data-views/outline-tree-menu.d.ts +36 -0
  119. package/dist/components/data-views/outline-tree-menu.js +131 -0
  120. package/dist/components/data-views/outline-tree-menu.js.map +1 -0
  121. package/dist/components/table-properties/column-row.d.ts +22 -0
  122. package/dist/components/table-properties/column-row.js +153 -0
  123. package/dist/components/table-properties/column-row.js.map +1 -0
  124. package/dist/components/table-properties/draggable-list.d.ts +24 -0
  125. package/dist/components/table-properties/draggable-list.js +53 -0
  126. package/dist/components/table-properties/draggable-list.js.map +1 -0
  127. package/dist/components/table-properties/drawer-button.d.ts +110 -0
  128. package/dist/components/table-properties/drawer-button.js +2748 -0
  129. package/dist/components/table-properties/drawer-button.js.map +1 -0
  130. package/dist/components/table-properties/drawer.d.ts +100 -0
  131. package/dist/components/table-properties/drawer.js +2595 -0
  132. package/dist/components/table-properties/drawer.js.map +1 -0
  133. package/dist/components/table-properties/filter-card.d.ts +24 -0
  134. package/dist/components/table-properties/filter-card.js +854 -0
  135. package/dist/components/table-properties/filter-card.js.map +1 -0
  136. package/dist/components/table-properties/index.d.ts +14 -0
  137. package/dist/components/table-properties/index.js +2768 -0
  138. package/dist/components/table-properties/index.js.map +1 -0
  139. package/dist/components/table-properties/sort-card.d.ts +20 -0
  140. package/dist/components/table-properties/sort-card.js +102 -0
  141. package/dist/components/table-properties/sort-card.js.map +1 -0
  142. package/dist/components/templates/dedicated-search-landing-template.d.ts +21 -0
  143. package/dist/components/templates/dedicated-search-landing-template.js +254 -0
  144. package/dist/components/templates/dedicated-search-landing-template.js.map +1 -0
  145. package/dist/components/templates/dedicated-search-results-template.d.ts +15 -0
  146. package/dist/components/templates/dedicated-search-results-template.js +16 -0
  147. package/dist/components/templates/dedicated-search-results-template.js.map +1 -0
  148. package/dist/components/templates/index.d.ts +9 -0
  149. package/dist/components/templates/index.js +2720 -0
  150. package/dist/components/templates/index.js.map +1 -0
  151. package/dist/components/templates/list-page.d.ts +83 -0
  152. package/dist/components/templates/list-page.js +2433 -0
  153. package/dist/components/templates/list-page.js.map +1 -0
  154. package/dist/components/templates/nested-secondary-panel-shell.d.ts +20 -0
  155. package/dist/components/templates/nested-secondary-panel-shell.js +54 -0
  156. package/dist/components/templates/nested-secondary-panel-shell.js.map +1 -0
  157. package/dist/components/ui/accordion.d.ts +10 -0
  158. package/dist/components/ui/accordion.js +74 -0
  159. package/dist/components/ui/accordion.js.map +1 -0
  160. package/dist/components/ui/alert-dialog.d.ts +37 -0
  161. package/dist/components/ui/alert-dialog.js +201 -0
  162. package/dist/components/ui/alert-dialog.js.map +1 -0
  163. package/dist/components/ui/avatar.d.ts +84 -0
  164. package/dist/components/ui/avatar.js +328 -0
  165. package/dist/components/ui/avatar.js.map +1 -0
  166. package/dist/components/ui/badge.d.ts +13 -0
  167. package/dist/components/ui/badge.js +49 -0
  168. package/dist/components/ui/badge.js.map +1 -0
  169. package/dist/components/ui/banner.d.ts +62 -0
  170. package/dist/components/ui/banner.js +364 -0
  171. package/dist/components/ui/banner.js.map +1 -0
  172. package/dist/components/ui/breadcrumb.d.ts +14 -0
  173. package/dist/components/ui/breadcrumb.js +114 -0
  174. package/dist/components/ui/breadcrumb.js.map +1 -0
  175. package/dist/components/ui/button.d.ts +16 -0
  176. package/dist/components/ui/button.js +59 -0
  177. package/dist/components/ui/button.js.map +1 -0
  178. package/dist/components/ui/calendar.d.ts +13 -0
  179. package/dist/components/ui/calendar.js +238 -0
  180. package/dist/components/ui/calendar.js.map +1 -0
  181. package/dist/components/ui/card.d.ts +14 -0
  182. package/dist/components/ui/card.js +102 -0
  183. package/dist/components/ui/card.js.map +1 -0
  184. package/dist/components/ui/chart.d.ts +58 -0
  185. package/dist/components/ui/chart.js +292 -0
  186. package/dist/components/ui/chart.js.map +1 -0
  187. package/dist/components/ui/checkbox.d.ts +23 -0
  188. package/dist/components/ui/checkbox.js +155 -0
  189. package/dist/components/ui/checkbox.js.map +1 -0
  190. package/dist/components/ui/coach-mark.d.ts +27 -0
  191. package/dist/components/ui/coach-mark.js +306 -0
  192. package/dist/components/ui/coach-mark.js.map +1 -0
  193. package/dist/components/ui/collapsible.d.ts +8 -0
  194. package/dist/components/ui/collapsible.js +35 -0
  195. package/dist/components/ui/collapsible.js.map +1 -0
  196. package/dist/components/ui/command.d.ts +36 -0
  197. package/dist/components/ui/command.js +274 -0
  198. package/dist/components/ui/command.js.map +1 -0
  199. package/dist/components/ui/context-menu.d.ts +32 -0
  200. package/dist/components/ui/context-menu.js +245 -0
  201. package/dist/components/ui/context-menu.js.map +1 -0
  202. package/dist/components/ui/date-picker-field.d.ts +38 -0
  203. package/dist/components/ui/date-picker-field.js +550 -0
  204. package/dist/components/ui/date-picker-field.js.map +1 -0
  205. package/dist/components/ui/dialog.d.ts +22 -0
  206. package/dist/components/ui/dialog.js +200 -0
  207. package/dist/components/ui/dialog.js.map +1 -0
  208. package/dist/components/ui/dot-pattern.d.ts +21 -0
  209. package/dist/components/ui/dot-pattern.js +139 -0
  210. package/dist/components/ui/dot-pattern.js.map +1 -0
  211. package/dist/components/ui/drag-handle-grip.d.ts +10 -0
  212. package/dist/components/ui/drag-handle-grip.js +15 -0
  213. package/dist/components/ui/drag-handle-grip.js.map +1 -0
  214. package/dist/components/ui/drawer.d.ts +16 -0
  215. package/dist/components/ui/drawer.js +125 -0
  216. package/dist/components/ui/drawer.js.map +1 -0
  217. package/dist/components/ui/dropdown-menu.d.ts +45 -0
  218. package/dist/components/ui/dropdown-menu.js +353 -0
  219. package/dist/components/ui/dropdown-menu.js.map +1 -0
  220. package/dist/components/ui/export-drawer.d.ts +11 -0
  221. package/dist/components/ui/export-drawer.js +1658 -0
  222. package/dist/components/ui/export-drawer.js.map +1 -0
  223. package/dist/components/ui/field.d.ts +30 -0
  224. package/dist/components/ui/field.js +249 -0
  225. package/dist/components/ui/field.js.map +1 -0
  226. package/dist/components/ui/form.d.ts +28 -0
  227. package/dist/components/ui/form.js +110 -0
  228. package/dist/components/ui/form.js.map +1 -0
  229. package/dist/components/ui/hover-card.d.ts +9 -0
  230. package/dist/components/ui/hover-card.js +43 -0
  231. package/dist/components/ui/hover-card.js.map +1 -0
  232. package/dist/components/ui/input-group.d.ts +20 -0
  233. package/dist/components/ui/input-group.js +219 -0
  234. package/dist/components/ui/input-group.js.map +1 -0
  235. package/dist/components/ui/input-mask.d.ts +39 -0
  236. package/dist/components/ui/input-mask.js +118 -0
  237. package/dist/components/ui/input-mask.js.map +1 -0
  238. package/dist/components/ui/input.d.ts +5 -0
  239. package/dist/components/ui/input.js +30 -0
  240. package/dist/components/ui/input.js.map +1 -0
  241. package/dist/components/ui/kbd.d.ts +20 -0
  242. package/dist/components/ui/kbd.js +45 -0
  243. package/dist/components/ui/kbd.js.map +1 -0
  244. package/dist/components/ui/key-metrics-context.d.ts +19 -0
  245. package/dist/components/ui/key-metrics-context.js +26 -0
  246. package/dist/components/ui/key-metrics-context.js.map +1 -0
  247. package/dist/components/ui/key-metrics.d.ts +131 -0
  248. package/dist/components/ui/key-metrics.js +1015 -0
  249. package/dist/components/ui/key-metrics.js.map +1 -0
  250. package/dist/components/ui/label.d.ts +6 -0
  251. package/dist/components/ui/label.js +28 -0
  252. package/dist/components/ui/label.js.map +1 -0
  253. package/dist/components/ui/list-page-view-frame.d.ts +22 -0
  254. package/dist/components/ui/list-page-view-frame.js +24 -0
  255. package/dist/components/ui/list-page-view-frame.js.map +1 -0
  256. package/dist/components/ui/page-header.d.ts +51 -0
  257. package/dist/components/ui/page-header.js +372 -0
  258. package/dist/components/ui/page-header.js.map +1 -0
  259. package/dist/components/ui/payment-card-fields.d.ts +10 -0
  260. package/dist/components/ui/payment-card-fields.js +80 -0
  261. package/dist/components/ui/payment-card-fields.js.map +1 -0
  262. package/dist/components/ui/popover.d.ts +10 -0
  263. package/dist/components/ui/popover.js +47 -0
  264. package/dist/components/ui/popover.js.map +1 -0
  265. package/dist/components/ui/radio-group.d.ts +29 -0
  266. package/dist/components/ui/radio-group.js +190 -0
  267. package/dist/components/ui/radio-group.js.map +1 -0
  268. package/dist/components/ui/resizable.d.ts +16 -0
  269. package/dist/components/ui/resizable.js +51 -0
  270. package/dist/components/ui/resizable.js.map +1 -0
  271. package/dist/components/ui/scroll-area.d.ts +8 -0
  272. package/dist/components/ui/scroll-area.js +66 -0
  273. package/dist/components/ui/scroll-area.js.map +1 -0
  274. package/dist/components/ui/select.d.ts +18 -0
  275. package/dist/components/ui/select.js +186 -0
  276. package/dist/components/ui/select.js.map +1 -0
  277. package/dist/components/ui/selection-tile-grid.d.ts +52 -0
  278. package/dist/components/ui/selection-tile-grid.js +347 -0
  279. package/dist/components/ui/selection-tile-grid.js.map +1 -0
  280. package/dist/components/ui/separator.d.ts +7 -0
  281. package/dist/components/ui/separator.js +33 -0
  282. package/dist/components/ui/separator.js.map +1 -0
  283. package/dist/components/ui/sheet.d.ts +18 -0
  284. package/dist/components/ui/sheet.js +181 -0
  285. package/dist/components/ui/sheet.js.map +1 -0
  286. package/dist/components/ui/sidebar.d.ts +94 -0
  287. package/dist/components/ui/sidebar.js +805 -0
  288. package/dist/components/ui/sidebar.js.map +1 -0
  289. package/dist/components/ui/skeleton.d.ts +5 -0
  290. package/dist/components/ui/skeleton.js +22 -0
  291. package/dist/components/ui/skeleton.js.map +1 -0
  292. package/dist/components/ui/slider.d.ts +7 -0
  293. package/dist/components/ui/slider.js +66 -0
  294. package/dist/components/ui/slider.js.map +1 -0
  295. package/dist/components/ui/sonner.d.ts +6 -0
  296. package/dist/components/ui/sonner.js +38 -0
  297. package/dist/components/ui/sonner.js.map +1 -0
  298. package/dist/components/ui/status-badge.d.ts +38 -0
  299. package/dist/components/ui/status-badge.js +77 -0
  300. package/dist/components/ui/status-badge.js.map +1 -0
  301. package/dist/components/ui/table.d.ts +13 -0
  302. package/dist/components/ui/table.js +115 -0
  303. package/dist/components/ui/table.js.map +1 -0
  304. package/dist/components/ui/tabs.d.ts +15 -0
  305. package/dist/components/ui/tabs.js +93 -0
  306. package/dist/components/ui/tabs.js.map +1 -0
  307. package/dist/components/ui/textarea.d.ts +6 -0
  308. package/dist/components/ui/textarea.js +25 -0
  309. package/dist/components/ui/textarea.js.map +1 -0
  310. package/dist/components/ui/tip.d.ts +12 -0
  311. package/dist/components/ui/tip.js +61 -0
  312. package/dist/components/ui/tip.js.map +1 -0
  313. package/dist/components/ui/toggle-group.d.ts +14 -0
  314. package/dist/components/ui/toggle-group.js +104 -0
  315. package/dist/components/ui/toggle-group.js.map +1 -0
  316. package/dist/components/ui/toggle-switch.d.ts +10 -0
  317. package/dist/components/ui/toggle-switch.js +33 -0
  318. package/dist/components/ui/toggle-switch.js.map +1 -0
  319. package/dist/components/ui/toggle.d.ts +13 -0
  320. package/dist/components/ui/toggle.js +51 -0
  321. package/dist/components/ui/toggle.js.map +1 -0
  322. package/dist/components/ui/tooltip.d.ts +10 -0
  323. package/dist/components/ui/tooltip.js +68 -0
  324. package/dist/components/ui/tooltip.js.map +1 -0
  325. package/dist/components/ui/view-segmented-control.d.ts +31 -0
  326. package/dist/components/ui/view-segmented-control.js +167 -0
  327. package/dist/components/ui/view-segmented-control.js.map +1 -0
  328. package/dist/data-list-view-registry-CyBoBML4.d.ts +73 -0
  329. package/dist/hooks/use-app-theme.d.ts +24 -0
  330. package/dist/hooks/use-app-theme.js +286 -0
  331. package/dist/hooks/use-app-theme.js.map +1 -0
  332. package/dist/hooks/use-coach-mark.d.ts +86 -0
  333. package/dist/hooks/use-coach-mark.js +218 -0
  334. package/dist/hooks/use-coach-mark.js.map +1 -0
  335. package/dist/hooks/use-mobile.d.ts +3 -0
  336. package/dist/hooks/use-mobile.js +29 -0
  337. package/dist/hooks/use-mobile.js.map +1 -0
  338. package/dist/hooks/use-mod-key-label.d.ts +6 -0
  339. package/dist/hooks/use-mod-key-label.js +25 -0
  340. package/dist/hooks/use-mod-key-label.js.map +1 -0
  341. package/dist/index.d.ts +120 -0
  342. package/dist/index.js +13421 -0
  343. package/dist/index.js.map +1 -0
  344. package/dist/lib/compose-refs.d.ts +6 -0
  345. package/dist/lib/compose-refs.js +17 -0
  346. package/dist/lib/compose-refs.js.map +1 -0
  347. package/dist/lib/conditional-rule-match.d.ts +30 -0
  348. package/dist/lib/conditional-rule-match.js +66 -0
  349. package/dist/lib/conditional-rule-match.js.map +1 -0
  350. package/dist/lib/data-list-display-options.d.ts +26 -0
  351. package/dist/lib/data-list-display-options.js +14 -0
  352. package/dist/lib/data-list-display-options.js.map +1 -0
  353. package/dist/lib/data-list-view-registry.d.ts +2 -0
  354. package/dist/lib/data-list-view-registry.js +102 -0
  355. package/dist/lib/data-list-view-registry.js.map +1 -0
  356. package/dist/lib/data-list-view-surface.d.ts +2 -0
  357. package/dist/lib/data-list-view-surface.js +80 -0
  358. package/dist/lib/data-list-view-surface.js.map +1 -0
  359. package/dist/lib/data-list-view.d.ts +21 -0
  360. package/dist/lib/data-list-view.js +25 -0
  361. package/dist/lib/data-list-view.js.map +1 -0
  362. package/dist/lib/date-filter.d.ts +22 -0
  363. package/dist/lib/date-filter.js +61 -0
  364. package/dist/lib/date-filter.js.map +1 -0
  365. package/dist/lib/dev-log.d.ts +8 -0
  366. package/dist/lib/dev-log.js +10 -0
  367. package/dist/lib/dev-log.js.map +1 -0
  368. package/dist/lib/dropdown-menu-surface.d.ts +14 -0
  369. package/dist/lib/dropdown-menu-surface.js +6 -0
  370. package/dist/lib/dropdown-menu-surface.js.map +1 -0
  371. package/dist/lib/editable-target.d.ts +12 -0
  372. package/dist/lib/editable-target.js +12 -0
  373. package/dist/lib/editable-target.js.map +1 -0
  374. package/dist/lib/list-page-table-properties.d.ts +35 -0
  375. package/dist/lib/list-page-table-properties.js +81 -0
  376. package/dist/lib/list-page-table-properties.js.map +1 -0
  377. package/dist/lib/raf-throttle.d.ts +23 -0
  378. package/dist/lib/raf-throttle.js +27 -0
  379. package/dist/lib/raf-throttle.js.map +1 -0
  380. package/dist/lib/row-height.d.ts +16 -0
  381. package/dist/lib/row-height.js +10 -0
  382. package/dist/lib/row-height.js.map +1 -0
  383. package/dist/lib/table-properties-types.d.ts +83 -0
  384. package/dist/lib/table-properties-types.js +19 -0
  385. package/dist/lib/table-properties-types.js.map +1 -0
  386. package/dist/lib/utils.d.ts +5 -0
  387. package/dist/lib/utils.js +11 -0
  388. package/dist/lib/utils.js.map +1 -0
  389. package/package.json +83 -19
  390. package/src/components/data-table/filter-date-calendar.tsx +38 -0
  391. package/src/components/data-table/filter-text-value-input.tsx +77 -0
  392. package/src/components/data-table/index.tsx +1678 -0
  393. package/src/components/data-table/pagination.tsx +259 -0
  394. package/src/components/data-table/types.ts +96 -0
  395. package/src/components/data-table/use-table-state.ts +767 -0
  396. package/src/components/data-views/board-card-primitives.tsx +93 -0
  397. package/src/components/data-views/data-row-list.tsx +183 -0
  398. package/src/components/data-views/finder-panel-view.tsx +405 -0
  399. package/src/components/data-views/folder-grid-view.tsx +86 -0
  400. package/src/components/data-views/hub-table.tsx +606 -0
  401. package/src/components/data-views/index.ts +28 -0
  402. package/src/components/data-views/list-page-board-card.tsx +192 -0
  403. package/src/components/data-views/list-page-board-template.tsx +122 -0
  404. package/src/components/data-views/list-page-connected-view-body.tsx +66 -0
  405. package/src/components/data-views/list-page-split-details-placeholder.tsx +39 -0
  406. package/src/components/data-views/list-page-split-hub-chrome.tsx +60 -0
  407. package/src/components/data-views/list-page-split-hub-tokens.ts +16 -0
  408. package/src/components/data-views/list-page-tree-column-header.tsx +31 -0
  409. package/src/components/data-views/list-page-tree-panel-shell.tsx +91 -0
  410. package/src/components/data-views/os-folder-glyph.tsx +141 -0
  411. package/src/components/data-views/outline-tree-menu.tsx +157 -0
  412. package/src/components/table-properties/column-row.tsx +90 -0
  413. package/src/components/table-properties/draggable-list.ts +54 -0
  414. package/src/components/table-properties/drawer-button.tsx +300 -0
  415. package/src/components/table-properties/drawer.tsx +1148 -0
  416. package/src/components/table-properties/filter-card.tsx +251 -0
  417. package/src/components/table-properties/index.ts +36 -0
  418. package/src/components/table-properties/sort-card.tsx +63 -0
  419. package/src/components/templates/dedicated-search-landing-template.tsx +124 -0
  420. package/src/components/templates/dedicated-search-results-template.tsx +19 -0
  421. package/src/components/templates/index.ts +33 -0
  422. package/src/components/templates/list-page.tsx +602 -0
  423. package/src/components/templates/nested-secondary-panel-shell.tsx +70 -0
  424. package/src/components/ui/accordion.tsx +92 -0
  425. package/src/components/ui/alert-dialog.tsx +221 -0
  426. package/src/components/ui/avatar.tsx +13 -2
  427. package/src/components/ui/banner.tsx +2 -2
  428. package/src/components/ui/button.tsx +4 -4
  429. package/src/components/ui/calendar.tsx +1 -1
  430. package/src/components/ui/coach-mark.tsx +1 -1
  431. package/src/components/ui/context-menu.tsx +291 -0
  432. package/src/components/ui/date-picker-field.tsx +2 -2
  433. package/src/components/ui/dot-pattern.tsx +183 -0
  434. package/src/components/ui/export-drawer.tsx +375 -0
  435. package/src/components/ui/hover-card.tsx +66 -0
  436. package/src/components/ui/key-metrics-context.tsx +78 -0
  437. package/src/components/ui/key-metrics.tsx +1133 -0
  438. package/src/components/ui/list-page-view-frame.tsx +64 -0
  439. package/src/components/ui/page-header.tsx +244 -0
  440. package/src/components/ui/payment-card-fields.tsx +2 -2
  441. package/src/components/ui/resizable.tsx +68 -0
  442. package/src/components/ui/scroll-area.tsx +72 -0
  443. package/src/components/ui/selection-tile-grid.tsx +9 -2
  444. package/src/components/ui/sidebar.tsx +84 -12
  445. package/src/components/ui/slider.tsx +83 -0
  446. package/src/globals.css +2201 -7
  447. package/src/globals.d.ts +20 -0
  448. package/src/index.ts +68 -1
  449. package/src/lib/conditional-rule-match.ts +119 -0
  450. package/src/lib/data-list-display-options.ts +35 -0
  451. package/src/lib/data-list-view-registry.ts +104 -0
  452. package/src/lib/data-list-view-surface.ts +83 -0
  453. package/src/lib/data-list-view.ts +47 -0
  454. package/src/lib/dev-log.ts +10 -0
  455. package/src/lib/editable-target.ts +20 -0
  456. package/src/lib/list-page-table-properties.ts +48 -0
  457. package/src/lib/raf-throttle.ts +45 -0
  458. package/src/lib/row-height.ts +19 -0
  459. package/src/lib/table-properties-types.ts +98 -0
  460. package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
  461. package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
  462. package/template/.cursor/rules/exxat-command-menu.mdc +2 -2
  463. package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +7 -7
  464. package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
  465. package/template/.cursor/rules/exxat-ds-agents.mdc +2 -2
  466. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +7 -7
  467. package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
  468. package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
  469. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
  470. package/template/AGENTS.md +135 -103
  471. package/template/app/(app)/columns/page.tsx +11 -0
  472. package/template/app/(app)/dashboard/loading.tsx +15 -3
  473. package/template/app/(app)/dashboard/page.tsx +14 -2
  474. package/template/app/(app)/layout.tsx +17 -4
  475. package/template/app/(app)/library/all/page.tsx +11 -0
  476. package/template/app/(app)/library/find/page.tsx +12 -0
  477. package/template/app/(app)/{question-bank → library}/layout.tsx +17 -17
  478. package/template/app/(app)/library/list/page.tsx +12 -0
  479. package/template/app/(app)/library/new/page.tsx +45 -0
  480. package/template/app/(app)/library/page.tsx +11 -0
  481. package/template/app/(app)/loading.tsx +18 -1
  482. package/template/app/(app)/settings/page.tsx +5 -4
  483. package/template/app/(app)/tokens-themes/page.tsx +11 -0
  484. package/template/app/globals.css +14 -16
  485. package/template/components/ask-leo-composer.tsx +2 -2
  486. package/template/components/ask-leo-sidebar.tsx +5 -1
  487. package/template/components/brand-color-picker.tsx +2 -2
  488. package/template/components/charts-overview.tsx +1 -1
  489. package/template/components/columns-client.tsx +158 -0
  490. package/template/components/columns-showcase.tsx +541 -0
  491. package/template/components/dashboard-report-charts.tsx +1 -1
  492. package/template/components/dashboard-tabs.tsx +1 -1
  493. package/template/components/data-table/filter-date-calendar.tsx +1 -38
  494. package/template/components/data-table/filter-text-value-input.tsx +1 -77
  495. package/template/components/data-table/index.tsx +1 -1634
  496. package/template/components/data-table/pagination.tsx +1 -255
  497. package/template/components/data-table/types.ts +1 -94
  498. package/template/components/data-table/use-table-state.test.ts +420 -0
  499. package/template/components/data-table/use-table-state.ts +1 -758
  500. package/template/components/data-views/board-card-primitives.tsx +1 -93
  501. package/template/components/data-views/data-row-list.tsx +1 -183
  502. package/template/components/data-views/finder-panel-view.tsx +1 -405
  503. package/template/components/data-views/folder-grid-view.tsx +1 -86
  504. package/template/components/data-views/hub-table.tsx +1 -0
  505. package/template/components/data-views/index.ts +77 -38
  506. package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
  507. package/template/components/data-views/list-page-board-card.tsx +1 -192
  508. package/template/components/data-views/list-page-board-template.tsx +1 -122
  509. package/template/components/data-views/list-page-connected-view-body.tsx +1 -66
  510. package/template/components/data-views/list-page-split-details-placeholder.tsx +1 -39
  511. package/template/components/data-views/list-page-split-hub-chrome.tsx +1 -68
  512. package/template/components/data-views/list-page-split-hub-tokens.ts +1 -16
  513. package/template/components/data-views/list-page-tree-column-header.tsx +1 -31
  514. package/template/components/data-views/list-page-tree-panel-shell.tsx +1 -91
  515. package/template/components/data-views/list-page-view-frame.tsx +5 -53
  516. package/template/components/data-views/os-folder-glyph.tsx +1 -129
  517. package/template/components/data-views/outline-tree-menu.tsx +1 -157
  518. package/template/components/data-views/table-cells.tsx +673 -0
  519. package/template/components/export-drawer.test.tsx +71 -0
  520. package/template/components/export-drawer.tsx +1 -375
  521. package/template/components/exxat-product-logo.tsx +5 -5
  522. package/template/components/folder-details-shell.tsx +11 -11
  523. package/template/components/hub-tree-panel-view.tsx +26 -26
  524. package/template/components/invite-collaborators-drawer.tsx +3 -3
  525. package/template/components/key-metrics-ask-leo-bridge.tsx +40 -0
  526. package/template/components/key-metrics.tsx +1 -1063
  527. package/template/components/leo-insight-indicator.tsx +2 -2
  528. package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
  529. package/template/components/{question-bank-client.tsx → library-client.tsx} +83 -83
  530. package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
  531. package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
  532. package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +44 -44
  533. package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +16 -16
  534. package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
  535. package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
  536. package/template/components/library-panel-activator.tsx +8 -0
  537. package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +63 -63
  538. package/template/components/library-table.tsx +839 -0
  539. package/template/components/list-hub-status-badge.tsx +2 -2
  540. package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +489 -441
  541. package/template/components/onboarding/index.ts +9 -0
  542. package/template/components/onboarding/onboarding-01.tsx +1 -1
  543. package/template/components/onboarding/onboarding-02.tsx +1 -1
  544. package/template/components/onboarding/onboarding-03.tsx +1 -1
  545. package/template/components/onboarding/onboarding-04.tsx +1 -1
  546. package/template/components/page-header.tsx +8 -226
  547. package/template/components/product-switcher.tsx +3 -4
  548. package/template/components/product-wordmark.tsx +2 -1
  549. package/template/components/settings-appearance-card.tsx +3 -4
  550. package/template/components/settings-client.tsx +15 -59
  551. package/template/components/settings-form-row.tsx +4 -9
  552. package/template/components/{app-sidebar-dynamic.tsx → sidebar/app-sidebar-dynamic.tsx} +1 -1
  553. package/template/components/{app-sidebar.tsx → sidebar/app-sidebar.tsx} +114 -73
  554. package/template/components/sidebar/index.ts +16 -0
  555. package/template/components/{secondary-nav.tsx → sidebar/secondary-nav.tsx} +2 -2
  556. package/template/components/sidebar/secondary-panel.tsx +316 -0
  557. package/template/components/sidebar/sidebar-auto-collapse.tsx +27 -0
  558. package/template/components/{sidebar-auto-open.tsx → sidebar/sidebar-auto-open.tsx} +2 -1
  559. package/template/components/{sidebar-shell.tsx → sidebar/sidebar-shell.tsx} +1 -1
  560. package/template/components/site-header.tsx +1 -1
  561. package/template/components/table-properties/column-row.tsx +1 -90
  562. package/template/components/table-properties/draggable-list.ts +1 -49
  563. package/template/components/table-properties/drawer-button.tsx +1 -262
  564. package/template/components/table-properties/drawer.tsx +1 -1166
  565. package/template/components/table-properties/filter-card.tsx +1 -251
  566. package/template/components/table-properties/sort-card.tsx +1 -59
  567. package/template/components/table-properties/types.ts +28 -71
  568. package/template/components/templates/dedicated-search-landing-template.tsx +1 -124
  569. package/template/components/templates/dedicated-search-results-template.tsx +1 -19
  570. package/template/components/templates/discovery-hub-template.tsx +1 -1
  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 +2 -2
  575. package/template/components/tokens-secondary-nav.tsx +192 -0
  576. package/template/components/tokens-themes-client.tsx +476 -0
  577. package/template/components/tokens-themes-section.tsx +386 -0
  578. package/template/components/ui/accordion.tsx +1 -0
  579. package/template/components/ui/alert-dialog.tsx +1 -0
  580. package/template/components/ui/context-menu.tsx +1 -0
  581. package/template/components/ui/dot-pattern.tsx +1 -183
  582. package/template/components/ui/hover-card.tsx +1 -0
  583. package/template/components/ui/resizable.tsx +1 -68
  584. package/template/components/ui/scroll-area.tsx +1 -0
  585. package/template/components/ui/slider.tsx +1 -0
  586. package/template/docs/HANDBOOK.md +187 -0
  587. package/template/docs/blueprints/README.md +86 -0
  588. package/template/docs/blueprints/_template.md +91 -0
  589. package/template/docs/blueprints/board-card.md +123 -0
  590. package/template/docs/blueprints/data-table.md +139 -0
  591. package/template/docs/blueprints/key-metrics.md +128 -0
  592. package/template/docs/blueprints/list-page-template.md +123 -0
  593. package/template/docs/blueprints/page-header.md +130 -0
  594. package/template/docs/collaboration-access-pattern.md +7 -7
  595. package/template/docs/command-menu-pattern.md +1 -1
  596. package/template/docs/component-selection-guide.md +224 -0
  597. package/template/docs/components-audit-2026-05.md +158 -0
  598. package/template/docs/data-views-pattern.md +31 -66
  599. package/template/docs/drawer-vs-dialog-pattern.md +1 -3
  600. package/template/docs/glossary.md +58 -0
  601. package/template/docs/kpi-flat-band-pattern.md +3 -3
  602. package/template/docs/kpi-trend-pattern.md +18 -3
  603. package/template/docs/large-dataset-strategy.md +155 -0
  604. package/template/docs/library-hub-header-pattern.md +25 -0
  605. package/template/docs/migrations/0001-brand-deep-alias-stabilization.md +95 -0
  606. package/template/docs/migrations/0002-exxat-token-namespace.md +154 -0
  607. package/template/docs/migrations/0003-globals-css-canonical.md +110 -0
  608. package/template/docs/migrations/README.md +100 -0
  609. package/template/docs/migrations/_template.md +64 -0
  610. package/template/docs/reference-implementations.md +151 -0
  611. package/template/docs/shell-surface-elevation-pattern.md +3 -5
  612. package/template/docs/token-taxonomy.md +416 -0
  613. package/template/docs/voice-and-tone.md +262 -0
  614. package/template/eslint.config.mjs +27 -0
  615. package/template/hooks/use-secondary-panel-hub-nav.ts +11 -11
  616. package/template/lib/ask-leo-route-context.ts +6 -18
  617. package/template/lib/coach-mark-registry.ts +0 -16
  618. package/template/lib/command-menu-config.ts +5 -13
  619. package/template/lib/command-menu-search-data.ts +8 -23
  620. package/template/lib/conditional-rule-match.ts +6 -97
  621. package/template/lib/data-list-display-options.ts +1 -49
  622. package/template/lib/data-list-view-registry.ts +1 -104
  623. package/template/lib/data-list-view-surface.ts +1 -83
  624. package/template/lib/data-list-view.ts +1 -47
  625. package/template/lib/data-view-dashboard-storage.ts +35 -38
  626. package/template/lib/dev-log.ts +1 -8
  627. package/template/lib/editable-target.ts +1 -10
  628. package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
  629. package/template/lib/library-dedicated-search.ts +19 -0
  630. package/template/lib/library-hub-search.ts +90 -0
  631. package/template/lib/library-nav.ts +477 -0
  632. package/template/lib/library-recent-searches.ts +22 -0
  633. package/template/lib/{question-bank-supported-views.ts → library-supported-views.ts} +2 -3
  634. package/template/lib/list-page-table-properties.ts +1 -48
  635. package/template/lib/list-status-badges.ts +16 -11
  636. package/template/lib/mock/dashboard.ts +1 -1
  637. package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
  638. package/template/lib/mock/library-header-collaborators.ts +54 -0
  639. package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
  640. package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
  641. package/template/lib/mock/library.ts +249 -0
  642. package/template/lib/mock/navigation.tsx +32 -35
  643. package/template/lib/raf-throttle.ts +1 -45
  644. package/template/lib/row-height.ts +4 -10
  645. package/template/lib/sidebar-state-cookie.ts +11 -2
  646. package/template/lib/table-state-lifecycle.ts +3 -3
  647. package/template/next.config.mjs +7 -4
  648. package/template/package.json +1 -0
  649. package/template/tests/setup.ts +25 -0
  650. package/consumer-extras/AGENTS.md +0 -76
  651. package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +0 -37
  652. package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +0 -57
  653. package/consumer-extras/patterns/consumer-app-pattern.md +0 -39
  654. package/consumer-extras/patterns/focused-workflow-page-pattern.md +0 -84
  655. package/src/components/ui/button-group.tsx +0 -81
  656. package/src/theme.css +0 -16
  657. package/src/tokens/README.md +0 -15
  658. package/src/tokens/base.css +0 -337
  659. package/src/tokens/high-contrast.css +0 -1195
  660. package/src/tokens/layers.css +0 -224
  661. package/src/tokens/tailwind-bridge.css +0 -118
  662. package/src/tokens/themes.css +0 -201
  663. package/template/app/(app)/data-list/layout.tsx +0 -43
  664. package/template/app/(app)/data-list/page.tsx +0 -10
  665. package/template/app/(app)/examples/focused-workflow/page.tsx +0 -5
  666. package/template/app/(app)/examples/page.tsx +0 -43
  667. package/template/app/(app)/question-bank/find/page.tsx +0 -13
  668. package/template/app/(app)/question-bank/library/page.tsx +0 -12
  669. package/template/app/(app)/question-bank/list/page.tsx +0 -13
  670. package/template/app/(app)/question-bank/new/page.tsx +0 -50
  671. package/template/app/(app)/question-bank/page.tsx +0 -12
  672. package/template/components/app-route-loading.tsx +0 -14
  673. package/template/components/dashboard-onboarding-gallery.tsx +0 -13
  674. package/template/components/dashboard-onboarding.tsx +0 -21
  675. package/template/components/data-views/list-page-calendar-view.tsx +0 -593
  676. package/template/components/data-views/list-page-folder-columns-panel.tsx +0 -345
  677. package/template/components/examples/focused-workflow-showcase.tsx +0 -183
  678. package/template/components/list-hub-board-view.tsx +0 -68
  679. package/template/components/list-hub-client.tsx +0 -186
  680. package/template/components/list-hub-list-view.tsx +0 -36
  681. package/template/components/list-hub-panel-activator.tsx +0 -8
  682. package/template/components/list-hub-secondary-nav.tsx +0 -121
  683. package/template/components/list-hub-table.tsx +0 -336
  684. package/template/components/question-bank-folder-columns-panel.tsx +0 -104
  685. package/template/components/question-bank-list-view.tsx +0 -53
  686. package/template/components/question-bank-panel-activator.tsx +0 -8
  687. package/template/components/question-bank-table.tsx +0 -729
  688. package/template/components/secondary-panel/nav-link-rows.tsx +0 -83
  689. package/template/components/secondary-panel.tsx +0 -220
  690. package/template/components/secondary-panels/list-hub-panel.tsx +0 -39
  691. package/template/components/secondary-panels/question-bank-panel.tsx +0 -39
  692. package/template/components/secondary-panels/registry.tsx +0 -15
  693. package/template/components/section-cards.tsx +0 -106
  694. package/template/components/sidebar-auto-collapse.tsx +0 -23
  695. package/template/components/templates/focused-workflow-layouts.tsx +0 -448
  696. package/template/components/templates/focused-workflow-page-template.tsx +0 -69
  697. package/template/components/templates/page-loading-shell.tsx +0 -262
  698. package/template/components/ui/button-group.tsx +0 -1
  699. package/template/docs/consumer-app-pattern.md +0 -39
  700. package/template/docs/focused-workflow-page-pattern.md +0 -84
  701. package/template/docs/question-bank-hub-header-pattern.md +0 -25
  702. package/template/lib/list-hub-nav.ts +0 -121
  703. package/template/lib/mock/list-hub-directory.ts +0 -27
  704. package/template/lib/mock/list-hub-kpi.ts +0 -27
  705. package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
  706. package/template/lib/mock/question-bank.ts +0 -249
  707. package/template/lib/page-loading-variant.ts +0 -40
  708. package/template/lib/question-bank-dedicated-search.ts +0 -19
  709. package/template/lib/question-bank-hub-search.ts +0 -90
  710. package/template/lib/question-bank-nav.ts +0 -477
  711. package/template/lib/question-bank-recent-searches.ts +0 -22
  712. /package/template/components/{getting-started.tsx → onboarding/getting-started.tsx} +0 -0
  713. /package/template/components/{nav-documents.tsx → sidebar/nav-documents.tsx} +0 -0
  714. /package/template/components/{nav-main.tsx → sidebar/nav-main.tsx} +0 -0
  715. /package/template/components/{nav-secondary.tsx → sidebar/nav-secondary.tsx} +0 -0
  716. /package/template/components/{nav-user.tsx → sidebar/nav-user.tsx} +0 -0
@@ -1,16 +1,27 @@
1
1
  "use client"
2
2
 
3
3
  /**
4
- * NewQuestionComposer — single-page authoring for the question bank.
4
+ * NewLibraryItemForm — single-page authoring for the library.
5
5
  *
6
- * IA (matches the rest of the question bank surfaces):
6
+ * IA (matches the rest of the library surfaces):
7
7
  *
8
8
  * ├─ PageHeader (title + actions; parent trail is in `SiteHeader`)
9
9
  * │ · "New question" + "V1 · Last updated …" subtitle
10
- * │ · Save question (⏎) + Save as draft + ⋯ discard (⌘⌥M)
11
- * └─ Single column (`FocusedWorkflowPageTemplate` see `docs/focused-workflow-page-pattern.md`)
12
- * · Details format, folder, difficulty, Bloom, NBME, tags
13
- * · Question prompt, answer block, explanation, references
10
+ * │ · single primary CTA Save as draft ()
11
+ * │ · overflow menu (⌘⌥M) for inspector toggle + discard
12
+ * ├─ 2-column layout (lg+): page scrolls as one; inspector card `sticky` on lg+
13
+ * │ ┌─ Builder (left, no card chrome)
14
+ * │ │ · Question prompt (h1-style Textarea — type-aware)
15
+ * │ │ · Answer block — varies by question type
16
+ * │ │ · Explanation / rubric / model answer
17
+ * │ │ · References (repeatable list)
18
+ * │ └─ Inspector (right, bg-card panel)
19
+ * │ · Question format (SelectionTileGrid → compact)
20
+ * │ · Location (folder SelectionTileGrid)
21
+ * │ · Level / Tier / Cognitive (chips)
22
+ * │ · Tags (Input + Badge list)
23
+ * │ Sidebar-style collapse (⌘⌥]) — collapsed rail mimics
24
+ * │ `NestedSecondaryPanelShell` icon mode.
14
25
  *
15
26
  * Composes existing primitives — `PageHeader`, `Form`/`FormField`,
16
27
  * `Input`, `Textarea`, `Checkbox`, `Badge`, `Button`, `Tip`, `Kbd`,
@@ -57,6 +68,7 @@ import {
57
68
  } from "@/components/ui/dropdown-menu"
58
69
  import { Tip } from "@/components/ui/tip"
59
70
  import { PageHeader } from "@/components/page-header"
71
+ import { NewFocusTemplate } from "@/components/templates/new-focus-template"
60
72
  import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
61
73
  import {
62
74
  SelectionTileGrid,
@@ -76,7 +88,7 @@ import {
76
88
  CommandList,
77
89
  CommandSeparator,
78
90
  } from "@/components/ui/command"
79
- import { QuestionBankNewFolderSheet } from "@/components/question-bank-new-folder-sheet"
91
+ import { LibraryNewFolderSheet } from "@/components/library-new-folder-sheet"
80
92
 
81
93
  import {
82
94
  AUTHORING_QUESTION_TYPES,
@@ -91,13 +103,13 @@ import {
91
103
  AUTHORING_RATIONALE_PLACEHOLDER,
92
104
  authoringQuestionType,
93
105
  type AuthoringQuestionType,
94
- } from "@/lib/question-bank-authoring"
106
+ } from "@/lib/library-authoring"
95
107
  import {
96
- DEFAULT_QUESTION_BANK_FOLDERS,
108
+ DEFAULT_LIBRARY_FOLDERS,
97
109
  newFolderId,
98
- type QuestionBankFolder,
99
- type QuestionBankFolderColorKey,
100
- } from "@/lib/mock/question-bank-folders"
110
+ type LibraryFolder,
111
+ type LibraryFolderColorKey,
112
+ } from "@/lib/mock/library-folders"
101
113
 
102
114
  // ─────────────────────────────────────────────────────────────────────────────
103
115
  // Schema
@@ -259,6 +271,10 @@ type QuestionFormValues = z.infer<typeof questionSchema>
259
271
 
260
272
  const OPTION_LETTERS = ["A", "B", "C", "D", "E", "F", "G", "H"] as const
261
273
 
274
+ // Runtime ID generators — called from user actions (Add option, Add pair, etc.) which
275
+ // only fire AFTER hydration, so `Math.random()` is safe here. NEVER call these from
276
+ // `buildInitial*` factories below — those run during the server render too and a
277
+ // random ID would mismatch the client tree on hydration.
262
278
  function newOptionId() {
263
279
  return `opt-${Math.random().toString(36).slice(2, 9)}`
264
280
  }
@@ -275,16 +291,20 @@ function newBlankId() {
275
291
  return `blk-${Math.random().toString(36).slice(2, 9)}`
276
292
  }
277
293
 
294
+ // SSR-safe defaults — deterministic, index-based IDs so the server and client render
295
+ // the same `id` / `key` / DOM attributes (e.g. `Checkbox` IDs derived from `option.id`).
296
+ // Hydration mismatch issue: `Math.random()` produces different values per call, so
297
+ // using it in initial defaults makes every render emit a different tree.
278
298
  function buildInitialOptions(type: AuthoringQuestionType): QuestionFormValues["options"] {
279
299
  if (type === "true_false") {
280
300
  return [
281
- { id: newOptionId(), text: "True", isCorrect: false, rationale: "" },
282
- { id: newOptionId(), text: "False", isCorrect: false, rationale: "" },
301
+ { id: "opt-true", text: "True", isCorrect: false, rationale: "" },
302
+ { id: "opt-false", text: "False", isCorrect: false, rationale: "" },
283
303
  ]
284
304
  }
285
305
  if (type === "mcq_single" || type === "mcq_multiple") {
286
- return Array.from({ length: AUTHORING_DEFAULT_OPTION_COUNT }, () => ({
287
- id: newOptionId(),
306
+ return Array.from({ length: AUTHORING_DEFAULT_OPTION_COUNT }, (_, i) => ({
307
+ id: `opt-init-${i + 1}`,
288
308
  text: "",
289
309
  isCorrect: false,
290
310
  rationale: "",
@@ -294,16 +314,23 @@ function buildInitialOptions(type: AuthoringQuestionType): QuestionFormValues["o
294
314
  }
295
315
 
296
316
  function buildInitialPairs(): QuestionFormValues["pairs"] {
297
- return Array.from({ length: 3 }, () => ({ id: newPairId(), left: "", right: "" }))
317
+ return Array.from({ length: 3 }, (_, i) => ({
318
+ id: `pair-init-${i + 1}`,
319
+ left: "",
320
+ right: "",
321
+ }))
298
322
  }
299
323
  function buildInitialOrderedItems(): QuestionFormValues["orderedItems"] {
300
- return Array.from({ length: 4 }, () => ({ id: newOrderedId(), text: "" }))
324
+ return Array.from({ length: 4 }, (_, i) => ({
325
+ id: `ord-init-${i + 1}`,
326
+ text: "",
327
+ }))
301
328
  }
302
329
  function buildInitialFillBlankAnswers(): QuestionFormValues["fillBlankAnswers"] {
303
- return [{ id: newBlankId(), accepted: "" }]
330
+ return [{ id: "blk-init-1", accepted: "" }]
304
331
  }
305
332
 
306
- function folderBreadcrumb(folderId: string, folders: QuestionBankFolder[]): string {
333
+ function folderBreadcrumb(folderId: string, folders: LibraryFolder[]): string {
307
334
  const f = folders.find(x => x.id === folderId)
308
335
  if (!f) return ""
309
336
  if (f.parentId == null) return f.name
@@ -324,7 +351,7 @@ function difficultyToPercent(value: "easy" | "medium" | "hard"): number {
324
351
  * build this comes from analytics; for the mock it is a stable seed so
325
352
  * the inspector reads as if the AI had crunched historical data.
326
353
  */
327
- function difficultyInsightForFolder(folder: QuestionBankFolder | undefined): {
354
+ function difficultyInsightForFolder(folder: LibraryFolder | undefined): {
328
355
  /** Predicted level based on the content the author is writing. */
329
356
  recommendation: "easy" | "medium" | "hard"
330
357
  /** Contextual note shown under the meter (e.g. folder distribution). */
@@ -408,7 +435,7 @@ function BuilderSection({
408
435
  <h2 className="text-sm font-semibold text-foreground">
409
436
  {title}
410
437
  {required ? (
411
- <span className="ml-1 text-destructive" aria-hidden="true">
438
+ <span className="ms-1 text-destructive" aria-hidden="true">
412
439
  *
413
440
  </span>
414
441
  ) : null}
@@ -425,11 +452,11 @@ function BuilderSection({
425
452
  // Compact selected tile — same visual rhythm as the collapsed
426
453
  // "Question format" card. Clicking the tile opens a Command popover with
427
454
  // search, full folder list, and an "Add new folder" footer that bridges to
428
- // `QuestionBankNewFolderSheet`. Visuals reuse the folder-color tokens from
429
- // `lib/mock/question-bank-folders.ts` so the inspector reads the same as
430
- // the rest of the question bank.
455
+ // `LibraryNewFolderSheet`. Visuals reuse the folder-color tokens from
456
+ // `lib/mock/library-folders.ts` so the inspector reads the same as
457
+ // the rest of the library.
431
458
 
432
- const FOLDER_TINT_BG: Record<QuestionBankFolderColorKey, string> = {
459
+ const FOLDER_TINT_BG: Record<LibraryFolderColorKey, string> = {
433
460
  brand: "bg-[var(--icon-disc-brand-bg)] text-[var(--icon-disc-brand-fg)]",
434
461
  success: "bg-[var(--icon-disc-chart-2-bg)] text-[var(--icon-disc-chart-2-fg)]",
435
462
  warning: "bg-[var(--icon-disc-chart-4-bg)] text-[var(--icon-disc-chart-4-fg)]",
@@ -441,7 +468,7 @@ const FOLDER_TINT_BG: Record<QuestionBankFolderColorKey, string> = {
441
468
  }
442
469
 
443
470
  interface FolderPickerControlProps {
444
- folders: QuestionBankFolder[]
471
+ folders: LibraryFolder[]
445
472
  value: string
446
473
  onChange: (id: string) => void
447
474
  open: boolean
@@ -452,14 +479,14 @@ interface FolderPickerControlProps {
452
479
  /** Build a tree-ordered array: parents first, then their children
453
480
  indented beneath. Each entry carries a `depth` for left-padding. */
454
481
  function buildFolderTree(
455
- folders: QuestionBankFolder[],
456
- ): Array<{ folder: QuestionBankFolder; depth: number }> {
482
+ folders: LibraryFolder[],
483
+ ): Array<{ folder: LibraryFolder; depth: number }> {
457
484
  const roots = folders.filter(f => f.parentId == null)
458
485
  const childrenOf = (parentId: string) =>
459
486
  folders.filter(f => f.parentId === parentId)
460
487
 
461
- const out: Array<{ folder: QuestionBankFolder; depth: number }> = []
462
- function walk(parent: QuestionBankFolder, depth: number) {
488
+ const out: Array<{ folder: LibraryFolder; depth: number }> = []
489
+ function walk(parent: LibraryFolder, depth: number) {
463
490
  out.push({ folder: parent, depth })
464
491
  for (const child of childrenOf(parent.id)) {
465
492
  walk(child, depth + 1)
@@ -628,7 +655,7 @@ function DifficultyMeter({
628
655
  : "bg-destructive"
629
656
 
630
657
  const levelLabel =
631
- value === "easy" ? "Easy" : value === "hard" ? "Hard" : "Medium"
658
+ value === "easy" ? "Low" : value === "hard" ? "High" : "Normal"
632
659
 
633
660
  return (
634
661
  <section className="flex flex-col gap-3">
@@ -753,26 +780,31 @@ interface NewQuestionComposerProps {
753
780
  defaultFolderId?: string
754
781
  /** Where to send the user when they cancel or save. */
755
782
  backHref: string
756
- folders?: QuestionBankFolder[]
783
+ /** Label displayed in the `SiteHeader` back-icon (e.g. "Back to Favorites"). */
784
+ backLabel?: string
785
+ folders?: LibraryFolder[]
757
786
  }
758
787
 
759
- export function NewQuestionComposer({
788
+ export function NewLibraryItemForm({
760
789
  draftQuestionId,
761
790
  defaultFolderId,
762
791
  backHref,
763
- folders = DEFAULT_QUESTION_BANK_FOLDERS,
792
+ backLabel = "Back",
793
+ folders = DEFAULT_LIBRARY_FOLDERS,
764
794
  }: NewQuestionComposerProps) {
765
795
  const router = useRouter()
766
796
  const [submitting, setSubmitting] = React.useState(false)
767
797
  const [tagDraft, setTagDraft] = React.useState("")
768
- const [moreOpen, setMoreOpen] = React.useState(false)
769
798
  const [inspectorOpen, setInspectorOpen] = React.useState(true)
770
- /** Question-type chooser collapses to a compact row after first pick. */
799
+ const [moreOpen, setMoreOpen] = React.useState(false)
800
+ /** Question-type chooser visibility — collapses into a single
801
+ "selected type" tile once the author picks the first time so the
802
+ inspector stays compact for the rest of the authoring flow. */
771
803
  const [typeChooserOpen, setTypeChooserOpen] = React.useState(true)
772
804
  /** Local folder list — extended in-place when the author adds one
773
805
  from the location picker so the new entry is selectable without
774
806
  a page navigation. */
775
- const [localFolders, setLocalFolders] = React.useState<QuestionBankFolder[]>(folders)
807
+ const [localFolders, setLocalFolders] = React.useState<LibraryFolder[]>(folders)
776
808
  React.useEffect(() => {
777
809
  setLocalFolders(prev =>
778
810
  prev.length === folders.length && prev.every((f, i) => f.id === folders[i]?.id)
@@ -1076,6 +1108,292 @@ export function NewQuestionComposer({
1076
1108
  </>
1077
1109
  )
1078
1110
 
1111
+ // Inspector body — wired into `NewFocusTemplate.form-inspector` via the `inspector`
1112
+ // render-prop. Renders both the collapsed-rail and expanded-card states so the composer
1113
+ // keeps its existing UX while the template owns the outer `<aside>` chrome (width
1114
+ // transition + sticky positioning).
1115
+ const inspectorContent = (
1116
+ <>
1117
+ {!inspectorOpen ? (
1118
+ <div
1119
+ className={cn(
1120
+ "flex w-12 flex-col items-center gap-1 rounded-xl bg-[var(--secondary-panel-bg)] px-1.5 py-2 ring-1 ring-border shadow-sm",
1121
+ )}
1122
+ >
1123
+ <Tip side="left" label="Show inspector">
1124
+ <button
1125
+ type="button"
1126
+ onClick={() => setInspectorOpen(true)}
1127
+ aria-label="Show inspector"
1128
+ aria-expanded={false}
1129
+ className={cn(
1130
+ "flex size-9 shrink-0 items-center justify-center rounded-md text-sidebar-foreground transition-colors",
1131
+ "hover:bg-sidebar-accent/50",
1132
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
1133
+ )}
1134
+ >
1135
+ <i
1136
+ className="fa-light fa-arrow-left-to-line text-[15px] leading-none"
1137
+ aria-hidden="true"
1138
+ />
1139
+ </button>
1140
+ </Tip>
1141
+ </div>
1142
+ ) : (
1143
+ <div
1144
+ className={cn(
1145
+ "flex flex-col gap-6 rounded-xl border border-border bg-card p-4",
1146
+ "lg:max-h-[calc(100dvh-var(--header-height)-2rem)] lg:overflow-y-auto lg:overscroll-y-contain",
1147
+ )}
1148
+ >
1149
+ {/* Inspector header — title + collapse control. */}
1150
+ <div className="flex items-center justify-between">
1151
+ <p className="text-xs font-medium text-muted-foreground">
1152
+ Inspector
1153
+ </p>
1154
+ <Tip side="bottom" label="Hide inspector">
1155
+ <Button
1156
+ type="button"
1157
+ variant="ghost"
1158
+ size="icon-sm"
1159
+ onClick={() => setInspectorOpen(false)}
1160
+ aria-label="Hide inspector"
1161
+ aria-expanded={true}
1162
+ >
1163
+ <i
1164
+ className="fa-light fa-arrow-right-to-line"
1165
+ aria-hidden="true"
1166
+ />
1167
+ </Button>
1168
+ </Tip>
1169
+ </div>
1170
+
1171
+ {/* Question format — first-time landing shows the full
1172
+ `SelectionTileGrid` (matches the "File format" pattern
1173
+ in `ExportDrawer`). After the author picks once it
1174
+ collapses into a single selected-type tile with a
1175
+ "Change" affordance that re-opens the grid. */}
1176
+ <FormField
1177
+ control={form.control}
1178
+ name="type"
1179
+ render={({ field }) => (
1180
+ <FormItem>
1181
+ <FormControl>
1182
+ {typeChooserOpen ? (
1183
+ <SelectionTileGrid
1184
+ sectionLabel="Question format"
1185
+ options={QUESTION_TYPE_TILES}
1186
+ columns={2}
1187
+ value={field.value}
1188
+ onValueChange={v => changeType(v)}
1189
+ interaction="radio"
1190
+ idPrefix="qb-format"
1191
+ itemVariant="outline"
1192
+ itemMotion="pop"
1193
+ />
1194
+ ) : (
1195
+ <div className="flex flex-col gap-2">
1196
+ <Label
1197
+ className="text-xs font-medium text-muted-foreground"
1198
+ >
1199
+ Question format
1200
+ </Label>
1201
+ <button
1202
+ type="button"
1203
+ onClick={() => setTypeChooserOpen(true)}
1204
+ aria-label={`Change question format — currently ${activeType.label}`}
1205
+ className={cn(
1206
+ "group flex items-center gap-3 rounded-lg border border-border bg-background px-3 py-2.5 text-left transition-colors",
1207
+ "hover:border-foreground/30 hover:bg-muted/40",
1208
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1209
+ )}
1210
+ >
1211
+ <span
1212
+ className="inline-flex size-9 shrink-0 items-center justify-center rounded-md bg-[var(--icon-disc-brand-bg)] text-[var(--icon-disc-brand-fg)]"
1213
+ aria-hidden="true"
1214
+ >
1215
+ <i className={cn("fa-light text-base", activeType.icon)} />
1216
+ </span>
1217
+ <span className="flex min-w-0 flex-1 flex-col">
1218
+ <span className="truncate text-sm font-medium text-foreground">
1219
+ {activeType.label}
1220
+ </span>
1221
+ <span className="truncate text-xs text-muted-foreground">
1222
+ {activeType.tileSummary ?? activeType.description}
1223
+ </span>
1224
+ </span>
1225
+ <span
1226
+ className="inline-flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors group-hover:bg-muted group-hover:text-foreground"
1227
+ aria-hidden="true"
1228
+ >
1229
+ <i className="fa-light fa-pen-to-square text-xs" />
1230
+ </span>
1231
+ </button>
1232
+ </div>
1233
+ )}
1234
+ </FormControl>
1235
+ {typeChooserOpen ? (
1236
+ <FormDescription>{activeType.description}</FormDescription>
1237
+ ) : null}
1238
+ <FormMessage />
1239
+ </FormItem>
1240
+ )}
1241
+ />
1242
+
1243
+ {/* Location — compact selected tile (mirrors the
1244
+ collapsed question-format card). Click opens a Command
1245
+ popover with search and an "Add new folder" footer
1246
+ that bridges into `LibraryNewFolderSheet`. */}
1247
+ <FormField
1248
+ control={form.control}
1249
+ name="folderId"
1250
+ render={({ field }) => (
1251
+ <FormItem>
1252
+ <Label
1253
+ className="text-xs font-medium text-muted-foreground"
1254
+ >
1255
+ Location
1256
+ </Label>
1257
+ <FormControl>
1258
+ <FolderPickerControl
1259
+ folders={localFolders}
1260
+ value={field.value}
1261
+ onChange={v => field.onChange(v)}
1262
+ open={folderPickerOpen}
1263
+ onOpenChange={setFolderPickerOpen}
1264
+ onRequestNewFolder={() => {
1265
+ setFolderPickerOpen(false)
1266
+ setNewFolderOpen(true)
1267
+ }}
1268
+ />
1269
+ </FormControl>
1270
+ <FormMessage />
1271
+ </FormItem>
1272
+ )}
1273
+ />
1274
+
1275
+ {/* Difficulty — meter + AI estimate + PBI + folder note.
1276
+ Defaults to AI mode (the meter follows the folder
1277
+ recommendation); "Override" flips to manual chips for
1278
+ authors who want to lock the level themselves. */}
1279
+ <FormField
1280
+ control={form.control}
1281
+ name="difficulty"
1282
+ render={({ field }) => (
1283
+ <DifficultyMeter
1284
+ value={field.value}
1285
+ onChange={v => field.onChange(v)}
1286
+ mode={difficultyMode}
1287
+ onModeChange={setDifficultyMode}
1288
+ insight={difficultyInsight}
1289
+ />
1290
+ )}
1291
+ />
1292
+
1293
+ <FormField
1294
+ control={form.control}
1295
+ name="bloom"
1296
+ render={({ field }) => (
1297
+ <InspectorSection title="Bloom's taxonomy">
1298
+ <ToggleGroup
1299
+ type="single"
1300
+ variant="outline"
1301
+ size="sm"
1302
+ spacing={1}
1303
+ value={field.value}
1304
+ onValueChange={v => field.onChange(v)}
1305
+ className="flex-wrap"
1306
+ >
1307
+ {AUTHORING_BLOOM_OPTIONS.map(b => (
1308
+ <ToggleGroupItem
1309
+ key={b.value}
1310
+ value={b.value}
1311
+ title={b.hint}
1312
+ className="rounded-full px-3"
1313
+ >
1314
+ {b.label}
1315
+ </ToggleGroupItem>
1316
+ ))}
1317
+ </ToggleGroup>
1318
+ </InspectorSection>
1319
+ )}
1320
+ />
1321
+
1322
+ <FormField
1323
+ control={form.control}
1324
+ name="cogLevel"
1325
+ render={({ field }) => (
1326
+ <InspectorSection
1327
+ title="Cognitive level"
1328
+ description="Broader bucket used for analytics — separate from the tier above."
1329
+ >
1330
+ <ToggleGroup
1331
+ type="single"
1332
+ variant="outline"
1333
+ size="sm"
1334
+ spacing={1}
1335
+ value={field.value}
1336
+ onValueChange={v => field.onChange(v)}
1337
+ className="flex-wrap"
1338
+ >
1339
+ {AUTHORING_COG_LEVEL_OPTIONS.map(c => (
1340
+ <ToggleGroupItem
1341
+ key={c.value}
1342
+ value={c.value}
1343
+ title={c.hint}
1344
+ className="rounded-full px-3"
1345
+ >
1346
+ {c.label}
1347
+ </ToggleGroupItem>
1348
+ ))}
1349
+ </ToggleGroup>
1350
+ </InspectorSection>
1351
+ )}
1352
+ />
1353
+
1354
+ <InspectorSection title="Tags" htmlFor="qb-tag-input">
1355
+ {watchedTags.length > 0 ? (
1356
+ <div className="flex flex-wrap gap-1.5">
1357
+ {watchedTags.map(t => (
1358
+ <Badge key={t} variant="secondary" className="gap-1.5">
1359
+ <span>#{t}</span>
1360
+ <Tip side="top" label={`Remove tag ${t}`}>
1361
+ <button
1362
+ type="button"
1363
+ onClick={() => removeTag(t)}
1364
+ aria-label={`Remove tag ${t}`}
1365
+ className="-me-0.5 inline-flex size-3.5 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-background hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1366
+ >
1367
+ <i
1368
+ className="fa-light fa-xmark text-[9px]"
1369
+ aria-hidden="true"
1370
+ />
1371
+ </button>
1372
+ </Tip>
1373
+ </Badge>
1374
+ ))}
1375
+ </div>
1376
+ ) : null}
1377
+ <Input
1378
+ id="qb-tag-input"
1379
+ value={tagDraft}
1380
+ onChange={e => setTagDraft(e.target.value)}
1381
+ onKeyDown={e => {
1382
+ if (e.key === "Enter" || e.key === ",") {
1383
+ e.preventDefault()
1384
+ commitTag()
1385
+ }
1386
+ }}
1387
+ onBlur={commitTag}
1388
+ placeholder="STEMI, antibiotics…"
1389
+ className="h-8 text-xs"
1390
+ />
1391
+ </InspectorSection>
1392
+ </div>
1393
+ )}
1394
+ </>
1395
+ )
1396
+
1079
1397
  return (
1080
1398
  <Form {...form}>
1081
1399
  {/* Global shortcuts — bound while the composer is mounted. The
@@ -1089,126 +1407,146 @@ export function NewQuestionComposer({
1089
1407
  disabled={submitting}
1090
1408
  onInvoke={() => setMoreOpen(o => !o)}
1091
1409
  />
1410
+ <Shortcut
1411
+ keys="⌘⌥]"
1412
+ disabled={submitting}
1413
+ onInvoke={() => setInspectorOpen(o => !o)}
1414
+ />
1092
1415
 
1416
+ {/*
1417
+ `<form>` participates in the (app)/layout flex row alongside the sidebar +
1418
+ secondary panel + Ask Leo rail, so it MUST behave like a normal flex column
1419
+ host (flex-1 + min-w-0). Without these classes the form shrinks to its
1420
+ intrinsic content width and the page collapses into a narrow column on the
1421
+ left with the rest of the viewport empty. See `new-placement-form.tsx`.
1422
+ */}
1093
1423
  <form
1094
1424
  onSubmit={form.handleSubmit(values => persist({ ...values, status: "in_review" }, "publish"))}
1095
1425
  noValidate
1096
1426
  aria-label="New question form"
1097
- className="flex flex-col gap-6"
1427
+ className="flex min-h-0 min-w-0 flex-1 flex-col"
1098
1428
  >
1099
- <PageHeader
1100
- title="New question"
1101
- subtitle={headerSubtitle}
1102
- actions={
1103
- <>
1104
- {/* Save as draft — icon-only button (leftmost). */}
1105
- <Tip side="bottom" label="Save as draft">
1429
+ <NewFocusTemplate
1430
+ variant="form-inspector"
1431
+ title="New question"
1432
+ back={{ href: backHref, label: backLabel, ariaLabel: `Back to ${backLabel}` }}
1433
+ useSiteHeaderBack
1434
+ hideInspectorToggle
1435
+ inspectorOpen={inspectorOpen}
1436
+ onInspectorOpenChange={setInspectorOpen}
1437
+ inspectorAriaLabel="Question inspector"
1438
+ inspector={() => inspectorContent}
1439
+ header={
1440
+ <PageHeader
1441
+ title="New question"
1442
+ subtitle={headerSubtitle}
1443
+ actions={
1444
+ <>
1445
+ {/* Save as draft — icon-only button (leftmost). */}
1446
+ <Tip side="bottom" label="Save as draft">
1447
+ <Button
1448
+ type="button"
1449
+ size="lg"
1450
+ variant="outline"
1451
+ className="aspect-square px-0"
1452
+ disabled={submitting}
1453
+ onClick={handleSaveAsDraft}
1454
+ aria-label="Save as draft"
1455
+ >
1456
+ <i className="fa-light fa-file-pen text-base" aria-hidden="true" />
1457
+ </Button>
1458
+ </Tip>
1459
+
1460
+ {/* Primary — Save Question. Full validation; the
1461
+ question moves to In review and the user is
1462
+ returned to the parent hub. */}
1106
1463
  <Button
1107
1464
  type="button"
1108
1465
  size="lg"
1109
- variant="outline"
1110
- className="aspect-square px-0"
1111
1466
  disabled={submitting}
1112
- onClick={handleSaveAsDraft}
1113
- aria-label="Save as draft"
1467
+ aria-busy={submitting}
1468
+ onClick={handleSaveQuestion}
1114
1469
  >
1115
- <i className="fa-light fa-file-pen text-base" aria-hidden="true" />
1470
+ {submitting ? (
1471
+ <>
1472
+ <i
1473
+ className="fa-light fa-spinner-third fa-spin text-[13px]"
1474
+ aria-hidden="true"
1475
+ />
1476
+ Saving…
1477
+ </>
1478
+ ) : (
1479
+ <>
1480
+ Save question
1481
+ <KbdGroup className="ms-1.5">
1482
+ <Kbd variant="bare">⏎</Kbd>
1483
+ </KbdGroup>
1484
+ </>
1485
+ )}
1116
1486
  </Button>
1117
- </Tip>
1118
-
1119
- {/* Primary — Save Question. Full validation; the
1120
- question moves to In review and the user is
1121
- returned to the parent hub. */}
1122
- <Button
1123
- type="button"
1124
- size="lg"
1125
- disabled={submitting}
1126
- aria-busy={submitting}
1127
- onClick={handleSaveQuestion}
1128
- >
1129
- {submitting ? (
1130
- <>
1131
- <i
1132
- className="fa-light fa-spinner-third fa-spin text-[13px]"
1133
- aria-hidden="true"
1134
- />
1135
- Saving…
1136
- </>
1137
- ) : (
1138
- <>
1139
- Save question
1140
- <KbdGroup className="ml-1.5">
1141
- <Kbd variant="bare">⏎</Kbd>
1142
- </KbdGroup>
1143
- </>
1144
- )}
1145
- </Button>
1146
1487
 
1147
- {/* More — overflow menu (⌘⌥M). */}
1148
- <DropdownMenu open={moreOpen} onOpenChange={setMoreOpen}>
1149
- <Tip side="bottom" label="More actions">
1150
- <DropdownMenuTrigger asChild>
1151
- <Button
1152
- type="button"
1153
- size="lg"
1154
- variant="outline"
1155
- className="aspect-square px-0"
1156
- aria-label="More actions"
1488
+ {/* More — overflow menu (⌘⌥M). */}
1489
+ <DropdownMenu open={moreOpen} onOpenChange={setMoreOpen}>
1490
+ <Tip side="bottom" label="More actions">
1491
+ <DropdownMenuTrigger asChild>
1492
+ <Button
1493
+ type="button"
1494
+ size="lg"
1495
+ variant="outline"
1496
+ className="aspect-square px-0"
1497
+ aria-label="More actions"
1498
+ >
1499
+ <i
1500
+ className="fa-light fa-ellipsis text-base"
1501
+ aria-hidden="true"
1502
+ />
1503
+ </Button>
1504
+ </DropdownMenuTrigger>
1505
+ </Tip>
1506
+ <DropdownMenuContent align="end">
1507
+ <DropdownMenuItem
1508
+ shortcut="⌘⌥]"
1509
+ onSelect={() => {
1510
+ window.setTimeout(
1511
+ () => setInspectorOpen(o => !o),
1512
+ 0,
1513
+ )
1514
+ }}
1157
1515
  >
1158
1516
  <i
1159
- className="fa-light fa-ellipsis text-base"
1517
+ className={cn(
1518
+ "fa-light",
1519
+ inspectorOpen ? "fa-sidebar-flip" : "fa-sidebar",
1520
+ )}
1160
1521
  aria-hidden="true"
1161
1522
  />
1162
- </Button>
1163
- </DropdownMenuTrigger>
1164
- </Tip>
1165
- <DropdownMenuContent align="end">
1166
- <DropdownMenuItem
1167
- shortcut="⌘⌥]"
1168
- onSelect={() => {
1169
- window.setTimeout(
1170
- () => setInspectorOpen(o => !o),
1171
- 0,
1172
- )
1173
- }}
1174
- >
1175
- <i
1176
- className={cn(
1177
- "fa-light",
1178
- inspectorOpen ? "fa-sidebar-flip" : "fa-sidebar",
1179
- )}
1180
- aria-hidden="true"
1181
- />
1182
- {inspectorOpen ? "Hide inspector" : "Show inspector"}
1183
- </DropdownMenuItem>
1184
- <DropdownMenuSeparator />
1185
- <DropdownMenuItem
1186
- shortcut="Esc"
1187
- onSelect={() => {
1188
- window.setTimeout(() => handleCancel(), 0)
1189
- }}
1190
- variant="destructive"
1191
- >
1192
- <i className="fa-light fa-trash-can" aria-hidden="true" />
1193
- Discard draft
1194
- </DropdownMenuItem>
1195
- </DropdownMenuContent>
1196
- </DropdownMenu>
1197
- </>
1198
- }
1199
- className="px-0 lg:px-0"
1200
- />
1201
-
1202
- {/* ── 2-column body — scrolls with the page; inspector sticky (lg+) */}
1203
- <div
1204
- className={cn(
1205
- "flex flex-col gap-8",
1206
- "lg:flex-row lg:items-start",
1207
- inspectorOpen ? "lg:gap-x-10" : "lg:gap-x-4",
1208
- )}
1523
+ {inspectorOpen ? "Hide inspector" : "Show inspector"}
1524
+ </DropdownMenuItem>
1525
+ <DropdownMenuSeparator />
1526
+ <DropdownMenuItem
1527
+ shortcut="Esc"
1528
+ onSelect={() => {
1529
+ window.setTimeout(() => handleCancel(), 0)
1530
+ }}
1531
+ variant="destructive"
1532
+ >
1533
+ <i className="fa-light fa-trash-can" aria-hidden="true" />
1534
+ Discard draft
1535
+ </DropdownMenuItem>
1536
+ </DropdownMenuContent>
1537
+ </DropdownMenu>
1538
+ </>
1539
+ }
1540
+ className="px-0 lg:px-0"
1541
+ />
1542
+ }
1543
+ maxWidthClassName="mx-auto w-full max-w-[1100px]"
1544
+ contentClassName="px-8 py-4 pb-16 flex flex-col gap-6"
1545
+ bodyClassName="min-h-0 flex-1 overflow-y-auto overscroll-y-contain"
1209
1546
  >
1210
- {/* ── Builder (no Card chrome) ─────────────────────────────── */}
1211
- <div className="flex min-w-0 flex-1 flex-col gap-7 lg:pr-2">
1547
+ {/* ── Builder (no Card chrome) direct child of the template's
1548
+ form-inspector grid; inspector lives in the `inspector` prop. */}
1549
+ <div className="flex flex-col gap-7 lg:pe-2">
1212
1550
  {/* Question prompt — a real bordered field, not an
1213
1551
  inline-editable headline. Larger heading-weight font keeps
1214
1552
  the question front-and-centre, but the visible border /
@@ -1256,7 +1594,7 @@ export function NewQuestionComposer({
1256
1594
  )}
1257
1595
  />
1258
1596
 
1259
- {/* MCQ family + True/False — bordered OptionRows. */}
1597
+ {/* Choice family + True/False — bordered OptionRows. */}
1260
1598
  {showOptionsBlock ? (
1261
1599
  <FormField
1262
1600
  control={form.control}
@@ -1269,7 +1607,7 @@ export function NewQuestionComposer({
1269
1607
  >
1270
1608
  <p className="text-xs text-muted-foreground">
1271
1609
  {isMulti
1272
- ? "NCLEX-style — mark every correct response."
1610
+ ? "Mark every correct response."
1273
1611
  : isTrueFalse
1274
1612
  ? "Mark whether the statement is True or False."
1275
1613
  : "Mark the single best answer. Distractors should be plausible — same length and grammar as the correct option."}
@@ -1604,10 +1942,10 @@ export function NewQuestionComposer({
1604
1942
  Image hotspot authoring
1605
1943
  </p>
1606
1944
  <p className="mt-1 text-xs text-muted-foreground">
1607
- Upload an anatomy diagram, x-ray, or ECG, then draw the correct
1608
- region(s). Image upload + region drawing tools arrive in the
1609
- next phase — for now, describe the expected target in the
1610
- explanation below.
1945
+ Upload an image, then draw the correct region(s). Image
1946
+ upload + region drawing tools arrive in the next phase —
1947
+ for now, describe the expected target in the explanation
1948
+ below.
1611
1949
  </p>
1612
1950
  </div>
1613
1951
  <Button type="button" variant="outline" size="sm" disabled>
@@ -1740,311 +2078,21 @@ export function NewQuestionComposer({
1740
2078
  />
1741
2079
  ) : null}
1742
2080
  </div>
1743
-
1744
- {/* ── Inspector (right rail) — sticky while the page scrolls (lg+) */}
1745
- <aside
1746
- aria-label="Question inspector"
1747
- className={cn(
1748
- "shrink-0",
1749
- "lg:pl-2",
1750
- inspectorOpen ? "lg:w-[320px]" : "lg:w-14",
1751
- )}
1752
- >
1753
- {!inspectorOpen ? (
1754
- <div
1755
- className={cn(
1756
- "flex w-12 flex-col items-center gap-1 rounded-xl bg-[var(--secondary-panel-bg)] px-1.5 py-2 ring-1 ring-border shadow-sm",
1757
- "lg:sticky lg:top-4 lg:z-10",
1758
- )}
1759
- >
1760
- <Tip side="left" label="Show inspector">
1761
- <button
1762
- type="button"
1763
- onClick={() => setInspectorOpen(true)}
1764
- aria-label="Show inspector"
1765
- aria-expanded={false}
1766
- className={cn(
1767
- "flex size-9 shrink-0 items-center justify-center rounded-md text-sidebar-foreground transition-colors",
1768
- "hover:bg-sidebar-accent/50",
1769
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
1770
- )}
1771
- >
1772
- <i
1773
- className="fa-light fa-arrow-left-to-line text-[15px] leading-none"
1774
- aria-hidden="true"
1775
- />
1776
- </button>
1777
- </Tip>
1778
- </div>
1779
- ) : (
1780
- <div
1781
- className={cn(
1782
- "flex flex-col gap-6 rounded-xl border border-border bg-card p-4",
1783
- "lg:sticky lg:top-4 lg:z-10",
1784
- "lg:max-h-[calc(100dvh-var(--header-height)-2rem)] lg:overflow-y-auto lg:overscroll-y-contain",
1785
- )}
1786
- >
1787
- {/* Inspector header — title + collapse control. */}
1788
- <div className="flex items-center justify-between">
1789
- <p className="text-xs font-medium text-muted-foreground">
1790
- Inspector
1791
- </p>
1792
- <Tip side="bottom" label="Hide inspector">
1793
- <Button
1794
- type="button"
1795
- variant="ghost"
1796
- size="icon-sm"
1797
- onClick={() => setInspectorOpen(false)}
1798
- aria-label="Hide inspector"
1799
- aria-expanded={true}
1800
- >
1801
- <i
1802
- className="fa-light fa-arrow-right-to-line"
1803
- aria-hidden="true"
1804
- />
1805
- </Button>
1806
- </Tip>
1807
- </div>
1808
-
1809
- {/* Question format — first-time landing shows the full
1810
- `SelectionTileGrid` (matches the "File format" pattern
1811
- in `ExportDrawer`). After the author picks once it
1812
- collapses into a single selected-type tile with a
1813
- "Change" affordance that re-opens the grid. */}
1814
- <FormField
1815
- control={form.control}
1816
- name="type"
1817
- render={({ field }) => (
1818
- <FormItem>
1819
- <FormControl>
1820
- {typeChooserOpen ? (
1821
- <SelectionTileGrid
1822
- sectionLabel="Question format"
1823
- options={QUESTION_TYPE_TILES}
1824
- columns={2}
1825
- value={field.value}
1826
- onValueChange={v => changeType(v)}
1827
- interaction="radio"
1828
- idPrefix="qb-format"
1829
- itemVariant="outline"
1830
- itemMotion="pop"
1831
- />
1832
- ) : (
1833
- <div className="flex flex-col gap-2">
1834
- <Label
1835
- className="text-xs font-medium text-muted-foreground"
1836
- >
1837
- Question format
1838
- </Label>
1839
- <button
1840
- type="button"
1841
- onClick={() => setTypeChooserOpen(true)}
1842
- aria-label={`Change question format — currently ${activeType.label}`}
1843
- className={cn(
1844
- "group flex items-center gap-3 rounded-lg border border-border bg-background px-3 py-2.5 text-left transition-colors",
1845
- "hover:border-foreground/30 hover:bg-muted/40",
1846
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1847
- )}
1848
- >
1849
- <span
1850
- className="inline-flex size-9 shrink-0 items-center justify-center rounded-md bg-[var(--icon-disc-brand-bg)] text-[var(--icon-disc-brand-fg)]"
1851
- aria-hidden="true"
1852
- >
1853
- <i className={cn("fa-light text-base", activeType.icon)} />
1854
- </span>
1855
- <span className="flex min-w-0 flex-1 flex-col">
1856
- <span className="truncate text-sm font-medium text-foreground">
1857
- {activeType.label}
1858
- </span>
1859
- <span className="truncate text-xs text-muted-foreground">
1860
- {activeType.tileSummary ?? activeType.description}
1861
- </span>
1862
- </span>
1863
- <span
1864
- className="inline-flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors group-hover:bg-muted group-hover:text-foreground"
1865
- aria-hidden="true"
1866
- >
1867
- <i className="fa-light fa-pen-to-square text-xs" />
1868
- </span>
1869
- </button>
1870
- </div>
1871
- )}
1872
- </FormControl>
1873
- {typeChooserOpen ? (
1874
- <FormDescription>{activeType.description}</FormDescription>
1875
- ) : null}
1876
- <FormMessage />
1877
- </FormItem>
1878
- )}
1879
- />
1880
-
1881
- {/* Location — compact selected tile (mirrors the
1882
- collapsed question-format card). Click opens a Command
1883
- popover with search and an "Add new folder" footer
1884
- that bridges into `QuestionBankNewFolderSheet`. */}
1885
- <FormField
1886
- control={form.control}
1887
- name="folderId"
1888
- render={({ field }) => (
1889
- <FormItem>
1890
- <Label
1891
- className="text-xs font-medium text-muted-foreground"
1892
- >
1893
- Location
1894
- </Label>
1895
- <FormControl>
1896
- <FolderPickerControl
1897
- folders={localFolders}
1898
- value={field.value}
1899
- onChange={v => field.onChange(v)}
1900
- open={folderPickerOpen}
1901
- onOpenChange={setFolderPickerOpen}
1902
- onRequestNewFolder={() => {
1903
- setFolderPickerOpen(false)
1904
- setNewFolderOpen(true)
1905
- }}
1906
- />
1907
- </FormControl>
1908
- <FormMessage />
1909
- </FormItem>
1910
- )}
1911
- />
1912
-
1913
- {/* Difficulty — meter + AI estimate + PBI + folder note.
1914
- Defaults to AI mode (the meter follows the folder
1915
- recommendation); "Override" flips to manual chips for
1916
- authors who want to lock the level themselves. */}
1917
- <FormField
1918
- control={form.control}
1919
- name="difficulty"
1920
- render={({ field }) => (
1921
- <DifficultyMeter
1922
- value={field.value}
1923
- onChange={v => field.onChange(v)}
1924
- mode={difficultyMode}
1925
- onModeChange={setDifficultyMode}
1926
- insight={difficultyInsight}
1927
- />
1928
- )}
1929
- />
1930
-
1931
- <FormField
1932
- control={form.control}
1933
- name="bloom"
1934
- render={({ field }) => (
1935
- <InspectorSection title="Bloom's taxonomy">
1936
- <ToggleGroup
1937
- type="single"
1938
- variant="outline"
1939
- size="sm"
1940
- spacing={1}
1941
- value={field.value}
1942
- onValueChange={v => field.onChange(v)}
1943
- className="flex-wrap"
1944
- >
1945
- {AUTHORING_BLOOM_OPTIONS.map(b => (
1946
- <ToggleGroupItem
1947
- key={b.value}
1948
- value={b.value}
1949
- title={b.hint}
1950
- className="rounded-full px-3"
1951
- >
1952
- {b.label}
1953
- </ToggleGroupItem>
1954
- ))}
1955
- </ToggleGroup>
1956
- </InspectorSection>
1957
- )}
1958
- />
1959
-
1960
- <FormField
1961
- control={form.control}
1962
- name="cogLevel"
1963
- render={({ field }) => (
1964
- <InspectorSection
1965
- title="NBME cognitive level"
1966
- description="Distinct from Bloom — broader buckets used by NBME item writers."
1967
- >
1968
- <ToggleGroup
1969
- type="single"
1970
- variant="outline"
1971
- size="sm"
1972
- spacing={1}
1973
- value={field.value}
1974
- onValueChange={v => field.onChange(v)}
1975
- className="flex-wrap"
1976
- >
1977
- {AUTHORING_COG_LEVEL_OPTIONS.map(c => (
1978
- <ToggleGroupItem
1979
- key={c.value}
1980
- value={c.value}
1981
- title={c.hint}
1982
- className="rounded-full px-3"
1983
- >
1984
- {c.label}
1985
- </ToggleGroupItem>
1986
- ))}
1987
- </ToggleGroup>
1988
- </InspectorSection>
1989
- )}
1990
- />
1991
-
1992
- <InspectorSection title="Tags" htmlFor="qb-tag-input">
1993
- {watchedTags.length > 0 ? (
1994
- <div className="flex flex-wrap gap-1.5">
1995
- {watchedTags.map(t => (
1996
- <Badge key={t} variant="secondary" className="gap-1.5">
1997
- <span>#{t}</span>
1998
- <Tip side="top" label={`Remove tag ${t}`}>
1999
- <button
2000
- type="button"
2001
- onClick={() => removeTag(t)}
2002
- aria-label={`Remove tag ${t}`}
2003
- className="-mr-0.5 inline-flex size-3.5 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-background hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2004
- >
2005
- <i
2006
- className="fa-light fa-xmark text-[9px]"
2007
- aria-hidden="true"
2008
- />
2009
- </button>
2010
- </Tip>
2011
- </Badge>
2012
- ))}
2013
- </div>
2014
- ) : null}
2015
- <Input
2016
- id="qb-tag-input"
2017
- value={tagDraft}
2018
- onChange={e => setTagDraft(e.target.value)}
2019
- onKeyDown={e => {
2020
- if (e.key === "Enter" || e.key === ",") {
2021
- e.preventDefault()
2022
- commitTag()
2023
- }
2024
- }}
2025
- onBlur={commitTag}
2026
- placeholder="STEMI, antibiotics…"
2027
- className="h-8 text-xs"
2028
- />
2029
- </InspectorSection>
2030
- </div>
2031
- )}
2032
- </aside>
2033
- </div>
2081
+ </NewFocusTemplate>
2034
2082
  </form>
2035
2083
 
2036
2084
  {/* New folder — invoked from the location picker. Re-uses the
2037
- shared `QuestionBankNewFolderSheet` (same shell as the folder
2085
+ shared `LibraryNewFolderSheet` (same shell as the folder
2038
2086
  hub) so the surface stays consistent. The created folder is
2039
2087
  appended to `localFolders` and immediately selected. */}
2040
- <QuestionBankNewFolderSheet
2088
+ <LibraryNewFolderSheet
2041
2089
  open={newFolderOpen}
2042
2090
  onOpenChange={setNewFolderOpen}
2043
2091
  parentFolderId={null}
2044
2092
  descriptionText="Drafts created from this composer can land in the new folder right away."
2045
2093
  onCreated={f => {
2046
2094
  const id = newFolderId()
2047
- const created: QuestionBankFolder = { id, ...f }
2095
+ const created: LibraryFolder = { id, ...f }
2048
2096
  setLocalFolders(prev => [...prev, created])
2049
2097
  form.setValue("folderId", id, { shouldDirty: true, shouldValidate: false })
2050
2098
  }}
@@ -2170,7 +2218,7 @@ function OptionRow({
2170
2218
  </div>
2171
2219
 
2172
2220
  {rationaleOpen && !locked ? (
2173
- <div className="pl-10">
2221
+ <div className="ps-10">
2174
2222
  <Textarea
2175
2223
  value={option.rationale}
2176
2224
  onChange={e => onRationaleChange(e.target.value)}