@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exxatdesignux/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Exxat shared design system (components, hooks, tokens). Monorepo setup: clone repo then pnpm bootstrap at workspace root — see github.com/ExxatDesign/Exxat-DS-Workspace README.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Exxat Design",
|
|
@@ -98,7 +98,8 @@
|
|
|
98
98
|
"src",
|
|
99
99
|
"bin",
|
|
100
100
|
"template",
|
|
101
|
-
"consumer-extras"
|
|
101
|
+
"consumer-extras",
|
|
102
|
+
"tokens"
|
|
102
103
|
],
|
|
103
104
|
"dependencies": {
|
|
104
105
|
"@hookform/resolvers": "^5.2.2",
|
|
@@ -1100,8 +1100,8 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
1100
1100
|
setHeaderScrollLeft((e.currentTarget as HTMLDivElement).scrollLeft)
|
|
1101
1101
|
}}
|
|
1102
1102
|
className={cn(
|
|
1103
|
-
"mx-4 lg:mx-6 overflow-x-auto border border-border",
|
|
1104
|
-
hasFooter ? "rounded-t-lg" : "rounded-lg",
|
|
1103
|
+
"mx-4 lg:mx-6 overflow-x-auto border-t border-x border-border",
|
|
1104
|
+
hasFooter ? "rounded-t-lg" : "border-b rounded-lg",
|
|
1105
1105
|
)}
|
|
1106
1106
|
>
|
|
1107
1107
|
<table
|
|
@@ -215,7 +215,11 @@ export function DataTablePaginated<TData extends Record<string, unknown>>({
|
|
|
215
215
|
paginationOverride={{ page: safePage, pageSize }}
|
|
216
216
|
hasFooter
|
|
217
217
|
/>
|
|
218
|
-
|
|
218
|
+
{/* z-40 sits above pinned cells (z-20), group headers (z-25), and pinned headers
|
|
219
|
+
(z-40) so the pagination chrome stays opaque over any table content that
|
|
220
|
+
scrolls behind it. Pinned-left cells ship with their own `bg-dt-row-bg`,
|
|
221
|
+
which would otherwise paint over a lower-z footer. */}
|
|
222
|
+
<div className="sticky bottom-0 z-40 mx-4 lg:mx-6 border-x border-b border-border rounded-b-lg overflow-hidden bg-background">
|
|
219
223
|
<PaginationBar
|
|
220
224
|
page={safePage}
|
|
221
225
|
pageSize={pageSize}
|
|
@@ -93,7 +93,7 @@ export function useTableState<TData extends Record<string, unknown>>(
|
|
|
93
93
|
paginationOverride?: { page: number; pageSize: number },
|
|
94
94
|
/**
|
|
95
95
|
* When defined (including `""`), toolbar search is synced from the URL (`?q=`).
|
|
96
|
-
* Use `searchParams.get("q") ?? ""` on
|
|
96
|
+
* Use `searchParams.get("q") ?? ""` on library list routes; omit for other hubs.
|
|
97
97
|
*/
|
|
98
98
|
syncedSearchFromUrl?: string,
|
|
99
99
|
) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* DataRowList — generic vertical-stack list view used by every hub's "list"
|
|
5
|
-
* tab (placements, team, compliance, sites,
|
|
5
|
+
* tab (placements, team, compliance, sites, library, …). Replaces the
|
|
6
6
|
* hand-rolled `<ul …flex-col gap-2 px-4 pb-8 pt-2 lg:px-6> {rows.map(<li>…)}`
|
|
7
7
|
* shell that was duplicated across `*-list-view.tsx` files.
|
|
8
8
|
*
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* FinderPanelView — Miller-style 3-column split for list-page hubs.
|
|
5
5
|
*
|
|
6
|
-
* Visual shell matches
|
|
6
|
+
* Visual shell matches Library panel (`ListPageTreeColumnHeader`, `LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS`,
|
|
7
7
|
* shared resizable handles) — see `list-page-split-hub-tokens.ts`.
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -50,7 +50,7 @@ export interface FinderPanelViewProps<T> {
|
|
|
50
50
|
* `ListPageSplitHubChrome` (shared split surface across hubs).
|
|
51
51
|
*/
|
|
52
52
|
embedded?: boolean
|
|
53
|
-
/** Left column title (
|
|
53
|
+
/** Left column title (Library: “Categories”). */
|
|
54
54
|
groupsColumnTitle?: string
|
|
55
55
|
/** Middle column title; defaults from the active group label. */
|
|
56
56
|
getListColumnTitle?: (activeGroup: FinderGroup | undefined) => string
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `<HubTable<TRow>>` — single centralized table surface used by every list-page hub.
|
|
5
5
|
*
|
|
6
6
|
* Owns all the per-hub scaffolding that was previously duplicated in `placements-table.tsx`,
|
|
7
|
-
* `team-table.tsx`, `compliance-table.tsx`, `
|
|
7
|
+
* `team-table.tsx`, `compliance-table.tsx`, `library-table.tsx`, and `sites-table.tsx`:
|
|
8
8
|
*
|
|
9
9
|
* • `useTableState` setup tied to the centralized row dataset
|
|
10
10
|
* • `displayOptions` state + `patchDisplay`
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import * as React from "react"
|
|
30
|
+
import { cn } from "../../lib/utils"
|
|
30
31
|
import { DataTable, DataTableToolbar } from "../data-table"
|
|
32
|
+
import { CountSyncer, PaginationBar } from "../data-table/pagination"
|
|
31
33
|
import type { ColumnDef } from "../data-table/types"
|
|
32
34
|
import { useTableState } from "../data-table/use-table-state"
|
|
33
35
|
import type { DataListViewType } from "../../lib/data-list-view"
|
|
@@ -204,11 +206,17 @@ export interface HubTableProps<TRow extends Record<string, unknown>> {
|
|
|
204
206
|
tableRenderer?: (args: HubTableRendererArgs<TRow>) => React.ReactNode
|
|
205
207
|
/**
|
|
206
208
|
* Forwarded to `useTableState` so the hub can switch on server-style pagination
|
|
207
|
-
*
|
|
209
|
+
* with externally-controlled page/pageSize (advanced; most hubs should leave
|
|
210
|
+
* this undefined and let `HubTable` own the internal page state — see
|
|
211
|
+
* `pagination` + `paginationPageSizeOptions`).
|
|
208
212
|
*/
|
|
209
213
|
paginationOverride?: { page: number; pageSize: number }
|
|
214
|
+
/** Page size options shown in the toolbar `<PaginationBar>`. Default `[10, 25, 50, 100]`. */
|
|
215
|
+
paginationPageSizeOptions?: number[]
|
|
216
|
+
/** Initial page size when `HubTable` owns pagination internally. Default `10`. */
|
|
217
|
+
paginationInitialPageSize?: number
|
|
210
218
|
/**
|
|
211
|
-
* Forwarded to `useTableState` to sync toolbar search from `?q=` (
|
|
219
|
+
* Forwarded to `useTableState` to sync toolbar search from `?q=` (Library search routes).
|
|
212
220
|
* Defining as `""` enables sync without an initial query.
|
|
213
221
|
*/
|
|
214
222
|
syncedSearchFromUrl?: string
|
|
@@ -273,6 +281,8 @@ export function HubTable<TRow extends Record<string, unknown>>({
|
|
|
273
281
|
handleRef,
|
|
274
282
|
tableRenderer,
|
|
275
283
|
paginationOverride,
|
|
284
|
+
paginationPageSizeOptions = [10, 25, 50, 100],
|
|
285
|
+
paginationInitialPageSize = 10,
|
|
276
286
|
syncedSearchFromUrl,
|
|
277
287
|
renderListRow,
|
|
278
288
|
listAriaLabel,
|
|
@@ -324,14 +334,33 @@ export function HubTable<TRow extends Record<string, unknown>>({
|
|
|
324
334
|
[],
|
|
325
335
|
)
|
|
326
336
|
|
|
337
|
+
// ─── Pagination chrome (centralized) ─────────────────────────────────────
|
|
338
|
+
// When `pagination === true` and the parent did NOT supply `paginationOverride`,
|
|
339
|
+
// `HubTable` owns the page/pageSize internally and wraps the default table +
|
|
340
|
+
// list renderers with `<CountSyncer>` + `<PaginationBar>`. Hubs that need full
|
|
341
|
+
// control (e.g. server-side pagination) keep using `paginationOverride`.
|
|
342
|
+
const [internalPage, setInternalPage] = React.useState(1)
|
|
343
|
+
const [internalPageSize, setInternalPageSize] = React.useState(paginationInitialPageSize)
|
|
344
|
+
const chromeOwnedPagination = pagination === true && paginationOverride === undefined
|
|
345
|
+
const effectivePaginationOverride =
|
|
346
|
+
paginationOverride ??
|
|
347
|
+
(chromeOwnedPagination ? { page: internalPage, pageSize: internalPageSize } : undefined)
|
|
348
|
+
|
|
327
349
|
const tableState = useTableState<TRow>(
|
|
328
350
|
rows,
|
|
329
351
|
columns,
|
|
330
352
|
defaultSort,
|
|
331
|
-
|
|
353
|
+
effectivePaginationOverride,
|
|
332
354
|
syncedSearchFromUrl,
|
|
333
355
|
)
|
|
334
356
|
|
|
357
|
+
const handlePageChange = React.useCallback((p: number) => setInternalPage(p), [])
|
|
358
|
+
const handlePageSizeChange = React.useCallback((n: number) => {
|
|
359
|
+
setInternalPageSize(n)
|
|
360
|
+
setInternalPage(1)
|
|
361
|
+
}, [])
|
|
362
|
+
const resetPage = React.useCallback(() => setInternalPage(1), [])
|
|
363
|
+
|
|
335
364
|
// Extract the stable setter from `useTableState` first so the
|
|
336
365
|
// `useImperativeHandle` deps array sees the exact value the hook reads.
|
|
337
366
|
// `setSheetOpen` is referentially stable, so the handle is created once.
|
|
@@ -395,31 +424,70 @@ export function HubTable<TRow extends Record<string, unknown>>({
|
|
|
395
424
|
],
|
|
396
425
|
)
|
|
397
426
|
|
|
398
|
-
// Default `data-table` renderer — full DataTable with toolbar + bulk actions.
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
427
|
+
// Default `data-table` renderer — full DataTable with toolbar + bulk actions. When
|
|
428
|
+
// `pagination === true` and the parent did not provide `paginationOverride`, the chrome
|
|
429
|
+
// (CountSyncer + PaginationBar) is wrapped automatically so the drawer toggle "Show
|
|
430
|
+
// pagination" works out of the box. Hubs that need finer control (custom chrome,
|
|
431
|
+
// server-side paging) can still override via `tableRenderer`.
|
|
432
|
+
const defaultTableRenderer = (args: HubTableRendererArgs<TRow>) => {
|
|
433
|
+
const filteredCount = (args.state.rows as TRow[]).length
|
|
434
|
+
const totalPages = Math.max(1, Math.ceil(filteredCount / Math.max(1, internalPageSize)))
|
|
435
|
+
const safePage = Math.min(internalPage, totalPages)
|
|
436
|
+
return (
|
|
437
|
+
<div className="pb-6">
|
|
438
|
+
{chromeOwnedPagination ? (
|
|
439
|
+
<CountSyncer
|
|
440
|
+
count={filteredCount}
|
|
441
|
+
onSync={(n) => {
|
|
442
|
+
const next = Math.max(1, Math.ceil(n / Math.max(1, internalPageSize)))
|
|
443
|
+
if (safePage > next) setInternalPage(next)
|
|
444
|
+
}}
|
|
445
|
+
onReset={resetPage}
|
|
446
|
+
/>
|
|
447
|
+
) : null}
|
|
448
|
+
<DataTable<TRow>
|
|
449
|
+
data={rows}
|
|
450
|
+
columns={columns}
|
|
451
|
+
getRowId={getRowId}
|
|
452
|
+
getRowSelectionLabel={getRowSelectionLabel}
|
|
453
|
+
selectable={selectable}
|
|
454
|
+
searchable={displayOptions.showToolbarSearch}
|
|
455
|
+
showColumnHeaders={displayOptions.showColumnLabels}
|
|
456
|
+
groupable={groupable}
|
|
457
|
+
defaultSort={defaultSort}
|
|
458
|
+
emptyState={emptyState ?? <p className="text-sm text-muted-foreground">No records match your filters.</p>}
|
|
459
|
+
conditionalRules={conditionalRules}
|
|
460
|
+
state={args.state}
|
|
461
|
+
renderFilterOptionValue={renderFilterOptionValue}
|
|
462
|
+
toolbarSlot={s => <TablePropertiesDrawerButton {...drawerToolbarProps} state={s} />}
|
|
463
|
+
bulkActionsSlot={bulkActionsSlot}
|
|
464
|
+
onRowClick={onRowClick}
|
|
465
|
+
hasFooter={chromeOwnedPagination}
|
|
466
|
+
/>
|
|
467
|
+
{chromeOwnedPagination ? (
|
|
468
|
+
<div
|
|
469
|
+
className={cn(
|
|
470
|
+
"mx-4 lg:mx-6 border-x border-b border-border rounded-b-lg overflow-hidden bg-background",
|
|
471
|
+
// z-40 sits above pinned cells (z-20), group headers (z-25), and column headers
|
|
472
|
+
// (z-30 / z-40) so the sticky footer paints over any table content that scrolls
|
|
473
|
+
// behind it. Pinned-left cells ship with their own `bg-dt-row-bg`, which
|
|
474
|
+
// otherwise wins because of z-20 > z-10.
|
|
475
|
+
"sticky bottom-0 z-40",
|
|
476
|
+
)}
|
|
477
|
+
>
|
|
478
|
+
<PaginationBar
|
|
479
|
+
page={safePage}
|
|
480
|
+
pageSize={internalPageSize}
|
|
481
|
+
total={filteredCount}
|
|
482
|
+
pageSizeOptions={paginationPageSizeOptions}
|
|
483
|
+
onPageChange={handlePageChange}
|
|
484
|
+
onPageSizeChange={handlePageSizeChange}
|
|
485
|
+
/>
|
|
486
|
+
</div>
|
|
487
|
+
) : null}
|
|
488
|
+
</div>
|
|
489
|
+
)
|
|
490
|
+
}
|
|
423
491
|
|
|
424
492
|
const args: HubTableRendererArgs<TRow> = {
|
|
425
493
|
state: tableState,
|
|
@@ -455,20 +523,60 @@ export function HubTable<TRow extends Record<string, unknown>>({
|
|
|
455
523
|
}
|
|
456
524
|
|
|
457
525
|
// Default centralized list renderer: same DataRowList shell every hub used to roll
|
|
458
|
-
// by hand. Hub provides only the per-row body via `renderListRow`.
|
|
526
|
+
// by hand. Hub provides only the per-row body via `renderListRow`. When `pagination`
|
|
527
|
+
// is on (and `paginationOverride` is not externally supplied), the list view reads
|
|
528
|
+
// `state.pagedRows` and adds the same `CountSyncer` + `PaginationBar` chrome as the
|
|
529
|
+
// table view.
|
|
459
530
|
if (renderers["list-with-toolbar"] == null && renderListRow != null) {
|
|
460
|
-
composed["list-with-toolbar"] = () =>
|
|
461
|
-
args.
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
{
|
|
469
|
-
|
|
470
|
-
|
|
531
|
+
composed["list-with-toolbar"] = () => {
|
|
532
|
+
const fullRows = args.state.rows as TRow[]
|
|
533
|
+
const pagedRows = chromeOwnedPagination ? (args.state.pagedRows as TRow[]) : fullRows
|
|
534
|
+
const filteredCount = fullRows.length
|
|
535
|
+
const totalPages = Math.max(1, Math.ceil(filteredCount / Math.max(1, internalPageSize)))
|
|
536
|
+
const safePage = Math.min(internalPage, totalPages)
|
|
537
|
+
return args.toolbarShell(
|
|
538
|
+
<>
|
|
539
|
+
{chromeOwnedPagination ? (
|
|
540
|
+
<CountSyncer
|
|
541
|
+
count={filteredCount}
|
|
542
|
+
onSync={(n) => {
|
|
543
|
+
const next = Math.max(1, Math.ceil(n / Math.max(1, internalPageSize)))
|
|
544
|
+
if (safePage > next) setInternalPage(next)
|
|
545
|
+
}}
|
|
546
|
+
onReset={resetPage}
|
|
547
|
+
/>
|
|
548
|
+
) : null}
|
|
549
|
+
<DataRowList<TRow>
|
|
550
|
+
rows={pagedRows}
|
|
551
|
+
getRowId={row => getRowId(row)}
|
|
552
|
+
ariaLabel={listAriaLabel ?? hubLabel}
|
|
553
|
+
emptyState={listEmptyState ?? "No records match your filters."}
|
|
554
|
+
{...(listVirtualizeThreshold !== undefined ? { virtualizeThreshold: listVirtualizeThreshold } : {})}
|
|
555
|
+
{...(listEstimatedRowHeight !== undefined ? { estimatedRowHeight: listEstimatedRowHeight } : {})}
|
|
556
|
+
renderRow={renderListRow}
|
|
557
|
+
/>
|
|
558
|
+
{chromeOwnedPagination ? (
|
|
559
|
+
<div
|
|
560
|
+
className={cn(
|
|
561
|
+
"mx-4 lg:mx-6 border-x border-b border-border rounded-b-lg overflow-hidden bg-background",
|
|
562
|
+
// Match the table-view footer — above pinned cells (z-20) and column
|
|
563
|
+
// headers (z-30 / z-40) so the sticky chrome paints over scrolling rows.
|
|
564
|
+
"sticky bottom-0 z-40",
|
|
565
|
+
)}
|
|
566
|
+
>
|
|
567
|
+
<PaginationBar
|
|
568
|
+
page={safePage}
|
|
569
|
+
pageSize={internalPageSize}
|
|
570
|
+
total={filteredCount}
|
|
571
|
+
pageSizeOptions={paginationPageSizeOptions}
|
|
572
|
+
onPageChange={handlePageChange}
|
|
573
|
+
onPageSizeChange={handlePageSizeChange}
|
|
574
|
+
/>
|
|
575
|
+
</div>
|
|
576
|
+
) : null}
|
|
577
|
+
</>,
|
|
471
578
|
)
|
|
579
|
+
}
|
|
472
580
|
}
|
|
473
581
|
|
|
474
582
|
// Default centralized board renderer: same ListPageBoardTemplate every hub used to wrap.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared layout tokens for list-hub split surfaces (Miller columns, tree + details).
|
|
3
|
-
* Keeps
|
|
3
|
+
* Keeps Library panel / tree and generic `FinderPanelView` visually aligned.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/** `ResizableHandle` between miller / tree columns — matches
|
|
6
|
+
/** `ResizableHandle` between miller / tree columns — matches Library panel. */
|
|
7
7
|
export const LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS =
|
|
8
8
|
"w-1 bg-border/40 hover:bg-brand/20 transition-colors"
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ export interface ListPageTreeColumnHeaderProps {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Shared left-column header for tree / outline surfaces — matches
|
|
14
|
+
* Shared left-column header for tree / outline surfaces — matches Library “Questions” bar.
|
|
15
15
|
*/
|
|
16
16
|
export function ListPageTreeColumnHeader({
|
|
17
17
|
title,
|
|
@@ -10,7 +10,7 @@ import { cn } from "../../lib/utils"
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Color palette tones shared across folder hubs. Domain-specific palette
|
|
13
|
-
* names (e.g. `
|
|
13
|
+
* names (e.g. `LibraryFolderColorKey`) are structurally identical to
|
|
14
14
|
* this union and pass through without conversion.
|
|
15
15
|
*/
|
|
16
16
|
export type FolderGlyphColorKey =
|
|
@@ -301,7 +301,7 @@ function metricsRowColumnsClass(rowLength: number, metricsHalfWidthLayout: boole
|
|
|
301
301
|
// Step 1 → 2 (2×2 grid) → 4. Skip 3 — that's the awkward 3+1 layout.
|
|
302
302
|
// Aggressive 4-col thresholds so the strip fits all four tiles even
|
|
303
303
|
// when the primary sidebar + secondary panel + insight rail are all
|
|
304
|
-
// expanded (typical
|
|
304
|
+
// expanded (typical library layout puts the KPI grid at ~27rem).
|
|
305
305
|
return half
|
|
306
306
|
? "grid-cols-1 @[14rem]:grid-cols-2 @[26rem]:grid-cols-4"
|
|
307
307
|
: "grid-cols-1 @[18rem]:grid-cols-2 @[30rem]:grid-cols-4"
|
|
@@ -112,17 +112,18 @@ ListPageTemplate
|
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
**Reference implementations:**
|
|
115
|
-
- `components/
|
|
116
|
-
- `components/
|
|
115
|
+
- `components/columns-showcase.tsx` + `components/columns-client.tsx` — smallest single-view hub (start here)
|
|
116
|
+
- `components/tokens-themes-client.tsx` + `components/tokens-secondary-nav.tsx` — hub with secondary panel + URL-driven scope + built-in pagination chrome
|
|
117
|
+
- `components/library-table.tsx` + `components/library-hub-client.tsx` — full multi-view hub (table, board, dashboard)
|
|
117
118
|
|
|
118
119
|
**Files to create for a new hub page `Foo`:**
|
|
119
120
|
| File | Purpose |
|
|
120
121
|
|------|---------|
|
|
121
122
|
| `lib/mock/foo.ts` | Mock data + TypeScript interface (12+ rows) |
|
|
122
|
-
| `lib/mock/foo-kpi.ts` | `fooKpiMetrics()` + `fooKpiInsight()` |
|
|
123
|
+
| `lib/mock/foo-kpi.ts` | `fooKpiMetrics()` (≤ 4 `MetricItem`s, each with `onClick` or `href`) + `fooKpiInsight()` returning a single `MetricInsight` |
|
|
123
124
|
| `components/foo-page-header.tsx` | `PageHeader` + primary CTA + ⋯ menu |
|
|
124
|
-
| `components/foo-table.tsx` | `DataTable`
|
|
125
|
-
| `components/foo-client.tsx` | `ListPageTemplate` orchestrator |
|
|
125
|
+
| `components/foo-showcase.tsx` *or* `components/foo-table.tsx` | `HubTable` (NOT raw `DataTable`) — pass `pagination` / `onPaginationChange` if the hub needs pagination chrome |
|
|
126
|
+
| `components/foo-client.tsx` | `ListPageTemplate` orchestrator, mounts `KeyMetrics` with `items` + `insight` |
|
|
126
127
|
| `app/(app)/foo/page.tsx` | Thin server component |
|
|
127
128
|
|
|
128
129
|
**Do not** ship a **nav-linked hub** as an **empty page** or a single “replace this later” paragraph. If the route appears in **`lib/mock/navigation.tsx`**, implement the full hub (mock rows, **`ListPageTemplate`**, connected views per **`exxat-ds/AGENTS.md` §4.1**) unless the product explicitly defines a non-data shell.
|
|
@@ -148,7 +149,7 @@ Align with **`exxat-ds/AGENTS.md` §6.4**, **`docs/data-views-pattern.md`**, **`
|
|
|
148
149
|
| `ColumnDef` from `@/components/data-table/types` | Column type |
|
|
149
150
|
| `FilterFieldDef`, `FilterOperator`, `ConditionalRule` from `@/components/table-properties/types` | Filter types |
|
|
150
151
|
|
|
151
|
-
**Board (kanban) cards:** Use **`ListPageBoardCard`** and related parts from **`components/data-views/list-page-board-card.tsx`**; **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`** from **`board-card-primitives.tsx`**. **List hub** status (Team, Compliance,
|
|
152
|
+
**Board (kanban) cards:** Use **`ListPageBoardCard`** and related parts from **`components/data-views/list-page-board-card.tsx`**; **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`** from **`board-card-primitives.tsx`**. **List hub** status (Team, Compliance, Library, …): maps in **`lib/list-status-badges.ts`**; render with **`ListHubStatusBadge`** (**`surface="table"`** in table/list, **`surface="board"`** on cards); semantic tints **`LIST_HUB_STATUS_TINT_*`** for new domains; no **`uppercase`**. **Placements** uses **`StatusBadge`** in **`data-list-table-cells.tsx`** (wrapper over **`ListHubStatusBadge`** + **`PLACEMENT_STATUS_*`**). **Full rules:** **`exxat-ds/AGENTS.md` §4.4**, **`.cursor/rules/exxat-board-cards.mdc`**, **`.cursor/skills/exxat-board-cards/SKILL.md`**.
|
|
152
153
|
|
|
153
154
|
**Minimum required features on any data list page:**
|
|
154
155
|
- Search (wire `searchable={displayOptions.showToolbarSearch}`)
|
|
@@ -245,7 +246,7 @@ Use `PageHeader` from `@/components/page-header` for the content-area header (be
|
|
|
245
246
|
|
|
246
247
|
When a hub is **shared**, use **`PageHeader` `variant="collaboration"`**: **empty roster** → outline **Add collaborator**; **non-empty** → face rail (faces / **`+N`** open the invite sheet). **Invite people** also lives under the entity header **⋯ More** and opens **`InviteCollaboratorsDrawer`** via **`CollaborationAccessFlow`** when possible. Library access (Owner / Editor / Commenter / Viewer) comes from **`lib/collaborator-access.ts`**; directory tags (Faculty, Program coordinator, Director) use **`PageHeaderCollaborator.roles`**.
|
|
247
248
|
|
|
248
|
-
**Handbook:** `apps/web/AGENTS.md` §4.7 · **Doc:** `docs/collaboration-access-pattern.md` · **Skill:** `.cursor/skills/exxat-collaboration-access/SKILL.md` · **Reference:**
|
|
249
|
+
**Handbook:** `apps/web/AGENTS.md` §4.7 · **Doc:** `docs/collaboration-access-pattern.md` · **Skill:** `.cursor/skills/exxat-collaboration-access/SKILL.md` · **Reference:** Library header + client.
|
|
249
250
|
|
|
250
251
|
---
|
|
251
252
|
|
|
@@ -26,7 +26,7 @@ alwaysApply: true
|
|
|
26
26
|
- **C. Interactive icon-only button/link** (close `×`, chevron, overflow `⋯`, sort, filter-dismiss, copy, Ask Leo toggle, row actions) → MUST pair **`aria-label`** on the `<button>` with a wrapping **`Tooltip`**. `aria-label` alone is not enough — sighted users rely on the tooltip too.
|
|
27
27
|
|
|
28
28
|
In all cases the inner `<i>` / `<svg>` is `aria-hidden`; tooltip text matches the accessible name. Narrow exception: chevron inside a labelled composite (`Select`, `Combobox`). See **`AGENTS.md` §8.6 (Case A/B/C)**.
|
|
29
|
-
10. **Keyboard shortcut hints inside buttons** MUST use **`<Kbd variant="bare">`** (no background/border, inherits `currentColor` at 70%). The default `tile` variant is for **tooltips** and **menu `shortcut=` slots** only. Glue multi-key chords into one bare kbd (e.g. `<Kbd variant="bare">⌘⌥K</Kbd>`). Reference: Next/Back in `new-
|
|
29
|
+
10. **Keyboard shortcut hints inside buttons** MUST use **`<Kbd variant="bare">`** (no background/border, inherits `currentColor` at 70%). The default `tile` variant is for **tooltips** and **menu `shortcut=` slots** only. Glue multi-key chords into one bare kbd (e.g. `<Kbd variant="bare">⌘⌥K</Kbd>`). Reference: Next/Back in `new-library-item-form.tsx`; see **`.cursor/rules/exxat-kbd-shortcuts.mdc`**.
|
|
30
30
|
|
|
31
31
|
Re-run **axe** on **Placements** (or affected page) after changing **views toolbar** or **tabs**.
|
|
32
32
|
|
|
@@ -7,7 +7,7 @@ alwaysApply: true
|
|
|
7
7
|
|
|
8
8
|
## Intent
|
|
9
9
|
|
|
10
|
-
- **`CommandMenu`** (**⌘K** / **Ctrl+K**) is **global search** (routes, library, patterns, AI starters, optional row data such as
|
|
10
|
+
- **`CommandMenu`** (**⌘K** / **Ctrl+K**) is **global search** (routes, library, patterns, AI starters, optional row data such as student names / question stems) — see **`AGENTS.md` §7.1** and **`docs/command-menu-pattern.md`**.
|
|
11
11
|
- **Quick / lookup / short AI:** Prefer **results inside the palette** when the product can return compact answers or lightweight “research” without leaving the flow.
|
|
12
12
|
- **Long or complex answers:** **Ask Leo** side panel (**⌘⌥K** / **Ctrl+Alt+K**)—not forced into the palette.
|
|
13
13
|
|
|
@@ -12,7 +12,7 @@ alwaysApply: true
|
|
|
12
12
|
| Surface | Entry | Shared building blocks |
|
|
13
13
|
|--------|--------|-------------------------|
|
|
14
14
|
| **Full-page dashboard** | `app/(app)/dashboard/page.tsx` | `DashboardTabs`, `ChartsOverview` / gallery demos in `charts-overview.tsx` |
|
|
15
|
-
| **Data tab** on
|
|
15
|
+
| **Data tab** on a hub | `library-table.tsx` → `LibraryDashboardChartsSection` (reference) | `ChartFigure`, `ChartCard`, `useChartVariant()`, the hub's own `*-dashboard-charts.tsx` module |
|
|
16
16
|
|
|
17
17
|
**MUST NOT** duplicate “another” chart system for Data view — extend **`charts-overview`** patterns and **`lib/chart-keyboard-selection`**.
|
|
18
18
|
|
|
@@ -36,8 +36,8 @@ alwaysApply: true
|
|
|
36
36
|
|
|
37
37
|
## MUST — persistence (centralized)
|
|
38
38
|
|
|
39
|
-
- **One bundle:** **`lib/data-view-dashboard-storage.ts`** — key **`exxat-ds:data-view-dashboards:v1
|
|
40
|
-
-
|
|
39
|
+
- **One bundle:** **`lib/data-view-dashboard-storage.ts`** — key **`exxat-ds:data-view-dashboards:v1`**. Each hub registers under a scope string (e.g. **`library`**); add new scopes there, never invent a sibling `localStorage` key.
|
|
40
|
+
- Hub-side: pair **`loadDataViewLayout`** + **`saveDataViewLayout`** with **`mergeDashboardLayoutGeneric`** (`lib/dashboard-layout-merge.ts`) for default-layout safety. Reference: the Library dashboard wiring inside **`library-table.tsx`** + **`library-dashboard-charts.tsx`**.
|
|
41
41
|
- **MUST NOT** introduce parallel **`localStorage`** keys for the same **`DashboardLayout`** shape without updating the storage module.
|
|
42
42
|
|
|
43
43
|
## SHOULD — coach marks
|
|
@@ -47,7 +47,7 @@ alwaysApply: true
|
|
|
47
47
|
|
|
48
48
|
## Reference files
|
|
49
49
|
|
|
50
|
-
- `components/
|
|
51
|
-
- `components/
|
|
52
|
-
- `components/
|
|
50
|
+
- `components/library-dashboard-charts.tsx` — canonical Data-tab dashboard section (`LibraryDashboardChartsSection`) — chart cards, `MetricsCard`, layout-edit toolbar.
|
|
51
|
+
- `components/library-table.tsx` — dashboard tab body wires `LibraryDashboardChartsSection` + layout-edit toolbar inline (no separate `DashboardShell` component).
|
|
52
|
+
- `components/charts-overview.tsx` — full-page dashboard chart gallery referenced from `app/(app)/dashboard/page.tsx`.
|
|
53
53
|
- `lib/chart-keyboard-selection.ts`, `lib/data-view-dashboard-storage.ts`, `lib/dashboard-customize-coach-mark.ts`
|
|
@@ -7,16 +7,16 @@ alwaysApply: true
|
|
|
7
7
|
|
|
8
8
|
## Use one stack for product data lists
|
|
9
9
|
|
|
10
|
-
For **any app screen that shows a browsable, filterable grid of records** (
|
|
10
|
+
For **any app screen that shows a browsable, filterable grid of records** (directories, tokens, columns showcase, question banks, etc.):
|
|
11
11
|
|
|
12
12
|
1. **Base table:** `DataTable` from `@/components/data-table` (optionally wrapped with `DataTablePaginated` when pagination is required).
|
|
13
13
|
2. **Search:** Wire the table’s search/query UX (toolbar search or equivalent) — do not ship a “bare” table without find-in-list behavior when the page is a data list.
|
|
14
14
|
3. **Filters:** Use the shared filter model (filter chips / `FilterFieldDef` and operators) consistent with existing list pages — not one-off filter UIs that bypass the table stack.
|
|
15
|
-
4. **Table properties:** Include **Table properties** via `TablePropertiesDrawer` from `@/components/table-properties/drawer` (or the same toolbar + drawer pattern used on reference pages
|
|
15
|
+
4. **Table properties:** Include **Table properties** via `TablePropertiesDrawer` from `@/components/table-properties/drawer` (or the same toolbar + drawer pattern used on the reference pages — `library-table.tsx`, `columns-showcase.tsx`, `tokens-themes-client.tsx`). Users must be able to adjust columns, density, and related table settings from one place.
|
|
16
16
|
5. **Active view:** On **`ListPageTemplate`** pages with **table / list / board / dashboard** tabs, **`TablePropertiesDrawer`** **MUST** receive **`currentView`** and **`onViewChange`** (see **`./AGENTS.md` §4.2** and **`.cursor/rules/exxat-table-properties-drawer.mdc`**) so Properties matches the selected view (not table-only copy on Board).
|
|
17
17
|
6. **Dropdown menus:** **`DropdownMenuContent`** uses the shared **`@exxatdesignux/ui`** default (**intrinsic `w-max`**, **`min-w-52`**, capped **`max-w`**) for view settings, row ⋯, column menus, and filter pickers — **pure CSS**, no **`ResizeObserver`**. Override only for deliberate narrow/wide rails (e.g. pagination **`w-20`**, account trigger-width, school switcher **`!w-max min-w-72 …`**). See **`docs/data-views-pattern.md`** (“Dropdown menus”).
|
|
18
18
|
|
|
19
|
-
**Reference
|
|
19
|
+
**Reference implementations:** `components/library-table.tsx` (full hub: table / board / dashboard, conditional rules, bulk actions), `components/columns-showcase.tsx` (catalog hub composing every `table-cells.tsx` primitive — built-in pagination via `HubTable`), and `components/tokens-themes-client.tsx` (table + secondary-panel category rail). Each shows how `HubTable`, filters, and `TablePropertiesDrawer` compose together.
|
|
20
20
|
|
|
21
21
|
## Do not
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@ Use `@/components/ui/kbd` (`Kbd` + `KbdGroup`) anywhere users discover actions b
|
|
|
24
24
|
| Inside a `DropdownMenuItem` via `shortcut=` | menu handles it — pass the chord string |
|
|
25
25
|
| Standalone helper text on a surface | **default `tile`** |
|
|
26
26
|
|
|
27
|
-
Glue multi-key chords into **one** bare kbd (`<Kbd variant="bare">⌘⌥K</Kbd>`), not one tile per key. See `new-
|
|
27
|
+
Glue multi-key chords into **one** bare kbd (`<Kbd variant="bare">⌘⌥K</Kbd>`), not one tile per key. See `new-library-item-form.tsx` (Next = `{mod}⏎`, Back = `{mod}{alt}←`) and the primary "Ask Leo" button inside chart insight popovers.
|
|
28
28
|
|
|
29
29
|
1. **Pair hint with behavior** — If `Kbd` shows a chord, implement the same shortcut. **Preferred:** the shared primitives from `@/components/ui/dropdown-menu`:
|
|
30
30
|
|
|
@@ -58,8 +58,8 @@ Use `@/components/ui/kbd` (`Kbd` + `KbdGroup`) anywhere users discover actions b
|
|
|
58
58
|
| Toggle main sidebar | ⌘/Ctrl + **B** (`components/ui/sidebar.tsx`) |
|
|
59
59
|
| Table search | ⌘/Ctrl + **K** (no Alt — `DataTable`) |
|
|
60
60
|
| Ask Leo | ⌘/Ctrl + **⌥/Alt** + **K** |
|
|
61
|
-
| New
|
|
62
|
-
|
|
|
61
|
+
| New record (primary hub header) | ⌘/Ctrl + **⌥/Alt** + **N** |
|
|
62
|
+
| Hub overflow menu (⋯) | ⌘/Ctrl + **⌥/Alt** + **M** |
|
|
63
63
|
| Export | ⌘/Ctrl + **⇧/Shift** + **E** |
|
|
64
64
|
| Hide/Show metric section | ⌘/Ctrl + **⌥/Alt** + **H** |
|
|
65
65
|
| Rename (view, tab) | **F2** |
|
|
@@ -79,14 +79,14 @@ Every **workflow surface** (form, dialog, drawer, sheet, multi-step wizard final
|
|
|
79
79
|
1. **Primary action (submit/commit)** — **Enter** (⏎). Render the `<Kbd>⏎</Kbd>` **inline inside the button** (after the label, inside a `<KbdGroup className="ml-1.5">`) — NOT inside a hover `Tip`. Primary/secondary workflow buttons must expose the shortcut at rest so it is discoverable without hovering. Pair with a `<Shortcut keys="Enter" onInvoke={...}>` mounted while the surface is open. The shared `useShortcut` hook skips events from inputs/textarea/contenteditable, so Enter inside a text field still types normally — it only fires when focus is on the surface chrome.
|
|
80
80
|
2. **Secondary action (Cancel/Dismiss)** — **Esc**. Inline `<Kbd>Esc</Kbd>` inside the Cancel button (same `ml-1.5` pattern). Radix `Dialog` / `Sheet` / `AlertDialog` already bind Esc natively.
|
|
81
81
|
|
|
82
|
-
> Tip-on-hover Kbd hints remain correct for **page-level** actions (e.g. "New
|
|
82
|
+
> Tip-on-hover Kbd hints remain correct for **page-level** actions (e.g. primary "New …" CTA, ⋯ overflow triggers) where the button is part of dense page chrome and a persistent Kbd would crowd the layout. Workflow buttons inside a form/drawer/dialog are spacious enough to render the Kbd inline.
|
|
83
83
|
|
|
84
84
|
**Variant inside a button:** always use `<Kbd variant="bare">` — no background, no border, inherits `currentColor` at 70% opacity. The default tile variant looks like a pasted-on patch on filled primary buttons. Glue multi-key chords into one `<Kbd variant="bare">⌘⌥←</Kbd>` rather than one tile per key.
|
|
85
85
|
3. **Multi-step wizards** — plain **Enter** must NOT submit on intermediate steps (it would auto-close the review/final step when users hit Enter inside an input). Either:
|
|
86
86
|
- Gate `form.onSubmit` on `step === lastStep` (`if (step !== N) { e.preventDefault(); return }`), **or**
|
|
87
87
|
- Remove `type="submit"` on intermediate Next buttons and bind **⌘Enter** to "Next" via `<Shortcut>`.
|
|
88
88
|
On the final step, plain **Enter** submits and the Kbd hint shows **⏎**.
|
|
89
|
-
4. Examples in-app: `new-
|
|
89
|
+
4. Examples in-app: `new-library-item-form.tsx` (Create = Enter on the final step, Back = ⌘⌥←), `export-drawer.tsx` (Export = Enter, Cancel = Esc).
|
|
90
90
|
|
|
91
91
|
## Every action menu MUST carry shortcuts
|
|
92
92
|
|
|
@@ -16,7 +16,7 @@ Use this when rendering **system identifiers** — values a user copies, searche
|
|
|
16
16
|
|
|
17
17
|
## SHOULD
|
|
18
18
|
|
|
19
|
-
- Match existing hubs: **`
|
|
19
|
+
- Match existing hubs: **`library-table.tsx`**, **`columns-showcase.tsx`** (mono record IDs in the showcase row), **`new-library-item-form.tsx`** (header subtitle).
|
|
20
20
|
- Prefer **`truncate`** / **`min-w-0`** on mono IDs in tight layouts so long tokens do not blow out columns.
|
|
21
21
|
|
|
22
22
|
## MUST NOT
|
|
@@ -13,7 +13,7 @@ alwaysApply: true
|
|
|
13
13
|
## Product examples (this repo)
|
|
14
14
|
|
|
15
15
|
- **Drawer-appropriate:** `TablePropertiesDrawer`, `ExportDrawer`, lightweight panels that supplement a hub.
|
|
16
|
-
- **Page-appropriate:** Full
|
|
16
|
+
- **Page-appropriate:** Full settings flows, multi-step record-creation wizards (e.g. `new-library-item-form.tsx`), or any task that is itself the user's primary intent.
|
|
17
17
|
|
|
18
18
|
## Authoritative detail
|
|
19
19
|
|
|
@@ -28,7 +28,7 @@ When **`ListPageTemplate`** drives **`tab.viewType`** and the page renders **`Ta
|
|
|
28
28
|
|
|
29
29
|
3. Thread **`view`** and **`onViewChange`** through: **client → table component → drawer toolbar → `TablePropertiesDrawer`**.
|
|
30
30
|
|
|
31
|
-
**Reference implementations:** `components/
|
|
31
|
+
**Reference implementations:** `components/library-hub-client.tsx` + `library-table.tsx` (full hub: table / board / dashboard, conditional rules, bulk actions), `components/columns-showcase.tsx` (single-table catalog + built-in pagination via `HubTable`), `components/tokens-themes-client.tsx` (table + `SecondaryPanel` category rail).
|
|
32
32
|
|
|
33
33
|
## MUST NOT
|
|
34
34
|
|