@exxatdesignux/ui 0.3.0 → 0.4.1
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 +701 -6
- package/README.md +138 -0
- package/bin/init.mjs +134 -31
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +2 -2
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +2 -0
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +3 -3
- package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +6 -6
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -1
- package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
- 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 +7 -7
- 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 +8 -8
- package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
- package/consumer-extras/handbook/HANDBOOK.md +2 -0
- package/consumer-extras/handbook/glossary.md +2 -1
- package/consumer-extras/handbook/reference-implementations.md +31 -4
- package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
- package/consumer-extras/patterns/data-views-pattern.md +18 -16
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
- package/dist/components/data-table/index.js +2 -2
- package/dist/components/data-table/index.js.map +1 -1
- package/dist/components/data-table/pagination.js +3 -3
- package/dist/components/data-table/pagination.js.map +1 -1
- package/dist/components/data-table/use-table-state.d.ts +1 -1
- package/dist/components/data-table/use-table-state.js.map +1 -1
- package/dist/components/data-views/data-row-list.js.map +1 -1
- package/dist/components/data-views/finder-panel-view.d.ts +1 -1
- package/dist/components/data-views/finder-panel-view.js.map +1 -1
- package/dist/components/data-views/hub-table.d.ts +9 -3
- package/dist/components/data-views/hub-table.js +262 -40
- package/dist/components/data-views/hub-table.js.map +1 -1
- package/dist/components/data-views/index.js +262 -40
- package/dist/components/data-views/index.js.map +1 -1
- package/dist/components/data-views/list-page-split-hub-tokens.d.ts +2 -2
- package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -1
- package/dist/components/data-views/list-page-tree-column-header.d.ts +1 -1
- package/dist/components/data-views/list-page-tree-column-header.js.map +1 -1
- package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -1
- package/dist/components/data-views/os-folder-glyph.d.ts +1 -1
- package/dist/components/data-views/os-folder-glyph.js.map +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/key-metrics.js.map +1 -1
- package/dist/index.js +136 -39
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/components/data-table/index.tsx +2 -2
- package/src/components/data-table/pagination.tsx +5 -1
- package/src/components/data-table/use-table-state.ts +1 -1
- package/src/components/data-views/data-row-list.tsx +1 -1
- package/src/components/data-views/finder-panel-view.tsx +2 -2
- package/src/components/data-views/hub-table.tsx +149 -41
- package/src/components/data-views/list-page-split-hub-tokens.ts +2 -2
- package/src/components/data-views/list-page-tree-column-header.tsx +1 -1
- package/src/components/data-views/os-folder-glyph.tsx +1 -1
- package/src/components/ui/key-metrics.tsx +1 -1
- 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 +1 -1
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +6 -6
- package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +5 -5
- 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 +43 -37
- package/template/app/(app)/columns/page.tsx +11 -0
- 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 +16 -16
- package/template/app/(app)/library/list/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/new/page.tsx +10 -10
- package/template/app/(app)/library/page.tsx +11 -0
- package/template/app/(app)/tokens-themes/page.tsx +11 -0
- package/template/components/ask-leo-composer.tsx +2 -2
- package/template/components/columns-client.tsx +158 -0
- package/template/components/columns-showcase.tsx +541 -0
- package/template/components/data-views/index.ts +32 -6
- package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
- package/template/components/data-views/table-cells.tsx +673 -0
- package/template/components/folder-details-shell.tsx +11 -11
- package/template/components/hub-tree-panel-view.tsx +24 -24
- package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
- package/template/components/{question-bank-client.tsx → library-client.tsx} +82 -82
- 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} +43 -43
- package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +14 -14
- 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} +60 -60
- package/template/components/{question-bank-table.tsx → library-table.tsx} +97 -97
- package/template/components/list-hub-status-badge.tsx +2 -2
- package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +37 -37
- package/template/components/sidebar/app-sidebar.tsx +61 -5
- package/template/components/sidebar/secondary-panel.tsx +109 -56
- package/template/components/sidebar/sidebar-auto-collapse.tsx +2 -2
- package/template/components/sidebar/sidebar-auto-open.tsx +2 -1
- package/template/components/table-properties/types.ts +1 -1
- package/template/components/templates/discovery-hub-template.tsx +1 -1
- package/template/components/templates/new-focus-template.tsx +2 -2
- package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
- 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/docs/HANDBOOK.md +187 -0
- package/template/docs/blueprints/README.md +1 -1
- package/template/docs/blueprints/board-card.md +1 -1
- package/template/docs/blueprints/data-table.md +2 -2
- package/template/docs/blueprints/list-page-template.md +3 -3
- package/template/docs/blueprints/page-header.md +4 -4
- package/template/docs/collaboration-access-pattern.md +7 -7
- package/template/docs/component-selection-guide.md +1 -1
- package/template/docs/data-views-pattern.md +18 -16
- 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/_template.md +1 -1
- package/template/docs/reference-implementations.md +151 -0
- package/template/docs/token-taxonomy.md +1 -1
- package/template/docs/voice-and-tone.md +262 -0
- package/template/eslint.config.mjs +9 -39
- package/template/hooks/use-secondary-panel-hub-nav.ts +10 -10
- 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 -12
- package/template/lib/command-menu-search-data.ts +8 -39
- 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/{placements-supported-views.ts → library-supported-views.ts} +2 -2
- package/template/lib/list-status-badges.ts +16 -104
- 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 -26
- package/template/lib/table-state-lifecycle.ts +1 -1
- package/template/next.config.mjs +7 -4
- package/template/package.json +0 -1
- package/tokens/hooks-index.json +2874 -0
- package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +0 -28
- package/template/app/(app)/examples/page.tsx +0 -41
- package/template/app/(app)/question-bank/find/page.tsx +0 -12
- package/template/app/(app)/question-bank/library/page.tsx +0 -11
- package/template/app/(app)/question-bank/list/page.tsx +0 -12
- package/template/app/(app)/question-bank/page.tsx +0 -11
- package/template/components/compliance-board-view.tsx +0 -142
- package/template/components/compliance-client.tsx +0 -92
- package/template/components/compliance-page-header.tsx +0 -89
- package/template/components/compliance-table.tsx +0 -468
- package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
- package/template/components/data-view-dashboard-charts-team.tsx +0 -971
- package/template/components/data-view-dashboard-charts.tsx +0 -1503
- package/template/components/new-placement-back-btn.tsx +0 -28
- package/template/components/new-placement-form.tsx +0 -942
- package/template/components/placement-board-card.tsx +0 -250
- package/template/components/placement-detail.tsx +0 -438
- package/template/components/placements-board-view.tsx +0 -397
- package/template/components/placements-client.tsx +0 -220
- package/template/components/placements-list-view.tsx +0 -124
- package/template/components/placements-page-header.tsx +0 -166
- package/template/components/placements-table-cells.test.tsx +0 -22
- package/template/components/placements-table-cells.tsx +0 -173
- package/template/components/placements-table-columns.tsx +0 -210
- package/template/components/placements-table.tsx +0 -934
- package/template/components/question-bank-panel-activator.tsx +0 -8
- package/template/components/rotations-empty-state.tsx +0 -50
- package/template/components/rotations-panel-activator.tsx +0 -8
- package/template/components/sites-board-view.tsx +0 -67
- package/template/components/sites-client.tsx +0 -154
- package/template/components/sites-table.tsx +0 -249
- package/template/components/team-board-view.tsx +0 -122
- package/template/components/team-client.tsx +0 -100
- package/template/components/team-page-header.tsx +0 -92
- package/template/components/team-table.tsx +0 -553
- package/template/docs/question-bank-hub-header-pattern.md +0 -25
- package/template/lib/compliance-supported-views.ts +0 -10
- package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
- package/template/lib/mock/compliance-kpi.ts +0 -61
- package/template/lib/mock/compliance.ts +0 -146
- package/template/lib/mock/placements-kpi.ts +0 -134
- package/template/lib/mock/placements.ts +0 -176
- package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
- package/template/lib/mock/question-bank.ts +0 -249
- package/template/lib/mock/sites-directory.ts +0 -16
- package/template/lib/mock/sites-kpi.ts +0 -25
- package/template/lib/mock/team-kpi.ts +0 -60
- package/template/lib/mock/team.ts +0 -118
- package/template/lib/placement-board-card-layout.ts +0 -79
- 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/lib/question-bank-supported-views.ts +0 -12
- package/template/lib/sites-supported-views.ts +0 -10
- package/template/lib/team-supported-views.ts +0 -10
|
@@ -1,9 +1,9 @@
|
|
|
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
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* │ └─ Inspector (right, bg-card panel)
|
|
19
19
|
* │ · Question format (SelectionTileGrid → compact)
|
|
20
20
|
* │ · Location (folder SelectionTileGrid)
|
|
21
|
-
* │ ·
|
|
21
|
+
* │ · Level / Tier / Cognitive (chips)
|
|
22
22
|
* │ · Tags (Input + Badge list)
|
|
23
23
|
* │ Sidebar-style collapse (⌘⌥]) — collapsed rail mimics
|
|
24
24
|
* │ `NestedSecondaryPanelShell` icon mode.
|
|
@@ -88,7 +88,7 @@ import {
|
|
|
88
88
|
CommandList,
|
|
89
89
|
CommandSeparator,
|
|
90
90
|
} from "@/components/ui/command"
|
|
91
|
-
import {
|
|
91
|
+
import { LibraryNewFolderSheet } from "@/components/library-new-folder-sheet"
|
|
92
92
|
|
|
93
93
|
import {
|
|
94
94
|
AUTHORING_QUESTION_TYPES,
|
|
@@ -103,13 +103,13 @@ import {
|
|
|
103
103
|
AUTHORING_RATIONALE_PLACEHOLDER,
|
|
104
104
|
authoringQuestionType,
|
|
105
105
|
type AuthoringQuestionType,
|
|
106
|
-
} from "@/lib/
|
|
106
|
+
} from "@/lib/library-authoring"
|
|
107
107
|
import {
|
|
108
|
-
|
|
108
|
+
DEFAULT_LIBRARY_FOLDERS,
|
|
109
109
|
newFolderId,
|
|
110
|
-
type
|
|
111
|
-
type
|
|
112
|
-
} from "@/lib/mock/
|
|
110
|
+
type LibraryFolder,
|
|
111
|
+
type LibraryFolderColorKey,
|
|
112
|
+
} from "@/lib/mock/library-folders"
|
|
113
113
|
|
|
114
114
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
115
115
|
// Schema
|
|
@@ -330,7 +330,7 @@ function buildInitialFillBlankAnswers(): QuestionFormValues["fillBlankAnswers"]
|
|
|
330
330
|
return [{ id: "blk-init-1", accepted: "" }]
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
function folderBreadcrumb(folderId: string, folders:
|
|
333
|
+
function folderBreadcrumb(folderId: string, folders: LibraryFolder[]): string {
|
|
334
334
|
const f = folders.find(x => x.id === folderId)
|
|
335
335
|
if (!f) return ""
|
|
336
336
|
if (f.parentId == null) return f.name
|
|
@@ -351,7 +351,7 @@ function difficultyToPercent(value: "easy" | "medium" | "hard"): number {
|
|
|
351
351
|
* build this comes from analytics; for the mock it is a stable seed so
|
|
352
352
|
* the inspector reads as if the AI had crunched historical data.
|
|
353
353
|
*/
|
|
354
|
-
function difficultyInsightForFolder(folder:
|
|
354
|
+
function difficultyInsightForFolder(folder: LibraryFolder | undefined): {
|
|
355
355
|
/** Predicted level based on the content the author is writing. */
|
|
356
356
|
recommendation: "easy" | "medium" | "hard"
|
|
357
357
|
/** Contextual note shown under the meter (e.g. folder distribution). */
|
|
@@ -452,11 +452,11 @@ function BuilderSection({
|
|
|
452
452
|
// Compact selected tile — same visual rhythm as the collapsed
|
|
453
453
|
// "Question format" card. Clicking the tile opens a Command popover with
|
|
454
454
|
// search, full folder list, and an "Add new folder" footer that bridges to
|
|
455
|
-
// `
|
|
456
|
-
// `lib/mock/
|
|
457
|
-
// 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.
|
|
458
458
|
|
|
459
|
-
const FOLDER_TINT_BG: Record<
|
|
459
|
+
const FOLDER_TINT_BG: Record<LibraryFolderColorKey, string> = {
|
|
460
460
|
brand: "bg-[var(--icon-disc-brand-bg)] text-[var(--icon-disc-brand-fg)]",
|
|
461
461
|
success: "bg-[var(--icon-disc-chart-2-bg)] text-[var(--icon-disc-chart-2-fg)]",
|
|
462
462
|
warning: "bg-[var(--icon-disc-chart-4-bg)] text-[var(--icon-disc-chart-4-fg)]",
|
|
@@ -468,7 +468,7 @@ const FOLDER_TINT_BG: Record<QuestionBankFolderColorKey, string> = {
|
|
|
468
468
|
}
|
|
469
469
|
|
|
470
470
|
interface FolderPickerControlProps {
|
|
471
|
-
folders:
|
|
471
|
+
folders: LibraryFolder[]
|
|
472
472
|
value: string
|
|
473
473
|
onChange: (id: string) => void
|
|
474
474
|
open: boolean
|
|
@@ -479,14 +479,14 @@ interface FolderPickerControlProps {
|
|
|
479
479
|
/** Build a tree-ordered array: parents first, then their children
|
|
480
480
|
indented beneath. Each entry carries a `depth` for left-padding. */
|
|
481
481
|
function buildFolderTree(
|
|
482
|
-
folders:
|
|
483
|
-
): Array<{ folder:
|
|
482
|
+
folders: LibraryFolder[],
|
|
483
|
+
): Array<{ folder: LibraryFolder; depth: number }> {
|
|
484
484
|
const roots = folders.filter(f => f.parentId == null)
|
|
485
485
|
const childrenOf = (parentId: string) =>
|
|
486
486
|
folders.filter(f => f.parentId === parentId)
|
|
487
487
|
|
|
488
|
-
const out: Array<{ folder:
|
|
489
|
-
function walk(parent:
|
|
488
|
+
const out: Array<{ folder: LibraryFolder; depth: number }> = []
|
|
489
|
+
function walk(parent: LibraryFolder, depth: number) {
|
|
490
490
|
out.push({ folder: parent, depth })
|
|
491
491
|
for (const child of childrenOf(parent.id)) {
|
|
492
492
|
walk(child, depth + 1)
|
|
@@ -655,7 +655,7 @@ function DifficultyMeter({
|
|
|
655
655
|
: "bg-destructive"
|
|
656
656
|
|
|
657
657
|
const levelLabel =
|
|
658
|
-
value === "easy" ? "
|
|
658
|
+
value === "easy" ? "Low" : value === "hard" ? "High" : "Normal"
|
|
659
659
|
|
|
660
660
|
return (
|
|
661
661
|
<section className="flex flex-col gap-3">
|
|
@@ -782,15 +782,15 @@ interface NewQuestionComposerProps {
|
|
|
782
782
|
backHref: string
|
|
783
783
|
/** Label displayed in the `SiteHeader` back-icon (e.g. "Back to Favorites"). */
|
|
784
784
|
backLabel?: string
|
|
785
|
-
folders?:
|
|
785
|
+
folders?: LibraryFolder[]
|
|
786
786
|
}
|
|
787
787
|
|
|
788
|
-
export function
|
|
788
|
+
export function NewLibraryItemForm({
|
|
789
789
|
draftQuestionId,
|
|
790
790
|
defaultFolderId,
|
|
791
791
|
backHref,
|
|
792
792
|
backLabel = "Back",
|
|
793
|
-
folders =
|
|
793
|
+
folders = DEFAULT_LIBRARY_FOLDERS,
|
|
794
794
|
}: NewQuestionComposerProps) {
|
|
795
795
|
const router = useRouter()
|
|
796
796
|
const [submitting, setSubmitting] = React.useState(false)
|
|
@@ -804,7 +804,7 @@ export function NewQuestionComposer({
|
|
|
804
804
|
/** Local folder list — extended in-place when the author adds one
|
|
805
805
|
from the location picker so the new entry is selectable without
|
|
806
806
|
a page navigation. */
|
|
807
|
-
const [localFolders, setLocalFolders] = React.useState<
|
|
807
|
+
const [localFolders, setLocalFolders] = React.useState<LibraryFolder[]>(folders)
|
|
808
808
|
React.useEffect(() => {
|
|
809
809
|
setLocalFolders(prev =>
|
|
810
810
|
prev.length === folders.length && prev.every((f, i) => f.id === folders[i]?.id)
|
|
@@ -1243,7 +1243,7 @@ export function NewQuestionComposer({
|
|
|
1243
1243
|
{/* Location — compact selected tile (mirrors the
|
|
1244
1244
|
collapsed question-format card). Click opens a Command
|
|
1245
1245
|
popover with search and an "Add new folder" footer
|
|
1246
|
-
that bridges into `
|
|
1246
|
+
that bridges into `LibraryNewFolderSheet`. */}
|
|
1247
1247
|
<FormField
|
|
1248
1248
|
control={form.control}
|
|
1249
1249
|
name="folderId"
|
|
@@ -1324,8 +1324,8 @@ export function NewQuestionComposer({
|
|
|
1324
1324
|
name="cogLevel"
|
|
1325
1325
|
render={({ field }) => (
|
|
1326
1326
|
<InspectorSection
|
|
1327
|
-
title="
|
|
1328
|
-
description="
|
|
1327
|
+
title="Cognitive level"
|
|
1328
|
+
description="Broader bucket used for analytics — separate from the tier above."
|
|
1329
1329
|
>
|
|
1330
1330
|
<ToggleGroup
|
|
1331
1331
|
type="single"
|
|
@@ -1594,7 +1594,7 @@ export function NewQuestionComposer({
|
|
|
1594
1594
|
)}
|
|
1595
1595
|
/>
|
|
1596
1596
|
|
|
1597
|
-
{/*
|
|
1597
|
+
{/* Choice family + True/False — bordered OptionRows. */}
|
|
1598
1598
|
{showOptionsBlock ? (
|
|
1599
1599
|
<FormField
|
|
1600
1600
|
control={form.control}
|
|
@@ -1607,7 +1607,7 @@ export function NewQuestionComposer({
|
|
|
1607
1607
|
>
|
|
1608
1608
|
<p className="text-xs text-muted-foreground">
|
|
1609
1609
|
{isMulti
|
|
1610
|
-
? "
|
|
1610
|
+
? "Mark every correct response."
|
|
1611
1611
|
: isTrueFalse
|
|
1612
1612
|
? "Mark whether the statement is True or False."
|
|
1613
1613
|
: "Mark the single best answer. Distractors should be plausible — same length and grammar as the correct option."}
|
|
@@ -1942,10 +1942,10 @@ export function NewQuestionComposer({
|
|
|
1942
1942
|
Image hotspot authoring
|
|
1943
1943
|
</p>
|
|
1944
1944
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
1945
|
-
Upload an
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
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.
|
|
1949
1949
|
</p>
|
|
1950
1950
|
</div>
|
|
1951
1951
|
<Button type="button" variant="outline" size="sm" disabled>
|
|
@@ -2082,17 +2082,17 @@ export function NewQuestionComposer({
|
|
|
2082
2082
|
</form>
|
|
2083
2083
|
|
|
2084
2084
|
{/* New folder — invoked from the location picker. Re-uses the
|
|
2085
|
-
shared `
|
|
2085
|
+
shared `LibraryNewFolderSheet` (same shell as the folder
|
|
2086
2086
|
hub) so the surface stays consistent. The created folder is
|
|
2087
2087
|
appended to `localFolders` and immediately selected. */}
|
|
2088
|
-
<
|
|
2088
|
+
<LibraryNewFolderSheet
|
|
2089
2089
|
open={newFolderOpen}
|
|
2090
2090
|
onOpenChange={setNewFolderOpen}
|
|
2091
2091
|
parentFolderId={null}
|
|
2092
2092
|
descriptionText="Drafts created from this composer can land in the new folder right away."
|
|
2093
2093
|
onCreated={f => {
|
|
2094
2094
|
const id = newFolderId()
|
|
2095
|
-
const created:
|
|
2095
|
+
const created: LibraryFolder = { id, ...f }
|
|
2096
2096
|
setLocalFolders(prev => [...prev, created])
|
|
2097
2097
|
form.setValue("folderId", id, { shouldDirty: true, shouldValidate: false })
|
|
2098
2098
|
}}
|
|
@@ -105,6 +105,53 @@ function normalizedLocationHash(locationHash: string): string {
|
|
|
105
105
|
return locationHash.startsWith("#") ? locationHash.slice(1) : locationHash
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Path → set of hash fragments claimed by *another* nav item at the same path.
|
|
110
|
+
*
|
|
111
|
+
* Why: several rows deliberately share a route and disambiguate via `#fragment`.
|
|
112
|
+
* Example: `Tokens & themes → /settings#appearance` and `Settings → /settings`
|
|
113
|
+
* both render under the same page. Without a registry, when the user is on
|
|
114
|
+
* `/settings#appearance` the no-fragment "Settings" row matches by path-equality
|
|
115
|
+
* (line below: `pathname === pathOnly`) and lights up too — so *both* rows
|
|
116
|
+
* appear active.
|
|
117
|
+
*
|
|
118
|
+
* The registry is computed once from the static nav config and consulted by
|
|
119
|
+
* `isNavActive` to make the no-fragment item defer when a hash-bearing sibling
|
|
120
|
+
* claims the current `location.hash`.
|
|
121
|
+
*/
|
|
122
|
+
const NAV_HASH_CLAIMS: ReadonlyMap<string, ReadonlySet<string>> = (() => {
|
|
123
|
+
const map = new Map<string, Set<string>>()
|
|
124
|
+
const record = (url: string) => {
|
|
125
|
+
const p = navUrlPath(url)
|
|
126
|
+
const f = navUrlFragment(url)
|
|
127
|
+
if (!p || f === null) return
|
|
128
|
+
let set = map.get(p)
|
|
129
|
+
if (!set) { set = new Set<string>(); map.set(p, set) }
|
|
130
|
+
set.add(f)
|
|
131
|
+
}
|
|
132
|
+
const walk = (items: ReadonlyArray<{ url?: string; children?: ReadonlyArray<{ url?: string; children?: unknown }> }>) => {
|
|
133
|
+
for (const it of items) {
|
|
134
|
+
if (typeof it.url === "string") record(it.url)
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
+
if (Array.isArray((it as any).children)) walk((it as any).children)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
walk(NAV_PRIMARY)
|
|
140
|
+
walk(NAV_DOCUMENTS)
|
|
141
|
+
walk(NAV_QUICK_ACTIONS)
|
|
142
|
+
walk(NAV_SECONDARY)
|
|
143
|
+
return map
|
|
144
|
+
})()
|
|
145
|
+
|
|
146
|
+
/** True when another nav item at the same path claims the current location hash. */
|
|
147
|
+
function navHasMoreSpecificMatch(pathname: string, locationHash: string): boolean {
|
|
148
|
+
const claims = NAV_HASH_CLAIMS.get(pathname)
|
|
149
|
+
if (!claims) return false
|
|
150
|
+
const h = normalizedLocationHash(locationHash)
|
|
151
|
+
if (h === "") return false
|
|
152
|
+
return claims.has(h)
|
|
153
|
+
}
|
|
154
|
+
|
|
108
155
|
/**
|
|
109
156
|
* Whether `pathname` (+ optional `location.hash`) matches a sidebar `href`.
|
|
110
157
|
* When several links share the same path (e.g. `/settings`), disambiguate with `#fragment`
|
|
@@ -129,9 +176,18 @@ function isNavActive(pathname: string, url: string, locationHash = ""): boolean
|
|
|
129
176
|
return pathname === pathOnly && h === frag
|
|
130
177
|
}
|
|
131
178
|
|
|
132
|
-
if (pathOnly === "/")
|
|
133
|
-
|
|
134
|
-
|
|
179
|
+
if (pathOnly === "/") {
|
|
180
|
+
if (pathname !== "/" || h !== "") return false
|
|
181
|
+
return !navHasMoreSpecificMatch(pathname, locationHash)
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Exact path match — ignore `location.hash` when the nav `href` has no `#…` fragment (QB view tabs use hash).
|
|
185
|
+
* EXCEPTION: when another nav item at the same path claims the current hash
|
|
186
|
+
* (e.g. `Tokens & themes → /settings#appearance` while we're evaluating
|
|
187
|
+
* `Settings → /settings`), defer to that more-specific row — otherwise both
|
|
188
|
+
* would light up. See `NAV_HASH_CLAIMS`.
|
|
189
|
+
*/
|
|
190
|
+
if (pathname === pathOnly) return !navHasMoreSpecificMatch(pathname, locationHash)
|
|
135
191
|
// Design system library — active on hub and detail routes.
|
|
136
192
|
if (pathOnly === "/library") {
|
|
137
193
|
return pathname.startsWith("/library/")
|
|
@@ -167,7 +223,7 @@ function isCollapsibleChildActive(
|
|
|
167
223
|
|
|
168
224
|
if (!isNavActive(pathname, child.url, locationHash)) return false
|
|
169
225
|
|
|
170
|
-
/** Hub entry (`/
|
|
226
|
+
/** Hub entry (`/library`) must not stay “active” on `/library/all` etc. */
|
|
171
227
|
if (parent.primaryHubChildKey && child.key === parent.primaryHubChildKey) {
|
|
172
228
|
const hubPath = navUrlPath(parent.url)
|
|
173
229
|
if (hubPath) {
|
|
@@ -298,7 +354,7 @@ function CollapsibleNavItem({ item, pathname }: { item: NavLinkItem; pathname: s
|
|
|
298
354
|
const iconRailCollapsed = state === "collapsed" && !isMobile
|
|
299
355
|
// In the icon rail the parent icon is the ONLY visible thing for this item
|
|
300
356
|
// (no sub-list, no labels) — so it must reflect "I'm somewhere inside this
|
|
301
|
-
// section" by lighting up on any descendant route (e.g. `/
|
|
357
|
+
// section" by lighting up on any descendant route (e.g. `/library/all`),
|
|
302
358
|
// not only on the parent URL itself. In the expanded view we keep the
|
|
303
359
|
// parent neutral and let the active child row carry `data-active` (see
|
|
304
360
|
// `isCollapsibleParentMenuButtonActive`).
|
|
@@ -4,28 +4,29 @@
|
|
|
4
4
|
* SecondaryPanel — nested rail between the primary icon sidebar and content.
|
|
5
5
|
* Full width shows hub scope nav; **compact** matches the primary sidebar icon rail (`w-12`).
|
|
6
6
|
*
|
|
7
|
-
* Chrome uses {@link NestedSecondaryPanelShell}.
|
|
8
|
-
* `
|
|
7
|
+
* Chrome uses {@link NestedSecondaryPanelShell}. Library body stays in
|
|
8
|
+
* `library-secondary-nav.tsx` (domain-specific), not duplicated here.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import * as React from "react"
|
|
12
12
|
import { useSidebar } from "@/components/ui/sidebar"
|
|
13
13
|
import { Tip } from "@/components/ui/tip"
|
|
14
14
|
import { Button } from "@/components/ui/button"
|
|
15
|
-
import {
|
|
15
|
+
import { LibrarySecondaryNav } from "@/components/library-secondary-nav"
|
|
16
|
+
import { TokensSecondaryNav } from "@/components/tokens-secondary-nav"
|
|
16
17
|
import { NestedSecondaryPanelShell } from "@/components/templates/nested-secondary-panel-shell"
|
|
17
18
|
import { useSidebarReflowZoom } from "@/hooks/use-sidebar-reflow-zoom"
|
|
18
|
-
import type {
|
|
19
|
-
import type {
|
|
20
|
-
|
|
21
|
-
export type
|
|
22
|
-
folders:
|
|
23
|
-
onFoldersChange: React.Dispatch<React.SetStateAction<
|
|
24
|
-
items:
|
|
25
|
-
onItemsChange: React.Dispatch<React.SetStateAction<
|
|
19
|
+
import type { LibraryItem } from "@/lib/mock/library"
|
|
20
|
+
import type { LibraryFolder } from "@/lib/mock/library-folders"
|
|
21
|
+
|
|
22
|
+
export type LibraryFolderBridge = {
|
|
23
|
+
folders: LibraryFolder[]
|
|
24
|
+
onFoldersChange: React.Dispatch<React.SetStateAction<LibraryFolder[]>>
|
|
25
|
+
items: LibraryItem[]
|
|
26
|
+
onItemsChange: React.Dispatch<React.SetStateAction<LibraryItem[]>>
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export type
|
|
29
|
+
export type LibraryAccessBridge = {
|
|
29
30
|
openManageAccess: () => void
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -36,11 +37,18 @@ export type QuestionBankAccessBridge = {
|
|
|
36
37
|
export type ClosePanelOptions = {
|
|
37
38
|
/**
|
|
38
39
|
* Main app sidebar after the secondary panel closes.
|
|
39
|
-
* - `
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* - `restore` (default): snap back to the user's saved preference
|
|
41
|
+
* (`sidebar_state_v2` cookie, or `defaultOpen` if unset). Use this when
|
|
42
|
+
* leaving a page whose secondary panel caused an incidental collapse — the
|
|
43
|
+
* rail returns to the state the user explicitly set elsewhere via ⌘B / the
|
|
44
|
+
* sidebar button.
|
|
45
|
+
* - `leave`: only clear the active panel — keep the rail in whatever state it
|
|
46
|
+
* was in (rare; prefer `restore`).
|
|
47
|
+
* - `expand`: force the full primary rail open. Use only when the product
|
|
48
|
+
* explicitly wants the wide rail after dismiss.
|
|
49
|
+
* - `collapse`: keep the icon rail. Use when the next route also wants compact.
|
|
42
50
|
*/
|
|
43
|
-
mainSidebar?: "expand" | "collapse" | "leave"
|
|
51
|
+
mainSidebar?: "restore" | "expand" | "collapse" | "leave"
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
interface SecondaryPanelContextValue {
|
|
@@ -54,12 +62,12 @@ interface SecondaryPanelContextValue {
|
|
|
54
62
|
secondaryPanelCompact: boolean
|
|
55
63
|
/** Narrow icon rail (primary-sidebar-style); keeps {@link activePanel} mounted. */
|
|
56
64
|
collapseActiveSecondaryPanel: () => void
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
/** Library folder tree shared with the secondary nav while the hub is mounted. */
|
|
66
|
+
libraryFolderBridge: LibraryFolderBridge | null
|
|
67
|
+
setLibraryFolderBridge: (bridge: LibraryFolderBridge | null) => void
|
|
60
68
|
/** Opens the hub collaborators sheet from the secondary nav. */
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
libraryAccessBridge: LibraryAccessBridge | null
|
|
70
|
+
setLibraryAccessBridge: (bridge: LibraryAccessBridge | null) => void
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
const SecondaryPanelContext = React.createContext<SecondaryPanelContextValue>({
|
|
@@ -68,10 +76,10 @@ const SecondaryPanelContext = React.createContext<SecondaryPanelContextValue>({
|
|
|
68
76
|
closePanel: () => {},
|
|
69
77
|
secondaryPanelCompact: false,
|
|
70
78
|
collapseActiveSecondaryPanel: () => {},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
libraryFolderBridge: null,
|
|
80
|
+
setLibraryFolderBridge: () => {},
|
|
81
|
+
libraryAccessBridge: null,
|
|
82
|
+
setLibraryAccessBridge: () => {},
|
|
75
83
|
})
|
|
76
84
|
|
|
77
85
|
export function useSecondaryPanel() {
|
|
@@ -81,11 +89,11 @@ export function useSecondaryPanel() {
|
|
|
81
89
|
export function SecondaryPanelProvider({ children }: { children: React.ReactNode }) {
|
|
82
90
|
const [activePanel, setActivePanel] = React.useState<string | null>(null)
|
|
83
91
|
const [secondaryPanelCompact, setSecondaryPanelCompact] = React.useState(false)
|
|
84
|
-
const [
|
|
85
|
-
React.useState<
|
|
86
|
-
const [
|
|
87
|
-
React.useState<
|
|
88
|
-
const { setOpen } = useSidebar()
|
|
92
|
+
const [libraryFolderBridge, setLibraryFolderBridge] =
|
|
93
|
+
React.useState<LibraryFolderBridge | null>(null)
|
|
94
|
+
const [libraryAccessBridge, setLibraryAccessBridge] =
|
|
95
|
+
React.useState<LibraryAccessBridge | null>(null)
|
|
96
|
+
const { setOpen, restoreSavedOpen } = useSidebar()
|
|
89
97
|
|
|
90
98
|
/**
|
|
91
99
|
* Browser zoom ≥ 200% (or very short viewport) — same `useSidebarReflowZoom`
|
|
@@ -122,14 +130,25 @@ export function SecondaryPanelProvider({ children }: { children: React.ReactNode
|
|
|
122
130
|
const closePanel = React.useCallback((opts?: ClosePanelOptions) => {
|
|
123
131
|
setSecondaryPanelCompact(false)
|
|
124
132
|
setActivePanel(null)
|
|
125
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Default = `restore`. `openPanel` collapsed the rail with `persist: false`
|
|
135
|
+
* (the saved cookie is untouched), so on close we ask the sidebar context
|
|
136
|
+
* to re-apply whatever the user truly saved. This makes the visual collapse
|
|
137
|
+
* scoped to the page that needed it — leave the page → rail comes back to
|
|
138
|
+
* the user's preference.
|
|
139
|
+
*/
|
|
140
|
+
const mainSidebar = opts?.mainSidebar ?? "restore"
|
|
126
141
|
if (mainSidebar === "leave") return
|
|
142
|
+
if (mainSidebar === "restore") {
|
|
143
|
+
restoreSavedOpen()
|
|
144
|
+
return
|
|
145
|
+
}
|
|
127
146
|
if (mainSidebar === "collapse") {
|
|
128
147
|
setOpen(false, { persist: false })
|
|
129
|
-
|
|
130
|
-
setOpen(true, { persist: false }) // expand main sidebar back
|
|
148
|
+
return
|
|
131
149
|
}
|
|
132
|
-
|
|
150
|
+
setOpen(true, { persist: false }) // mainSidebar === "expand"
|
|
151
|
+
}, [restoreSavedOpen, setOpen])
|
|
133
152
|
|
|
134
153
|
const collapseActiveSecondaryPanel = React.useCallback(() => {
|
|
135
154
|
setSecondaryPanelCompact(true)
|
|
@@ -142,10 +161,10 @@ export function SecondaryPanelProvider({ children }: { children: React.ReactNode
|
|
|
142
161
|
closePanel,
|
|
143
162
|
secondaryPanelCompact,
|
|
144
163
|
collapseActiveSecondaryPanel,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
libraryFolderBridge,
|
|
165
|
+
setLibraryFolderBridge,
|
|
166
|
+
libraryAccessBridge,
|
|
167
|
+
setLibraryAccessBridge,
|
|
149
168
|
}),
|
|
150
169
|
[
|
|
151
170
|
activePanel,
|
|
@@ -153,8 +172,8 @@ export function SecondaryPanelProvider({ children }: { children: React.ReactNode
|
|
|
153
172
|
closePanel,
|
|
154
173
|
secondaryPanelCompact,
|
|
155
174
|
collapseActiveSecondaryPanel,
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
libraryFolderBridge,
|
|
176
|
+
libraryAccessBridge,
|
|
158
177
|
],
|
|
159
178
|
)
|
|
160
179
|
|
|
@@ -169,11 +188,11 @@ export function SecondaryPanelProvider({ children }: { children: React.ReactNode
|
|
|
169
188
|
// SecondaryPanel — the actual rendered panel
|
|
170
189
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
171
190
|
|
|
172
|
-
function
|
|
191
|
+
function LibraryPanel() {
|
|
173
192
|
const { collapseActiveSecondaryPanel, secondaryPanelCompact } = useSecondaryPanel()
|
|
174
193
|
|
|
175
194
|
if (secondaryPanelCompact) {
|
|
176
|
-
return <
|
|
195
|
+
return <LibrarySecondaryNav />
|
|
177
196
|
}
|
|
178
197
|
|
|
179
198
|
return (
|
|
@@ -197,14 +216,48 @@ function QuestionBankPanel() {
|
|
|
197
216
|
</Button>
|
|
198
217
|
</Tip>
|
|
199
218
|
</div>
|
|
200
|
-
<
|
|
219
|
+
<LibrarySecondaryNav />
|
|
220
|
+
</>
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function TokensPanel() {
|
|
225
|
+
const { collapseActiveSecondaryPanel, secondaryPanelCompact } = useSecondaryPanel()
|
|
226
|
+
|
|
227
|
+
if (secondaryPanelCompact) {
|
|
228
|
+
return <TokensSecondaryNav />
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<>
|
|
233
|
+
<div className="flex items-center justify-between gap-2 px-4 pt-4 pb-2">
|
|
234
|
+
<h2
|
|
235
|
+
className="text-xl font-semibold leading-tight text-sidebar-foreground"
|
|
236
|
+
style={{ fontFamily: "var(--font-heading)" }}
|
|
237
|
+
>
|
|
238
|
+
Tokens
|
|
239
|
+
</h2>
|
|
240
|
+
<Tip label="Collapse to icons" side="bottom">
|
|
241
|
+
<Button
|
|
242
|
+
type="button"
|
|
243
|
+
size="icon"
|
|
244
|
+
variant="ghost"
|
|
245
|
+
onClick={() => collapseActiveSecondaryPanel()}
|
|
246
|
+
aria-label="Collapse to icons"
|
|
247
|
+
>
|
|
248
|
+
<i className="fa-light fa-angles-left" aria-hidden="true" />
|
|
249
|
+
</Button>
|
|
250
|
+
</Tip>
|
|
251
|
+
</div>
|
|
252
|
+
<TokensSecondaryNav />
|
|
201
253
|
</>
|
|
202
254
|
)
|
|
203
255
|
}
|
|
204
256
|
|
|
205
257
|
/** Register panel components by id when a route opts into `secondaryPanel` in nav. */
|
|
206
258
|
const PANELS: Record<string, React.FC> = {
|
|
207
|
-
"
|
|
259
|
+
"library": LibraryPanel,
|
|
260
|
+
tokens: TokensPanel,
|
|
208
261
|
}
|
|
209
262
|
|
|
210
263
|
export function SecondaryPanel() {
|
|
@@ -234,30 +287,30 @@ export function useAutoPanel(panelId: string) {
|
|
|
234
287
|
}, [panelId])
|
|
235
288
|
}
|
|
236
289
|
|
|
237
|
-
/** Sync hub folder state into the
|
|
238
|
-
export function
|
|
290
|
+
/** Sync hub folder state into the library secondary nav while the route is mounted. */
|
|
291
|
+
export function LibraryFolderBridge({
|
|
239
292
|
folders,
|
|
240
293
|
onFoldersChange,
|
|
241
294
|
items,
|
|
242
295
|
onItemsChange,
|
|
243
|
-
}:
|
|
244
|
-
const {
|
|
296
|
+
}: LibraryFolderBridge) {
|
|
297
|
+
const { setLibraryFolderBridge } = useSecondaryPanel()
|
|
245
298
|
|
|
246
299
|
React.useEffect(() => {
|
|
247
|
-
|
|
248
|
-
return () =>
|
|
249
|
-
}, [folders, onFoldersChange, items, onItemsChange,
|
|
300
|
+
setLibraryFolderBridge({ folders, onFoldersChange, items, onItemsChange })
|
|
301
|
+
return () => setLibraryFolderBridge(null)
|
|
302
|
+
}, [folders, onFoldersChange, items, onItemsChange, setLibraryFolderBridge])
|
|
250
303
|
|
|
251
304
|
return null
|
|
252
305
|
}
|
|
253
306
|
|
|
254
|
-
export function
|
|
255
|
-
const {
|
|
307
|
+
export function LibraryAccessBridge({ openManageAccess }: LibraryAccessBridge) {
|
|
308
|
+
const { setLibraryAccessBridge } = useSecondaryPanel()
|
|
256
309
|
|
|
257
310
|
React.useEffect(() => {
|
|
258
|
-
|
|
259
|
-
return () =>
|
|
260
|
-
}, [openManageAccess,
|
|
311
|
+
setLibraryAccessBridge({ openManageAccess })
|
|
312
|
+
return () => setLibraryAccessBridge(null)
|
|
313
|
+
}, [openManageAccess, setLibraryAccessBridge])
|
|
261
314
|
|
|
262
315
|
return null
|
|
263
316
|
}
|
|
@@ -5,8 +5,8 @@ import { useSidebar } from "@/components/ui/sidebar"
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Collapses the sidebar on mount. Restores previous state on unmount.
|
|
8
|
-
*
|
|
9
|
-
* and reverts to whatever it was when leaving.
|
|
8
|
+
* Use on focused full-page forms / wizards so the sidebar collapses when
|
|
9
|
+
* entering and reverts to whatever it was when leaving.
|
|
10
10
|
*
|
|
11
11
|
* Both transitions pass `{ persist: false }` so the visual collapse never
|
|
12
12
|
* overwrites the user's saved sidebar preference (the `sidebar_state_v2`
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import { useEffect, useRef } from "react"
|
|
4
4
|
import { useSidebar } from "@/components/ui/sidebar"
|
|
5
5
|
|
|
6
|
-
/** Reopens the sidebar once
|
|
6
|
+
/** Reopens the sidebar once on mount — pair with `SidebarAutoCollapse` on focused
|
|
7
|
+
* full-page forms / wizards so leaving the form restores the sidebar. */
|
|
7
8
|
export function SidebarAutoOpen() {
|
|
8
9
|
const { setOpen } = useSidebar()
|
|
9
10
|
const ran = useRef(false)
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* `FILTER_FIELDS` and `COLUMNS` below are **product data**, not
|
|
10
10
|
* design-system primitives — they encode the columns + filter operators
|
|
11
|
-
* of the Placements hub. Other hubs (Team, Compliance,
|
|
11
|
+
* of the Placements hub. Other hubs (Team, Compliance, Library)
|
|
12
12
|
* compose their own arrays and pass them into the drawer.
|
|
13
13
|
*/
|
|
14
14
|
|
|
@@ -78,7 +78,7 @@ const DiscoveryHubSearchRow = React.memo(function DiscoveryHubSearchRow({
|
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Discovery hub — centered command-style natural language search, create CTA, and Ask Leo promo.
|
|
81
|
-
* Hubs can pass `groups` from a domain module (e.g. Ask Leo prompt suggestions from `lib/
|
|
81
|
+
* Hubs can pass `groups` from a domain module (e.g. Ask Leo prompt suggestions from `lib/library-hub-search.ts`).
|
|
82
82
|
*/
|
|
83
83
|
export function DiscoveryHubTemplate({
|
|
84
84
|
title,
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* </form>
|
|
40
40
|
* ```
|
|
41
41
|
*
|
|
42
|
-
* See `new-placement-form.tsx` and `new-
|
|
42
|
+
* See `new-placement-form.tsx` and `new-library-item-form.tsx` for canonical usage.
|
|
43
43
|
*
|
|
44
44
|
* The template owns:
|
|
45
45
|
* • Page chrome (`PrimaryPageTemplate` underneath: `SidebarInset`, `SiteHeader`).
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
* ⌘⌥← back, plain Enter submit on final step).
|
|
50
50
|
* • For `form-inspector`: 2-column scaffold with sticky inspector rail.
|
|
51
51
|
*
|
|
52
|
-
* WCAG 2.1 AA — same rules as `new-placement-form` / `new-
|
|
52
|
+
* WCAG 2.1 AA — same rules as `new-placement-form` / `new-library-item-form`:
|
|
53
53
|
* ✓ Hero `<h1>` carries the page title (only one h1 per route).
|
|
54
54
|
* ✓ Step indicator uses `aria-current="step"` and visible labels (1.3.1).
|
|
55
55
|
* ✓ Focus moves to step content when step changes (2.4.3).
|
|
@@ -24,7 +24,7 @@ export interface SecondaryPanelHubTemplateProps
|
|
|
24
24
|
/**
|
|
25
25
|
* Primary hub shell with optional bridges + `PrimaryPageTemplate` body.
|
|
26
26
|
* Mount `useAutoPanel` / `SecondaryPanelHubActivator` on a parent layout that stays mounted across
|
|
27
|
-
* hub child routes (see `app/(app)/
|
|
27
|
+
* hub child routes (see `app/(app)/library/layout.tsx`) so the panel does not close between navigations.
|
|
28
28
|
*
|
|
29
29
|
* Pair with `useSecondaryPanelHubNav` and hub-specific `lib/*-nav.ts` helpers for URL scope.
|
|
30
30
|
*/
|