@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.
- package/CHANGELOG.md +662 -7
- package/bin/sync-extras.mjs +116 -29
- package/consumer-extras/README.md +42 -7
- package/consumer-extras/cursor-rules/exxat-accessibility.mdc +39 -0
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +26 -0
- package/consumer-extras/cursor-rules/exxat-breadcrumbs-no-back.mdc +21 -0
- package/consumer-extras/cursor-rules/exxat-card-vs-list-rows.mdc +21 -0
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +44 -0
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +32 -0
- package/consumer-extras/cursor-rules/exxat-command-menu.mdc +22 -0
- package/consumer-extras/cursor-rules/exxat-dashboard-view-charts.mdc +53 -0
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +43 -0
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +25 -0
- package/consumer-extras/cursor-rules/exxat-drawer-vs-dialog.mdc +22 -0
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +56 -0
- package/consumer-extras/cursor-rules/exxat-fontawesome-icons.mdc +31 -0
- package/consumer-extras/cursor-rules/exxat-kbd-shortcuts.mdc +100 -0
- package/consumer-extras/cursor-rules/exxat-kpi-flat-band.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-kpi-max-four.mdc +21 -0
- package/consumer-extras/cursor-rules/exxat-kpi-trends.mdc +31 -0
- package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-list-page-connected-views.mdc +24 -0
- package/consumer-extras/cursor-rules/exxat-list-page-view-shells.mdc +31 -0
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +30 -0
- package/consumer-extras/cursor-rules/exxat-no-slds-leakage.mdc +78 -0
- package/consumer-extras/cursor-rules/exxat-no-toast.mdc +25 -0
- package/consumer-extras/cursor-rules/exxat-page-vs-drawer.mdc +23 -0
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +47 -0
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +52 -0
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +34 -0
- package/consumer-extras/cursor-rules/exxat-table-properties-drawer.mdc +77 -0
- package/consumer-extras/cursor-rules/exxat-token-discipline.mdc +103 -0
- package/consumer-extras/cursor-skills/exxat-accessibility/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +3 -3
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +5 -16
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -3
- package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +19 -34
- package/consumer-extras/cursor-skills/exxat-ds-skill/references/data-table-pattern.md +1 -1
- package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +10 -12
- package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
- package/consumer-extras/handbook/HANDBOOK.md +187 -0
- package/consumer-extras/handbook/glossary.md +58 -0
- package/consumer-extras/handbook/reference-implementations.md +153 -0
- package/consumer-extras/handbook/voice-and-tone.md +262 -0
- package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
- package/consumer-extras/patterns/command-menu-pattern.md +1 -1
- package/consumer-extras/patterns/consumer-upgrade-checklist.md +0 -20
- package/consumer-extras/patterns/data-views-pattern.md +31 -66
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
- package/consumer-extras/patterns/shell-surface-elevation-pattern.md +3 -5
- package/dist/components/data-table/filter-date-calendar.d.ts +10 -0
- package/dist/components/data-table/filter-date-calendar.js +280 -0
- package/dist/components/data-table/filter-date-calendar.js.map +1 -0
- package/dist/components/data-table/filter-text-value-input.d.ts +15 -0
- package/dist/components/data-table/filter-text-value-input.js +561 -0
- package/dist/components/data-table/filter-text-value-input.js.map +1 -0
- package/dist/components/data-table/index.d.ts +45 -0
- package/dist/components/data-table/index.js +3085 -0
- package/dist/components/data-table/index.js.map +1 -0
- package/dist/components/data-table/pagination.d.ts +28 -0
- package/dist/components/data-table/pagination.js +3264 -0
- package/dist/components/data-table/pagination.js.map +1 -0
- package/dist/components/data-table/types.d.ts +84 -0
- package/dist/components/data-table/types.js +3 -0
- package/dist/components/data-table/types.js.map +1 -0
- package/dist/components/data-table/use-table-state.d.ts +116 -0
- package/dist/components/data-table/use-table-state.js +670 -0
- package/dist/components/data-table/use-table-state.js.map +1 -0
- package/dist/components/data-views/board-card-primitives.d.ts +22 -0
- package/dist/components/data-views/board-card-primitives.js +84 -0
- package/dist/components/data-views/board-card-primitives.js.map +1 -0
- package/dist/components/data-views/data-row-list.d.ts +33 -0
- package/dist/components/data-views/data-row-list.js +106 -0
- package/dist/components/data-views/data-row-list.js.map +1 -0
- package/dist/components/data-views/finder-panel-view.d.ts +54 -0
- package/dist/components/data-views/finder-panel-view.js +388 -0
- package/dist/components/data-views/finder-panel-view.js.map +1 -0
- package/dist/components/data-views/folder-grid-view.d.ts +22 -0
- package/dist/components/data-views/folder-grid-view.js +58 -0
- package/dist/components/data-views/folder-grid-view.js.map +1 -0
- package/dist/components/data-views/hub-table.d.ts +173 -0
- package/dist/components/data-views/hub-table.js +5783 -0
- package/dist/components/data-views/hub-table.js.map +1 -0
- package/dist/components/data-views/index.d.ts +27 -0
- package/dist/components/data-views/index.js +6797 -0
- package/dist/components/data-views/index.js.map +1 -0
- package/dist/components/data-views/list-page-board-card.d.ts +72 -0
- package/dist/components/data-views/list-page-board-card.js +264 -0
- package/dist/components/data-views/list-page-board-card.js.map +1 -0
- package/dist/components/data-views/list-page-board-template.d.ts +24 -0
- package/dist/components/data-views/list-page-board-template.js +137 -0
- package/dist/components/data-views/list-page-board-template.js.map +1 -0
- package/dist/components/data-views/list-page-connected-view-body.d.ts +19 -0
- package/dist/components/data-views/list-page-connected-view-body.js +116 -0
- package/dist/components/data-views/list-page-connected-view-body.js.map +1 -0
- package/dist/components/data-views/list-page-split-details-placeholder.d.ts +14 -0
- package/dist/components/data-views/list-page-split-details-placeholder.js +38 -0
- package/dist/components/data-views/list-page-split-details-placeholder.js.map +1 -0
- package/dist/components/data-views/list-page-split-hub-chrome.d.ts +17 -0
- package/dist/components/data-views/list-page-split-hub-chrome.js +54 -0
- package/dist/components/data-views/list-page-split-hub-chrome.js.map +1 -0
- package/dist/components/data-views/list-page-split-hub-tokens.d.ts +12 -0
- package/dist/components/data-views/list-page-split-hub-tokens.js +8 -0
- package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -0
- package/dist/components/data-views/list-page-tree-column-header.d.ts +15 -0
- package/dist/components/data-views/list-page-tree-column-header.js +22 -0
- package/dist/components/data-views/list-page-tree-column-header.js.map +1 -0
- package/dist/components/data-views/list-page-tree-panel-shell.d.ts +25 -0
- package/dist/components/data-views/list-page-tree-panel-shell.js +146 -0
- package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -0
- package/dist/components/data-views/os-folder-glyph.d.ts +35 -0
- package/dist/components/data-views/os-folder-glyph.js +104 -0
- package/dist/components/data-views/os-folder-glyph.js.map +1 -0
- package/dist/components/data-views/outline-tree-menu.d.ts +36 -0
- package/dist/components/data-views/outline-tree-menu.js +131 -0
- package/dist/components/data-views/outline-tree-menu.js.map +1 -0
- package/dist/components/table-properties/column-row.d.ts +22 -0
- package/dist/components/table-properties/column-row.js +153 -0
- package/dist/components/table-properties/column-row.js.map +1 -0
- package/dist/components/table-properties/draggable-list.d.ts +24 -0
- package/dist/components/table-properties/draggable-list.js +53 -0
- package/dist/components/table-properties/draggable-list.js.map +1 -0
- package/dist/components/table-properties/drawer-button.d.ts +110 -0
- package/dist/components/table-properties/drawer-button.js +2748 -0
- package/dist/components/table-properties/drawer-button.js.map +1 -0
- package/dist/components/table-properties/drawer.d.ts +100 -0
- package/dist/components/table-properties/drawer.js +2595 -0
- package/dist/components/table-properties/drawer.js.map +1 -0
- package/dist/components/table-properties/filter-card.d.ts +24 -0
- package/dist/components/table-properties/filter-card.js +854 -0
- package/dist/components/table-properties/filter-card.js.map +1 -0
- package/dist/components/table-properties/index.d.ts +14 -0
- package/dist/components/table-properties/index.js +2768 -0
- package/dist/components/table-properties/index.js.map +1 -0
- package/dist/components/table-properties/sort-card.d.ts +20 -0
- package/dist/components/table-properties/sort-card.js +102 -0
- package/dist/components/table-properties/sort-card.js.map +1 -0
- package/dist/components/templates/dedicated-search-landing-template.d.ts +21 -0
- package/dist/components/templates/dedicated-search-landing-template.js +254 -0
- package/dist/components/templates/dedicated-search-landing-template.js.map +1 -0
- package/dist/components/templates/dedicated-search-results-template.d.ts +15 -0
- package/dist/components/templates/dedicated-search-results-template.js +16 -0
- package/dist/components/templates/dedicated-search-results-template.js.map +1 -0
- package/dist/components/templates/index.d.ts +9 -0
- package/dist/components/templates/index.js +2720 -0
- package/dist/components/templates/index.js.map +1 -0
- package/dist/components/templates/list-page.d.ts +83 -0
- package/dist/components/templates/list-page.js +2433 -0
- package/dist/components/templates/list-page.js.map +1 -0
- package/dist/components/templates/nested-secondary-panel-shell.d.ts +20 -0
- package/dist/components/templates/nested-secondary-panel-shell.js +54 -0
- package/dist/components/templates/nested-secondary-panel-shell.js.map +1 -0
- package/dist/components/ui/accordion.d.ts +10 -0
- package/dist/components/ui/accordion.js +74 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +37 -0
- package/dist/components/ui/alert-dialog.js +201 -0
- package/dist/components/ui/alert-dialog.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +84 -0
- package/dist/components/ui/avatar.js +328 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +13 -0
- package/dist/components/ui/badge.js +49 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/banner.d.ts +62 -0
- package/dist/components/ui/banner.js +364 -0
- package/dist/components/ui/banner.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +14 -0
- package/dist/components/ui/breadcrumb.js +114 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/button.d.ts +16 -0
- package/dist/components/ui/button.js +59 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/calendar.d.ts +13 -0
- package/dist/components/ui/calendar.js +238 -0
- package/dist/components/ui/calendar.js.map +1 -0
- package/dist/components/ui/card.d.ts +14 -0
- package/dist/components/ui/card.js +102 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/chart.d.ts +58 -0
- package/dist/components/ui/chart.js +292 -0
- package/dist/components/ui/chart.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +23 -0
- package/dist/components/ui/checkbox.js +155 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/coach-mark.d.ts +27 -0
- package/dist/components/ui/coach-mark.js +306 -0
- package/dist/components/ui/coach-mark.js.map +1 -0
- package/dist/components/ui/collapsible.d.ts +8 -0
- package/dist/components/ui/collapsible.js +35 -0
- package/dist/components/ui/collapsible.js.map +1 -0
- package/dist/components/ui/command.d.ts +36 -0
- package/dist/components/ui/command.js +274 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/context-menu.d.ts +32 -0
- package/dist/components/ui/context-menu.js +245 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/components/ui/date-picker-field.d.ts +38 -0
- package/dist/components/ui/date-picker-field.js +550 -0
- package/dist/components/ui/date-picker-field.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +22 -0
- package/dist/components/ui/dialog.js +200 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/dot-pattern.d.ts +21 -0
- package/dist/components/ui/dot-pattern.js +139 -0
- package/dist/components/ui/dot-pattern.js.map +1 -0
- package/dist/components/ui/drag-handle-grip.d.ts +10 -0
- package/dist/components/ui/drag-handle-grip.js +15 -0
- package/dist/components/ui/drag-handle-grip.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +16 -0
- package/dist/components/ui/drawer.js +125 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +45 -0
- package/dist/components/ui/dropdown-menu.js +353 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/export-drawer.d.ts +11 -0
- package/dist/components/ui/export-drawer.js +1658 -0
- package/dist/components/ui/export-drawer.js.map +1 -0
- package/dist/components/ui/field.d.ts +30 -0
- package/dist/components/ui/field.js +249 -0
- package/dist/components/ui/field.js.map +1 -0
- package/dist/components/ui/form.d.ts +28 -0
- package/dist/components/ui/form.js +110 -0
- package/dist/components/ui/form.js.map +1 -0
- package/dist/components/ui/hover-card.d.ts +9 -0
- package/dist/components/ui/hover-card.js +43 -0
- package/dist/components/ui/hover-card.js.map +1 -0
- package/dist/components/ui/input-group.d.ts +20 -0
- package/dist/components/ui/input-group.js +219 -0
- package/dist/components/ui/input-group.js.map +1 -0
- package/dist/components/ui/input-mask.d.ts +39 -0
- package/dist/components/ui/input-mask.js +118 -0
- package/dist/components/ui/input-mask.js.map +1 -0
- package/dist/components/ui/input.d.ts +5 -0
- package/dist/components/ui/input.js +30 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/kbd.d.ts +20 -0
- package/dist/components/ui/kbd.js +45 -0
- package/dist/components/ui/kbd.js.map +1 -0
- package/dist/components/ui/key-metrics-context.d.ts +19 -0
- package/dist/components/ui/key-metrics-context.js +26 -0
- package/dist/components/ui/key-metrics-context.js.map +1 -0
- package/dist/components/ui/key-metrics.d.ts +131 -0
- package/dist/components/ui/key-metrics.js +1015 -0
- package/dist/components/ui/key-metrics.js.map +1 -0
- package/dist/components/ui/label.d.ts +6 -0
- package/dist/components/ui/label.js +28 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/list-page-view-frame.d.ts +22 -0
- package/dist/components/ui/list-page-view-frame.js +24 -0
- package/dist/components/ui/list-page-view-frame.js.map +1 -0
- package/dist/components/ui/page-header.d.ts +51 -0
- package/dist/components/ui/page-header.js +372 -0
- package/dist/components/ui/page-header.js.map +1 -0
- package/dist/components/ui/payment-card-fields.d.ts +10 -0
- package/dist/components/ui/payment-card-fields.js +80 -0
- package/dist/components/ui/payment-card-fields.js.map +1 -0
- package/dist/components/ui/popover.d.ts +10 -0
- package/dist/components/ui/popover.js +47 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +29 -0
- package/dist/components/ui/radio-group.js +190 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/resizable.d.ts +16 -0
- package/dist/components/ui/resizable.js +51 -0
- package/dist/components/ui/resizable.js.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +8 -0
- package/dist/components/ui/scroll-area.js +66 -0
- package/dist/components/ui/scroll-area.js.map +1 -0
- package/dist/components/ui/select.d.ts +18 -0
- package/dist/components/ui/select.js +186 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/selection-tile-grid.d.ts +52 -0
- package/dist/components/ui/selection-tile-grid.js +347 -0
- package/dist/components/ui/selection-tile-grid.js.map +1 -0
- package/dist/components/ui/separator.d.ts +7 -0
- package/dist/components/ui/separator.js +33 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/sheet.d.ts +18 -0
- package/dist/components/ui/sheet.js +181 -0
- package/dist/components/ui/sheet.js.map +1 -0
- package/dist/components/ui/sidebar.d.ts +94 -0
- package/dist/components/ui/sidebar.js +805 -0
- package/dist/components/ui/sidebar.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +5 -0
- package/dist/components/ui/skeleton.js +22 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/slider.d.ts +7 -0
- package/dist/components/ui/slider.js +66 -0
- package/dist/components/ui/slider.js.map +1 -0
- package/dist/components/ui/sonner.d.ts +6 -0
- package/dist/components/ui/sonner.js +38 -0
- package/dist/components/ui/sonner.js.map +1 -0
- package/dist/components/ui/status-badge.d.ts +38 -0
- package/dist/components/ui/status-badge.js +77 -0
- package/dist/components/ui/status-badge.js.map +1 -0
- package/dist/components/ui/table.d.ts +13 -0
- package/dist/components/ui/table.js +115 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +15 -0
- package/dist/components/ui/tabs.js +93 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +6 -0
- package/dist/components/ui/textarea.js +25 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/tip.d.ts +12 -0
- package/dist/components/ui/tip.js +61 -0
- package/dist/components/ui/tip.js.map +1 -0
- package/dist/components/ui/toggle-group.d.ts +14 -0
- package/dist/components/ui/toggle-group.js +104 -0
- package/dist/components/ui/toggle-group.js.map +1 -0
- package/dist/components/ui/toggle-switch.d.ts +10 -0
- package/dist/components/ui/toggle-switch.js +33 -0
- package/dist/components/ui/toggle-switch.js.map +1 -0
- package/dist/components/ui/toggle.d.ts +13 -0
- package/dist/components/ui/toggle.js +51 -0
- package/dist/components/ui/toggle.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +10 -0
- package/dist/components/ui/tooltip.js +68 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/components/ui/view-segmented-control.d.ts +31 -0
- package/dist/components/ui/view-segmented-control.js +167 -0
- package/dist/components/ui/view-segmented-control.js.map +1 -0
- package/dist/data-list-view-registry-CyBoBML4.d.ts +73 -0
- package/dist/hooks/use-app-theme.d.ts +24 -0
- package/dist/hooks/use-app-theme.js +286 -0
- package/dist/hooks/use-app-theme.js.map +1 -0
- package/dist/hooks/use-coach-mark.d.ts +86 -0
- package/dist/hooks/use-coach-mark.js +218 -0
- package/dist/hooks/use-coach-mark.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +3 -0
- package/dist/hooks/use-mobile.js +29 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/hooks/use-mod-key-label.d.ts +6 -0
- package/dist/hooks/use-mod-key-label.js +25 -0
- package/dist/hooks/use-mod-key-label.js.map +1 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +13421 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/compose-refs.d.ts +6 -0
- package/dist/lib/compose-refs.js +17 -0
- package/dist/lib/compose-refs.js.map +1 -0
- package/dist/lib/conditional-rule-match.d.ts +30 -0
- package/dist/lib/conditional-rule-match.js +66 -0
- package/dist/lib/conditional-rule-match.js.map +1 -0
- package/dist/lib/data-list-display-options.d.ts +26 -0
- package/dist/lib/data-list-display-options.js +14 -0
- package/dist/lib/data-list-display-options.js.map +1 -0
- package/dist/lib/data-list-view-registry.d.ts +2 -0
- package/dist/lib/data-list-view-registry.js +102 -0
- package/dist/lib/data-list-view-registry.js.map +1 -0
- package/dist/lib/data-list-view-surface.d.ts +2 -0
- package/dist/lib/data-list-view-surface.js +80 -0
- package/dist/lib/data-list-view-surface.js.map +1 -0
- package/dist/lib/data-list-view.d.ts +21 -0
- package/dist/lib/data-list-view.js +25 -0
- package/dist/lib/data-list-view.js.map +1 -0
- package/dist/lib/date-filter.d.ts +22 -0
- package/dist/lib/date-filter.js +61 -0
- package/dist/lib/date-filter.js.map +1 -0
- package/dist/lib/dev-log.d.ts +8 -0
- package/dist/lib/dev-log.js +10 -0
- package/dist/lib/dev-log.js.map +1 -0
- package/dist/lib/dropdown-menu-surface.d.ts +14 -0
- package/dist/lib/dropdown-menu-surface.js +6 -0
- package/dist/lib/dropdown-menu-surface.js.map +1 -0
- package/dist/lib/editable-target.d.ts +12 -0
- package/dist/lib/editable-target.js +12 -0
- package/dist/lib/editable-target.js.map +1 -0
- package/dist/lib/list-page-table-properties.d.ts +35 -0
- package/dist/lib/list-page-table-properties.js +81 -0
- package/dist/lib/list-page-table-properties.js.map +1 -0
- package/dist/lib/raf-throttle.d.ts +23 -0
- package/dist/lib/raf-throttle.js +27 -0
- package/dist/lib/raf-throttle.js.map +1 -0
- package/dist/lib/row-height.d.ts +16 -0
- package/dist/lib/row-height.js +10 -0
- package/dist/lib/row-height.js.map +1 -0
- package/dist/lib/table-properties-types.d.ts +83 -0
- package/dist/lib/table-properties-types.js +19 -0
- package/dist/lib/table-properties-types.js.map +1 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +11 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +83 -19
- package/src/components/data-table/filter-date-calendar.tsx +38 -0
- package/src/components/data-table/filter-text-value-input.tsx +77 -0
- package/src/components/data-table/index.tsx +1678 -0
- package/src/components/data-table/pagination.tsx +259 -0
- package/src/components/data-table/types.ts +96 -0
- package/src/components/data-table/use-table-state.ts +767 -0
- package/src/components/data-views/board-card-primitives.tsx +93 -0
- package/src/components/data-views/data-row-list.tsx +183 -0
- package/src/components/data-views/finder-panel-view.tsx +405 -0
- package/src/components/data-views/folder-grid-view.tsx +86 -0
- package/src/components/data-views/hub-table.tsx +606 -0
- package/src/components/data-views/index.ts +28 -0
- package/src/components/data-views/list-page-board-card.tsx +192 -0
- package/src/components/data-views/list-page-board-template.tsx +122 -0
- package/src/components/data-views/list-page-connected-view-body.tsx +66 -0
- package/src/components/data-views/list-page-split-details-placeholder.tsx +39 -0
- package/src/components/data-views/list-page-split-hub-chrome.tsx +60 -0
- package/src/components/data-views/list-page-split-hub-tokens.ts +16 -0
- package/src/components/data-views/list-page-tree-column-header.tsx +31 -0
- package/src/components/data-views/list-page-tree-panel-shell.tsx +91 -0
- package/src/components/data-views/os-folder-glyph.tsx +141 -0
- package/src/components/data-views/outline-tree-menu.tsx +157 -0
- package/src/components/table-properties/column-row.tsx +90 -0
- package/src/components/table-properties/draggable-list.ts +54 -0
- package/src/components/table-properties/drawer-button.tsx +300 -0
- package/src/components/table-properties/drawer.tsx +1148 -0
- package/src/components/table-properties/filter-card.tsx +251 -0
- package/src/components/table-properties/index.ts +36 -0
- package/src/components/table-properties/sort-card.tsx +63 -0
- package/src/components/templates/dedicated-search-landing-template.tsx +124 -0
- package/src/components/templates/dedicated-search-results-template.tsx +19 -0
- package/src/components/templates/index.ts +33 -0
- package/src/components/templates/list-page.tsx +602 -0
- package/src/components/templates/nested-secondary-panel-shell.tsx +70 -0
- package/src/components/ui/accordion.tsx +92 -0
- package/src/components/ui/alert-dialog.tsx +221 -0
- package/src/components/ui/avatar.tsx +13 -2
- package/src/components/ui/banner.tsx +2 -2
- package/src/components/ui/button.tsx +4 -4
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/coach-mark.tsx +1 -1
- package/src/components/ui/context-menu.tsx +291 -0
- package/src/components/ui/date-picker-field.tsx +2 -2
- package/src/components/ui/dot-pattern.tsx +183 -0
- package/src/components/ui/export-drawer.tsx +375 -0
- package/src/components/ui/hover-card.tsx +66 -0
- package/src/components/ui/key-metrics-context.tsx +78 -0
- package/src/components/ui/key-metrics.tsx +1133 -0
- package/src/components/ui/list-page-view-frame.tsx +64 -0
- package/src/components/ui/page-header.tsx +244 -0
- package/src/components/ui/payment-card-fields.tsx +2 -2
- package/src/components/ui/resizable.tsx +68 -0
- package/src/components/ui/scroll-area.tsx +72 -0
- package/src/components/ui/selection-tile-grid.tsx +9 -2
- package/src/components/ui/sidebar.tsx +84 -12
- package/src/components/ui/slider.tsx +83 -0
- package/src/globals.css +2201 -7
- package/src/globals.d.ts +20 -0
- package/src/index.ts +68 -1
- package/src/lib/conditional-rule-match.ts +119 -0
- package/src/lib/data-list-display-options.ts +35 -0
- package/src/lib/data-list-view-registry.ts +104 -0
- package/src/lib/data-list-view-surface.ts +83 -0
- package/src/lib/data-list-view.ts +47 -0
- package/src/lib/dev-log.ts +10 -0
- package/src/lib/editable-target.ts +20 -0
- package/src/lib/list-page-table-properties.ts +48 -0
- package/src/lib/raf-throttle.ts +45 -0
- package/src/lib/row-height.ts +19 -0
- package/src/lib/table-properties-types.ts +98 -0
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
- package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
- package/template/.cursor/rules/exxat-command-menu.mdc +2 -2
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +7 -7
- package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
- package/template/.cursor/rules/exxat-ds-agents.mdc +2 -2
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +7 -7
- package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
- package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
- package/template/AGENTS.md +135 -103
- package/template/app/(app)/columns/page.tsx +11 -0
- package/template/app/(app)/dashboard/loading.tsx +15 -3
- package/template/app/(app)/dashboard/page.tsx +14 -2
- package/template/app/(app)/layout.tsx +17 -4
- package/template/app/(app)/library/all/page.tsx +11 -0
- package/template/app/(app)/library/find/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/layout.tsx +17 -17
- package/template/app/(app)/library/list/page.tsx +12 -0
- package/template/app/(app)/library/new/page.tsx +45 -0
- package/template/app/(app)/library/page.tsx +11 -0
- package/template/app/(app)/loading.tsx +18 -1
- package/template/app/(app)/settings/page.tsx +5 -4
- package/template/app/(app)/tokens-themes/page.tsx +11 -0
- package/template/app/globals.css +14 -16
- package/template/components/ask-leo-composer.tsx +2 -2
- package/template/components/ask-leo-sidebar.tsx +5 -1
- package/template/components/brand-color-picker.tsx +2 -2
- package/template/components/charts-overview.tsx +1 -1
- package/template/components/columns-client.tsx +158 -0
- package/template/components/columns-showcase.tsx +541 -0
- package/template/components/dashboard-report-charts.tsx +1 -1
- package/template/components/dashboard-tabs.tsx +1 -1
- package/template/components/data-table/filter-date-calendar.tsx +1 -38
- package/template/components/data-table/filter-text-value-input.tsx +1 -77
- package/template/components/data-table/index.tsx +1 -1634
- package/template/components/data-table/pagination.tsx +1 -255
- package/template/components/data-table/types.ts +1 -94
- package/template/components/data-table/use-table-state.test.ts +420 -0
- package/template/components/data-table/use-table-state.ts +1 -758
- package/template/components/data-views/board-card-primitives.tsx +1 -93
- package/template/components/data-views/data-row-list.tsx +1 -183
- package/template/components/data-views/finder-panel-view.tsx +1 -405
- package/template/components/data-views/folder-grid-view.tsx +1 -86
- package/template/components/data-views/hub-table.tsx +1 -0
- package/template/components/data-views/index.ts +77 -38
- package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
- package/template/components/data-views/list-page-board-card.tsx +1 -192
- package/template/components/data-views/list-page-board-template.tsx +1 -122
- package/template/components/data-views/list-page-connected-view-body.tsx +1 -66
- package/template/components/data-views/list-page-split-details-placeholder.tsx +1 -39
- package/template/components/data-views/list-page-split-hub-chrome.tsx +1 -68
- package/template/components/data-views/list-page-split-hub-tokens.ts +1 -16
- package/template/components/data-views/list-page-tree-column-header.tsx +1 -31
- package/template/components/data-views/list-page-tree-panel-shell.tsx +1 -91
- package/template/components/data-views/list-page-view-frame.tsx +5 -53
- package/template/components/data-views/os-folder-glyph.tsx +1 -129
- package/template/components/data-views/outline-tree-menu.tsx +1 -157
- package/template/components/data-views/table-cells.tsx +673 -0
- package/template/components/export-drawer.test.tsx +71 -0
- package/template/components/export-drawer.tsx +1 -375
- package/template/components/exxat-product-logo.tsx +5 -5
- package/template/components/folder-details-shell.tsx +11 -11
- package/template/components/hub-tree-panel-view.tsx +26 -26
- package/template/components/invite-collaborators-drawer.tsx +3 -3
- package/template/components/key-metrics-ask-leo-bridge.tsx +40 -0
- package/template/components/key-metrics.tsx +1 -1063
- package/template/components/leo-insight-indicator.tsx +2 -2
- package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
- package/template/components/{question-bank-client.tsx → library-client.tsx} +83 -83
- package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
- package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
- package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +44 -44
- package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +16 -16
- package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
- package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
- package/template/components/library-panel-activator.tsx +8 -0
- package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +63 -63
- package/template/components/library-table.tsx +839 -0
- package/template/components/list-hub-status-badge.tsx +2 -2
- package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +489 -441
- package/template/components/onboarding/index.ts +9 -0
- package/template/components/onboarding/onboarding-01.tsx +1 -1
- package/template/components/onboarding/onboarding-02.tsx +1 -1
- package/template/components/onboarding/onboarding-03.tsx +1 -1
- package/template/components/onboarding/onboarding-04.tsx +1 -1
- package/template/components/page-header.tsx +8 -226
- package/template/components/product-switcher.tsx +3 -4
- package/template/components/product-wordmark.tsx +2 -1
- package/template/components/settings-appearance-card.tsx +3 -4
- package/template/components/settings-client.tsx +15 -59
- package/template/components/settings-form-row.tsx +4 -9
- package/template/components/{app-sidebar-dynamic.tsx → sidebar/app-sidebar-dynamic.tsx} +1 -1
- package/template/components/{app-sidebar.tsx → sidebar/app-sidebar.tsx} +114 -73
- package/template/components/sidebar/index.ts +16 -0
- package/template/components/{secondary-nav.tsx → sidebar/secondary-nav.tsx} +2 -2
- package/template/components/sidebar/secondary-panel.tsx +316 -0
- package/template/components/sidebar/sidebar-auto-collapse.tsx +27 -0
- package/template/components/{sidebar-auto-open.tsx → sidebar/sidebar-auto-open.tsx} +2 -1
- package/template/components/{sidebar-shell.tsx → sidebar/sidebar-shell.tsx} +1 -1
- package/template/components/site-header.tsx +1 -1
- package/template/components/table-properties/column-row.tsx +1 -90
- package/template/components/table-properties/draggable-list.ts +1 -49
- package/template/components/table-properties/drawer-button.tsx +1 -262
- package/template/components/table-properties/drawer.tsx +1 -1166
- package/template/components/table-properties/filter-card.tsx +1 -251
- package/template/components/table-properties/sort-card.tsx +1 -59
- package/template/components/table-properties/types.ts +28 -71
- package/template/components/templates/dedicated-search-landing-template.tsx +1 -124
- package/template/components/templates/dedicated-search-results-template.tsx +1 -19
- package/template/components/templates/discovery-hub-template.tsx +1 -1
- package/template/components/templates/list-page.tsx +1 -608
- package/template/components/templates/nested-secondary-panel-shell.tsx +1 -63
- package/template/components/templates/new-focus-template.tsx +659 -0
- package/template/components/templates/secondary-panel-hub-template.tsx +2 -2
- package/template/components/tokens-secondary-nav.tsx +192 -0
- package/template/components/tokens-themes-client.tsx +476 -0
- package/template/components/tokens-themes-section.tsx +386 -0
- package/template/components/ui/accordion.tsx +1 -0
- package/template/components/ui/alert-dialog.tsx +1 -0
- package/template/components/ui/context-menu.tsx +1 -0
- package/template/components/ui/dot-pattern.tsx +1 -183
- package/template/components/ui/hover-card.tsx +1 -0
- package/template/components/ui/resizable.tsx +1 -68
- package/template/components/ui/scroll-area.tsx +1 -0
- package/template/components/ui/slider.tsx +1 -0
- package/template/docs/HANDBOOK.md +187 -0
- package/template/docs/blueprints/README.md +86 -0
- package/template/docs/blueprints/_template.md +91 -0
- package/template/docs/blueprints/board-card.md +123 -0
- package/template/docs/blueprints/data-table.md +139 -0
- package/template/docs/blueprints/key-metrics.md +128 -0
- package/template/docs/blueprints/list-page-template.md +123 -0
- package/template/docs/blueprints/page-header.md +130 -0
- package/template/docs/collaboration-access-pattern.md +7 -7
- package/template/docs/command-menu-pattern.md +1 -1
- package/template/docs/component-selection-guide.md +224 -0
- package/template/docs/components-audit-2026-05.md +158 -0
- package/template/docs/data-views-pattern.md +31 -66
- package/template/docs/drawer-vs-dialog-pattern.md +1 -3
- package/template/docs/glossary.md +58 -0
- package/template/docs/kpi-flat-band-pattern.md +3 -3
- package/template/docs/kpi-trend-pattern.md +18 -3
- package/template/docs/large-dataset-strategy.md +155 -0
- package/template/docs/library-hub-header-pattern.md +25 -0
- package/template/docs/migrations/0001-brand-deep-alias-stabilization.md +95 -0
- package/template/docs/migrations/0002-exxat-token-namespace.md +154 -0
- package/template/docs/migrations/0003-globals-css-canonical.md +110 -0
- package/template/docs/migrations/README.md +100 -0
- package/template/docs/migrations/_template.md +64 -0
- package/template/docs/reference-implementations.md +151 -0
- package/template/docs/shell-surface-elevation-pattern.md +3 -5
- package/template/docs/token-taxonomy.md +416 -0
- package/template/docs/voice-and-tone.md +262 -0
- package/template/eslint.config.mjs +27 -0
- package/template/hooks/use-secondary-panel-hub-nav.ts +11 -11
- package/template/lib/ask-leo-route-context.ts +6 -18
- package/template/lib/coach-mark-registry.ts +0 -16
- package/template/lib/command-menu-config.ts +5 -13
- package/template/lib/command-menu-search-data.ts +8 -23
- package/template/lib/conditional-rule-match.ts +6 -97
- package/template/lib/data-list-display-options.ts +1 -49
- package/template/lib/data-list-view-registry.ts +1 -104
- package/template/lib/data-list-view-surface.ts +1 -83
- package/template/lib/data-list-view.ts +1 -47
- package/template/lib/data-view-dashboard-storage.ts +35 -38
- package/template/lib/dev-log.ts +1 -8
- package/template/lib/editable-target.ts +1 -10
- package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
- package/template/lib/library-dedicated-search.ts +19 -0
- package/template/lib/library-hub-search.ts +90 -0
- package/template/lib/library-nav.ts +477 -0
- package/template/lib/library-recent-searches.ts +22 -0
- package/template/lib/{question-bank-supported-views.ts → library-supported-views.ts} +2 -3
- package/template/lib/list-page-table-properties.ts +1 -48
- package/template/lib/list-status-badges.ts +16 -11
- package/template/lib/mock/dashboard.ts +1 -1
- package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
- package/template/lib/mock/library-header-collaborators.ts +54 -0
- package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
- package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
- package/template/lib/mock/library.ts +249 -0
- package/template/lib/mock/navigation.tsx +32 -35
- package/template/lib/raf-throttle.ts +1 -45
- package/template/lib/row-height.ts +4 -10
- package/template/lib/sidebar-state-cookie.ts +11 -2
- package/template/lib/table-state-lifecycle.ts +3 -3
- package/template/next.config.mjs +7 -4
- package/template/package.json +1 -0
- package/template/tests/setup.ts +25 -0
- package/consumer-extras/AGENTS.md +0 -76
- package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +0 -37
- package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +0 -57
- package/consumer-extras/patterns/consumer-app-pattern.md +0 -39
- package/consumer-extras/patterns/focused-workflow-page-pattern.md +0 -84
- package/src/components/ui/button-group.tsx +0 -81
- package/src/theme.css +0 -16
- package/src/tokens/README.md +0 -15
- package/src/tokens/base.css +0 -337
- package/src/tokens/high-contrast.css +0 -1195
- package/src/tokens/layers.css +0 -224
- package/src/tokens/tailwind-bridge.css +0 -118
- package/src/tokens/themes.css +0 -201
- package/template/app/(app)/data-list/layout.tsx +0 -43
- package/template/app/(app)/data-list/page.tsx +0 -10
- package/template/app/(app)/examples/focused-workflow/page.tsx +0 -5
- package/template/app/(app)/examples/page.tsx +0 -43
- package/template/app/(app)/question-bank/find/page.tsx +0 -13
- package/template/app/(app)/question-bank/library/page.tsx +0 -12
- package/template/app/(app)/question-bank/list/page.tsx +0 -13
- package/template/app/(app)/question-bank/new/page.tsx +0 -50
- package/template/app/(app)/question-bank/page.tsx +0 -12
- package/template/components/app-route-loading.tsx +0 -14
- package/template/components/dashboard-onboarding-gallery.tsx +0 -13
- package/template/components/dashboard-onboarding.tsx +0 -21
- package/template/components/data-views/list-page-calendar-view.tsx +0 -593
- package/template/components/data-views/list-page-folder-columns-panel.tsx +0 -345
- package/template/components/examples/focused-workflow-showcase.tsx +0 -183
- package/template/components/list-hub-board-view.tsx +0 -68
- package/template/components/list-hub-client.tsx +0 -186
- package/template/components/list-hub-list-view.tsx +0 -36
- package/template/components/list-hub-panel-activator.tsx +0 -8
- package/template/components/list-hub-secondary-nav.tsx +0 -121
- package/template/components/list-hub-table.tsx +0 -336
- package/template/components/question-bank-folder-columns-panel.tsx +0 -104
- package/template/components/question-bank-list-view.tsx +0 -53
- package/template/components/question-bank-panel-activator.tsx +0 -8
- package/template/components/question-bank-table.tsx +0 -729
- package/template/components/secondary-panel/nav-link-rows.tsx +0 -83
- package/template/components/secondary-panel.tsx +0 -220
- package/template/components/secondary-panels/list-hub-panel.tsx +0 -39
- package/template/components/secondary-panels/question-bank-panel.tsx +0 -39
- package/template/components/secondary-panels/registry.tsx +0 -15
- package/template/components/section-cards.tsx +0 -106
- package/template/components/sidebar-auto-collapse.tsx +0 -23
- package/template/components/templates/focused-workflow-layouts.tsx +0 -448
- package/template/components/templates/focused-workflow-page-template.tsx +0 -69
- package/template/components/templates/page-loading-shell.tsx +0 -262
- package/template/components/ui/button-group.tsx +0 -1
- package/template/docs/consumer-app-pattern.md +0 -39
- package/template/docs/focused-workflow-page-pattern.md +0 -84
- package/template/docs/question-bank-hub-header-pattern.md +0 -25
- package/template/lib/list-hub-nav.ts +0 -121
- package/template/lib/mock/list-hub-directory.ts +0 -27
- package/template/lib/mock/list-hub-kpi.ts +0 -27
- package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
- package/template/lib/mock/question-bank.ts +0 -249
- package/template/lib/page-loading-variant.ts +0 -40
- package/template/lib/question-bank-dedicated-search.ts +0 -19
- package/template/lib/question-bank-hub-search.ts +0 -90
- package/template/lib/question-bank-nav.ts +0 -477
- package/template/lib/question-bank-recent-searches.ts +0 -22
- /package/template/components/{getting-started.tsx → onboarding/getting-started.tsx} +0 -0
- /package/template/components/{nav-documents.tsx → sidebar/nav-documents.tsx} +0 -0
- /package/template/components/{nav-main.tsx → sidebar/nav-main.tsx} +0 -0
- /package/template/components/{nav-secondary.tsx → sidebar/nav-secondary.tsx} +0 -0
- /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
|
-
*
|
|
4
|
+
* NewLibraryItemForm — single-page authoring for the library.
|
|
5
5
|
*
|
|
6
|
-
* IA (matches the rest of the
|
|
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
|
-
* │ ·
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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 {
|
|
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/
|
|
106
|
+
} from "@/lib/library-authoring"
|
|
95
107
|
import {
|
|
96
|
-
|
|
108
|
+
DEFAULT_LIBRARY_FOLDERS,
|
|
97
109
|
newFolderId,
|
|
98
|
-
type
|
|
99
|
-
type
|
|
100
|
-
} from "@/lib/mock/
|
|
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:
|
|
282
|
-
{ id:
|
|
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:
|
|
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 }, () => ({
|
|
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 }, () => ({
|
|
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:
|
|
330
|
+
return [{ id: "blk-init-1", accepted: "" }]
|
|
304
331
|
}
|
|
305
332
|
|
|
306
|
-
function folderBreadcrumb(folderId: string, folders:
|
|
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:
|
|
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="
|
|
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
|
-
// `
|
|
429
|
-
// `lib/mock/
|
|
430
|
-
// the rest of the
|
|
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<
|
|
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:
|
|
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:
|
|
456
|
-
): Array<{ folder:
|
|
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:
|
|
462
|
-
function walk(parent:
|
|
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" ? "
|
|
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
|
-
|
|
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
|
|
788
|
+
export function NewLibraryItemForm({
|
|
760
789
|
draftQuestionId,
|
|
761
790
|
defaultFolderId,
|
|
762
791
|
backHref,
|
|
763
|
-
|
|
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
|
-
|
|
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<
|
|
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-
|
|
1427
|
+
className="flex min-h-0 min-w-0 flex-1 flex-col"
|
|
1098
1428
|
>
|
|
1099
|
-
<
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
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
|
-
|
|
1113
|
-
|
|
1467
|
+
aria-busy={submitting}
|
|
1468
|
+
onClick={handleSaveQuestion}
|
|
1114
1469
|
>
|
|
1115
|
-
|
|
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
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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=
|
|
1517
|
+
className={cn(
|
|
1518
|
+
"fa-light",
|
|
1519
|
+
inspectorOpen ? "fa-sidebar-flip" : "fa-sidebar",
|
|
1520
|
+
)}
|
|
1160
1521
|
aria-hidden="true"
|
|
1161
1522
|
/>
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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
|
-
|
|
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
|
-
{/*
|
|
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
|
-
? "
|
|
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
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
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 `
|
|
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
|
-
<
|
|
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:
|
|
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="
|
|
2221
|
+
<div className="ps-10">
|
|
2174
2222
|
<Textarea
|
|
2175
2223
|
value={option.rationale}
|
|
2176
2224
|
onChange={e => onRationaleChange(e.target.value)}
|