@exxatdesignux/ui 0.2.6 → 0.2.8
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/package.json +2 -1
- package/template/.agents/skills/shadcn/SKILL.md +242 -0
- package/template/.agents/skills/shadcn/agents/openai.yml +5 -0
- package/template/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
- package/template/.agents/skills/shadcn/assets/shadcn.png +0 -0
- package/template/.agents/skills/shadcn/cli.md +257 -0
- package/template/.agents/skills/shadcn/customization.md +202 -0
- package/template/.agents/skills/shadcn/evals/evals.json +47 -0
- package/template/.agents/skills/shadcn/mcp.md +94 -0
- package/template/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/template/.agents/skills/shadcn/rules/composition.md +195 -0
- package/template/.agents/skills/shadcn/rules/forms.md +192 -0
- package/template/.agents/skills/shadcn/rules/icons.md +101 -0
- package/template/.agents/skills/shadcn/rules/styling.md +162 -0
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +712 -0
- package/template/.cursor/rules/exxat-accessibility.mdc +33 -0
- package/template/.cursor/rules/exxat-command-menu.mdc +23 -0
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +53 -0
- package/template/.cursor/rules/exxat-data-tables.mdc +31 -0
- package/template/.cursor/rules/exxat-ds-agents.mdc +26 -0
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +100 -0
- package/template/.cursor/rules/exxat-list-page-connected-views.mdc +16 -0
- package/template/.cursor/rules/exxat-no-toast.mdc +26 -0
- package/template/.cursor/rules/exxat-page-vs-drawer.mdc +22 -0
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +40 -0
- package/template/AGENTS.md +52 -11
- package/template/app/(app)/dashboard/page.tsx +1 -1
- package/template/app/(app)/data-list/[id]/page.tsx +24 -8
- package/template/app/(app)/data-list/new/page.tsx +7 -4
- package/template/app/(app)/data-list/page.tsx +1 -1
- package/template/app/(app)/examples/page.tsx +41 -0
- package/template/app/(app)/question-bank/page.tsx +3 -3
- package/template/components/app-sidebar.tsx +52 -35
- package/template/components/compliance-table.tsx +79 -0
- package/template/components/data-list-client.tsx +36 -25
- package/template/components/data-list-table.tsx +797 -10
- package/template/components/data-views/finder-panel-view.tsx +405 -0
- package/template/components/data-views/folder-grid-view.tsx +86 -0
- package/template/components/data-views/index.ts +59 -0
- package/template/components/data-views/list-page-split-details-placeholder.tsx +39 -0
- package/template/components/data-views/list-page-split-hub-chrome.tsx +60 -0
- package/template/components/data-views/list-page-split-hub-tokens.ts +16 -0
- package/template/components/data-views/list-page-tree-column-header.tsx +31 -0
- package/template/components/data-views/list-page-tree-panel-shell.tsx +91 -0
- package/template/components/data-views/list-page-view-frame.tsx +53 -0
- package/template/components/data-views/os-folder-glyph.tsx +121 -0
- package/template/components/folder-details-shell.tsx +230 -0
- package/template/components/hub-tree-panel-view.tsx +672 -0
- package/template/components/list-hub-status-badge.tsx +17 -3
- package/template/components/placements-page-header.tsx +14 -8
- package/template/components/placements-table-columns.tsx +8 -8
- package/template/components/question-bank-client.tsx +157 -40
- package/template/components/question-bank-new-folder-sheet.tsx +248 -0
- package/template/components/question-bank-os-folder-view.tsx +648 -0
- package/template/components/question-bank-page-header.tsx +3 -3
- package/template/components/question-bank-panel-activator.tsx +9 -0
- package/template/components/question-bank-secondary-nav.tsx +226 -0
- package/template/components/question-bank-table.tsx +707 -22
- package/template/components/secondary-panel.tsx +41 -107
- package/template/components/sites-table.tsx +66 -0
- package/template/components/team-client.tsx +7 -0
- package/template/components/team-table.tsx +156 -1
- package/template/components/templates/list-page.tsx +2 -2
- package/template/components/ui/avatar.tsx +1 -1
- package/template/components/ui/badge.tsx +1 -1
- package/template/components/ui/banner.tsx +1 -1
- package/template/components/ui/breadcrumb.tsx +1 -1
- package/template/components/ui/button.tsx +1 -1
- package/template/components/ui/calendar.tsx +1 -1
- package/template/components/ui/card.tsx +1 -1
- package/template/components/ui/chart.tsx +1 -1
- package/template/components/ui/checkbox.tsx +1 -1
- package/template/components/ui/coach-mark.tsx +1 -1
- package/template/components/ui/collapsible.tsx +1 -1
- package/template/components/ui/command.tsx +1 -1
- package/template/components/ui/date-picker-field.tsx +1 -1
- package/template/components/ui/dialog.tsx +1 -1
- package/template/components/ui/drag-handle-grip.tsx +1 -1
- package/template/components/ui/drawer.tsx +1 -1
- package/template/components/ui/dropdown-menu.tsx +1 -1
- package/template/components/ui/field.tsx +1 -1
- package/template/components/ui/form.tsx +1 -1
- package/template/components/ui/input-group.tsx +1 -1
- package/template/components/ui/input-mask.tsx +1 -1
- package/template/components/ui/input.tsx +1 -1
- package/template/components/ui/kbd.tsx +1 -1
- package/template/components/ui/label.tsx +1 -1
- package/template/components/ui/payment-card-fields.tsx +1 -1
- package/template/components/ui/popover.tsx +1 -1
- package/template/components/ui/radio-group.tsx +1 -1
- package/template/components/ui/resizable.tsx +68 -0
- package/template/components/ui/select.tsx +1 -1
- package/template/components/ui/selection-tile-grid.tsx +1 -1
- package/template/components/ui/separator.tsx +1 -1
- package/template/components/ui/sheet.tsx +1 -1
- package/template/components/ui/sidebar.tsx +1 -1
- package/template/components/ui/skeleton.tsx +1 -1
- package/template/components/ui/sonner.tsx +1 -1
- package/template/components/ui/status-badge.tsx +1 -1
- package/template/components/ui/table.tsx +1 -1
- package/template/components/ui/tabs.tsx +1 -1
- package/template/components/ui/textarea.tsx +1 -1
- package/template/components/ui/tip.tsx +1 -1
- package/template/components/ui/toggle-group.tsx +1 -1
- package/template/components/ui/toggle-switch.tsx +1 -1
- package/template/components/ui/toggle.tsx +1 -1
- package/template/components/ui/tooltip.tsx +1 -1
- package/template/components/ui/view-segmented-control.tsx +1 -1
- package/template/docs/data-views-pattern.md +7 -0
- package/template/fontawesome-subset.manifest.json +2 -2
- package/template/hooks/use-location-hash.ts +15 -0
- package/template/hooks/use-sidebar-reflow-zoom.ts +40 -0
- package/template/lib/ask-leo-route-context.ts +25 -57
- package/template/lib/coach-mark-registry.ts +13 -13
- package/template/lib/command-menu-config.ts +28 -23
- package/template/lib/command-menu-search-data.ts +10 -9
- package/template/lib/data-list-view-surface.ts +12 -1
- package/template/lib/data-list-view.ts +6 -3
- package/template/lib/mock/dashboard.ts +11 -11
- package/template/lib/mock/navigation.tsx +22 -63
- package/template/lib/mock/placements-kpi.ts +19 -19
- package/template/lib/mock/question-bank-folders.ts +167 -0
- package/template/lib/mock/question-bank-inspector.ts +109 -0
- package/template/lib/mock/question-bank-kpi.ts +1 -1
- package/template/lib/mock/question-bank.ts +80 -0
- package/template/lib/question-bank-nav.ts +91 -0
- package/template/next.config.mjs +8 -0
- package/template/package.json +1 -0
- package/template/public/folders/icons8-folder-windows-11.svg +1 -0
- package/template/scripts/fontawesome-subset-audit.mjs +2 -3
- package/template/app/(app)/compliance/page.tsx +0 -10
- package/template/app/(app)/rotations/page.tsx +0 -15
- package/template/app/(app)/sites/all/page.tsx +0 -13
- package/template/app/(app)/team/page.tsx +0 -10
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — WCAG 2.1 AA, ARIA tablists, 24px targets, contrast; see AGENTS.md §8
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — accessibility (binding summary)
|
|
7
|
+
|
|
8
|
+
**Full checklist:** monorepo **`.cursor/skills/exxat-accessibility/SKILL.md`** when the parent repo is open.
|
|
9
|
+
|
|
10
|
+
**Product rules:** **`./AGENTS.md` §8**.
|
|
11
|
+
|
|
12
|
+
## Non‑negotiables
|
|
13
|
+
|
|
14
|
+
1. **Target:** **WCAG 2.1 Level AA** (2.2 where noted — e.g. target size).
|
|
15
|
+
2. **`role="tablist"`** — only **`role="tab"`** as tab semantics. **MUST NOT** put `role="button"`, menus (`aria-haspopup`), or other controls **inside** the same **`tablist`** container.
|
|
16
|
+
3. **Composite view switchers** (tabs + per-tab menu + remove): **`role="toolbar"`** + **`aria-label`**; **`aria-pressed`** on toggles — **MUST NOT** misuse `tab`/`tablist`.
|
|
17
|
+
4. **Touch targets (2.5.8):** **≥ 24×24 CSS px** or **24px** spacing — **`min-h-6 min-w-6` / `size-6`** for icon-only; avoid **`size-4`** as sole target.
|
|
18
|
+
5. **Contrast:** normal text **≥ 4.5:1**; UI / focus **≥ 3:1** where required; muted on tinted surfaces use correct surface tokens.
|
|
19
|
+
6. **Minimum text size:** visible product copy **≥ 11px** — **`text-xs`** or larger (**`AGENTS.md` §8.3**, **`app/globals.css`** `--text-xs`).
|
|
20
|
+
7. **Dialogs / sheets / drawers:** must have a **Title** (`DialogTitle` / `SheetTitle` / `DrawerTitle`); **`sr-only`** if hidden.
|
|
21
|
+
8. **Format hints persistent, not placeholders (SC 3.3.2, 1.3.1).** Fields with required formats — **date, time, phone, currency, GPA, IDs, URLs, unit-bearing numbers** — MUST render the format via **`FormDescription`** (or equivalent `aria-describedby` helper text). Placeholders disappear on focus and **MUST NOT** be the sole carrier. Prefer picker primitives (e.g. `DatePickerField`) over free-text where available.
|
|
22
|
+
9. **Every icon that communicates information MUST have a text alternative** — not just icon-only buttons. Three cases (SC 1.1.1, 3.3.2, 2.4.6):
|
|
23
|
+
|
|
24
|
+
- **A. Decorative icon next to text that already names it** (`<i class="fa-light fa-calendar-days" aria-hidden /> 12/14/2025`) → icon is `aria-hidden`, no `aria-label`, no tooltip. The text is the alt.
|
|
25
|
+
- **B. Informational icon standing alone** (calendar = "date range", clock = "updated at", pin = "site", cap = "student", trend arrow, status dot, icon-only chart legend) → MUST pair **`role="img"` + `aria-label`** with a visible **`Tooltip`**. Wrapper MUST be keyboard-focusable (`tabIndex={0}`) so the tooltip opens on focus too.
|
|
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
|
+
|
|
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-placement-form.tsx`; see **`.cursor/rules/exxat-kbd-shortcuts.mdc`**.
|
|
30
|
+
|
|
31
|
+
Re-run **axe** on **Placements** (or affected page) after changing **views toolbar** or **tabs**.
|
|
32
|
+
|
|
33
|
+
**Charts:** Keyboard exploration uses **`ChartFigure`**; selected points use **`chart-keyboard-selection`** (ring/stroke parity with gallery) — see **`AGENTS.md` §4.3** and **`.cursor/rules/exxat-dashboard-view-charts.mdc`**.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — global command palette (⌘K) as search + quick AI vs Ask Leo for long answers.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — global command palette (`CommandMenu`)
|
|
7
|
+
|
|
8
|
+
## Intent
|
|
9
|
+
|
|
10
|
+
- **`CommandMenu`** (**⌘K** / **Ctrl+K**) is **global search** (routes, library, patterns, AI starters, optional row data such as placements / student names)—see **`AGENTS.md` §7.1** and **`docs/command-menu-pattern.md`**.
|
|
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
|
+
- **Long or complex answers:** **Ask Leo** side panel (**⌘⌥K** / **Ctrl+Alt+K**)—not forced into the palette.
|
|
13
|
+
|
|
14
|
+
## Implementation pointers
|
|
15
|
+
|
|
16
|
+
- Shell: `components/command-menu.tsx`; config **`buildCommandMenuConfig()`** in **`lib/command-menu-config.ts`**; optional **`dataGroups`** from **`lib/command-menu-search-data.ts`** (e.g. **`getCommandMenuSearchDataGroups()`**), wired in **`app/(app)/layout.tsx`**. Keep domain mapping out of the shell.
|
|
17
|
+
- Large indexes: set **`searchOnly: true`** on **`CommandMenuGroup`** so **`command-menu.tsx`** skips the group until the user types (avoids listing every row on open).
|
|
18
|
+
- Sidebar **“Search or ask Leo”** opens the same palette.
|
|
19
|
+
|
|
20
|
+
## See also
|
|
21
|
+
|
|
22
|
+
- **`exxat-ds/AGENTS.md` §7.1**
|
|
23
|
+
- **`.cursor/rules/exxat-kbd-shortcuts.mdc`** (⌘K vs ⌘⌥K)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Data view dashboard — centralized charts, storage, keyboard parity with gallery, edit layout, coach marks
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — Data view dashboard (list hubs)
|
|
7
|
+
|
|
8
|
+
**Authoritative detail:** **`exxat-ds/AGENTS.md` §4.3** and **`exxat-dashboard-view-charts`** (this rule).
|
|
9
|
+
|
|
10
|
+
## Two dashboard surfaces (do not fork chart engines)
|
|
11
|
+
|
|
12
|
+
| Surface | Entry | Shared building blocks |
|
|
13
|
+
|--------|--------|-------------------------|
|
|
14
|
+
| **Full-page dashboard** | `app/(app)/dashboard/page.tsx` | `DashboardTabs`, `ChartsOverview` / gallery demos in `charts-overview.tsx` |
|
|
15
|
+
| **Data tab** on Placements / Team / Compliance | `DataListTable`, `TeamTable`, `ComplianceTable` → `*DashboardChartsSection` | `ChartFigure`, `ChartCard`, `useChartVariant()`, `data-view-dashboard-charts*.tsx` |
|
|
16
|
+
|
|
17
|
+
**MUST NOT** duplicate “another” chart system for Data view — extend **`charts-overview`** patterns and **`lib/chart-keyboard-selection`**.
|
|
18
|
+
|
|
19
|
+
## MUST — accessibility & data
|
|
20
|
+
|
|
21
|
+
1. **`ChartFigure`** + **`ChartDataTable`** (`sr-only`) for every chart on the Data dashboard — same as **`charts-overview.tsx`**.
|
|
22
|
+
2. **`ChartCard`** wraps chart content; **`KeyMetrics`** **`variant="card"`** for the **`key-metrics`** tile; KPI count **1–4** persisted in layout.
|
|
23
|
+
|
|
24
|
+
## MUST — keyboard selection (parity with `/dashboard` gallery)
|
|
25
|
+
|
|
26
|
+
- Import **`CHART_KBD_ACTIVE_BAR`** and **`CHART_KBD_ACTIVE_PIE_SHAPE`** from **`@/lib/chart-keyboard-selection`**.
|
|
27
|
+
- **Bar** series: **`activeBar={CHART_KBD_ACTIVE_BAR}`** + **`activeIndex={activeIndex ?? undefined}`** (and **`Cell`** only for per-bar **fill**, not opacity-only selection).
|
|
28
|
+
- **Pie / donut:** **`activeShape={CHART_KBD_ACTIVE_PIE_SHAPE}`** + **`activeIndex`** + slice **`stroke`** / **`strokeWidth`** consistent with gallery donuts.
|
|
29
|
+
- **MUST NOT** use **`fillOpacity` dimming alone** on **`Cell`** as the sole “selected” state for keyboard exploration.
|
|
30
|
+
|
|
31
|
+
## MUST — customisation UX
|
|
32
|
+
|
|
33
|
+
- **Edit layout** control: **`aria-label="Edit dashboard layout"`** (toolbar pen-ruler). Coach marks may target **`[aria-label='Edit dashboard layout']`**.
|
|
34
|
+
- On-canvas: drag reorder, remove, width (half / full), chart type, add/remove cards, reset. **No** separate Sheet for layout.
|
|
35
|
+
- While **`layoutEditMode`**: hide **`DataTableToolbar`** (search / filters / Properties row); **Done** / **Cancel** on canvas.
|
|
36
|
+
|
|
37
|
+
## MUST — persistence (centralized)
|
|
38
|
+
|
|
39
|
+
- **One bundle:** **`lib/data-view-dashboard-storage.ts`** — key **`exxat-ds:data-view-dashboards:v1`**, scopes **`placements` | `team` | `compliance`**.
|
|
40
|
+
- Placements helpers: **`loadDashboardLayout`** / **`saveDashboardLayout`** in **`data-view-dashboard-charts.tsx`** (wrap storage API).
|
|
41
|
+
- **MUST NOT** introduce parallel **`localStorage`** keys for the same **`DashboardLayout`** shape without updating the storage module.
|
|
42
|
+
|
|
43
|
+
## SHOULD — coach marks
|
|
44
|
+
|
|
45
|
+
- Register **customize dashboard** flows in **`lib/coach-mark-registry.ts`**; shared copy in **`lib/dashboard-customize-coach-mark.ts`**.
|
|
46
|
+
- Use **`useCoachMark`** **`enabled`** / **`dependsOnDismissedFlowId`** when a tour only applies on **dashboard** view or after another flow (**`COACH_MARK_FLOW_COMPLETED_EVENT`**).
|
|
47
|
+
|
|
48
|
+
## Reference files
|
|
49
|
+
|
|
50
|
+
- `components/data-view-dashboard-charts.tsx` (Placements)
|
|
51
|
+
- `components/data-view-dashboard-charts-team.tsx`, `data-view-dashboard-charts-compliance.tsx`
|
|
52
|
+
- `components/data-list-table.tsx` — `DataListDashboardShell`
|
|
53
|
+
- `lib/chart-keyboard-selection.ts`, `lib/data-view-dashboard-storage.ts`, `lib/dashboard-customize-coach-mark.ts`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — product data tables must use DataTable with search, filters, and table properties; no alternate table stacks.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — data tables (mandatory pattern)
|
|
7
|
+
|
|
8
|
+
## Use one stack for product data lists
|
|
9
|
+
|
|
10
|
+
For **any app screen that shows a browsable, filterable grid of records** (lists, directories, placements, etc.):
|
|
11
|
+
|
|
12
|
+
1. **Base table:** `DataTable` from `@/components/data-table` (optionally wrapped with `DataTablePaginated` when pagination is required).
|
|
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
|
+
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 such as placements / data list). Users must be able to adjust columns, density, and related table settings from one place.
|
|
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
|
+
|
|
18
|
+
**Reference implementation:** `components/data-list-table.tsx` (placements) shows how `DataTable`, filters, and `TablePropertiesDrawer` compose together.
|
|
19
|
+
|
|
20
|
+
## Do not
|
|
21
|
+
|
|
22
|
+
- Do **not** build product list pages with `@/components/ui/table` alone, raw `<table>` markup, or third-party data grids.
|
|
23
|
+
- Do **not** introduce a second “table component” pattern for the same product surfaces (splitting search/filters/properties across incompatible implementations).
|
|
24
|
+
|
|
25
|
+
## Exceptions
|
|
26
|
+
|
|
27
|
+
- **Tiny, read-only tables inside charts or analytics cards** (e.g. chart figure captions / summary matrices) may use minimal markup when they are not primary data-list experiences — still prefer tokens and accessibility, but the full DataTable stack is not required there.
|
|
28
|
+
|
|
29
|
+
## See also
|
|
30
|
+
|
|
31
|
+
- **`./AGENTS.md`** — full MUST/MUST NOT, list-page template, primary hubs, checklist.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — follow AGENTS.md; DataTable, ListPageTemplate, primary hubs, export, Kbd
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — AI handbook (binding)
|
|
7
|
+
|
|
8
|
+
**Read `./AGENTS.md` at this repo root** before implementing or reviewing list/table/board or data-heavy pages (rule precedence, MUST/MUST NOT, §4.1–§4.3, **§6.4** page vs drawer, **§6.5** no toast, §7.1 command palette, §13 checklist).
|
|
9
|
+
|
|
10
|
+
## Non‑negotiables (if anything conflicts, open AGENTS.md §12–§13)
|
|
11
|
+
|
|
12
|
+
1. **Product data lists** → `DataTable` + search + shared filters + `TablePropertiesDrawer` — not raw `<table>` / ui `Table` alone / ad-hoc grids. With **`ListPageTemplate`** view tabs, pass **`currentView`** + **`onViewChange`** into **`TablePropertiesDrawer`** (**`AGENTS.md` §4.2**, **`.cursor/rules/exxat-table-properties-drawer.mdc`**).
|
|
13
|
+
2. **Main `DataTable` surface** → wrapped in **`ListPageTemplate`** (view tabs). All tab view types share **`useTableState`**; list/board/dashboard read **`tableState.rows`**. Reference: `DataListClient`, `TeamClient`, `TeamTable`.
|
|
14
|
+
3. **Do not double-indent** the toolbar/table — avoid extra `px`/`mx` wrappers around `DataTable` when it already applies horizontal inset.
|
|
15
|
+
4. **Primary hub + large/complex data** → same composition as Placements/Team: `ListPageTemplate` + metrics/export pattern as in `DataListClient` / `TeamClient`, not `PageHeader`-only.
|
|
16
|
+
5. **Exportable pages** → filled primary CTA; **⋯** menu with Export → `ExportDrawer` pattern (see `PlacementsPageHeader`).
|
|
17
|
+
6. **Keyboard hints** → `.cursor/rules/exxat-kbd-shortcuts.mdc`; pair hints with behavior.
|
|
18
|
+
7. **Accessibility** → `.cursor/rules/exxat-accessibility.mdc` + **`AGENTS.md` §8** + `.cursor/skills/exxat-accessibility/SKILL.md` when present in your workspace.
|
|
19
|
+
8. **Data view dashboard (charts)** → **`AGENTS.md` §4.3** + **`exxat-dashboard-view-charts.mdc`** — `ChartFigure`, centralized **`data-view-dashboard-storage`**, **`chart-keyboard-selection`** parity with the `/dashboard` gallery.
|
|
20
|
+
9. **No toast** → **`AGENTS.md` §6.5** + **`exxat-no-toast.mdc`** — do not use **`toast()`** / Sonner / snackbars for product messaging.
|
|
21
|
+
|
|
22
|
+
## Also read
|
|
23
|
+
|
|
24
|
+
- `docs/data-views-pattern.md` — architecture narrative (keep aligned with `AGENTS.md`); **Page vs drawer** with **`AGENTS.md` §6.4**.
|
|
25
|
+
- `docs/command-menu-pattern.md` — global ⌘K palette (search + quick AI vs Ask Leo).
|
|
26
|
+
- `.cursor/rules/exxat-data-tables.mdc`, `exxat-list-page-connected-views.mdc`, `exxat-table-properties-drawer.mdc`, **`exxat-page-vs-drawer.mdc`** (this folder; repo root copy when parent repo open), **`exxat-no-toast.mdc`**, **`exxat-dashboard-view-charts.mdc`** (this folder), **`exxat-command-menu.mdc`**, `exxat-kbd-shortcuts.mdc`, and `exxat-accessibility.mdc`.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — show Kbd hints on primary/secondary actions, search, Ask Leo; avoid browser-reserved chords.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — keyboard shortcuts (`Kbd`)
|
|
7
|
+
|
|
8
|
+
## When to show `Kbd`
|
|
9
|
+
|
|
10
|
+
Use `@/components/ui/kbd` (`Kbd` + `KbdGroup`) anywhere users discover actions by hovering or reading tooltips:
|
|
11
|
+
|
|
12
|
+
- **Primary actions** — main page CTAs (e.g. “New …”, “Save”, “Submit”).
|
|
13
|
+
- **Secondary actions** — overflow menus, “More”, outline companions to a primary button.
|
|
14
|
+
- **Global affordances** — **Search** (table toolbar), **Ask Leo**, **Toggle sidebar**.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
0. **`Kbd` variant MUST match its host surface** (this is the #1 recurring mistake):
|
|
19
|
+
|
|
20
|
+
| Where the `Kbd` renders | Required variant |
|
|
21
|
+
|-------------------------|------------------|
|
|
22
|
+
| Inside a `Button` (primary, secondary, wizard Next/Back/Submit, full-width CTAs in popovers) | **`variant="bare"`** — no bg/border, inherits `currentColor` @ 70 % |
|
|
23
|
+
| Inside a `TooltipContent` | **default `tile`** (no prop) |
|
|
24
|
+
| Inside a `DropdownMenuItem` via `shortcut=` | menu handles it — pass the chord string |
|
|
25
|
+
| Standalone helper text on a surface | **default `tile`** |
|
|
26
|
+
|
|
27
|
+
Glue multi-key chords into **one** bare kbd (`<Kbd variant="bare">⌘⌥K</Kbd>`), not one tile per key. See `new-placement-form.tsx` (Next = `{mod}⏎`, Back = `{mod}{alt}←`) and the primary "Ask Leo" button inside chart insight popovers.
|
|
28
|
+
|
|
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
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { DropdownMenuItem, Shortcut } from "@/components/ui/dropdown-menu"
|
|
33
|
+
|
|
34
|
+
// Visual hint in a menu item:
|
|
35
|
+
<DropdownMenuItem shortcut="⌘⇧E" onSelect={onExport}>Export</DropdownMenuItem>
|
|
36
|
+
|
|
37
|
+
// Global binding — render in a parent that stays mounted (menu items unmount on close):
|
|
38
|
+
<Shortcut keys="⌘⇧E" onInvoke={onExport} />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The hook skips input/textarea/contenteditable targets and any open dialog. Accepts symbols (`⌘⇧⌥⌃⌫⌦⏎↑↓`) and words (`Cmd+Shift+D`, `Alt+P`). Avoid ad-hoc `document.addEventListener("keydown", …)` + `isEditableTarget` — use `<Shortcut>` instead.
|
|
42
|
+
2. **Modifier labels** — Use `useModKeyLabel()` (**⌘** / **Ctrl**) and `useAltKeyLabel()` (**⌥** / **Alt**) from `@/hooks/use-mod-key-label` for tooltips.
|
|
43
|
+
3. **Avoid browser-reserved chords** — Do **not** use combinations that match common browser defaults, including:
|
|
44
|
+
- **⌘⇧N / Ctrl+Shift+N** — private/incognito window
|
|
45
|
+
- **⌘⇧T / Ctrl+Shift+T** — reopen closed tab
|
|
46
|
+
- **⌘⇧O / Ctrl+Shift+O** — bookmark manager (Chromium)
|
|
47
|
+
- **⌘⇧B / Ctrl+Shift+B** — bookmarks bar
|
|
48
|
+
- **⌘L / Ctrl+L** — focus address bar (avoid plain **L** with only mod unless scoped)
|
|
49
|
+
- **⌃⌥L / Ctrl+Alt+L** — screen lock on many Linux desktops (avoid for in-app actions)
|
|
50
|
+
4. **Preferred pattern for app shortcuts** — Use **⌘⌥** / **Ctrl+Alt** + letter (e.g. **⌘⌥N** for “New”, **⌘⌥M** for “More”, **⌘⌥K** for Ask Leo). Table search stays **⌘K** / **Ctrl+K** **without** Alt so **⌘⌥K** does not collide.
|
|
51
|
+
5. **Tooltips** — `Tip` supports `label` as `React.ReactNode` so you can compose text + `KbdGroup`.
|
|
52
|
+
6. **Do not** decorate every control — skip dense tables, icon-only row actions that already have `aria-label`, and third-party widgets.
|
|
53
|
+
|
|
54
|
+
## Reference shortcuts (app)
|
|
55
|
+
|
|
56
|
+
| Action | Shortcut |
|
|
57
|
+
|--------|----------|
|
|
58
|
+
| Toggle main sidebar | ⌘/Ctrl + **B** (`components/ui/sidebar.tsx`) |
|
|
59
|
+
| Table search | ⌘/Ctrl + **K** (no Alt — `DataTable`) |
|
|
60
|
+
| Ask Leo | ⌘/Ctrl + **⌥/Alt** + **K** |
|
|
61
|
+
| New placement (Placements header) | ⌘/Ctrl + **⌥/Alt** + **N** |
|
|
62
|
+
| Placements overflow menu | ⌘/Ctrl + **⌥/Alt** + **M** |
|
|
63
|
+
| Export | ⌘/Ctrl + **⇧/Shift** + **E** |
|
|
64
|
+
| Hide/Show metric section | ⌘/Ctrl + **⌥/Alt** + **H** |
|
|
65
|
+
| Rename (view, tab) | **F2** |
|
|
66
|
+
| Duplicate | ⌘/Ctrl + **D** |
|
|
67
|
+
| Review / Info | ⌘/Ctrl + **I** |
|
|
68
|
+
| Remove / Delete | ⌘/Ctrl + **⌫** |
|
|
69
|
+
| Add view (1..n) | ⌘/Ctrl + **⇧/Shift** + **1..9** |
|
|
70
|
+
| **Submit a workflow** (Create, Save, Export, Apply) | **Enter** (⏎) — scoped to the form/drawer/dialog |
|
|
71
|
+
| **Cancel / dismiss** a workflow | **Esc** (Radix handles for Dialog/Sheet) |
|
|
72
|
+
| **Advance a multi-step wizard** | ⌘/Ctrl + **Enter** (plain Enter stays in the input) |
|
|
73
|
+
| **Back** in a wizard | ⌘/Ctrl + **⌥/Alt** + **←** |
|
|
74
|
+
|
|
75
|
+
## Every workflow primary/secondary action MUST carry shortcuts
|
|
76
|
+
|
|
77
|
+
Every **workflow surface** (form, dialog, drawer, sheet, multi-step wizard final step) MUST bind:
|
|
78
|
+
|
|
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
|
+
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
|
+
|
|
82
|
+
> Tip-on-hover Kbd hints remain correct for **page-level** actions (e.g. "New placement", ⋯ 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
|
+
|
|
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
|
+
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
|
+
- Gate `form.onSubmit` on `step === lastStep` (`if (step !== N) { e.preventDefault(); return }`), **or**
|
|
87
|
+
- Remove `type="submit"` on intermediate Next buttons and bind **⌘Enter** to "Next" via `<Shortcut>`.
|
|
88
|
+
On the final step, plain **Enter** submits and the Kbd hint shows **⏎**.
|
|
89
|
+
4. Examples in-app: `new-placement-form.tsx` (Create placement = Enter on step 5, Back = ⌘⌥←), `export-drawer.tsx` (Export = Enter, Cancel = Esc).
|
|
90
|
+
|
|
91
|
+
## Every action menu MUST carry shortcuts
|
|
92
|
+
|
|
93
|
+
All dropdown action menus (⋯ overflow, view-settings chevron, Add view, row actions) should declare a `shortcut=` on each `DropdownMenuItem` AND pair it with a `<Shortcut>` in a parent that stays mounted. This covers both discoverability (visual hint) and operability (global key binding).
|
|
94
|
+
|
|
95
|
+
Adjust this table when adding new global shortcuts.
|
|
96
|
+
|
|
97
|
+
## See also
|
|
98
|
+
|
|
99
|
+
- **`exxat-ds/AGENTS.md`** §7 — keyboard rules in project context.
|
|
100
|
+
- **`exxat-ds/AGENTS.md`** §7.1 — global command palette (⌘K) vs **Ask Leo** (**⌘⌥K**); **`exxat-ds/docs/command-menu-pattern.md`**.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: ListPageTemplate — shared table state across views, mock + KPI helpers, dashboard tab
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — list page connected views
|
|
7
|
+
|
|
8
|
+
**Authoritative detail:** **`./AGENTS.md` §4.1** and **`docs/data-views-pattern.md`** (mock data, connected views, dashboard view).
|
|
9
|
+
|
|
10
|
+
## When building `ListPageTemplate` + data client
|
|
11
|
+
|
|
12
|
+
1. **One `useTableState` per tab’s table** — Pass full mock/API rows into a single table component; **`list` / `board` / `dashboard`** surfaces consume **`tableState.rows`** (same filters/search/sort as the grid).
|
|
13
|
+
2. **`renderContent`** — Always pass **`view={tab.viewType}`**; use **`key={tab.id}`** (not `viewType`) so switching views does not reset table state. Pass **`onViewChange`** into the table component so **`TablePropertiesDrawer`** stays aligned (**`currentView`** + **`updateTab({ viewType, icon: dataListViewIcon(viewType) })`** — see **`./AGENTS.md` §4.2** and **`.cursor/rules/exxat-table-properties-drawer.mdc`**).
|
|
14
|
+
3. **Mock data** — Typed arrays in **`lib/mock/<entity>.ts`**; KPI builders in **`lib/mock/<entity>-kpi.ts`** returning **`MetricItem[]`** / **`MetricInsight`** from **`@/components/key-metrics`**.
|
|
15
|
+
4. **Dashboard view tab** — Use **`KeyMetrics`** (`variant="flat"` or `"card"`) and the **same** KPI functions with **`tableState.rows`**. Do **not** add standalone `Card` metric grids that duplicate those numbers. For chart-heavy dashboards reuse **`ChartsOverview`** / **`DashboardTabs`** patterns from the main dashboard route when appropriate.
|
|
16
|
+
5. **MUST NOT** ship “not wired” / “switch to table” placeholders for list/board/dashboard when the stack supports those views.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — do not use toast / sonner / transient snackbars for product messaging.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — no toast
|
|
7
|
+
|
|
8
|
+
## MUST NOT
|
|
9
|
+
|
|
10
|
+
- Do **not** use **`toast()`** (e.g. **Sonner**), **snackbar**, or other **transient corner notifications** for product feedback in this app.
|
|
11
|
+
- Do **not** add new imports from **`sonner`** or wire **`Toaster`** for feature surfaces.
|
|
12
|
+
|
|
13
|
+
## Use instead
|
|
14
|
+
|
|
15
|
+
- **`LocalBanner`** / **`SystemBanner`** for persistent, readable messaging.
|
|
16
|
+
- **Inline status** next to the action (e.g. button label change, subtle text under the control, row-level state).
|
|
17
|
+
- **Dialog / drawer** confirmations when the user must acknowledge an outcome.
|
|
18
|
+
|
|
19
|
+
## Why
|
|
20
|
+
|
|
21
|
+
Toasts are easy to miss, stack poorly with complex layouts, and behave inconsistently with screen readers and focus. This product standardizes on **banners + inline + dialogs**.
|
|
22
|
+
|
|
23
|
+
## Authoritative detail
|
|
24
|
+
|
|
25
|
+
- **`AGENTS.md` §6.5**
|
|
26
|
+
- Repo root **`.cursor/rules/exxat-no-toast.mdc`** when the monorepo parent is open
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — when to use a drawer vs a new page for actions and auxiliary UI.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — page vs drawer
|
|
7
|
+
|
|
8
|
+
## Rule
|
|
9
|
+
|
|
10
|
+
- **Drawer / sheet** — Use when the user needs **the current page’s context** *and* a **quick view**, **quick actions**, or a **short auxiliary flow** (parent list or hub stays meaningful behind the panel).
|
|
11
|
+
- **New page (route)** — Use **otherwise**: primary tasks, long-form or multi-step work, or anything that should have its **own URL** / history without the parent page visible.
|
|
12
|
+
|
|
13
|
+
## Product examples (this repo)
|
|
14
|
+
|
|
15
|
+
- **Drawer-appropriate:** `TablePropertiesDrawer`, `ExportDrawer`, lightweight panels that supplement a hub.
|
|
16
|
+
- **Page-appropriate:** Full placement or settings flows that are the main task, multi-screen wizards.
|
|
17
|
+
|
|
18
|
+
## Authoritative detail
|
|
19
|
+
|
|
20
|
+
- **`AGENTS.md` §6.4**
|
|
21
|
+
- **`docs/data-views-pattern.md`** (Page vs drawer)
|
|
22
|
+
- Repo root **`.cursor/rules/exxat-page-vs-drawer.mdc`** when the monorepo parent is open
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: TablePropertiesDrawer must receive currentView and onViewChange when used with ListPageTemplate view tabs
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exxat DS — Table properties + active view
|
|
7
|
+
|
|
8
|
+
**Authoritative detail:** **`AGENTS.md` §4.2** (at repo root).
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
`TablePropertiesDrawer` uses **`currentView`** (`DataListViewType`) for the first summary row (“Board display” vs “Table display”, matching icons/descriptions) and to show **table-only** vs **board-only** sub-panels. If **`currentView`** is omitted, the drawer assumes **table** — wrong when the tab is **Board**, **List**, or **Dashboard**.
|
|
13
|
+
|
|
14
|
+
## MUST
|
|
15
|
+
|
|
16
|
+
When **`ListPageTemplate`** drives **`tab.viewType`** and the page renders **`TablePropertiesDrawer`** (directly or via a toolbar slot):
|
|
17
|
+
|
|
18
|
+
1. Pass **`currentView={view}`** (same value as **`tab.viewType`** passed into your table component).
|
|
19
|
+
2. Pass **`onViewChange`** from **`renderContent={(tab, updateTab) => ...}`** so the drawer’s view-type tiles stay in sync with the tab:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { dataListViewIcon, type DataListViewType } from "@/lib/data-list-view"
|
|
23
|
+
|
|
24
|
+
onViewChange={(v: DataListViewType) =>
|
|
25
|
+
updateTab({ viewType: v, icon: dataListViewIcon(v) })
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
3. Thread **`view`** and **`onViewChange`** through: **client → table component → drawer toolbar → `TablePropertiesDrawer`**.
|
|
30
|
+
|
|
31
|
+
**Reference implementations:** `components/data-list-table.tsx` (`DataListTable`), `components/team-client.tsx` + `team-table.tsx`, `components/compliance-client.tsx` + `compliance-table.tsx`.
|
|
32
|
+
|
|
33
|
+
## MUST NOT
|
|
34
|
+
|
|
35
|
+
- Mount **`TablePropertiesDrawer`** on a multi-view list page **without** **`currentView`** when the active view is known from the tab.
|
|
36
|
+
- Omit **`onViewChange`** if the product shows the **view type** control inside Properties (otherwise tiles cannot update the tab).
|
|
37
|
+
|
|
38
|
+
## See also
|
|
39
|
+
|
|
40
|
+
- **`./AGENTS.md` §4.2**, **§13** checklist
|
package/template/AGENTS.md
CHANGED
|
@@ -16,15 +16,18 @@ Cross-cutting Cursor rules also live in the repo root `.cursor/rules/` (data tab
|
|
|
16
16
|
4. **Before** building or changing **tabs, nav, dialogs, icon-only controls, or color/contrast**, read **§8** and **`.cursor/skills/exxat-accessibility/SKILL.md`** (from monorepo root when the parent repo is open).
|
|
17
17
|
5. **Before** adding or changing **Data view charts** (dashboard tab on list hubs) or **graph keyboard styling**, read **§4.3** and **`exxat-ds/.cursor/rules/exxat-dashboard-view-charts.mdc`**.
|
|
18
18
|
6. **Before** adding or changing **board (kanban) cards** on list hubs, read **§4.4** and the **`exxat-board-cards`** skill (**`.cursor/skills/`** or **`.claude/skills/`** at repo root — same content).
|
|
19
|
-
7. **Before** adding **
|
|
20
|
-
8. **Before** changing
|
|
21
|
-
9. **Before**
|
|
22
|
-
10. **Before** adding **
|
|
23
|
-
11.
|
|
19
|
+
7. **Before** adding **folder, panel, or other non-table view bodies** (centered grids, reusable shells), read **§4.5** and **`.cursor/rules/exxat-list-page-view-shells.mdc`** / **`.cursor/skills/exxat-list-page-view-shells/SKILL.md`**.
|
|
20
|
+
8. **Before** adding or changing **Font Awesome** icons in app UI, read **`.cursor/rules/exxat-fontawesome-icons.mdc`** (Kit subsetting, weights, **`aria-hidden`** on **`<i>`**).
|
|
21
|
+
9. **Before** adding a **primary nav row** that opens a **nested secondary nav panel** (Question bank style), read **§4.6** and **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**.
|
|
22
|
+
10. **Before** adding **onboarding tours, feature walkthroughs, or coach marks**, read **§11** and `references/coach-marks.md`.
|
|
23
|
+
11. **Before** changing the **global command palette (⌘K)** or search/AI entry UX, read **§7.1** and **`docs/command-menu-pattern.md`**.
|
|
24
|
+
12. **Before** choosing **drawer vs new page** for a task flow, read **§6.4** and **`docs/data-views-pattern.md`** (Page vs drawer).
|
|
25
|
+
13. **Before** adding **success/error/confirmation feedback**, read **§6.5** and **`.cursor/rules/exxat-no-toast.mdc`** (no toast or snackbars).
|
|
26
|
+
14. Prefer **composing existing components** over new one-off UI. If something is missing, **extend** shared components under `components/`, not a single page file.
|
|
24
27
|
- **MUST** scan `components/` (especially `components/ui/`, `components/data-views/`, `components/templates/`, `components/key-metrics.tsx`, `components/page-header.tsx`, and the charts/banner/dot-pattern surfaces) **before** writing any new UI. If a primitive or composition already exists, **use it** — don't build a parallel one.
|
|
25
|
-
- **Examples of existing surfaces to reuse:** card grid → `ListPageBoardCard` + `BoardCardIconRow` / `BoardCardTwoLineBlock`; AI / dot animation → `AiThinkingOverlay` + `DotPattern`; search input → `InputGroup` + `InputGroupAddon` + `InputGroupInput`; page title → `PageHeader` (serif via `font-heading`); list hub shell → `ListPageTemplate` (`metrics`, `defaultTabs`, `renderContent`); metrics strip → `KeyMetrics
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
- **Examples of existing surfaces to reuse:** card grid → `ListPageBoardCard` + `BoardCardIconRow` / `BoardCardTwoLineBlock`; AI / dot animation → `AiThinkingOverlay` + `DotPattern`; search input → `InputGroup` + `InputGroupAddon` + `InputGroupInput`; page title → `PageHeader` (serif via `font-heading`); list hub shell → `ListPageTemplate` (`metrics`, `defaultTabs`, `renderContent`); metrics strip → `KeyMetrics`; **view body gutter + centered max-width** → **`ListPageViewFrame`** (**§4.5**).
|
|
29
|
+
15. **Match** naming, imports, and patterns of the nearest reference implementation (usually Placements).
|
|
30
|
+
16. **Before** adding entity **mock data**, a **new view tab**, or **detail/inspector** panels on a list hub, read **`.cursor/rules/exxat-centralized-list-dataset.mdc`** and **`.cursor/skills/exxat-centralized-list-dataset/SKILL.md`** (single **`useTableState`** row bag for every view; **no** parallel mock arrays per view).
|
|
28
31
|
|
|
29
32
|
**Longer narrative and architecture:** `docs/data-views-pattern.md`, `docs/command-menu-pattern.md` (keep in sync with this handbook for big refactors).
|
|
30
33
|
|
|
@@ -34,8 +37,8 @@ Cross-cutting Cursor rules also live in the repo root `.cursor/rules/` (data tab
|
|
|
34
37
|
|
|
35
38
|
1. **User / task instructions** in the current session (highest).
|
|
36
39
|
2. This **`AGENTS.md`** for Exxat DS product patterns.
|
|
37
|
-
3. **`.cursor/rules/*.mdc`** at repo root (`exxat-data-tables`, `exxat-list-page-connected-views`, `exxat-table-properties-drawer`, `exxat-board-cards`, `exxat-page-vs-drawer`, `exxat-no-toast`, `exxat-kbd-shortcuts`, `exxat-accessibility`, `exxat-ds-agents`) and any rules under **`exxat-ds/.cursor/rules/`** (including **`exxat-dashboard-view-charts`** for Data view charts).
|
|
38
|
-
4. Project **skills** under `.cursor/skills/` when relevant — e.g. **shadcn**, **exxat-accessibility** (WCAG / ARIA / touch / contrast), **exxat-board-cards** (kanban card shell, status badges, primitives).
|
|
40
|
+
3. **`.cursor/rules/*.mdc`** at repo root (`exxat-data-tables`, `exxat-list-page-connected-views`, `exxat-centralized-list-dataset`, `exxat-list-page-view-shells`, `exxat-table-properties-drawer`, `exxat-board-cards`, `exxat-page-vs-drawer`, `exxat-no-toast`, `exxat-kbd-shortcuts`, `exxat-accessibility`, `exxat-fontawesome-icons`, `exxat-primary-nav-secondary-panel`, `exxat-ds-agents`) and any rules under **`exxat-ds/.cursor/rules/`** (including **`exxat-dashboard-view-charts`** for Data view charts).
|
|
41
|
+
4. Project **skills** under `.cursor/skills/` when relevant — e.g. **shadcn**, **exxat-accessibility** (WCAG / ARIA / touch / contrast), **exxat-board-cards** (kanban card shell, status badges, primitives), **exxat-list-page-view-shells** (centered view bodies, **`ListPageViewFrame`**), **exxat-centralized-list-dataset** (one **`useTableState`** row bag + shared maps across all list-hub views and **`TablePropertiesDrawer`**).
|
|
39
42
|
|
|
40
43
|
If two documents conflict, prefer the **more specific** rule for the file type, then **newer** team decisions captured in `AGENTS.md`.
|
|
41
44
|
|
|
@@ -76,6 +79,8 @@ If two documents conflict, prefer the **more specific** rule for the file type,
|
|
|
76
79
|
|
|
77
80
|
**Mock data:** Put typed row arrays in **`lib/mock/<entity>.ts`**. Add **`lib/mock/<entity>-kpi.ts`** (or colocated helpers) with pure functions **`entityKpiMetrics(rows)`** / **`entityKpiInsight(rows)`** returning **`MetricItem[]`** / **`MetricInsight`** for **`KeyMetrics`**. The page client passes full mock rows into one table component; KPI helpers receive **`tableState.rows`** inside that component so search/filters apply to list, board, dashboard, and table together.
|
|
78
81
|
|
|
82
|
+
**Centralized dataset (rows + table properties + alternate views):** **MUST** use one **`useTableState`** row bag for the **`DataTable`**, **`TablePropertiesDrawer`** (columns/density on **that** table), and **every** record-bearing **`DataListViewType`** — **folder**, **panel**, **tree**, etc. — via **`tableState.rows`**. **MUST NOT** import a second **`lib/mock/<entity>`** array into a view-only module while the grid filters state; **MUST NOT** fork a duplicate row type for inspectors. Shared **properties**: tab labels **`DATA_LIST_VIEW_TILES`** (`lib/data-list-view.ts`), status **`lib/list-status-badges.ts`**, KPI helpers from **`tableState.rows`**. **Presentation:** non-table bodies use **`ListPageViewFrame`** and **`components/data-views/`** primitives fed by the **same** **`tableState.rows`** (**§4.5**). **Rule + skill:** **`.cursor/rules/exxat-centralized-list-dataset.mdc`**, **`.cursor/skills/exxat-centralized-list-dataset/SKILL.md`**.
|
|
83
|
+
|
|
79
84
|
**Dashboard view tab:** **MUST** reuse **`KeyMetrics`** (same component as the optional template metrics strip) and the same KPI helpers — **MUST NOT** introduce bespoke `Card`-only metric grids for the same numbers. Full-page dashboards may also use **`DashboardTabs`**, **`ChartsOverview`**, etc. (`app/(app)/dashboard/page.tsx`); use those **shared** dashboard components when charts or multi-section layouts are product-appropriate, not one-off duplicates.
|
|
80
85
|
|
|
81
86
|
**Details:** `docs/data-views-pattern.md` (mock data, connected views, dashboard view).
|
|
@@ -125,6 +130,35 @@ Thread **`view`** and **`onViewChange`** from the **client** → **table / toolb
|
|
|
125
130
|
|
|
126
131
|
**Reference:** **`components/data-views/placement-board-card.tsx`**, **`components/team-board-view.tsx`**, **`components/compliance-board-view.tsx`**, **`components/question-bank-board-view.tsx`**, **`components/list-hub-status-badge.tsx`**, **`lib/list-status-badges.ts`**, **`components/data-views/list-page-board-template.tsx`**. **Skill (Cursor + Claude):** **`.cursor/skills/exxat-board-cards/SKILL.md`** and **`.claude/skills/exxat-board-cards/SKILL.md`** (duplicate for Claude Code).
|
|
127
132
|
|
|
133
|
+
### 4.5 View layout shells (centered, reusable, not page-tied)
|
|
134
|
+
|
|
135
|
+
**MUST** keep **shared view chrome** (horizontal gutter, optional **centered max-width** on ultra-wide screens) in **`ListPageViewFrame`** from **`components/data-views/list-page-view-frame.tsx`** (re-exported from **`components/data-views/index.ts`**). Use the exported constants **`LIST_PAGE_VIEW_FRAME_GUTTER`**, **`LIST_PAGE_VIEW_FRAME_MAX_ICON_GRID`** (`max-w-6xl`), and **`LIST_PAGE_VIEW_FRAME_MAX_WIDE`** (`max-w-7xl`) instead of ad-hoc `mx-*` / `max-w-*` pairs in each hub.
|
|
136
|
+
|
|
137
|
+
| Topic | Rule |
|
|
138
|
+
|-------|------|
|
|
139
|
+
| **Folder / icon / panel-style views** | Compose **`FolderGridView`**, **`FinderPanelView`**, or another **`data-views/`** primitive; those shells **should** use **`ListPageViewFrame`** internally or as a direct wrapper so every hub gets the same rhythm. |
|
|
140
|
+
| **New view types** | Implement the **generic** grid/shell under **`components/data-views/`** with render props; **`TeamTable`** / **`QuestionBankTable`** **MUST** only wire data + callbacks — **MUST NOT** own one-off full-page grid markup that another entity would duplicate. |
|
|
141
|
+
| **`DataTable` branch** | **MUST NOT** wrap **`DataTable`** in **`ListPageViewFrame`** when it would **stack** horizontal inset with the table toolbar (**§5**). |
|
|
142
|
+
|
|
143
|
+
**Cursor rule:** **`.cursor/rules/exxat-list-page-view-shells.mdc`**. **Skill:** **`.cursor/skills/exxat-list-page-view-shells/SKILL.md`**.
|
|
144
|
+
|
|
145
|
+
### 4.6 Primary nav hub → secondary panel (nested scope nav)
|
|
146
|
+
|
|
147
|
+
**Use when** a **single primary nav destination** needs **inner scopes** (All / Mine / tree / filters) in a **panel** beside content — **not** as extra **primary** or **collapsible child** rows.
|
|
148
|
+
|
|
149
|
+
**MUST:**
|
|
150
|
+
|
|
151
|
+
| Step | Action |
|
|
152
|
+
|------|--------|
|
|
153
|
+
| **Nav** | Set **`secondaryPanel`** on the **`NavLinkItem`** in **`lib/mock/navigation.tsx`** to a **stable id** (e.g. **`"question-bank"`**). **`url`** stays the **hub route**. |
|
|
154
|
+
| **Registry** | Map that id in **`PANELS`** inside **`components/secondary-panel.tsx`** to a component that renders **panel header** + **secondary nav** UI. |
|
|
155
|
+
| **Route** | Mount **`*PanelActivator`** on the hub that calls **`useAutoPanel(id)`** with the **same id** so the panel opens while the route is mounted (see **`QuestionBankPanelActivator`** on **`QuestionBankClient`**). |
|
|
156
|
+
| **Data** | Keep using **one** **`useTableState`** row bag; **scope** filters via **URL** + shared helpers (**`lib/question-bank-nav.ts`**) so **refresh** and **links** match the panel — **`.cursor/rules/exxat-centralized-list-dataset.mdc`**. |
|
|
157
|
+
|
|
158
|
+
**MUST NOT:** Set **`secondaryPanel`** without **`PANELS[id]`** and **`useAutoPanel`** — users see **collapsed** main nav with **no** panel body.
|
|
159
|
+
|
|
160
|
+
**Cursor rule:** **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**. **Icons in panel:** **`.cursor/rules/exxat-fontawesome-icons.mdc`**.
|
|
161
|
+
|
|
128
162
|
---
|
|
129
163
|
|
|
130
164
|
## 5. Layout alignment (avoid double inset)
|
|
@@ -451,6 +485,9 @@ New pages **SHOULD** namespace keys and version JSON (`v: 1`) for future migrati
|
|
|
451
485
|
| Use `CoachMark` + `useCoachMark` for onboarding tours (§11); register in `coach-mark-registry` | Build one-off walkthrough overlays or custom onboarding modals |
|
|
452
486
|
| Data view charts: **`ChartFigure`** + **`ChartDataTable`**; keyboard highlight via **`chart-keyboard-selection`** (§4.3); layout via **`data-view-dashboard-storage`** | Ad-hoc `localStorage` keys for dashboard layout; opacity-only “selection” without `activeBar`/`activeShape` |
|
|
453
487
|
| Board cards: **`ListPageBoardCard`** shell; status via **`ListHubStatusBadge`** + **`lib/list-status-badges`**; no **`uppercase`** on status chips (§4.4) | One-off board card markup; status as plain body text; duplicated status maps outside **`list-status-badges`**; **empty placeholder** primary hubs (§4.1) |
|
|
488
|
+
| **§4.5** — Non-table view bodies use **`ListPageViewFrame`** (+ **`data-views/`** shells); new grids are generic components, not route-only markup | Duplicated `mx-4` / `max-w-*` per hub; wrapping **`DataTable`** so inset **doubles** (**§5**) |
|
|
489
|
+
| **§4.6** — **`secondaryPanel`** + **`PANELS`** + **`useAutoPanel`** together for nested scope nav | **`secondaryPanel`** id with no panel component or activator |
|
|
490
|
+
| **Font Awesome** — Kit in **`app/layout.tsx`**; **`fa-light` / `fa-solid`** conventions; **`aria-hidden`** on decorative **`<i>`**; run **`fa:subset-audit`** when adding glyphs (**`exxat-fontawesome-icons.mdc`**) | Parallel icon libraries for the same product chrome |
|
|
454
491
|
|
|
455
492
|
---
|
|
456
493
|
|
|
@@ -458,9 +495,11 @@ New pages **SHOULD** namespace keys and version JSON (`v: 1`) for future migrati
|
|
|
458
495
|
|
|
459
496
|
Copy and complete when implementing or reviewing:
|
|
460
497
|
|
|
498
|
+
- [ ] **Centralized dataset:** One **`useTableState`** / **`tableState.rows`** for **all** view tabs and inspectors; **TablePropertiesDrawer** on the **same** `DataTable`; **no** parallel mock arrays per view — **`.cursor/rules/exxat-centralized-list-dataset.mdc`**.
|
|
461
499
|
- [ ] **Reuse:** `ListPageTemplate`, `DataTable` / `useTableState`, `TablePropertiesDrawer` — no parallel bespoke tabs/filters.
|
|
462
500
|
- [ ] **Tabs:** Any main `DataTable` sits under `ListPageTemplate` with appropriate view tabs.
|
|
463
501
|
- [ ] **Inset:** No double horizontal padding around `DataTable`.
|
|
502
|
+
- [ ] **§4.5 View shells:** Folder / panel / icon views use **`ListPageViewFrame`** (or a **`data-views/`** component that uses it); no page-tied-only grid wrappers; **`DataTable`** not double-wrapped (**§5**).
|
|
464
503
|
- [ ] **> ~10 items:** Search, filter, sort, properties (per surface type in §6.1).
|
|
465
504
|
- [ ] **Exportable data:** Filled primary CTA; **⋯** menu with Export → `ExportDrawer`.
|
|
466
505
|
- [ ] **Primary hub + large data:** Same composition as `DataListClient` / `TeamClient` (template + metrics when applicable).
|
|
@@ -479,7 +518,9 @@ Copy and complete when implementing or reviewing:
|
|
|
479
518
|
- [ ] **Accessibility:** §8 — tablist/toolbar patterns, **≥24px** targets for icon-only controls, contrast on tinted surfaces, dialog/sheet/drawer **titles**; **every icon that communicates info has a text alternative** — adjacent label (preferred) OR `aria-label` + `Tooltip` (§8.6 Case A/B/C, covers informational icons like calendar-for-date, status dots, AND icon-only buttons); **kbd inside a button uses `<Kbd variant="bare">`** (§8.7); re-run **axe** on Placements when changing views toolbar.
|
|
480
519
|
- [ ] **Coach marks (§11):** `CoachMark` + `useCoachMark`; register in **`coach-mark-registry`**; use **`enabled`** / **`dependsOnDismissedFlowId`** when a tour must wait for another flow or a specific view (e.g. **dashboard**); customize-dashboard flows use **`lib/dashboard-customize-coach-mark.ts`**.
|
|
481
520
|
- [ ] **Application sidebar (§9.1):** **`ExxatProductLogo`** for product; **`logoDevUrl`** for schools; team switcher **`DropdownMenuContent`** not trigger-width-only (**`!w-max`** + min/max width); expanded switcher **`h-auto min-h-12`**; no **`CollapsibleTrigger` → `SidebarMenuButton` with `tooltip` prop**; child links **popover** on icon rail; profile **`stockPortraitUrl`** + **`referrerPolicy="no-referrer"`** on **`AvatarImage`**.
|
|
521
|
+
- [ ] **Secondary panel (§4.6):** If **`NavLinkItem.secondaryPanel`** is set — **`PANELS[id]`** in **`secondary-panel.tsx`**, hub mounts **`useAutoPanel(id)`**, scope syncs to URL + **`tableState.rows`** — **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**.
|
|
522
|
+
- [ ] **Font Awesome:** New glyphs covered by **`fa:subset-audit`** / Kit subset; decorative **`<i>`** has **`aria-hidden`**; icon-only controls follow **§8.6** — **`.cursor/rules/exxat-fontawesome-icons.mdc`**.
|
|
482
523
|
|
|
483
524
|
---
|
|
484
525
|
|
|
485
|
-
*Last updated: §
|
|
526
|
+
*Last updated: §4.1 centralized dataset + presentation; §4.5–§4.6 view shells + secondary panel; Font Awesome rule; §9.1 application sidebar shell; §4.4 board cards; §6.5 no toast; §7.1 command palette; §13 checklist.*
|
|
@@ -27,7 +27,7 @@ export default function Page() {
|
|
|
27
27
|
<PrimaryPageTemplate siteHeader={{ title: "Dashboard" }}>
|
|
28
28
|
<DashboardTabs
|
|
29
29
|
title="Dashboard"
|
|
30
|
-
subtitle="
|
|
30
|
+
subtitle="Design system shell · sample metrics and charts"
|
|
31
31
|
metrics={DASHBOARD_METRICS}
|
|
32
32
|
insight={DASHBOARD_INSIGHT}
|
|
33
33
|
/>
|
|
@@ -1,28 +1,44 @@
|
|
|
1
|
+
import Link from "next/link"
|
|
1
2
|
import { notFound } from "next/navigation"
|
|
2
|
-
import { PlacementDetail } from "@/components/placement-detail"
|
|
3
3
|
import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
4
5
|
import { getPlacementById } from "@/lib/mock/placements"
|
|
5
6
|
|
|
6
|
-
export default async function
|
|
7
|
+
export default async function RecordDetailPage({
|
|
7
8
|
params,
|
|
8
9
|
}: {
|
|
9
10
|
params: Promise<{ id: string }>
|
|
10
11
|
}) {
|
|
11
12
|
const { id } = await params
|
|
12
|
-
const
|
|
13
|
-
if (!
|
|
13
|
+
const row = getPlacementById(Number(id))
|
|
14
|
+
if (!row) notFound()
|
|
14
15
|
|
|
15
16
|
return (
|
|
16
17
|
<PrimaryPageTemplate
|
|
17
18
|
siteHeader={{
|
|
18
|
-
title: "
|
|
19
|
-
breadcrumbs: [{ label: "
|
|
19
|
+
title: "Record",
|
|
20
|
+
breadcrumbs: [{ label: "List hub", href: "/data-list" }],
|
|
20
21
|
}}
|
|
21
|
-
maxWidthClassName="max-w-
|
|
22
|
+
maxWidthClassName="max-w-2xl"
|
|
22
23
|
contentClassName="px-4 lg:px-6 py-6"
|
|
23
24
|
bodyClassName="overflow-y-auto"
|
|
24
25
|
>
|
|
25
|
-
<
|
|
26
|
+
<p className="text-sm text-muted-foreground mb-6">
|
|
27
|
+
Demo read-only detail — replace with your domain route and data fetch.
|
|
28
|
+
</p>
|
|
29
|
+
<dl className="grid gap-3 text-sm sm:grid-cols-[minmax(0,10rem)_1fr]">
|
|
30
|
+
<dt className="text-muted-foreground">Primary label</dt>
|
|
31
|
+
<dd className="font-medium text-foreground">{row.student}</dd>
|
|
32
|
+
<dt className="text-muted-foreground">Status</dt>
|
|
33
|
+
<dd>{row.status}</dd>
|
|
34
|
+
<dt className="text-muted-foreground">Site</dt>
|
|
35
|
+
<dd>{row.site}</dd>
|
|
36
|
+
<dt className="text-muted-foreground">Program</dt>
|
|
37
|
+
<dd>{row.program}</dd>
|
|
38
|
+
</dl>
|
|
39
|
+
<Button asChild variant="outline" className="mt-8">
|
|
40
|
+
<Link href="/data-list">Back to list</Link>
|
|
41
|
+
</Button>
|
|
26
42
|
</PrimaryPageTemplate>
|
|
27
43
|
)
|
|
28
44
|
}
|
|
@@ -3,7 +3,7 @@ import { NewPlacementForm } from "@/components/new-placement-form"
|
|
|
3
3
|
import { SidebarAutoCollapse } from "@/components/sidebar-auto-collapse"
|
|
4
4
|
import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
|
|
5
5
|
|
|
6
|
-
export default function
|
|
6
|
+
export default function NewRecordPage() {
|
|
7
7
|
return (
|
|
8
8
|
<PrimaryPageTemplate
|
|
9
9
|
beforeSiteHeader={<SidebarAutoCollapse />}
|
|
@@ -14,17 +14,20 @@ export default function NewPlacementPage() {
|
|
|
14
14
|
<Link
|
|
15
15
|
href="/data-list"
|
|
16
16
|
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-interactive-hover-foreground transition-colors mb-5 group"
|
|
17
|
-
aria-label="Back to
|
|
17
|
+
aria-label="Back to list hub"
|
|
18
18
|
>
|
|
19
19
|
<i className="fa-light fa-arrow-left text-xs transition-transform group-hover:-translate-x-0.5" aria-hidden="true" />
|
|
20
20
|
Back
|
|
21
21
|
</Link>
|
|
22
22
|
<h1
|
|
23
|
-
className="text-[2.25rem] font-semibold tracking-tight leading-none text-foreground mb-
|
|
23
|
+
className="text-[2.25rem] font-semibold tracking-tight leading-none text-foreground mb-2"
|
|
24
24
|
style={{ fontFamily: "var(--font-heading)" }}
|
|
25
25
|
>
|
|
26
|
-
New
|
|
26
|
+
New record
|
|
27
27
|
</h1>
|
|
28
|
+
<p className="text-sm text-muted-foreground mb-8">
|
|
29
|
+
Multi-step wizard shell (demo fields) — swap the form for your product flow.
|
|
30
|
+
</p>
|
|
28
31
|
<NewPlacementForm />
|
|
29
32
|
</PrimaryPageTemplate>
|
|
30
33
|
)
|