@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
|
@@ -12,38 +12,38 @@ import { Shortcut } from "@/components/ui/dropdown-menu"
|
|
|
12
12
|
import { useSidebar } from "@/components/ui/sidebar"
|
|
13
13
|
import { useAltKeyLabel, useModKeyLabel } from "@/hooks/use-mod-key-label"
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
type
|
|
17
|
-
type
|
|
18
|
-
} from "@/lib/mock/
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
15
|
+
DEFAULT_LIBRARY_FOLDERS,
|
|
16
|
+
type LibraryFolder,
|
|
17
|
+
type LibraryFolderColorKey,
|
|
18
|
+
} from "@/lib/mock/library-folders"
|
|
19
|
+
import { LIBRARY_ITEMS, type LibraryItem } from "@/lib/mock/library"
|
|
20
|
+
import { LIBRARY_HUB_ASK_LEO_PROMPTS } from "@/lib/library-hub-search"
|
|
21
21
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} from "@/lib/
|
|
22
|
+
LIBRARY_ALL_PATH,
|
|
23
|
+
LIBRARY_NAV_MY_AUTHOR,
|
|
24
|
+
libraryNavHref,
|
|
25
|
+
} from "@/lib/library-nav"
|
|
26
26
|
import { cn } from "@/lib/utils"
|
|
27
27
|
|
|
28
|
-
const NEW_QUESTION_AUTHORING_PATH = "/
|
|
28
|
+
const NEW_QUESTION_AUTHORING_PATH = "/library/new"
|
|
29
29
|
|
|
30
30
|
const DRAFT_WITH_LEO_PROMPT =
|
|
31
|
-
"Help me draft a new
|
|
31
|
+
"Help me draft a new library item. Ask me for the category, item type (single choice / multi-select / true-false / short answer), tier, and track, then propose name, prompt, answer options, and notes I can paste into the composer."
|
|
32
32
|
|
|
33
33
|
const TEMPLATE_PROMPT =
|
|
34
|
-
"Walk me through choosing
|
|
34
|
+
"Walk me through choosing an item template (single choice, multi-select, short answer, true / false) and produce a starter item with name, options, notes, and tags."
|
|
35
35
|
|
|
36
36
|
const IMPORT_PROMPT =
|
|
37
37
|
"Guide me through importing assessment questions in bulk. Ask about source format (CSV, QTI, copy/paste), then outline what columns and mappings I need."
|
|
38
38
|
|
|
39
39
|
/** Rotating example queries — read like something a user would actually type into search. */
|
|
40
40
|
const HUB_COMPOSER_PLACEHOLDERS = [
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"find
|
|
45
|
-
"
|
|
46
|
-
"
|
|
41
|
+
"items tagged with Tag 1 and Category 2",
|
|
42
|
+
"everything Owner A edited this month",
|
|
43
|
+
"Folder 1 entries marked High",
|
|
44
|
+
"find LIB-2026-001 and anything like it",
|
|
45
|
+
"drafts from the most recent reference set",
|
|
46
|
+
"Type 1 items I still need for the demo block",
|
|
47
47
|
] as const
|
|
48
48
|
|
|
49
49
|
interface ScopeChip {
|
|
@@ -52,7 +52,7 @@ interface ScopeChip {
|
|
|
52
52
|
href: string
|
|
53
53
|
count: number
|
|
54
54
|
folderGlyph: {
|
|
55
|
-
colorKey:
|
|
55
|
+
colorKey: LibraryFolderColorKey
|
|
56
56
|
icon: string
|
|
57
57
|
variant?: "solid" | "outline"
|
|
58
58
|
}
|
|
@@ -69,33 +69,33 @@ interface CreateTile {
|
|
|
69
69
|
shortcutKeys?: string
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
interface FolderTile extends
|
|
72
|
+
interface FolderTile extends LibraryFolder {
|
|
73
73
|
count: number
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function buildScopeChips(items:
|
|
76
|
+
function buildScopeChips(items: LibraryItem[]): ScopeChip[] {
|
|
77
77
|
const mine = items.filter(
|
|
78
|
-
i => i.author ===
|
|
78
|
+
i => i.author === LIBRARY_NAV_MY_AUTHOR || i.createdBy === LIBRARY_NAV_MY_AUTHOR,
|
|
79
79
|
).length
|
|
80
80
|
return [
|
|
81
81
|
{
|
|
82
82
|
id: "all",
|
|
83
83
|
label: "All",
|
|
84
84
|
count: items.length,
|
|
85
|
-
href:
|
|
85
|
+
href: LIBRARY_ALL_PATH,
|
|
86
86
|
folderGlyph: { colorKey: "muted", icon: "fa-layer-group", variant: "outline" },
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
id: "my",
|
|
90
90
|
label: "Mine",
|
|
91
91
|
count: mine,
|
|
92
|
-
href:
|
|
92
|
+
href: libraryNavHref({ scope: "my" }),
|
|
93
93
|
folderGlyph: { colorKey: "brand", icon: "fa-user" },
|
|
94
94
|
},
|
|
95
95
|
]
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function buildFolderTiles(items:
|
|
98
|
+
function buildFolderTiles(items: LibraryItem[], folders: LibraryFolder[]): FolderTile[] {
|
|
99
99
|
const byFolder = new Map<string, number>()
|
|
100
100
|
for (const i of items) byFolder.set(i.folderId, (byFolder.get(i.folderId) ?? 0) + 1)
|
|
101
101
|
return folders
|
|
@@ -105,24 +105,24 @@ function buildFolderTiles(items: QuestionBankItem[], folders: QuestionBankFolder
|
|
|
105
105
|
|
|
106
106
|
// Static derivations of immutable mock data — computed once at module load,
|
|
107
107
|
// not per render of the hub. Re-derive only if the underlying mock arrays change.
|
|
108
|
-
const HUB_SCOPES = buildScopeChips(
|
|
109
|
-
const HUB_FOLDER_TILES = buildFolderTiles(
|
|
108
|
+
const HUB_SCOPES = buildScopeChips(LIBRARY_ITEMS)
|
|
109
|
+
const HUB_FOLDER_TILES = buildFolderTiles(LIBRARY_ITEMS, DEFAULT_LIBRARY_FOLDERS)
|
|
110
110
|
|
|
111
|
-
function
|
|
112
|
-
return i.author ===
|
|
111
|
+
function isMineLibraryItem(i: LibraryItem): boolean {
|
|
112
|
+
return i.author === LIBRARY_NAV_MY_AUTHOR || i.createdBy === LIBRARY_NAV_MY_AUTHOR
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const HUB_MINE_RECENTS = [...
|
|
116
|
-
.filter(
|
|
115
|
+
const HUB_MINE_RECENTS = [...LIBRARY_ITEMS]
|
|
116
|
+
.filter(isMineLibraryItem)
|
|
117
117
|
.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1))
|
|
118
118
|
.slice(0, 3)
|
|
119
119
|
|
|
120
120
|
const HUB_ASK_LEO_PAGE_CONTEXT = {
|
|
121
121
|
title: "Question hub",
|
|
122
122
|
description:
|
|
123
|
-
"Browse and organize assessment items with AI-assisted workflows. The hub search field opens discovery results on `/
|
|
124
|
-
suggestions: [...
|
|
125
|
-
data: { surface: "
|
|
123
|
+
"Browse and organize assessment items with AI-assisted workflows. The hub search field opens discovery results on `/library/find` with your wording applied to the list; use the library’s Search in the sidebar for `/library/list`. Pick a suggestion below when you want a full Ask Leo thread.",
|
|
124
|
+
suggestions: [...LIBRARY_HUB_ASK_LEO_PROMPTS],
|
|
125
|
+
data: { surface: "library-discovery-hub" as const },
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
function formatRelativeDate(iso: string): string {
|
|
@@ -138,7 +138,7 @@ function formatRelativeDate(iso: string): string {
|
|
|
138
138
|
return `${Math.round(diffDays / 365)}y ago`
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
export function
|
|
141
|
+
export function LibraryHubClient() {
|
|
142
142
|
const router = useRouter()
|
|
143
143
|
const { openWithPrompt } = useAskLeo()
|
|
144
144
|
const { setOpen: setMainSidebarOpen } = useSidebar()
|
|
@@ -162,7 +162,7 @@ export function QuestionBankHubClient() {
|
|
|
162
162
|
)
|
|
163
163
|
|
|
164
164
|
/**
|
|
165
|
-
* Navigate to the full-page authoring composer (`/
|
|
165
|
+
* Navigate to the full-page authoring composer (`/library/new`).
|
|
166
166
|
* Mirrors the Placements "New placement" pre-collapse: animates the sidebar
|
|
167
167
|
* closed first so the user sees one smooth transition into the focused flow
|
|
168
168
|
* (the route also mounts `SidebarAutoCollapse` to lock it shut while there).
|
|
@@ -178,7 +178,7 @@ export function QuestionBankHubClient() {
|
|
|
178
178
|
|
|
179
179
|
const onHubComposerSubmit = React.useCallback(
|
|
180
180
|
(message: string) => {
|
|
181
|
-
router.push(
|
|
181
|
+
router.push(libraryNavHref({ scope: "all", q: message.trim(), hubFind: true }))
|
|
182
182
|
},
|
|
183
183
|
[router],
|
|
184
184
|
)
|
|
@@ -214,7 +214,7 @@ export function QuestionBankHubClient() {
|
|
|
214
214
|
{
|
|
215
215
|
id: "template",
|
|
216
216
|
label: "From template",
|
|
217
|
-
description: "Pick
|
|
217
|
+
description: "Pick choice-style, multi-select, short answer or true / false — Leo fills the scaffold.",
|
|
218
218
|
icon: "fa-clone",
|
|
219
219
|
iconTint: "bg-sky-500/15 text-sky-700 dark:text-sky-300",
|
|
220
220
|
onClick: () => sendLeoSuggestion(TEMPLATE_PROMPT),
|
|
@@ -334,7 +334,7 @@ export function QuestionBankHubClient() {
|
|
|
334
334
|
Continue where you left off
|
|
335
335
|
</h2>
|
|
336
336
|
<Link
|
|
337
|
-
href={
|
|
337
|
+
href={libraryNavHref({ scope: "my" })}
|
|
338
338
|
className="text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
|
|
339
339
|
>
|
|
340
340
|
View all
|
|
@@ -345,7 +345,7 @@ export function QuestionBankHubClient() {
|
|
|
345
345
|
{recents.map(item => (
|
|
346
346
|
<li key={item.id}>
|
|
347
347
|
<Link
|
|
348
|
-
href={
|
|
348
|
+
href={libraryNavHref({ scope: "my" })}
|
|
349
349
|
className="group flex h-full flex-col gap-3 rounded-xl border border-border bg-card p-4 transition hover:border-interactive-hover hover:bg-interactive-hover/30 hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
350
350
|
>
|
|
351
351
|
<div className="flex items-center gap-2">
|
|
@@ -371,7 +371,7 @@ export function QuestionBankHubClient() {
|
|
|
371
371
|
Browse the library
|
|
372
372
|
</h2>
|
|
373
373
|
<Link
|
|
374
|
-
href={
|
|
374
|
+
href={LIBRARY_ALL_PATH}
|
|
375
375
|
className="text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
|
|
376
376
|
>
|
|
377
377
|
Open full library
|
|
@@ -406,7 +406,7 @@ export function QuestionBankHubClient() {
|
|
|
406
406
|
{folderTiles.map(f => (
|
|
407
407
|
<div key={f.id} role="listitem" className="min-w-0">
|
|
408
408
|
<Link
|
|
409
|
-
href={
|
|
409
|
+
href={libraryNavHref({ scope: "folder", folderId: f.id })}
|
|
410
410
|
className={hubFolderBrowserTileClass}
|
|
411
411
|
>
|
|
412
412
|
<OsFolderGlyph colorKey={f.colorKey} icon={f.icon} size="md" />
|
package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx}
RENAMED
|
@@ -14,13 +14,13 @@ import { Tip } from "@/components/ui/tip"
|
|
|
14
14
|
import { Kbd, KbdGroup } from "@/components/ui/kbd"
|
|
15
15
|
import { cn } from "@/lib/utils"
|
|
16
16
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
type
|
|
20
|
-
} from "@/lib/mock/
|
|
17
|
+
LIBRARY_FOLDER_COLOR_STYLES,
|
|
18
|
+
LIBRARY_FOLDER_ICON_OPTIONS,
|
|
19
|
+
type LibraryFolderColorKey,
|
|
20
|
+
} from "@/lib/mock/library-folders"
|
|
21
21
|
import { OsFolderGlyph } from "@/components/data-views/os-folder-glyph"
|
|
22
22
|
|
|
23
|
-
const COLOR_OPTIONS:
|
|
23
|
+
const COLOR_OPTIONS: LibraryFolderColorKey[] = [
|
|
24
24
|
"brand",
|
|
25
25
|
"success",
|
|
26
26
|
"warning",
|
|
@@ -38,7 +38,7 @@ function FolderTilePreview({
|
|
|
38
38
|
className,
|
|
39
39
|
}: {
|
|
40
40
|
name: string
|
|
41
|
-
colorKey:
|
|
41
|
+
colorKey: LibraryFolderColorKey
|
|
42
42
|
icon: string
|
|
43
43
|
className?: string
|
|
44
44
|
}) {
|
|
@@ -64,7 +64,7 @@ function FolderTilePreview({
|
|
|
64
64
|
)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
export interface
|
|
67
|
+
export interface LibraryNewFolderSheetProps {
|
|
68
68
|
open: boolean
|
|
69
69
|
onOpenChange: (open: boolean) => void
|
|
70
70
|
/** Parent folder id for the new folder (`null` = top level). */
|
|
@@ -75,28 +75,28 @@ export interface QuestionBankNewFolderSheetProps {
|
|
|
75
75
|
customizingFolder?: {
|
|
76
76
|
name: string
|
|
77
77
|
icon: string
|
|
78
|
-
colorKey:
|
|
78
|
+
colorKey: LibraryFolderColorKey
|
|
79
79
|
parentId: string | null
|
|
80
80
|
} | null
|
|
81
81
|
onCreated: (folder: {
|
|
82
82
|
name: string
|
|
83
83
|
icon: string
|
|
84
|
-
colorKey:
|
|
84
|
+
colorKey: LibraryFolderColorKey
|
|
85
85
|
parentId: string | null
|
|
86
86
|
}) => void
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
export function
|
|
89
|
+
export function LibraryNewFolderSheet({
|
|
90
90
|
open,
|
|
91
91
|
onOpenChange,
|
|
92
92
|
parentFolderId,
|
|
93
93
|
customizingFolder,
|
|
94
94
|
descriptionText = "Name, color, and icon update the preview. The folder is created in the location shown in the breadcrumb above the grid.",
|
|
95
95
|
onCreated,
|
|
96
|
-
}:
|
|
96
|
+
}: LibraryNewFolderSheetProps) {
|
|
97
97
|
const [draft, setDraft] = React.useState<{
|
|
98
98
|
name: string
|
|
99
|
-
colorKey:
|
|
99
|
+
colorKey: LibraryFolderColorKey
|
|
100
100
|
icon: string
|
|
101
101
|
}>({ name: "Untitled", colorKey: "brand", icon: "fa-folder" })
|
|
102
102
|
|
|
@@ -200,7 +200,7 @@ export function QuestionBankNewFolderSheet({
|
|
|
200
200
|
aria-pressed={draft.colorKey === c}
|
|
201
201
|
className={cn(
|
|
202
202
|
"size-10 rounded-xl border-2 transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
203
|
-
|
|
203
|
+
LIBRARY_FOLDER_COLOR_STYLES[c].tile,
|
|
204
204
|
draft.colorKey === c
|
|
205
205
|
? "ring-2 ring-ring"
|
|
206
206
|
: "border-transparent opacity-85 hover:opacity-100",
|
|
@@ -214,7 +214,7 @@ export function QuestionBankNewFolderSheet({
|
|
|
214
214
|
<div>
|
|
215
215
|
<p className="mb-2 text-xs font-medium text-muted-foreground">Icon</p>
|
|
216
216
|
<div className="grid max-h-48 grid-cols-5 gap-2 overflow-y-auto rounded-xl border border-border p-3">
|
|
217
|
-
{
|
|
217
|
+
{LIBRARY_FOLDER_ICON_OPTIONS.map(ic => (
|
|
218
218
|
<button
|
|
219
219
|
key={ic}
|
|
220
220
|
type="button"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* OS-style icon folder view for
|
|
4
|
+
* OS-style icon folder view for Library — hierarchy, appearance (color + icon),
|
|
5
5
|
* create (floating sheet drawer like `ExportDrawer` + preview), inline rename, move / delete, and move questions between folders.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -29,24 +29,24 @@ import { Input } from "@/components/ui/input"
|
|
|
29
29
|
import { Label } from "@/components/ui/label"
|
|
30
30
|
import { Tip } from "@/components/ui/tip"
|
|
31
31
|
import { cn } from "@/lib/utils"
|
|
32
|
-
import type {
|
|
32
|
+
import type { LibraryItem } from "@/lib/mock/library"
|
|
33
33
|
import {
|
|
34
34
|
collectFolderDescendantIds,
|
|
35
35
|
isValidFolderMove,
|
|
36
36
|
newFolderId,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
type
|
|
40
|
-
type
|
|
41
|
-
} from "@/lib/mock/
|
|
37
|
+
LIBRARY_FOLDER_COLOR_STYLES,
|
|
38
|
+
LIBRARY_FOLDER_ICON_OPTIONS,
|
|
39
|
+
type LibraryFolder,
|
|
40
|
+
type LibraryFolderColorKey,
|
|
41
|
+
} from "@/lib/mock/library-folders"
|
|
42
42
|
import {
|
|
43
43
|
ListPageViewFrame,
|
|
44
44
|
LIST_PAGE_VIEW_FRAME_MAX_WIDE,
|
|
45
45
|
} from "@/components/data-views/list-page-view-frame"
|
|
46
46
|
import { OsFolderGlyph } from "@/components/data-views/os-folder-glyph"
|
|
47
|
-
import {
|
|
47
|
+
import { LibraryNewFolderSheet } from "@/components/library-new-folder-sheet"
|
|
48
48
|
|
|
49
|
-
const COLOR_OPTIONS:
|
|
49
|
+
const COLOR_OPTIONS: LibraryFolderColorKey[] = [
|
|
50
50
|
"brand",
|
|
51
51
|
"success",
|
|
52
52
|
"warning",
|
|
@@ -57,17 +57,17 @@ const COLOR_OPTIONS: QuestionBankFolderColorKey[] = [
|
|
|
57
57
|
"chart3",
|
|
58
58
|
]
|
|
59
59
|
|
|
60
|
-
export interface
|
|
61
|
-
folders:
|
|
62
|
-
onFoldersChange: React.Dispatch<React.SetStateAction<
|
|
63
|
-
questions:
|
|
64
|
-
onQuestionsChange: React.Dispatch<React.SetStateAction<
|
|
60
|
+
export interface LibraryOsFolderViewProps {
|
|
61
|
+
folders: LibraryFolder[]
|
|
62
|
+
onFoldersChange: React.Dispatch<React.SetStateAction<LibraryFolder[]>>
|
|
63
|
+
questions: LibraryItem[]
|
|
64
|
+
onQuestionsChange: React.Dispatch<React.SetStateAction<LibraryItem[]>>
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function folderTrail(folders:
|
|
67
|
+
function folderTrail(folders: LibraryFolder[], folderId: string | null): LibraryFolder[] {
|
|
68
68
|
if (!folderId) return []
|
|
69
69
|
const byId = new Map(folders.map(f => [f.id, f]))
|
|
70
|
-
const trail:
|
|
70
|
+
const trail: LibraryFolder[] = []
|
|
71
71
|
let cur: string | null = folderId
|
|
72
72
|
while (cur) {
|
|
73
73
|
const f = byId.get(cur)
|
|
@@ -79,9 +79,9 @@ function folderTrail(folders: QuestionBankFolder[], folderId: string | null): Qu
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function folderHoverCounts(
|
|
82
|
-
folder:
|
|
83
|
-
folders:
|
|
84
|
-
questions:
|
|
82
|
+
folder: LibraryFolder,
|
|
83
|
+
folders: LibraryFolder[],
|
|
84
|
+
questions: LibraryItem[],
|
|
85
85
|
) {
|
|
86
86
|
const subfolders = folders.filter(f => f.parentId === folder.id).length
|
|
87
87
|
const questionsInFolder = questions.filter(q => q.folderId === folder.id).length
|
|
@@ -89,10 +89,10 @@ function folderHoverCounts(
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function validMoveTargets(
|
|
92
|
-
folders:
|
|
92
|
+
folders: LibraryFolder[],
|
|
93
93
|
movingId: string,
|
|
94
94
|
): Array<{ id: string | null; label: string }> {
|
|
95
|
-
const out: Array<{ id: string | null; label: string }> = [{ id: null, label: "
|
|
95
|
+
const out: Array<{ id: string | null; label: string }> = [{ id: null, label: "Library (root)" }]
|
|
96
96
|
for (const f of folders) {
|
|
97
97
|
if (f.id === movingId) continue
|
|
98
98
|
if (!isValidFolderMove(folders, movingId, f.id)) continue
|
|
@@ -101,12 +101,12 @@ function validMoveTargets(
|
|
|
101
101
|
return out
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
export function
|
|
104
|
+
export function LibraryOsFolderView({
|
|
105
105
|
folders,
|
|
106
106
|
onFoldersChange,
|
|
107
107
|
questions,
|
|
108
108
|
onQuestionsChange,
|
|
109
|
-
}:
|
|
109
|
+
}: LibraryOsFolderViewProps) {
|
|
110
110
|
const [currentId, setCurrentId] = React.useState<string | null>(null)
|
|
111
111
|
|
|
112
112
|
const childFolders = React.useMemo(
|
|
@@ -123,13 +123,13 @@ export function QuestionBankOsFolderView({
|
|
|
123
123
|
|
|
124
124
|
const [createFolderOpen, setCreateFolderOpen] = React.useState(false)
|
|
125
125
|
const [customizeFolderOpen, setCustomizeFolderOpen] = React.useState(false)
|
|
126
|
-
const [customizingFolder, setCustomizingFolder] = React.useState<
|
|
126
|
+
const [customizingFolder, setCustomizingFolder] = React.useState<LibraryFolder | null>(null)
|
|
127
127
|
|
|
128
128
|
const [renamingFolderId, setRenamingFolderId] = React.useState<string | null>(null)
|
|
129
129
|
const [renameValue, setRenameValue] = React.useState("")
|
|
130
130
|
const renameInputRef = React.useRef<HTMLInputElement>(null)
|
|
131
131
|
|
|
132
|
-
const [appearanceDialog, setAppearanceDialog] = React.useState<
|
|
132
|
+
const [appearanceDialog, setAppearanceDialog] = React.useState<LibraryFolder | null>(null)
|
|
133
133
|
const [moveFolderId, setMoveFolderId] = React.useState<string | null>(null)
|
|
134
134
|
const [deleteFolderId, setDeleteFolderId] = React.useState<string | null>(null)
|
|
135
135
|
|
|
@@ -144,7 +144,7 @@ export function QuestionBankOsFolderView({
|
|
|
144
144
|
setCreateFolderOpen(true)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
function startRename(folder:
|
|
147
|
+
function startRename(folder: LibraryFolder) {
|
|
148
148
|
setRenamingFolderId(folder.id)
|
|
149
149
|
setRenameValue(folder.name)
|
|
150
150
|
}
|
|
@@ -223,7 +223,7 @@ export function QuestionBankOsFolderView({
|
|
|
223
223
|
onClick={() => setCurrentId(null)}
|
|
224
224
|
className="font-sans text-sm text-muted-foreground hover:text-interactive-hover-foreground transition-colors tracking-normal"
|
|
225
225
|
>
|
|
226
|
-
|
|
226
|
+
Library
|
|
227
227
|
</button>
|
|
228
228
|
{trail.length > 0 && (
|
|
229
229
|
<i className="fa-light fa-chevron-right text-xs text-muted-foreground/50" aria-hidden="true" />
|
|
@@ -474,7 +474,7 @@ export function QuestionBankOsFolderView({
|
|
|
474
474
|
</p>
|
|
475
475
|
)}
|
|
476
476
|
|
|
477
|
-
<
|
|
477
|
+
<LibraryNewFolderSheet
|
|
478
478
|
open={createFolderOpen}
|
|
479
479
|
onOpenChange={setCreateFolderOpen}
|
|
480
480
|
parentFolderId={currentId}
|
|
@@ -484,7 +484,7 @@ export function QuestionBankOsFolderView({
|
|
|
484
484
|
/>
|
|
485
485
|
|
|
486
486
|
{/* Customize folder */}
|
|
487
|
-
<
|
|
487
|
+
<LibraryNewFolderSheet
|
|
488
488
|
open={customizeFolderOpen}
|
|
489
489
|
onOpenChange={setCustomizeFolderOpen}
|
|
490
490
|
parentFolderId={customizingFolder?.parentId ?? null}
|
|
@@ -531,7 +531,7 @@ export function QuestionBankOsFolderView({
|
|
|
531
531
|
aria-pressed={appearanceDialog.colorKey === c}
|
|
532
532
|
className={cn(
|
|
533
533
|
"size-9 rounded-lg border-2 transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
534
|
-
|
|
534
|
+
LIBRARY_FOLDER_COLOR_STYLES[c].tile,
|
|
535
535
|
appearanceDialog.colorKey === c ? "ring-2 ring-ring" : "border-transparent opacity-80 hover:opacity-100",
|
|
536
536
|
)}
|
|
537
537
|
onClick={() =>
|
|
@@ -548,7 +548,7 @@ export function QuestionBankOsFolderView({
|
|
|
548
548
|
<div>
|
|
549
549
|
<p className="mb-2 text-xs font-medium text-muted-foreground">Icon</p>
|
|
550
550
|
<div className="grid max-h-40 grid-cols-5 gap-2 overflow-y-auto rounded-md border border-border p-2">
|
|
551
|
-
{
|
|
551
|
+
{LIBRARY_FOLDER_ICON_OPTIONS.map(ic => (
|
|
552
552
|
<button
|
|
553
553
|
key={ic}
|
|
554
554
|
type="button"
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
} from "@/components/ui/dropdown-menu"
|
|
16
16
|
import { Tip } from "@/components/ui/tip"
|
|
17
17
|
import { COLLABORATION_HEADER_ADD_LABEL } from "@/lib/collaborator-access"
|
|
18
|
-
import {
|
|
18
|
+
import { LIBRARY_HEADER_COLLABORATORS } from "@/lib/mock/library-header-collaborators"
|
|
19
19
|
|
|
20
|
-
export interface
|
|
20
|
+
export interface LibraryPageHeaderProps {
|
|
21
21
|
/** Scoped hub title (All / My / folder name) — keep in sync with `SiteHeader`. */
|
|
22
22
|
title: string
|
|
23
23
|
questionCount: number
|
|
@@ -51,7 +51,7 @@ export interface QuestionBankPageHeaderProps {
|
|
|
51
51
|
onCustomizeFolder?: () => void
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export function
|
|
54
|
+
export function LibraryPageHeader({
|
|
55
55
|
title,
|
|
56
56
|
questionCount,
|
|
57
57
|
onNewQuestion,
|
|
@@ -61,7 +61,7 @@ export function QuestionBankPageHeader({
|
|
|
61
61
|
showTitleBlock = true,
|
|
62
62
|
variant = "default",
|
|
63
63
|
accessInfo,
|
|
64
|
-
collaborators =
|
|
64
|
+
collaborators = LIBRARY_HEADER_COLLABORATORS,
|
|
65
65
|
collaboratorDisplayLimit = 3,
|
|
66
66
|
onAddCollaborator = () => {},
|
|
67
67
|
onCollaboratorsOpen,
|
|
@@ -71,7 +71,7 @@ export function QuestionBankPageHeader({
|
|
|
71
71
|
subtitleOverride,
|
|
72
72
|
hideActions = false,
|
|
73
73
|
onCustomizeFolder,
|
|
74
|
-
}:
|
|
74
|
+
}: LibraryPageHeaderProps) {
|
|
75
75
|
const [moreOpen, setMoreOpen] = React.useState(false)
|
|
76
76
|
const countLine =
|
|
77
77
|
subtitleOverride ??
|
|
@@ -91,7 +91,7 @@ export function QuestionBankPageHeader({
|
|
|
91
91
|
showTitleBlock={showTitleBlock}
|
|
92
92
|
actions={
|
|
93
93
|
hideActions ? undefined : (
|
|
94
|
-
<div className="flex items-center gap-2" role="group" aria-label="
|
|
94
|
+
<div className="flex items-center gap-2" role="group" aria-label="Library actions">
|
|
95
95
|
{!hideNewQuestion ? (
|
|
96
96
|
<Tip side="bottom" label="Create a new question (demo)">
|
|
97
97
|
<Button type="button" size="lg" onClick={onNewQuestion}>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { SecondaryPanelHubActivator } from "@/components/templates/secondary-panel-hub-template"
|
|
4
|
+
|
|
5
|
+
/** Opens the Library secondary panel while this route is mounted. */
|
|
6
|
+
export function LibraryPanelActivator() {
|
|
7
|
+
return <SecondaryPanelHubActivator panelId="library" />
|
|
8
|
+
}
|