@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,8 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Folder details panel — OsFolderGlyph header + aggregates (`
|
|
5
|
-
* Reusable across list hubs that share `
|
|
4
|
+
* Folder details panel — OsFolderGlyph header + aggregates (`library-inspector` helpers today).
|
|
5
|
+
* Reusable across list hubs that share `LibraryFolder` / `LibraryItem` shapes or adapters.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as React from "react"
|
|
@@ -12,13 +12,13 @@ import { Button } from "@/components/ui/button"
|
|
|
12
12
|
import { Separator } from "@/components/ui/separator"
|
|
13
13
|
import { Tip } from "@/components/ui/tip"
|
|
14
14
|
import { cn } from "@/lib/utils"
|
|
15
|
-
import type {
|
|
16
|
-
import type {
|
|
15
|
+
import type { LibraryItem } from "@/lib/mock/library"
|
|
16
|
+
import type { LibraryFolder } from "@/lib/mock/library-folders"
|
|
17
17
|
import {
|
|
18
18
|
aggregateFolderQuestions,
|
|
19
19
|
BLOOM_LEVEL_ORDER,
|
|
20
20
|
questionsInFolderSubtree,
|
|
21
|
-
} from "@/lib/mock/
|
|
21
|
+
} from "@/lib/mock/library-inspector"
|
|
22
22
|
|
|
23
23
|
function DetailBreadcrumbNav({ segments }: { segments: { id: string; label: string }[] }) {
|
|
24
24
|
if (segments.length === 0) return null
|
|
@@ -70,9 +70,9 @@ function InspectorSectionTitle({ children, id }: { children: React.ReactNode; id
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export interface FolderDetailsShellProps {
|
|
73
|
-
folder:
|
|
74
|
-
folders:
|
|
75
|
-
questions:
|
|
73
|
+
folder: LibraryFolder
|
|
74
|
+
folders: LibraryFolder[]
|
|
75
|
+
questions: LibraryItem[]
|
|
76
76
|
/** Clears selection (tree inspector dismiss). */
|
|
77
77
|
onClearSelection?: () => void
|
|
78
78
|
}
|
|
@@ -89,8 +89,8 @@ export function FolderDetailsShell({
|
|
|
89
89
|
const diffSum = diffAgg.easy + diffAgg.medium + diffAgg.hard
|
|
90
90
|
const maxBloomCount = Math.max(1, ...BLOOM_LEVEL_ORDER.map(level => bloom[level] ?? 0))
|
|
91
91
|
|
|
92
|
-
const breadcrumbs:
|
|
93
|
-
let cur:
|
|
92
|
+
const breadcrumbs: LibraryFolder[] = []
|
|
93
|
+
let cur: LibraryFolder | undefined = folder
|
|
94
94
|
while (cur) {
|
|
95
95
|
breadcrumbs.unshift(cur)
|
|
96
96
|
cur = folders.find(f => f.id === cur?.parentId)
|
|
@@ -114,7 +114,7 @@ export function FolderDetailsShell({
|
|
|
114
114
|
<div className="min-w-0 flex-1">
|
|
115
115
|
<h2 className="text-base font-semibold leading-tight tracking-tight text-foreground">
|
|
116
116
|
<span className="truncate">{folder.name}</span>
|
|
117
|
-
<span className="font-normal text-muted-foreground"> ·
|
|
117
|
+
<span className="font-normal text-muted-foreground"> · Library</span>
|
|
118
118
|
</h2>
|
|
119
119
|
<p className="mt-1 text-sm font-semibold tabular-nums text-foreground">
|
|
120
120
|
{totalQuestions} question{totalQuestions !== 1 ? "s" : ""}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* **Tree view** — `ListPageTreePanelShell` + outline tree + read-only details (`FolderDetailsShell`).
|
|
5
|
-
* Hub wiring (folders, mock sheet) stays in the caller; this module hosts
|
|
5
|
+
* Hub wiring (folders, mock sheet) stays in the caller; this module hosts library demo wiring only.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as React from "react"
|
|
@@ -39,12 +39,12 @@ import {
|
|
|
39
39
|
import { ListPageTreePanelShell } from "@/components/data-views/list-page-tree-panel-shell"
|
|
40
40
|
import { ListPageSplitDetailsPlaceholder } from "@/components/data-views/list-page-split-details-placeholder"
|
|
41
41
|
import { ListPageTreeColumnHeader } from "@/components/data-views/list-page-tree-column-header"
|
|
42
|
-
import {
|
|
42
|
+
import { LibraryNewFolderSheet } from "@/components/library-new-folder-sheet"
|
|
43
43
|
import type {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
} from "@/lib/mock/
|
|
47
|
-
import type {
|
|
44
|
+
LibraryItem,
|
|
45
|
+
LibraryLevel,
|
|
46
|
+
} from "@/lib/mock/library"
|
|
47
|
+
import type { LibraryFolder, LibraryFolderColorKey } from "@/lib/mock/library-folders"
|
|
48
48
|
import { formatDateUS } from "@/lib/date-filter"
|
|
49
49
|
import {
|
|
50
50
|
deriveBloomLevel,
|
|
@@ -52,14 +52,14 @@ import {
|
|
|
52
52
|
deriveQuestionItemCode,
|
|
53
53
|
deriveTags,
|
|
54
54
|
QUESTION_TYPE_ABBREV,
|
|
55
|
-
} from "@/lib/mock/
|
|
55
|
+
} from "@/lib/mock/library-inspector"
|
|
56
56
|
import { FolderDetailsShell } from "@/components/folder-details-shell"
|
|
57
57
|
import { initialsFromDisplayName } from "@/lib/initials-from-name"
|
|
58
58
|
|
|
59
|
-
const DIFFICULTY_LABEL: Record<
|
|
60
|
-
easy: "
|
|
61
|
-
medium: "
|
|
62
|
-
hard: "
|
|
59
|
+
const DIFFICULTY_LABEL: Record<LibraryLevel, string> = {
|
|
60
|
+
easy: "Low",
|
|
61
|
+
medium: "Normal",
|
|
62
|
+
hard: "High",
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// ============================================================================
|
|
@@ -67,9 +67,9 @@ const DIFFICULTY_LABEL: Record<QuestionBankDifficulty, string> = {
|
|
|
67
67
|
// ============================================================================
|
|
68
68
|
|
|
69
69
|
interface TreeItemProps {
|
|
70
|
-
folder:
|
|
71
|
-
folders:
|
|
72
|
-
questions:
|
|
70
|
+
folder: LibraryFolder
|
|
71
|
+
folders: LibraryFolder[]
|
|
72
|
+
questions: LibraryItem[]
|
|
73
73
|
selectedItemId: string | null
|
|
74
74
|
onSelectItem: (itemId: string) => void
|
|
75
75
|
}
|
|
@@ -226,8 +226,8 @@ function InspectorSectionTitle({ children, id }: { children: React.ReactNode; id
|
|
|
226
226
|
|
|
227
227
|
interface DetailsPanelProps {
|
|
228
228
|
selectedItemId: string | null
|
|
229
|
-
folders:
|
|
230
|
-
questions:
|
|
229
|
+
folders: LibraryFolder[]
|
|
230
|
+
questions: LibraryItem[]
|
|
231
231
|
/** Clears tree selection (header dismiss). */
|
|
232
232
|
onClearSelection?: () => void
|
|
233
233
|
}
|
|
@@ -534,14 +534,14 @@ function DetailsPanel({ selectedItemId, folders, questions, onClearSelection }:
|
|
|
534
534
|
}
|
|
535
535
|
|
|
536
536
|
// ============================================================================
|
|
537
|
-
// HubTreePanelView — tree + details wiring (
|
|
537
|
+
// HubTreePanelView — tree + details wiring (library demo; reusable shell above)
|
|
538
538
|
// ============================================================================
|
|
539
539
|
|
|
540
540
|
export interface HubTreePanelViewProps {
|
|
541
|
-
items:
|
|
542
|
-
folders:
|
|
543
|
-
onItemsChange: (items:
|
|
544
|
-
onFoldersChange: (folders:
|
|
541
|
+
items: LibraryItem[]
|
|
542
|
+
folders: LibraryFolder[]
|
|
543
|
+
onItemsChange: (items: LibraryItem[]) => void
|
|
544
|
+
onFoldersChange: (folders: LibraryFolder[]) => void
|
|
545
545
|
}
|
|
546
546
|
|
|
547
547
|
export function HubTreePanelView({
|
|
@@ -552,7 +552,7 @@ export function HubTreePanelView({
|
|
|
552
552
|
const [selectedItemId, setSelectedItemId] = React.useState<string | null>(null)
|
|
553
553
|
const [newFolderOpen, setNewFolderOpen] = React.useState(false)
|
|
554
554
|
const [newFolderParentId, setNewFolderParentId] = React.useState<string | null>(null)
|
|
555
|
-
const [customizingFolder, setCustomizingFolder] = React.useState<
|
|
555
|
+
const [customizingFolder, setCustomizingFolder] = React.useState<LibraryFolder | null>(null)
|
|
556
556
|
|
|
557
557
|
const rootFolders = React.useMemo(
|
|
558
558
|
() => folders.filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name)),
|
|
@@ -560,7 +560,7 @@ export function HubTreePanelView({
|
|
|
560
560
|
)
|
|
561
561
|
|
|
562
562
|
const handleNewFolderCreated = React.useCallback(
|
|
563
|
-
(newFolder: { name: string; icon: string; colorKey:
|
|
563
|
+
(newFolder: { name: string; icon: string; colorKey: LibraryFolderColorKey; parentId: string | null }) => {
|
|
564
564
|
if (customizingFolder) {
|
|
565
565
|
onFoldersChange(
|
|
566
566
|
folders.map(f =>
|
|
@@ -652,7 +652,7 @@ export function HubTreePanelView({
|
|
|
652
652
|
}
|
|
653
653
|
/>
|
|
654
654
|
|
|
655
|
-
<
|
|
655
|
+
<LibraryNewFolderSheet
|
|
656
656
|
open={newFolderOpen}
|
|
657
657
|
onOpenChange={setNewFolderOpen}
|
|
658
658
|
parentFolderId={customizingFolder?.parentId ?? newFolderParentId}
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { initialsFromDisplayName } from "@/lib/initials-from-name"
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "@/lib/mock/
|
|
6
|
+
LibraryLevel,
|
|
7
|
+
LibraryItem,
|
|
8
|
+
LibraryItemType,
|
|
9
|
+
} from "@/lib/mock/library"
|
|
10
10
|
import {
|
|
11
11
|
LIST_HUB_STATUS_TINT_DANGER,
|
|
12
12
|
LIST_HUB_STATUS_TINT_INFO,
|
|
@@ -28,81 +28,81 @@ import {
|
|
|
28
28
|
type ListPageBoardColumnDef,
|
|
29
29
|
} from "@/components/data-views/list-page-board-template"
|
|
30
30
|
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} from "@/components/
|
|
31
|
+
LibraryFavoriteButton,
|
|
32
|
+
LIBRARY_FAVORITE_HOVER_GROUP,
|
|
33
|
+
} from "@/components/library-favorite-button"
|
|
34
34
|
import { cn } from "@/lib/utils"
|
|
35
35
|
|
|
36
36
|
const NEUTRAL_COUNT_BADGE = "bg-muted/90 text-foreground"
|
|
37
37
|
|
|
38
|
-
const TYPE_LABEL: Record<
|
|
39
|
-
multiple_choice: "
|
|
40
|
-
true_false: "
|
|
41
|
-
short_answer: "
|
|
38
|
+
const TYPE_LABEL: Record<LibraryItemType, string> = {
|
|
39
|
+
multiple_choice: "Type 1",
|
|
40
|
+
true_false: "Type 2",
|
|
41
|
+
short_answer: "Type 3",
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const DIFF_LABEL: Record<
|
|
45
|
-
easy: "
|
|
46
|
-
medium: "
|
|
47
|
-
hard: "
|
|
44
|
+
const DIFF_LABEL: Record<LibraryLevel, string> = {
|
|
45
|
+
easy: "Low",
|
|
46
|
+
medium: "Normal",
|
|
47
|
+
hard: "High",
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const DIFF_BADGE: Record<
|
|
50
|
+
const DIFF_BADGE: Record<LibraryLevel, string> = {
|
|
51
51
|
easy: LIST_HUB_STATUS_TINT_SUCCESS,
|
|
52
52
|
medium: LIST_HUB_STATUS_TINT_WARNING,
|
|
53
53
|
hard: LIST_HUB_STATUS_TINT_DANGER,
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
const TYPE_BADGE: Record<
|
|
56
|
+
const TYPE_BADGE: Record<LibraryItemType, string> = {
|
|
57
57
|
multiple_choice: LIST_HUB_STATUS_TINT_NEUTRAL,
|
|
58
58
|
true_false: LIST_HUB_STATUS_TINT_INFO,
|
|
59
59
|
short_answer: LIST_HUB_STATUS_TINT_WARNING,
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const DIFF_ORDER:
|
|
63
|
-
const TYPE_ORDER:
|
|
62
|
+
const DIFF_ORDER: LibraryLevel[] = ["easy", "medium", "hard"]
|
|
63
|
+
const TYPE_ORDER: LibraryItemType[] = ["multiple_choice", "true_false", "short_answer"]
|
|
64
64
|
|
|
65
|
-
function topicBoardColumns(rows:
|
|
66
|
-
columns: ListPageBoardColumnDef<
|
|
65
|
+
function topicBoardColumns(rows: LibraryItem[]): {
|
|
66
|
+
columns: ListPageBoardColumnDef<LibraryItem>[]
|
|
67
67
|
badgeMap: Record<string, string>
|
|
68
68
|
} {
|
|
69
69
|
const topics = [...new Set(rows.map(r => r.topic))].sort((a, b) => a.localeCompare(b))
|
|
70
|
-
const columns: ListPageBoardColumnDef<
|
|
70
|
+
const columns: ListPageBoardColumnDef<LibraryItem>[] = topics.map(topic => ({
|
|
71
71
|
id: `topic:${topic}`,
|
|
72
72
|
label: topic,
|
|
73
|
-
filter: (r:
|
|
73
|
+
filter: (r: LibraryItem) => r.topic === topic,
|
|
74
74
|
}))
|
|
75
75
|
const badgeMap = Object.fromEntries(topics.map(t => [`topic:${t}`, NEUTRAL_COUNT_BADGE]))
|
|
76
76
|
return { columns, badgeMap }
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function difficultyBoardColumns(): {
|
|
80
|
-
columns: ListPageBoardColumnDef<
|
|
80
|
+
columns: ListPageBoardColumnDef<LibraryItem>[]
|
|
81
81
|
badgeMap: Record<string, string>
|
|
82
82
|
} {
|
|
83
|
-
const columns: ListPageBoardColumnDef<
|
|
83
|
+
const columns: ListPageBoardColumnDef<LibraryItem>[] = DIFF_ORDER.map(d => ({
|
|
84
84
|
id: d,
|
|
85
85
|
label: DIFF_LABEL[d],
|
|
86
|
-
filter: (r:
|
|
86
|
+
filter: (r: LibraryItem) => r.difficulty === d,
|
|
87
87
|
}))
|
|
88
88
|
const badgeMap = Object.fromEntries(DIFF_ORDER.map(d => [d, DIFF_BADGE[d]]))
|
|
89
89
|
return { columns, badgeMap }
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function typeBoardColumns(): {
|
|
93
|
-
columns: ListPageBoardColumnDef<
|
|
93
|
+
columns: ListPageBoardColumnDef<LibraryItem>[]
|
|
94
94
|
badgeMap: Record<string, string>
|
|
95
95
|
} {
|
|
96
|
-
const columns: ListPageBoardColumnDef<
|
|
96
|
+
const columns: ListPageBoardColumnDef<LibraryItem>[] = TYPE_ORDER.map(t => ({
|
|
97
97
|
id: t,
|
|
98
98
|
label: TYPE_LABEL[t],
|
|
99
|
-
filter: (r:
|
|
99
|
+
filter: (r: LibraryItem) => r.type === t,
|
|
100
100
|
}))
|
|
101
101
|
const badgeMap = Object.fromEntries(TYPE_ORDER.map(t => [t, TYPE_BADGE[t]]))
|
|
102
102
|
return { columns, badgeMap }
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
function
|
|
105
|
+
function useLibraryBoardModel(rows: LibraryItem[], groupByColumnKey: string) {
|
|
106
106
|
return React.useMemo(() => {
|
|
107
107
|
if (groupByColumnKey === "topic") {
|
|
108
108
|
const { columns, badgeMap } = topicBoardColumns(rows)
|
|
@@ -121,19 +121,19 @@ function useQuestionBankBoardModel(rows: QuestionBankItem[], groupByColumnKey: s
|
|
|
121
121
|
}, [rows, groupByColumnKey])
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function
|
|
124
|
+
function LibraryBoardCard({
|
|
125
125
|
row,
|
|
126
126
|
onToggleFavorite,
|
|
127
127
|
onRowActivate,
|
|
128
128
|
}: {
|
|
129
|
-
row:
|
|
130
|
-
onToggleFavorite: (row:
|
|
131
|
-
onRowActivate?: (row:
|
|
129
|
+
row: LibraryItem
|
|
130
|
+
onToggleFavorite: (row: LibraryItem) => void
|
|
131
|
+
onRowActivate?: (row: LibraryItem) => void
|
|
132
132
|
}) {
|
|
133
133
|
const initials = initialsFromDisplayName(row.author)
|
|
134
134
|
return (
|
|
135
135
|
<ListPageBoardCard
|
|
136
|
-
className={cn(
|
|
136
|
+
className={cn(LIBRARY_FAVORITE_HOVER_GROUP, "w-full")}
|
|
137
137
|
onClick={onRowActivate ? () => onRowActivate(row) : undefined}
|
|
138
138
|
>
|
|
139
139
|
<ListPageBoardCardHeader>
|
|
@@ -148,7 +148,7 @@ function QuestionBankBoardCard({
|
|
|
148
148
|
)}
|
|
149
149
|
trailing={(
|
|
150
150
|
<div className="flex shrink-0 items-start gap-1">
|
|
151
|
-
<
|
|
151
|
+
<LibraryFavoriteButton row={row} onToggleFavorite={onToggleFavorite} />
|
|
152
152
|
<ListPageBoardCardAvatar initials={initials} />
|
|
153
153
|
</div>
|
|
154
154
|
)}
|
|
@@ -166,26 +166,26 @@ function QuestionBankBoardCard({
|
|
|
166
166
|
)
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
export const
|
|
169
|
+
export const LIBRARY_BOARD_GROUP_OPTIONS = [
|
|
170
170
|
{ key: "topic", label: "Topic" },
|
|
171
171
|
{ key: "difficulty", label: "Difficulty" },
|
|
172
172
|
{ key: "type", label: "Type" },
|
|
173
173
|
] as const
|
|
174
174
|
|
|
175
|
-
export function
|
|
175
|
+
export function LibraryBoardView({
|
|
176
176
|
rows,
|
|
177
177
|
groupByColumnKey,
|
|
178
178
|
onToggleFavorite,
|
|
179
179
|
onRowActivate,
|
|
180
180
|
}: {
|
|
181
|
-
rows:
|
|
181
|
+
rows: LibraryItem[]
|
|
182
182
|
groupByColumnKey: string
|
|
183
|
-
onToggleFavorite: (row:
|
|
184
|
-
onRowActivate?: (row:
|
|
183
|
+
onToggleFavorite: (row: LibraryItem) => void
|
|
184
|
+
onRowActivate?: (row: LibraryItem) => void
|
|
185
185
|
}) {
|
|
186
|
-
const allowed =
|
|
186
|
+
const allowed = LIBRARY_BOARD_GROUP_OPTIONS.some(o => o.key === groupByColumnKey)
|
|
187
187
|
const key = allowed ? groupByColumnKey : "topic"
|
|
188
|
-
const { columns, badgeMap } =
|
|
188
|
+
const { columns, badgeMap } = useLibraryBoardModel(rows, key)
|
|
189
189
|
|
|
190
190
|
return (
|
|
191
191
|
<ListPageBoardTemplate
|
|
@@ -195,7 +195,7 @@ export function QuestionBankBoardView({
|
|
|
195
195
|
columnCountBadgeClassName={badgeMap}
|
|
196
196
|
emptyColumnLabel="No questions"
|
|
197
197
|
renderCard={row => (
|
|
198
|
-
<
|
|
198
|
+
<LibraryBoardCard row={row} onToggleFavorite={onToggleFavorite} onRowActivate={onRowActivate} />
|
|
199
199
|
)}
|
|
200
200
|
/>
|
|
201
201
|
)
|