@exxatdesignux/ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +608 -6
- 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/banner.d.ts +2 -2
- 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 +1 -1
- 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/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/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
|
@@ -8,10 +8,10 @@ import type * as React from "react"
|
|
|
8
8
|
import { logoDevUrl } from "@/lib/logo-dev"
|
|
9
9
|
import { stockPortraitUrl } from "@/lib/stock-portrait"
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "@/lib/
|
|
11
|
+
LIBRARY_ENTRY_PATH,
|
|
12
|
+
LIBRARY_HUB_FIND_PATH,
|
|
13
|
+
LIBRARY_ALL_PATH,
|
|
14
|
+
} from "@/lib/library-nav"
|
|
15
15
|
|
|
16
16
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
@@ -93,39 +93,32 @@ export const NAV_PRIMARY: NavLinkItem[] = [
|
|
|
93
93
|
iconActive: <i className="fa-solid fa-grid-2" aria-hidden="true" />,
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
|
-
key: "
|
|
97
|
-
title: "
|
|
98
|
-
url:
|
|
99
|
-
icon: <i className="fa-light fa-layer-group" aria-hidden="true" />,
|
|
100
|
-
iconActive: <i className="fa-solid fa-layer-group" aria-hidden="true" />,
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
key: "question-bank",
|
|
104
|
-
title: "Question bank",
|
|
105
|
-
url: QUESTION_BANK_ENTRY_PATH,
|
|
96
|
+
key: "library",
|
|
97
|
+
title: "Library",
|
|
98
|
+
url: LIBRARY_ENTRY_PATH,
|
|
106
99
|
icon: <i className="fa-light fa-books" aria-hidden="true" />,
|
|
107
100
|
iconActive: <i className="fa-solid fa-books" aria-hidden="true" />,
|
|
108
|
-
secondaryPanel: "
|
|
109
|
-
primaryHubChildKey: "
|
|
101
|
+
secondaryPanel: "library",
|
|
102
|
+
primaryHubChildKey: "library-hub",
|
|
110
103
|
children: [
|
|
111
104
|
{
|
|
112
|
-
key: "
|
|
113
|
-
title: "
|
|
114
|
-
url:
|
|
105
|
+
key: "library-hub",
|
|
106
|
+
title: "Library home",
|
|
107
|
+
url: LIBRARY_ENTRY_PATH,
|
|
115
108
|
icon: <i className="fa-light fa-sparkles" aria-hidden="true" />,
|
|
116
109
|
iconActive: <i className="fa-solid fa-sparkles" aria-hidden="true" />,
|
|
117
110
|
},
|
|
118
111
|
{
|
|
119
|
-
key: "
|
|
112
|
+
key: "library-search",
|
|
120
113
|
title: "Search",
|
|
121
|
-
url:
|
|
114
|
+
url: LIBRARY_HUB_FIND_PATH,
|
|
122
115
|
icon: <i className="fa-light fa-magnifying-glass" aria-hidden="true" />,
|
|
123
116
|
iconActive: <i className="fa-solid fa-magnifying-glass" aria-hidden="true" />,
|
|
124
117
|
},
|
|
125
118
|
{
|
|
126
|
-
key: "
|
|
127
|
-
title: "
|
|
128
|
-
url:
|
|
119
|
+
key: "library-all",
|
|
120
|
+
title: "All items",
|
|
121
|
+
url: LIBRARY_ALL_PATH,
|
|
129
122
|
icon: <i className="fa-light fa-table-list" aria-hidden="true" />,
|
|
130
123
|
iconActive: <i className="fa-solid fa-table-list" aria-hidden="true" />,
|
|
131
124
|
},
|
|
@@ -141,10 +134,23 @@ export const NAV_DOCUMENTS: NavLinkItem[] = [
|
|
|
141
134
|
{
|
|
142
135
|
key: "tokens",
|
|
143
136
|
title: "Tokens & themes",
|
|
144
|
-
/**
|
|
145
|
-
|
|
137
|
+
/** Dedicated route (was previously /settings#appearance — split out so the nav target
|
|
138
|
+
* is bookmarkable and there's no active-state collision with Settings). */
|
|
139
|
+
url: "/tokens-themes",
|
|
146
140
|
icon: <i className="fa-light fa-palette" aria-hidden="true" />,
|
|
147
141
|
iconActive: <i className="fa-solid fa-palette" aria-hidden="true" />,
|
|
142
|
+
/** Opens the `tokens` secondary panel — categories live in the rail, not in view tabs.
|
|
143
|
+
* `useAutoPanel("tokens")` inside the hub also collapses the main sidebar
|
|
144
|
+
* (`secondary-panel.tsx#openPanel`) per the Library library pattern. */
|
|
145
|
+
secondaryPanel: "tokens",
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
key: "columns",
|
|
149
|
+
title: "Column types",
|
|
150
|
+
/** DataTable column-pattern showcase — every cell renderer the DS supports. */
|
|
151
|
+
url: "/columns",
|
|
152
|
+
icon: <i className="fa-light fa-table-columns" aria-hidden="true" />,
|
|
153
|
+
iconActive: <i className="fa-solid fa-table-columns" aria-hidden="true" />,
|
|
148
154
|
},
|
|
149
155
|
{
|
|
150
156
|
key: "more",
|
|
@@ -388,7 +388,7 @@ export function schedulePageSave(namespace: string, payload: PersistedPageV1): v
|
|
|
388
388
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
389
389
|
|
|
390
390
|
export interface UseTableStateLifecycleOptions<TExtras extends Record<string, unknown> | void = void> {
|
|
391
|
-
/** Storage namespace, e.g. `"placements"`, `"team"`, `"
|
|
391
|
+
/** Storage namespace, e.g. `"placements"`, `"team"`, `"library"`. */
|
|
392
392
|
namespace: string
|
|
393
393
|
/**
|
|
394
394
|
* Sub-key per lifecycle tab. A hub with only one lifecycle should pass a
|
package/template/next.config.mjs
CHANGED
|
@@ -168,10 +168,13 @@ const nextConfig = {
|
|
|
168
168
|
},
|
|
169
169
|
async redirects() {
|
|
170
170
|
return [
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
{ source: "/
|
|
174
|
-
{ source: "/
|
|
171
|
+
// Legacy demo routes (placements, rotations, compliance, sites) were removed.
|
|
172
|
+
// Redirect anything pointing at them to Dashboard rather than 404.
|
|
173
|
+
{ source: "/rotations", destination: "/dashboard", permanent: false },
|
|
174
|
+
{ source: "/compliance", destination: "/dashboard", permanent: false },
|
|
175
|
+
{ source: "/sites/all", destination: "/dashboard", permanent: false },
|
|
176
|
+
{ source: "/sites/all/:path*", destination: "/dashboard", permanent: false },
|
|
177
|
+
{ source: "/examples", destination: "/dashboard", permanent: false },
|
|
175
178
|
]
|
|
176
179
|
},
|
|
177
180
|
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Question bank library — folder-scoped hub header More menu must expose Customize folder; sheet on hub client
|
|
3
|
-
globs: apps/web/components/question-bank-*.tsx, packages/ui/template/components/question-bank-*.tsx
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Exxat DS — Question bank hub header (folder scope)
|
|
8
|
-
|
|
9
|
-
When the question bank library URL is **scoped to a folder** (`parseQuestionBankNav` → **`scope === "folder"`** and **`folderId`** set), users are effectively on a **“folder page”**: the hub title matches that folder, and **global** actions belong in the **`QuestionBankPageHeader`** **⋯ More** menu — not only on per-row or per-tile overflow menus inside a single view tab.
|
|
10
|
-
|
|
11
|
-
**Pattern doc:** **`apps/web/docs/question-bank-hub-header-pattern.md`**. **Handbook:** **`apps/web/AGENTS.md` §4.6** (folder-scoped hub chrome).
|
|
12
|
-
|
|
13
|
-
## MUST
|
|
14
|
-
|
|
15
|
-
1. **`QuestionBankPageHeader`** — When **`navState.scope === "folder"`** and **`navState.folderId`** resolves to a row in **`folders`**, pass **`onCustomizeFolder`** so **⋯ More** includes **Customize folder** ( **`fa-wand-magic-sparkles`** + label **Customize folder** ), placed after **Invite people** (collaboration variant) and before **Export**.
|
|
16
|
-
2. **Hub client** — Mount **`QuestionBankNewFolderSheet`** on the **hub client** (e.g. **`QuestionBankClient`**) next to **`ListPageTemplate`**, driven by local **`open` / `customizingFolder`** state opened from **`onCustomizeFolder`**. **MUST NOT** rely on **`QuestionBankTable`** alone to host the sheet when some view branches (**table**, **list**, **board**, **dashboard**) do not render that sheet — users would lose **Customize folder** on those tabs.
|
|
17
|
-
3. **`onCreated`** — On save, **`setFolders`** (or equivalent) **maps** the scoped folder **`id`** to updated **`name`**, **`icon`**, **`colorKey`** — same contract as **`QuestionBankTable`** panel/tree customize handlers.
|
|
18
|
-
|
|
19
|
-
## MUST NOT
|
|
20
|
-
|
|
21
|
-
- Omit **Customize folder** from the header **⋯** when the URL is folder-scoped, expecting users to find it only on secondary-nav tree rows or OS-folder tiles.
|
|
22
|
-
- Mount **only** one customize sheet inside **`QuestionBankTable`** without a **client-level** sheet when the hub uses **`ListPageTemplate`** view tabs that omit that table subtree.
|
|
23
|
-
|
|
24
|
-
## See also
|
|
25
|
-
|
|
26
|
-
- **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`** — URL scope + secondary panel.
|
|
27
|
-
- **`.cursor/rules/exxat-collaboration-access.mdc`** — **`variant="collaboration"`** header + **⋯** invite pattern.
|
|
28
|
-
- **`lib/question-bank-nav.ts`** — **`parseQuestionBankNav`**, **`QuestionBankNavState`**.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import Link from "next/link"
|
|
2
|
-
import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
|
|
3
|
-
import { Button } from "@/components/ui/button"
|
|
4
|
-
|
|
5
|
-
const LINKS = [
|
|
6
|
-
{ href: "/dashboard", label: "Dashboard", description: "Metrics, charts, and layout patterns." },
|
|
7
|
-
{ href: "/question-bank", label: "Question bank", description: "Discovery hub for browsing folders, recents, and AI-assisted create/import flows." },
|
|
8
|
-
{ href: "/question-bank/library", label: "Question library", description: "Folders, OS folder view, panel, and tree demos on mock items." },
|
|
9
|
-
{ href: "/settings", label: "Settings", description: "Appearance, tours, and shell preferences." },
|
|
10
|
-
{ href: "/help", label: "Help", description: "Support and documentation entry points." },
|
|
11
|
-
] as const
|
|
12
|
-
|
|
13
|
-
export default function ExamplesPage() {
|
|
14
|
-
return (
|
|
15
|
-
<PrimaryPageTemplate
|
|
16
|
-
siteHeader={{ title: "Patterns" }}
|
|
17
|
-
maxWidthClassName="max-w-3xl"
|
|
18
|
-
contentClassName="px-4 lg:px-6 py-8"
|
|
19
|
-
bodyClassName="overflow-y-auto"
|
|
20
|
-
>
|
|
21
|
-
<p className="text-sm text-muted-foreground mb-6">
|
|
22
|
-
This workspace ships neutral chrome so you can reuse layouts, data views, and tokens as a design system.
|
|
23
|
-
</p>
|
|
24
|
-
<ul className="flex flex-col gap-3" role="list">
|
|
25
|
-
{LINKS.map((item) => (
|
|
26
|
-
<li key={item.href}>
|
|
27
|
-
<Button variant="outline" className="h-auto w-full justify-start gap-3 py-4 px-4" asChild>
|
|
28
|
-
<Link href={item.href}>
|
|
29
|
-
<span className="flex min-w-0 flex-1 flex-col items-start gap-0.5 text-left">
|
|
30
|
-
<span className="font-medium text-foreground">{item.label}</span>
|
|
31
|
-
<span className="text-xs font-normal text-muted-foreground">{item.description}</span>
|
|
32
|
-
</span>
|
|
33
|
-
<i className="fa-light fa-arrow-right shrink-0 text-muted-foreground" aria-hidden="true" />
|
|
34
|
-
</Link>
|
|
35
|
-
</Button>
|
|
36
|
-
</li>
|
|
37
|
-
))}
|
|
38
|
-
</ul>
|
|
39
|
-
</PrimaryPageTemplate>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Suspense } from "react"
|
|
2
|
-
|
|
3
|
-
import { QuestionBankClient } from "@/components/question-bank-client"
|
|
4
|
-
|
|
5
|
-
/** Discovery hub composer results — same hub chrome as the library, distinct from `/question-bank/list`. */
|
|
6
|
-
export default function QuestionBankHubFindPage() {
|
|
7
|
-
return (
|
|
8
|
-
<Suspense fallback={null}>
|
|
9
|
-
<QuestionBankClient />
|
|
10
|
-
</Suspense>
|
|
11
|
-
)
|
|
12
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Suspense } from "react"
|
|
2
|
-
|
|
3
|
-
import { QuestionBankClient } from "@/components/question-bank-client"
|
|
4
|
-
|
|
5
|
-
/** Question bank list surface — same hub as `/question-bank/library`, optimized for `?q=` search landings. */
|
|
6
|
-
export default function QuestionBankListPage() {
|
|
7
|
-
return (
|
|
8
|
-
<Suspense fallback={null}>
|
|
9
|
-
<QuestionBankClient />
|
|
10
|
-
</Suspense>
|
|
11
|
-
)
|
|
12
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { initialsFromDisplayName } from "@/lib/initials-from-name"
|
|
5
|
-
import {
|
|
6
|
-
COMPLIANCE_STATUS_BADGE_CLASS,
|
|
7
|
-
COMPLIANCE_STATUS_ICON,
|
|
8
|
-
COMPLIANCE_STATUS_LABEL,
|
|
9
|
-
} from "@/lib/list-status-badges"
|
|
10
|
-
import type { ComplianceItem } from "@/lib/mock/compliance"
|
|
11
|
-
import { BoardCardTwoLineBlock } from "@/components/data-views/board-card-primitives"
|
|
12
|
-
import { ListHubStatusBadge } from "@/components/list-hub-status-badge"
|
|
13
|
-
import {
|
|
14
|
-
ListPageBoardCard,
|
|
15
|
-
ListPageBoardCardAvatar,
|
|
16
|
-
ListPageBoardCardBadgeRow,
|
|
17
|
-
ListPageBoardCardBody,
|
|
18
|
-
ListPageBoardCardHeader,
|
|
19
|
-
ListPageBoardCardTitleRow,
|
|
20
|
-
} from "@/components/data-views/list-page-board-card"
|
|
21
|
-
import {
|
|
22
|
-
ListPageBoardTemplate,
|
|
23
|
-
type ListPageBoardColumnDef,
|
|
24
|
-
} from "@/components/data-views/list-page-board-template"
|
|
25
|
-
|
|
26
|
-
const NEUTRAL_COUNT_BADGE = "bg-muted/90 text-foreground"
|
|
27
|
-
|
|
28
|
-
const STATUS_BOARD_COLUMNS: ListPageBoardColumnDef<ComplianceItem>[] = [
|
|
29
|
-
{
|
|
30
|
-
id: "compliant",
|
|
31
|
-
label: "Compliant",
|
|
32
|
-
description: "On track",
|
|
33
|
-
filter: r => r.status === "compliant",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: "due_soon",
|
|
37
|
-
label: "Due soon",
|
|
38
|
-
description: "Within window",
|
|
39
|
-
filter: r => r.status === "due_soon",
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
id: "overdue",
|
|
43
|
-
label: "Overdue",
|
|
44
|
-
description: "Action required",
|
|
45
|
-
filter: r => r.status === "overdue",
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: "pending",
|
|
49
|
-
label: "Pending",
|
|
50
|
-
description: "Not started",
|
|
51
|
-
filter: r => r.status === "pending",
|
|
52
|
-
},
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
function categoryBoardColumns(rows: ComplianceItem[]): {
|
|
56
|
-
columns: ListPageBoardColumnDef<ComplianceItem>[]
|
|
57
|
-
badgeMap: Record<string, string>
|
|
58
|
-
} {
|
|
59
|
-
const labels = [...new Set(rows.map(r => r.category))].sort((a, b) => a.localeCompare(b))
|
|
60
|
-
const columns: ListPageBoardColumnDef<ComplianceItem>[] = labels.map(label => ({
|
|
61
|
-
id: `category:${label}`,
|
|
62
|
-
label,
|
|
63
|
-
filter: (r: ComplianceItem) => r.category === label,
|
|
64
|
-
}))
|
|
65
|
-
const badgeMap = Object.fromEntries(labels.map(l => [`category:${l}`, NEUTRAL_COUNT_BADGE]))
|
|
66
|
-
return { columns, badgeMap }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function useComplianceBoardModel(rows: ComplianceItem[], groupByColumnKey: string) {
|
|
70
|
-
return React.useMemo(() => {
|
|
71
|
-
if (groupByColumnKey === "category") {
|
|
72
|
-
const { columns, badgeMap } = categoryBoardColumns(rows)
|
|
73
|
-
return { columns, badgeMap }
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
columns: STATUS_BOARD_COLUMNS,
|
|
77
|
-
badgeMap: COMPLIANCE_STATUS_BADGE_CLASS as Record<string, string>,
|
|
78
|
-
}
|
|
79
|
-
}, [rows, groupByColumnKey])
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function ComplianceBoardCard({
|
|
83
|
-
row,
|
|
84
|
-
onRowActivate,
|
|
85
|
-
}: {
|
|
86
|
-
row: ComplianceItem
|
|
87
|
-
onRowActivate?: (row: ComplianceItem) => void
|
|
88
|
-
}) {
|
|
89
|
-
const ownerInitials = initialsFromDisplayName(row.owner)
|
|
90
|
-
return (
|
|
91
|
-
<ListPageBoardCard className="w-full" onClick={onRowActivate ? () => onRowActivate(row) : undefined}>
|
|
92
|
-
<ListPageBoardCardHeader>
|
|
93
|
-
<ListPageBoardCardTitleRow
|
|
94
|
-
title={row.title}
|
|
95
|
-
titleClassName="line-clamp-2"
|
|
96
|
-
trailing={<ListPageBoardCardAvatar initials={ownerInitials} />}
|
|
97
|
-
/>
|
|
98
|
-
<ListPageBoardCardBadgeRow>
|
|
99
|
-
<ListHubStatusBadge
|
|
100
|
-
surface="board"
|
|
101
|
-
label={COMPLIANCE_STATUS_LABEL[row.status]}
|
|
102
|
-
tintClassName={COMPLIANCE_STATUS_BADGE_CLASS[row.status]}
|
|
103
|
-
icon={COMPLIANCE_STATUS_ICON[row.status]}
|
|
104
|
-
/>
|
|
105
|
-
</ListPageBoardCardBadgeRow>
|
|
106
|
-
<ListPageBoardCardBody>
|
|
107
|
-
<BoardCardTwoLineBlock iconClass="fa-tag" line1={row.category} line2={`Due ${row.dueDate}`} />
|
|
108
|
-
<BoardCardTwoLineBlock iconClass="fa-user" line1={row.owner} line2="Owner" />
|
|
109
|
-
</ListPageBoardCardBody>
|
|
110
|
-
</ListPageBoardCardHeader>
|
|
111
|
-
</ListPageBoardCard>
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export const COMPLIANCE_BOARD_GROUP_OPTIONS = [
|
|
116
|
-
{ key: "status", label: "Status" },
|
|
117
|
-
{ key: "category", label: "Category" },
|
|
118
|
-
] as const
|
|
119
|
-
|
|
120
|
-
export function ComplianceBoardView({
|
|
121
|
-
rows,
|
|
122
|
-
groupByColumnKey,
|
|
123
|
-
onRowActivate,
|
|
124
|
-
}: {
|
|
125
|
-
rows: ComplianceItem[]
|
|
126
|
-
groupByColumnKey: string
|
|
127
|
-
onRowActivate?: (row: ComplianceItem) => void
|
|
128
|
-
}) {
|
|
129
|
-
const key = groupByColumnKey === "category" ? "category" : "status"
|
|
130
|
-
const { columns, badgeMap } = useComplianceBoardModel(rows, key)
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<ListPageBoardTemplate
|
|
134
|
-
columns={columns}
|
|
135
|
-
rows={rows}
|
|
136
|
-
getRowKey={r => r.id}
|
|
137
|
-
columnCountBadgeClassName={badgeMap}
|
|
138
|
-
emptyColumnLabel="No items"
|
|
139
|
-
renderCard={row => <ComplianceBoardCard row={row} onRowActivate={onRowActivate} />}
|
|
140
|
-
/>
|
|
141
|
-
)
|
|
142
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Compliance list page — `ListPageTemplate` + `ComplianceTable`; view types from `@/components/data-views`.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as React from "react"
|
|
8
|
-
import {
|
|
9
|
-
ListPageTemplate,
|
|
10
|
-
type ViewTab,
|
|
11
|
-
dataListViewIcon,
|
|
12
|
-
type DataListViewType,
|
|
13
|
-
} from "@/components/data-views"
|
|
14
|
-
import { CompliancePageHeader } from "@/components/compliance-page-header"
|
|
15
|
-
import { ComplianceTable, type ComplianceTableHandle } from "@/components/compliance-table"
|
|
16
|
-
import { KeyMetrics } from "@/components/key-metrics"
|
|
17
|
-
import { useAskLeoPageContext } from "@/components/ask-leo-sidebar"
|
|
18
|
-
import { COMPLIANCE_ITEMS } from "@/lib/mock/compliance"
|
|
19
|
-
import { complianceKpiInsight, complianceKpiMetrics } from "@/lib/mock/compliance-kpi"
|
|
20
|
-
|
|
21
|
-
const DEFAULT_TABS: ViewTab[] = [
|
|
22
|
-
{
|
|
23
|
-
id: "obligations",
|
|
24
|
-
label: "Obligations",
|
|
25
|
-
viewType: "table",
|
|
26
|
-
icon: "fa-table",
|
|
27
|
-
filterId: "all",
|
|
28
|
-
},
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
export function ComplianceClient() {
|
|
32
|
-
const [exportOpen, setExportOpen] = React.useState(false)
|
|
33
|
-
const [showMetrics, setShowMetrics] = React.useState(true)
|
|
34
|
-
const tableRef = React.useRef<ComplianceTableHandle>(null)
|
|
35
|
-
const count = COMPLIANCE_ITEMS.length
|
|
36
|
-
|
|
37
|
-
const metrics = React.useMemo(() => complianceKpiMetrics(COMPLIANCE_ITEMS), [])
|
|
38
|
-
const insight = React.useMemo(() => complianceKpiInsight(COMPLIANCE_ITEMS), [])
|
|
39
|
-
|
|
40
|
-
useAskLeoPageContext(
|
|
41
|
-
React.useMemo(
|
|
42
|
-
() => ({
|
|
43
|
-
title: "Compliance",
|
|
44
|
-
description: `${count} obligations tracked on this hub.`,
|
|
45
|
-
suggestions: [
|
|
46
|
-
"What’s due this week?",
|
|
47
|
-
"Summarize open items by student",
|
|
48
|
-
],
|
|
49
|
-
}),
|
|
50
|
-
[count],
|
|
51
|
-
),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<ListPageTemplate
|
|
56
|
-
defaultTabs={DEFAULT_TABS}
|
|
57
|
-
getTabCount={() => count}
|
|
58
|
-
tablePropertiesRef={tableRef}
|
|
59
|
-
header={
|
|
60
|
-
<CompliancePageHeader
|
|
61
|
-
itemCount={count}
|
|
62
|
-
onAddReview={() => {}}
|
|
63
|
-
onExport={() => setExportOpen(true)}
|
|
64
|
-
showMetrics={showMetrics}
|
|
65
|
-
onToggleMetrics={() => setShowMetrics(v => !v)}
|
|
66
|
-
/>
|
|
67
|
-
}
|
|
68
|
-
metrics={
|
|
69
|
-
<KeyMetrics
|
|
70
|
-
variant="flat"
|
|
71
|
-
metrics={metrics}
|
|
72
|
-
insight={insight}
|
|
73
|
-
showHeader={false}
|
|
74
|
-
metricsSingleRow
|
|
75
|
-
/>
|
|
76
|
-
}
|
|
77
|
-
showMetrics={showMetrics}
|
|
78
|
-
exportOpen={exportOpen}
|
|
79
|
-
onExportOpenChange={setExportOpen}
|
|
80
|
-
exportTotalRows={count}
|
|
81
|
-
renderContent={(tab, updateTab) => (
|
|
82
|
-
<ComplianceTable
|
|
83
|
-
key={tab.id}
|
|
84
|
-
ref={tableRef}
|
|
85
|
-
items={COMPLIANCE_ITEMS}
|
|
86
|
-
view={tab.viewType}
|
|
87
|
-
onViewChange={(v: DataListViewType) => updateTab({ viewType: v, icon: dataListViewIcon(v) })}
|
|
88
|
-
/>
|
|
89
|
-
)}
|
|
90
|
-
/>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { Button } from "@/components/ui/button"
|
|
5
|
-
import { PageHeader } from "@/components/page-header"
|
|
6
|
-
import {
|
|
7
|
-
DropdownMenu,
|
|
8
|
-
DropdownMenuContent,
|
|
9
|
-
DropdownMenuItem,
|
|
10
|
-
DropdownMenuSeparator,
|
|
11
|
-
DropdownMenuTrigger,
|
|
12
|
-
} from "@/components/ui/dropdown-menu"
|
|
13
|
-
import { Tip } from "@/components/ui/tip"
|
|
14
|
-
|
|
15
|
-
export interface CompliancePageHeaderProps {
|
|
16
|
-
itemCount: number
|
|
17
|
-
onAddReview: () => void
|
|
18
|
-
onExport: () => void
|
|
19
|
-
showMetrics: boolean
|
|
20
|
-
onToggleMetrics: () => void
|
|
21
|
-
showTitleBlock?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function CompliancePageHeader({
|
|
25
|
-
itemCount,
|
|
26
|
-
onAddReview,
|
|
27
|
-
onExport,
|
|
28
|
-
showMetrics,
|
|
29
|
-
onToggleMetrics,
|
|
30
|
-
showTitleBlock = true,
|
|
31
|
-
}: CompliancePageHeaderProps) {
|
|
32
|
-
const [moreOpen, setMoreOpen] = React.useState(false)
|
|
33
|
-
const countLine = `${itemCount} ${itemCount === 1 ? "item" : "items"} · Last updated now`
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<PageHeader
|
|
37
|
-
title="Compliance"
|
|
38
|
-
subtitle={countLine}
|
|
39
|
-
showTitleBlock={showTitleBlock}
|
|
40
|
-
actions={(
|
|
41
|
-
<div className="flex items-center gap-2" role="group" aria-label="Compliance actions">
|
|
42
|
-
<Tip side="bottom" label="Schedule a review (demo)">
|
|
43
|
-
<Button type="button" size="lg" onClick={onAddReview}>
|
|
44
|
-
<i className="fa-light fa-calendar-check" aria-hidden="true" />
|
|
45
|
-
New review
|
|
46
|
-
</Button>
|
|
47
|
-
</Tip>
|
|
48
|
-
<DropdownMenu open={moreOpen} onOpenChange={setMoreOpen}>
|
|
49
|
-
<Tip side="bottom" label="More actions">
|
|
50
|
-
<DropdownMenuTrigger asChild>
|
|
51
|
-
<Button
|
|
52
|
-
type="button"
|
|
53
|
-
size="lg"
|
|
54
|
-
variant="outline"
|
|
55
|
-
className="aspect-square px-0"
|
|
56
|
-
aria-label="More actions"
|
|
57
|
-
>
|
|
58
|
-
<i className="fa-light fa-ellipsis text-base" aria-hidden="true" />
|
|
59
|
-
</Button>
|
|
60
|
-
</DropdownMenuTrigger>
|
|
61
|
-
</Tip>
|
|
62
|
-
<DropdownMenuContent align="end">
|
|
63
|
-
<DropdownMenuItem
|
|
64
|
-
onSelect={() => {
|
|
65
|
-
window.setTimeout(() => onExport(), 0)
|
|
66
|
-
}}
|
|
67
|
-
>
|
|
68
|
-
<i className="fa-light fa-arrow-down-to-line" aria-hidden="true" />
|
|
69
|
-
Export
|
|
70
|
-
</DropdownMenuItem>
|
|
71
|
-
<DropdownMenuSeparator />
|
|
72
|
-
<DropdownMenuItem
|
|
73
|
-
onSelect={() => {
|
|
74
|
-
window.setTimeout(() => onToggleMetrics(), 0)
|
|
75
|
-
}}
|
|
76
|
-
>
|
|
77
|
-
<i
|
|
78
|
-
className={`fa-light ${showMetrics ? "fa-eye-slash" : "fa-eye"}`}
|
|
79
|
-
aria-hidden="true"
|
|
80
|
-
/>
|
|
81
|
-
{showMetrics ? "Hide metric section" : "Show metric section"}
|
|
82
|
-
</DropdownMenuItem>
|
|
83
|
-
</DropdownMenuContent>
|
|
84
|
-
</DropdownMenu>
|
|
85
|
-
</div>
|
|
86
|
-
)}
|
|
87
|
-
/>
|
|
88
|
-
)
|
|
89
|
-
}
|