@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,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Library — thin wrapper around the centralized `<HubTable>`. Owns column defs,
|
|
5
5
|
* folder/panel/tree-panel custom views, the new-folder + customize-folder sheet, and
|
|
6
6
|
* forwards URL search via `HubTable.syncedSearchFromUrl`.
|
|
7
7
|
*
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
type HubTableRenderers,
|
|
22
22
|
type HubTableRendererArgs,
|
|
23
23
|
} from "@/components/data-views"
|
|
24
|
-
import {
|
|
24
|
+
import { LIBRARY_SUPPORTED_VIEWS } from "@/lib/library-supported-views"
|
|
25
25
|
import { Button } from "@/components/ui/button"
|
|
26
26
|
import {
|
|
27
27
|
DropdownMenu,
|
|
@@ -48,14 +48,14 @@ import {
|
|
|
48
48
|
LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS,
|
|
49
49
|
} from "@/components/data-views/list-page-split-hub-tokens"
|
|
50
50
|
import { ListPageTreeColumnHeader } from "@/components/data-views/list-page-tree-column-header"
|
|
51
|
-
import {
|
|
51
|
+
import { LibraryBoardView, LIBRARY_BOARD_GROUP_OPTIONS } from "@/components/library-board-view"
|
|
52
52
|
import { ListPageBoardCard } from "@/components/data-views/list-page-board-card"
|
|
53
53
|
import {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} from "@/components/
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
54
|
+
LibraryFavoriteButton,
|
|
55
|
+
LIBRARY_FAVORITE_HOVER_GROUP,
|
|
56
|
+
} from "@/components/library-favorite-button"
|
|
57
|
+
import { LibraryOsFolderView } from "@/components/library-os-folder-view"
|
|
58
|
+
import { LibraryNewFolderSheet } from "@/components/library-new-folder-sheet"
|
|
59
59
|
import { FolderDetailsShell } from "@/components/folder-details-shell"
|
|
60
60
|
import { HubTreePanelView } from "@/components/hub-tree-panel-view"
|
|
61
61
|
import { AvatarInitials } from "@/components/ui/avatar"
|
|
@@ -63,29 +63,29 @@ import { cn } from "@/lib/utils"
|
|
|
63
63
|
import { formatDateUS } from "@/lib/date-filter"
|
|
64
64
|
import { initialsFromDisplayName } from "@/lib/initials-from-name"
|
|
65
65
|
import {
|
|
66
|
-
|
|
67
|
-
type
|
|
68
|
-
type
|
|
69
|
-
type
|
|
70
|
-
} from "@/lib/mock/
|
|
66
|
+
newLibraryQuestionId,
|
|
67
|
+
type LibraryLevel,
|
|
68
|
+
type LibraryItem,
|
|
69
|
+
type LibraryItemType,
|
|
70
|
+
} from "@/lib/mock/library"
|
|
71
71
|
import {
|
|
72
|
-
type
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} from "@/lib/mock/
|
|
72
|
+
type LibraryFolder,
|
|
73
|
+
LIBRARY_FOLDER_COLOR_STYLES,
|
|
74
|
+
LIBRARY_FOLDER_ICON_COLORS,
|
|
75
|
+
} from "@/lib/mock/library-folders"
|
|
76
76
|
import {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
type
|
|
80
|
-
type
|
|
81
|
-
} from "@/lib/
|
|
77
|
+
toggleLibraryItemFavorite,
|
|
78
|
+
applyLibraryHubDisplayFilters,
|
|
79
|
+
type LibraryLandingFilterState,
|
|
80
|
+
type LibraryNavState,
|
|
81
|
+
} from "@/lib/library-nav"
|
|
82
82
|
|
|
83
83
|
// ─── Dynamic dashboard charts section ────────────────────────────────────────
|
|
84
84
|
|
|
85
|
-
const
|
|
85
|
+
const LibraryDashboardChartsSection = dynamic(
|
|
86
86
|
() =>
|
|
87
|
-
import("@/components/
|
|
88
|
-
default: mod.
|
|
87
|
+
import("@/components/library-dashboard-charts").then(mod => ({
|
|
88
|
+
default: mod.LibraryDashboardChartsSection,
|
|
89
89
|
})),
|
|
90
90
|
{
|
|
91
91
|
ssr: false,
|
|
@@ -114,46 +114,46 @@ const QuestionBankDashboardChartsSection = dynamic(
|
|
|
114
114
|
|
|
115
115
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
116
116
|
|
|
117
|
-
const TYPE_LABEL: Record<
|
|
118
|
-
multiple_choice: "
|
|
119
|
-
true_false: "
|
|
120
|
-
short_answer: "
|
|
117
|
+
const TYPE_LABEL: Record<LibraryItemType, string> = {
|
|
118
|
+
multiple_choice: "Type 1",
|
|
119
|
+
true_false: "Type 2",
|
|
120
|
+
short_answer: "Type 3",
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
const DIFFICULTY_LABEL: Record<
|
|
124
|
-
easy: "
|
|
125
|
-
medium: "
|
|
126
|
-
hard: "
|
|
123
|
+
const DIFFICULTY_LABEL: Record<LibraryLevel, string> = {
|
|
124
|
+
easy: "Low",
|
|
125
|
+
medium: "Normal",
|
|
126
|
+
hard: "High",
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
const TYPE_FILTER_OPTS = (Object.keys(TYPE_LABEL) as
|
|
129
|
+
const TYPE_FILTER_OPTS = (Object.keys(TYPE_LABEL) as LibraryItemType[]).map(k => ({
|
|
130
130
|
value: k,
|
|
131
131
|
label: TYPE_LABEL[k],
|
|
132
132
|
}))
|
|
133
133
|
|
|
134
|
-
const DIFFICULTY_FILTER_OPTS = (Object.keys(DIFFICULTY_LABEL) as
|
|
134
|
+
const DIFFICULTY_FILTER_OPTS = (Object.keys(DIFFICULTY_LABEL) as LibraryLevel[]).map(k => ({
|
|
135
135
|
value: k,
|
|
136
136
|
label: DIFFICULTY_LABEL[k],
|
|
137
137
|
}))
|
|
138
138
|
|
|
139
|
-
function
|
|
139
|
+
function newLibraryItemId() {
|
|
140
140
|
return `q-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
function defaultFolderIdForColumnParent(parentId: string | null, folders:
|
|
143
|
+
function defaultFolderIdForColumnParent(parentId: string | null, folders: LibraryFolder[]): string | null {
|
|
144
144
|
if (parentId !== null) return parentId
|
|
145
145
|
const roots = [...folders].filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name))
|
|
146
146
|
return roots[0]?.id ?? null
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
function uniqueTopics(items:
|
|
149
|
+
function uniqueTopics(items: LibraryItem[]) {
|
|
150
150
|
return [...new Set(items.map(i => i.topic))].sort().map(t => ({ value: t, label: t }))
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function
|
|
154
|
-
items:
|
|
155
|
-
opts: { onToggleFavorite: (row:
|
|
156
|
-
): ColumnDef<
|
|
153
|
+
function buildLibraryColumns(
|
|
154
|
+
items: LibraryItem[],
|
|
155
|
+
opts: { onToggleFavorite: (row: LibraryItem) => void },
|
|
156
|
+
): ColumnDef<LibraryItem>[] {
|
|
157
157
|
const topicOpts = uniqueTopics(items)
|
|
158
158
|
const { onToggleFavorite } = opts
|
|
159
159
|
return [
|
|
@@ -168,12 +168,12 @@ function buildQuestionBankColumns(
|
|
|
168
168
|
defaultPin: "left",
|
|
169
169
|
filter: { type: "text", icon: "fa-file-lines", operators: ["contains", "not_contains"] },
|
|
170
170
|
cell: row => (
|
|
171
|
-
<div className={cn(
|
|
171
|
+
<div className={cn(LIBRARY_FAVORITE_HOVER_GROUP, "flex min-w-0 items-start gap-2")}>
|
|
172
172
|
<div className="flex min-w-0 flex-1 flex-col gap-0.5 pe-1">
|
|
173
173
|
<span className="line-clamp-2 text-sm font-medium text-foreground">{row.stem}</span>
|
|
174
174
|
<span className="font-mono text-xs text-muted-foreground">{row.questionId}</span>
|
|
175
175
|
</div>
|
|
176
|
-
<
|
|
176
|
+
<LibraryFavoriteButton row={row} onToggleFavorite={onToggleFavorite} />
|
|
177
177
|
</div>
|
|
178
178
|
),
|
|
179
179
|
},
|
|
@@ -283,21 +283,21 @@ function buildQuestionBankColumns(
|
|
|
283
283
|
// ─── Folder columns panel (custom multi-column miller view) ─────────────────
|
|
284
284
|
|
|
285
285
|
interface HubFolderColumnsPanelProps {
|
|
286
|
-
folders:
|
|
287
|
-
rows:
|
|
288
|
-
panelRenderDetail: (row:
|
|
286
|
+
folders: LibraryFolder[]
|
|
287
|
+
rows: LibraryItem[]
|
|
288
|
+
panelRenderDetail: (row: LibraryItem) => React.ReactNode
|
|
289
289
|
onAddFolder: (parentId: string | null) => void
|
|
290
290
|
onAddQuestion: (parentId: string | null) => void
|
|
291
|
-
onCustomizeFolder?: (folder:
|
|
291
|
+
onCustomizeFolder?: (folder: LibraryFolder) => void
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
type HierarchyItem =
|
|
294
|
+
type HierarchyItem = LibraryFolder | LibraryItem
|
|
295
295
|
|
|
296
|
-
function isFolder(item: HierarchyItem): item is
|
|
296
|
+
function isFolder(item: HierarchyItem): item is LibraryFolder {
|
|
297
297
|
return "parentId" in item
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
function isQuestion(item: HierarchyItem): item is
|
|
300
|
+
function isQuestion(item: HierarchyItem): item is LibraryItem {
|
|
301
301
|
return "stem" in item
|
|
302
302
|
}
|
|
303
303
|
|
|
@@ -332,7 +332,7 @@ function HubFolderColumnsPanel({
|
|
|
332
332
|
if (isFirstRenderRef.current && selectedPath.length > 0) {
|
|
333
333
|
const lastItem = selectedPath[selectedPath.length - 1]
|
|
334
334
|
if (isFolder(lastItem)) {
|
|
335
|
-
const folder = lastItem as
|
|
335
|
+
const folder = lastItem as LibraryFolder
|
|
336
336
|
const subfolders = folders.filter(f => f.parentId === folder.id).sort((a, b) => a.name.localeCompare(b.name))
|
|
337
337
|
const questionsInFolder = rows.filter(r => r.folderId === folder.id)
|
|
338
338
|
const items: HierarchyItem[] = [...subfolders, ...questionsInFolder]
|
|
@@ -362,8 +362,8 @@ function HubFolderColumnsPanel({
|
|
|
362
362
|
}, [selectedPath, rootFolders, folders, rows])
|
|
363
363
|
|
|
364
364
|
const selectedLeaf = selectedPath.length > 0 ? selectedPath.at(-1)! : null
|
|
365
|
-
const selectedQuestion = selectedLeaf && isQuestion(selectedLeaf) ? (selectedLeaf as
|
|
366
|
-
const selectedFolderLeaf = selectedLeaf && isFolder(selectedLeaf) ? (selectedLeaf as
|
|
365
|
+
const selectedQuestion = selectedLeaf && isQuestion(selectedLeaf) ? (selectedLeaf as LibraryItem) : null
|
|
366
|
+
const selectedFolderLeaf = selectedLeaf && isFolder(selectedLeaf) ? (selectedLeaf as LibraryFolder) : null
|
|
367
367
|
|
|
368
368
|
return (
|
|
369
369
|
<ResizablePanelGroup direction="horizontal" className="flex h-full min-h-0 w-full flex-1 overflow-hidden">
|
|
@@ -381,7 +381,7 @@ function HubFolderColumnsPanel({
|
|
|
381
381
|
depth === 0
|
|
382
382
|
? "Categories"
|
|
383
383
|
: selectedPath[depth - 1] && isFolder(selectedPath[depth - 1])
|
|
384
|
-
? (selectedPath[depth - 1] as
|
|
384
|
+
? (selectedPath[depth - 1] as LibraryFolder).name
|
|
385
385
|
: "Items"
|
|
386
386
|
}
|
|
387
387
|
trailing={
|
|
@@ -442,7 +442,7 @@ function HubFolderColumnsPanel({
|
|
|
442
442
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
443
443
|
isSelected ? "bg-accent text-accent-foreground" : "text-foreground",
|
|
444
444
|
isFolder_ && !isSelected && folder?.colorKey && depth > 0
|
|
445
|
-
?
|
|
445
|
+
? LIBRARY_FOLDER_COLOR_STYLES[folder.colorKey]?.tile
|
|
446
446
|
: "",
|
|
447
447
|
)}
|
|
448
448
|
aria-selected={isSelected}
|
|
@@ -453,7 +453,7 @@ function HubFolderColumnsPanel({
|
|
|
453
453
|
className={cn(
|
|
454
454
|
"fa-folder text-sm shrink-0",
|
|
455
455
|
isSelected ? "fa-solid" : "fa-light",
|
|
456
|
-
folder?.colorKey &&
|
|
456
|
+
folder?.colorKey && LIBRARY_FOLDER_ICON_COLORS[folder.colorKey],
|
|
457
457
|
)}
|
|
458
458
|
aria-hidden="true"
|
|
459
459
|
/>
|
|
@@ -472,7 +472,7 @@ function HubFolderColumnsPanel({
|
|
|
472
472
|
{isFolder_
|
|
473
473
|
? itemCount
|
|
474
474
|
: question?.type === "multiple_choice"
|
|
475
|
-
? "
|
|
475
|
+
? "T1"
|
|
476
476
|
: question?.difficulty?.charAt(0).toUpperCase()}
|
|
477
477
|
</span>
|
|
478
478
|
</button>
|
|
@@ -529,7 +529,7 @@ function HubFolderColumnsPanel({
|
|
|
529
529
|
|
|
530
530
|
// ─── Detail renderer reused by panel + tree-panel ───────────────────────────
|
|
531
531
|
|
|
532
|
-
function
|
|
532
|
+
function libraryPanelDetail(row: LibraryItem) {
|
|
533
533
|
return (
|
|
534
534
|
<div className="flex min-w-0 flex-col gap-4">
|
|
535
535
|
<div>
|
|
@@ -579,27 +579,27 @@ function questionBankPanelDetail(row: QuestionBankItem) {
|
|
|
579
579
|
|
|
580
580
|
// ─── Public component ───────────────────────────────────────────────────────
|
|
581
581
|
|
|
582
|
-
export type
|
|
582
|
+
export type LibraryTableHandle = HubTableHandle
|
|
583
583
|
|
|
584
|
-
export interface
|
|
585
|
-
items:
|
|
584
|
+
export interface LibraryTableProps {
|
|
585
|
+
items: LibraryItem[]
|
|
586
586
|
/** When set, table / board / tree rows are limited to this nav scope (secondary sidebar). */
|
|
587
|
-
navState?:
|
|
587
|
+
navState?: LibraryNavState
|
|
588
588
|
/** URL toolbar search binding (`?q=`) — omit on search landing so hub `q` does not pre-fill the grid search. */
|
|
589
589
|
urlListSearch?: string
|
|
590
590
|
/** When true, dedicated search shell: hub landing row filters; table toolbar search stays independent of URL `q`. */
|
|
591
591
|
searchLanding?: boolean
|
|
592
592
|
/** Applied with nav filters before `useTableState` when {@link searchLanding} is true. */
|
|
593
|
-
landingFilters?:
|
|
593
|
+
landingFilters?: LibraryLandingFilterState | null
|
|
594
594
|
view?: DataListViewType
|
|
595
595
|
onViewChange?: (v: DataListViewType) => void
|
|
596
|
-
folders:
|
|
597
|
-
onFoldersChange: React.Dispatch<React.SetStateAction<
|
|
598
|
-
onItemsChange: React.Dispatch<React.SetStateAction<
|
|
596
|
+
folders: LibraryFolder[]
|
|
597
|
+
onFoldersChange: React.Dispatch<React.SetStateAction<LibraryFolder[]>>
|
|
598
|
+
onItemsChange: React.Dispatch<React.SetStateAction<LibraryItem[]>>
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
-
export const
|
|
602
|
-
function
|
|
601
|
+
export const LibraryTable = React.forwardRef<LibraryTableHandle, LibraryTableProps>(
|
|
602
|
+
function LibraryTable(
|
|
603
603
|
{
|
|
604
604
|
items,
|
|
605
605
|
navState,
|
|
@@ -617,25 +617,25 @@ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, Quest
|
|
|
617
617
|
const tableSourceItems = React.useMemo(() => {
|
|
618
618
|
const nav = navState ?? { scope: "all" as const, folderId: null }
|
|
619
619
|
const landing = searchLanding ? (landingFilters ?? null) : null
|
|
620
|
-
return
|
|
620
|
+
return applyLibraryHubDisplayFilters(items, folders, nav, landing)
|
|
621
621
|
}, [items, folders, navState, searchLanding, landingFilters])
|
|
622
622
|
|
|
623
623
|
const toggleFavorite = React.useCallback(
|
|
624
|
-
(row:
|
|
625
|
-
onItemsChange(prev => prev.map(r => (r.id === row.id ?
|
|
624
|
+
(row: LibraryItem) => {
|
|
625
|
+
onItemsChange(prev => prev.map(r => (r.id === row.id ? toggleLibraryItemFavorite(r) : r)))
|
|
626
626
|
},
|
|
627
627
|
[onItemsChange],
|
|
628
628
|
)
|
|
629
629
|
|
|
630
630
|
const columns = React.useMemo(
|
|
631
|
-
() =>
|
|
631
|
+
() => buildLibraryColumns(tableSourceItems, { onToggleFavorite: toggleFavorite }),
|
|
632
632
|
[tableSourceItems, toggleFavorite],
|
|
633
633
|
)
|
|
634
634
|
|
|
635
635
|
// ─ New-folder / customize-folder modal state (shared by panel + tree-panel) ────
|
|
636
636
|
const [newFolderOpen, setNewFolderOpen] = React.useState(false)
|
|
637
637
|
const [newFolderParentId, setNewFolderParentId] = React.useState<string | null>(null)
|
|
638
|
-
const [customizingFolder, setCustomizingFolder] = React.useState<
|
|
638
|
+
const [customizingFolder, setCustomizingFolder] = React.useState<LibraryFolder | null>(null)
|
|
639
639
|
|
|
640
640
|
const openNewFolderForColumn = React.useCallback((parentId: string | null) => {
|
|
641
641
|
setNewFolderParentId(parentId)
|
|
@@ -643,7 +643,7 @@ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, Quest
|
|
|
643
643
|
setNewFolderOpen(true)
|
|
644
644
|
}, [])
|
|
645
645
|
|
|
646
|
-
const openCustomizeFolderSheet = React.useCallback((folder:
|
|
646
|
+
const openCustomizeFolderSheet = React.useCallback((folder: LibraryFolder) => {
|
|
647
647
|
setCustomizingFolder(folder)
|
|
648
648
|
setNewFolderOpen(true)
|
|
649
649
|
}, [])
|
|
@@ -659,8 +659,8 @@ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, Quest
|
|
|
659
659
|
onItemsChange(prev => [
|
|
660
660
|
...prev,
|
|
661
661
|
{
|
|
662
|
-
id:
|
|
663
|
-
questionId:
|
|
662
|
+
id: newLibraryItemId(),
|
|
663
|
+
questionId: newLibraryQuestionId(),
|
|
664
664
|
stem: "New question",
|
|
665
665
|
topic: "General",
|
|
666
666
|
type: "short_answer",
|
|
@@ -685,16 +685,16 @@ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, Quest
|
|
|
685
685
|
)
|
|
686
686
|
|
|
687
687
|
// ─ Renderers ──────────────────────────────────────────────────────────────
|
|
688
|
-
const renderers: HubTableRenderers<
|
|
688
|
+
const renderers: HubTableRenderers<LibraryItem> = {
|
|
689
689
|
"board-with-toolbar": ({ state, toolbarShell, displayOptions }) => {
|
|
690
|
-
const boardGroupKey =
|
|
690
|
+
const boardGroupKey = LIBRARY_BOARD_GROUP_OPTIONS.some(
|
|
691
691
|
o => o.key === displayOptions.boardGroupByColumnKey,
|
|
692
692
|
)
|
|
693
693
|
? displayOptions.boardGroupByColumnKey
|
|
694
694
|
: "topic"
|
|
695
695
|
return toolbarShell(
|
|
696
|
-
<
|
|
697
|
-
rows={state.rows as
|
|
696
|
+
<LibraryBoardView
|
|
697
|
+
rows={state.rows as LibraryItem[]}
|
|
698
698
|
groupByColumnKey={boardGroupKey}
|
|
699
699
|
onToggleFavorite={toggleFavorite}
|
|
700
700
|
onRowActivate={row => state.toggleRow(row.id)}
|
|
@@ -703,20 +703,20 @@ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, Quest
|
|
|
703
703
|
},
|
|
704
704
|
"folder-with-toolbar": ({ state, toolbarShell }) =>
|
|
705
705
|
toolbarShell(
|
|
706
|
-
<
|
|
706
|
+
<LibraryOsFolderView
|
|
707
707
|
folders={folders}
|
|
708
708
|
onFoldersChange={onFoldersChange}
|
|
709
|
-
questions={state.rows as
|
|
709
|
+
questions={state.rows as LibraryItem[]}
|
|
710
710
|
onQuestionsChange={onItemsChange}
|
|
711
711
|
/>,
|
|
712
712
|
),
|
|
713
713
|
"panel-with-toolbar": ({ state, toolbarShell }) =>
|
|
714
714
|
toolbarShell(
|
|
715
|
-
<ListPageSplitHubChrome aria-label="
|
|
715
|
+
<ListPageSplitHubChrome aria-label="Library folder columns">
|
|
716
716
|
<HubFolderColumnsPanel
|
|
717
717
|
folders={folders}
|
|
718
|
-
rows={state.rows as
|
|
719
|
-
panelRenderDetail={
|
|
718
|
+
rows={state.rows as LibraryItem[]}
|
|
719
|
+
panelRenderDetail={libraryPanelDetail}
|
|
720
720
|
onAddFolder={openNewFolderForColumn}
|
|
721
721
|
onAddQuestion={addQuestionInColumn}
|
|
722
722
|
onCustomizeFolder={openCustomizeFolderSheet}
|
|
@@ -727,7 +727,7 @@ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, Quest
|
|
|
727
727
|
toolbarShell(
|
|
728
728
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
729
729
|
<HubTreePanelView
|
|
730
|
-
items={state.rows as
|
|
730
|
+
items={state.rows as LibraryItem[]}
|
|
731
731
|
folders={folders}
|
|
732
732
|
onItemsChange={onItemsChange}
|
|
733
733
|
onFoldersChange={onFoldersChange}
|
|
@@ -737,39 +737,39 @@ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, Quest
|
|
|
737
737
|
"dashboard-with-toolbar": ({ state, toolbar }) => (
|
|
738
738
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
739
739
|
{toolbar}
|
|
740
|
-
<
|
|
740
|
+
<LibraryDashboardChartsSection rows={state.rows as LibraryItem[]} />
|
|
741
741
|
</div>
|
|
742
742
|
),
|
|
743
743
|
}
|
|
744
744
|
|
|
745
745
|
return (
|
|
746
746
|
<>
|
|
747
|
-
<HubTable<
|
|
747
|
+
<HubTable<LibraryItem>
|
|
748
748
|
rows={tableSourceItems}
|
|
749
749
|
columns={columns}
|
|
750
750
|
view={view}
|
|
751
751
|
onViewChange={onViewChange}
|
|
752
|
-
supportedViewTypes={
|
|
753
|
-
hubLabel="
|
|
754
|
-
lifecycleTabLabel="
|
|
752
|
+
supportedViewTypes={LIBRARY_SUPPORTED_VIEWS}
|
|
753
|
+
hubLabel="Library"
|
|
754
|
+
lifecycleTabLabel="Library"
|
|
755
755
|
searchAriaLabel="Search questions"
|
|
756
756
|
getRowId={row => row.id}
|
|
757
757
|
getRowSelectionLabel={row => row.stem}
|
|
758
758
|
defaultSort={{ key: "updatedAt", dir: "desc" }}
|
|
759
759
|
emptyState={<p className="text-sm text-muted-foreground">No questions in the bank.</p>}
|
|
760
|
-
boardGroupByColumnOptions={[...
|
|
760
|
+
boardGroupByColumnOptions={[...LIBRARY_BOARD_GROUP_OPTIONS]}
|
|
761
761
|
renderFilterOptionValue={renderFilterOptionValue}
|
|
762
762
|
syncedSearchFromUrl={searchLanding ? undefined : urlListSearch}
|
|
763
763
|
listAriaLabel="Questions"
|
|
764
764
|
listEmptyState="No questions match your filters."
|
|
765
765
|
renderListRow={row => (
|
|
766
766
|
<ListPageBoardCard
|
|
767
|
-
className={
|
|
767
|
+
className={LIBRARY_FAVORITE_HOVER_GROUP}
|
|
768
768
|
layout="row"
|
|
769
769
|
rowContainerClassName="flex w-full flex-col gap-1 sm:flex-row sm:items-center sm:gap-4"
|
|
770
770
|
rowEnd={
|
|
771
771
|
<div className="flex shrink-0 items-center gap-1">
|
|
772
|
-
<
|
|
772
|
+
<LibraryFavoriteButton row={row} onToggleFavorite={toggleFavorite} />
|
|
773
773
|
<i className="fa-light fa-chevron-right text-xs text-muted-foreground" aria-hidden="true" />
|
|
774
774
|
</div>
|
|
775
775
|
}
|
|
@@ -801,7 +801,7 @@ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, Quest
|
|
|
801
801
|
renderers={renderers}
|
|
802
802
|
handleRef={ref}
|
|
803
803
|
/>
|
|
804
|
-
<
|
|
804
|
+
<LibraryNewFolderSheet
|
|
805
805
|
open={newFolderOpen}
|
|
806
806
|
onOpenChange={setNewFolderOpen}
|
|
807
807
|
parentFolderId={customizingFolder?.parentId ?? newFolderParentId}
|
|
@@ -836,4 +836,4 @@ export const QuestionBankTable = React.forwardRef<QuestionBankTableHandle, Quest
|
|
|
836
836
|
},
|
|
837
837
|
)
|
|
838
838
|
|
|
839
|
-
|
|
839
|
+
LibraryTable.displayName = "LibraryTable"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Consistent status chip for list hubs (Team, Compliance,
|
|
4
|
+
* Consistent status chip for list hubs (Team, Compliance, Library, future entities).
|
|
5
5
|
* Pair label + tint + icon from `lib/list-status-badges.ts`; do not hand-roll Badge markup per page.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ import * as React from "react"
|
|
|
9
9
|
import { Badge } from "@/components/ui/badge"
|
|
10
10
|
import { cn } from "@/lib/utils"
|
|
11
11
|
|
|
12
|
-
/** Table column + list view rows — same shell as Team / Compliance /
|
|
12
|
+
/** Table column + list view rows — same shell as Team / Compliance / Library grids. */
|
|
13
13
|
export const LIST_HUB_STATUS_BADGE_TABLE_SHELL =
|
|
14
14
|
"inline-flex items-center gap-1 text-xs font-medium"
|
|
15
15
|
|