@exxatdesignux/ui 0.1.0 → 0.2.7
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/bin/cli.mjs +176 -0
- package/bin/init.mjs +15 -1
- package/bin/sync-extras.mjs +65 -0
- package/consumer-extras/README.md +21 -0
- package/consumer-extras/cursor-skills/exxat-accessibility/SKILL.md +282 -0
- package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +68 -0
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +99 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +713 -0
- package/consumer-extras/cursor-skills/exxat-fontawesome-icons/SKILL.md +31 -0
- package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +36 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +27 -0
- package/consumer-extras/patterns/command-menu-pattern.md +45 -0
- package/consumer-extras/patterns/data-views-pattern.md +167 -0
- package/package.json +7 -3
- package/src/components/ui/sidebar.tsx +7 -2
- 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/app/globals.css +1 -1
- 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/page-header.tsx +149 -7
- 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 -39
- 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 +31 -2
- 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/hooks/use-app-theme.ts +1 -1
- package/template/hooks/use-coach-mark.ts +1 -1
- package/template/hooks/use-location-hash.ts +15 -0
- package/template/hooks/use-mobile.ts +1 -1
- package/template/hooks/use-mod-key-label.ts +1 -1
- 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/date-filter.ts +1 -1
- 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-header-collaborators.ts +14 -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/lib/utils.ts +1 -1
- 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/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,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: exxat-fontawesome-icons
|
|
3
|
+
description: Font Awesome Pro in Exxat DS — Kit script, fa-light/fa-solid, subset audit (fa:subset-audit), aria-hidden on decorative icons, no parallel icon libraries for product chrome. Use when adding or changing icons, nav glyphs, table toolbar icons, or debugging missing kit glyphs.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exxat DS — Font Awesome icons
|
|
8
|
+
|
|
9
|
+
**Cursor rule:** `.cursor/rules/exxat-fontawesome-icons.mdc`
|
|
10
|
+
**Handbook:** `apps/web/AGENTS.md` §1 (item 8), §8 accessibility for icon-only / informational cases.
|
|
11
|
+
|
|
12
|
+
## Stack
|
|
13
|
+
|
|
14
|
+
- **Kit:** `apps/web/app/layout.tsx` loads the Font Awesome Pro kit (subset via `fontawesome-subset.manifest.json`).
|
|
15
|
+
- **Audit:** `pnpm --filter web fa:subset-audit` — refresh kit icon selection when adding new glyph names.
|
|
16
|
+
|
|
17
|
+
## MUST
|
|
18
|
+
|
|
19
|
+
1. **`<i className="fa-light fa-…" aria-hidden="true" />`** when a parent provides the accessible name (`Button` + `aria-label`, `Tip`, adjacent visible text).
|
|
20
|
+
2. **`fa-light`** for default; **`fa-solid`** for active/selected where the app already does (sidebar rows, tabs).
|
|
21
|
+
3. **Static** `className` strings where possible so the audit script can find class names.
|
|
22
|
+
|
|
23
|
+
## MUST NOT
|
|
24
|
+
|
|
25
|
+
- Introduce a **second** icon library for the same surfaces (duplicate Lucide/Heroicons for nav, hubs, toolbars).
|
|
26
|
+
- Put the **only** accessible name on `<i>` without parent labelling — see **exxat-accessibility** Case B/C.
|
|
27
|
+
|
|
28
|
+
## See also
|
|
29
|
+
|
|
30
|
+
- `.cursor/rules/exxat-accessibility.mdc`
|
|
31
|
+
- `.cursor/skills/exxat-accessibility/SKILL.md`
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: exxat-list-page-view-shells
|
|
3
|
+
description: >-
|
|
4
|
+
Centered, reusable layout for ListPageTemplate view bodies — ListPageViewFrame,
|
|
5
|
+
data-views/ primitives, and rules to avoid page-tied view markup. Use when adding
|
|
6
|
+
folder/panel/custom views, fixing wide-viewport layout, or extracting view UI from a single hub.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# List-page view shells (Exxat DS)
|
|
10
|
+
|
|
11
|
+
## When to use this skill
|
|
12
|
+
|
|
13
|
+
- Adding or changing **`viewType`** surfaces: **folder**, **panel**, list/board chrome, icon grids, OS-style explorers.
|
|
14
|
+
- User or PR asks for **“centered”**, **“reusable view”**, or **“not tied to one page”** layout.
|
|
15
|
+
- You see **duplicated** `mx-4 lg:mx-6`, `mx-auto max-w-6xl`, or similar wrappers in multiple `*-table.tsx` files.
|
|
16
|
+
|
|
17
|
+
## Instructions
|
|
18
|
+
|
|
19
|
+
1. **Gutter + optional max width** — Import **`ListPageViewFrame`** from **`@/components/data-views/list-page-view-frame`** (or **`@/components/data-views`**). Wrap the view body (usually below **`DataTableToolbar`** when the view shares the table toolbar).
|
|
20
|
+
- Pass **`maxWidthClassName={LIST_PAGE_VIEW_FRAME_MAX_ICON_GRID}`** for dense tile grids.
|
|
21
|
+
- Pass **`maxWidthClassName={LIST_PAGE_VIEW_FRAME_MAX_WIDE}`** when the view includes toolbar rows + breadcrumbs + grid.
|
|
22
|
+
2. **Do not double-gutter** — If **`DataTable`** already provides horizontal inset for the **table** view, do **not** wrap that branch in **`ListPageViewFrame`**. Use the frame on **sibling** view branches (folder, panel, etc.) only.
|
|
23
|
+
3. **Reuse before inventing** — Prefer **`FolderGridView`**, **`FinderPanelView`**, **`ListPageBoardTemplate`**, **`ListPageViewFrame`**. If a new pattern appears twice, **promote** it to **`components/data-views/`** with domain-agnostic props.
|
|
24
|
+
4. **Entity-specific → generic** — If logic is “any tree + any row type”, build **`components/data-views/<generic-name>.tsx`** and keep **`question-bank-*`** (or similar) as a thin composition + mock types.
|
|
25
|
+
|
|
26
|
+
## Checklist
|
|
27
|
+
|
|
28
|
+
- [ ] View body uses **`ListPageViewFrame`** (or a component that uses it internally, e.g. **`FolderGridView`**).
|
|
29
|
+
- [ ] No **extra** horizontal padding around **`DataTable`** vs **`AGENTS.md` §5**.
|
|
30
|
+
- [ ] New grid/shell lives under **`components/data-views/`** when a second hub could reuse it.
|
|
31
|
+
|
|
32
|
+
## References
|
|
33
|
+
|
|
34
|
+
- **`apps/web/components/data-views/list-page-view-frame.tsx`** — implementation + exported constants.
|
|
35
|
+
- **`apps/web/AGENTS.md` §4.5** — MUST/MUST NOT.
|
|
36
|
+
- **`.cursor/rules/exxat-list-page-view-shells.mdc`**.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: exxat-primary-nav-secondary-panel
|
|
3
|
+
description: Exxat DS pattern — one primary sidebar row opens a nested SecondaryPanel (Question bank). NavLinkItem.secondaryPanel id, PANELS registry, useAutoPanel on hub, URL scope + same useTableState rows. Use when adding hub scoped nav (All/My/tree) beside content.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exxat DS — primary nav → secondary panel
|
|
8
|
+
|
|
9
|
+
**Cursor rule:** `.cursor/rules/exxat-primary-nav-secondary-panel.mdc`
|
|
10
|
+
**Handbook:** `apps/web/AGENTS.md` §4.6
|
|
11
|
+
|
|
12
|
+
## Wiring checklist
|
|
13
|
+
|
|
14
|
+
1. **`lib/mock/navigation.tsx`** — set **`secondaryPanel: "<id>"`** on the primary **`NavLinkItem`**; **`url`** = hub route.
|
|
15
|
+
2. **`components/secondary-panel.tsx`** — add **`PANELS["<id>"]`** → panel shell (title, optional search) + secondary nav component.
|
|
16
|
+
3. **Hub client** — mount **`*PanelActivator`** with **`useAutoPanel("<id>")`** (same id) for the lifetime of the route (e.g. `QuestionBankPanelActivator`).
|
|
17
|
+
4. **Data** — keep **one** **`useTableState`** / **`tableState.rows`**; drive scope from **URL** + small helpers (see **`lib/question-bank-nav.ts`**).
|
|
18
|
+
|
|
19
|
+
## MUST NOT
|
|
20
|
+
|
|
21
|
+
- Set **`secondaryPanel`** without **`PANELS[id]`** + **`useAutoPanel`** — broken empty rail.
|
|
22
|
+
- Use this for full product areas that belong as **primary** or **collapsible child** rows.
|
|
23
|
+
|
|
24
|
+
## Reference
|
|
25
|
+
|
|
26
|
+
- `components/app-sidebar.tsx` — `openPanel` on same-route primary click.
|
|
27
|
+
- `components/question-bank-secondary-nav.tsx` + `lib/question-bank-nav.ts`.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Global command palette (⌘K)
|
|
2
|
+
|
|
3
|
+
**Code:** `components/command-menu.tsx`, config **`buildCommandMenuConfig()`** in **`lib/command-menu-config.ts`**, wired in **`app/(app)/layout.tsx`** via **`CommandMenuProvider`**. Searchable row data is built in **`lib/command-menu-search-data.ts`** (not inside the shell component).
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
The command menu is the app’s **global search and AI entry**—one place to jump to routes, library pages, patterns, and AI starters. It is **not** a duplicate of the left sidebar; it **indexes** product surfaces for fast discovery.
|
|
8
|
+
|
|
9
|
+
## User model
|
|
10
|
+
|
|
11
|
+
| Need | Where it lives |
|
|
12
|
+
|------|----------------|
|
|
13
|
+
| **Find a page, component, or pattern** | Palette — filterable list, **Enter** to navigate. |
|
|
14
|
+
| **Quick AI / natural language** | Prefer **short answers or “research” snippets inside the palette** when the product can return compact, citeable results without leaving the flow. |
|
|
15
|
+
| **Long or complex answers** | **Ask Leo** right sidebar — multi-step reasoning, long-form help, or anything that does not fit a small results surface. |
|
|
16
|
+
|
|
17
|
+
**Design rule:** Treat the palette as **progressive disclosure**—lightweight results first in-menu; **escalate** to Ask Leo when the answer is too large or needs a dedicated conversation surface.
|
|
18
|
+
|
|
19
|
+
## Keyboard
|
|
20
|
+
|
|
21
|
+
- **⌘K / Ctrl+K** — open / toggle the palette (global listener in `CommandMenu`).
|
|
22
|
+
- **Ask Leo** remains **⌘⌥K / Ctrl+Alt+K** (see `.cursor/rules/exxat-kbd-shortcuts.mdc`) so it does not collide with table search where both exist.
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
- **Static groups** (AI suggestions, Navigation, Components, Patterns) live in **`lib/command-menu-config.ts`**.
|
|
27
|
+
- Optional **`dataGroups`** are passed to **`buildCommandMenuConfig({ dataGroups })`** from the app shell. **Implementation:** **`getCommandMenuSearchDataGroups()`** in **`lib/command-menu-search-data.ts`** maps mock/API rows (e.g. placements with student names) to **`CommandMenuItem`** rows with **`label`**, **`href`**, and **`keywords`** for cmdk filtering. The layout uses **`buildCommandMenuConfig({ dataGroups: getCommandMenuSearchDataGroups() })`**. Keep domain mapping **outside** `command-menu.tsx` so data sources can change without editing the shell.
|
|
28
|
+
- **`searchOnly` on `CommandMenuGroup`:** cmdk shows **every** mounted item when the search box is empty. For large indexes (hundreds of placements), set **`searchOnly: true`** on that group so **`command-menu.tsx`** does not render it until the user has typed a non-empty query. Static groups (AI, Navigation, …) stay visible on open; the data group appears once the user searches.
|
|
29
|
+
|
|
30
|
+
## Files (quick reference)
|
|
31
|
+
|
|
32
|
+
| Piece | Location |
|
|
33
|
+
|-------|----------|
|
|
34
|
+
| Palette UI | `components/command-menu.tsx` |
|
|
35
|
+
| Static groups + `buildCommandMenuConfig` | `lib/command-menu-config.ts` (`CommandMenuGroup.searchOnly`) |
|
|
36
|
+
| Row → menu items (placements, etc.) | `lib/command-menu-search-data.ts` |
|
|
37
|
+
| Provider + `dataGroups` | `app/(app)/layout.tsx` |
|
|
38
|
+
|
|
39
|
+
## Sidebar
|
|
40
|
+
|
|
41
|
+
**“Search or ask Leo”** in the sidebar opens the same palette; shortcuts and copy should stay aligned with **`AGENTS.md` §7.1**.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
Keep this document aligned with **`exxat-ds/AGENTS.md` §7.1** when behavior or copy changes.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Data views pattern (table, list, board)
|
|
2
|
+
|
|
3
|
+
> **Canonical rules for agents (MUST/MUST NOT, checklists):** [`AGENTS.md`](../AGENTS.md) in this package (including **§8 Accessibility**). This file is the long-form narrative; keep both aligned when patterns change.
|
|
4
|
+
|
|
5
|
+
This document describes how list pages combine **views**, **toolbar** behavior, **filters**, **properties**, and **persistence** in this codebase.
|
|
6
|
+
|
|
7
|
+
## Reuse existing components (required)
|
|
8
|
+
|
|
9
|
+
**Prefer composing what already exists** over rebuilding one-off tabs, search, filters, or property panels. The **Placements** flow is the reference implementation; new list/table/board pages should wire the same building blocks with new data and column definitions.
|
|
10
|
+
|
|
11
|
+
| Need | Reuse | Where Placements uses it |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| **View tabs** (table / list / board, lifecycle filters) | `ListPageTemplate` (`ViewTab`, `renderContent`, optional metrics + export) | `components/data-list-client.tsx` + `components/templates/list-page.tsx` |
|
|
14
|
+
| **Table shell** (search, filter bar, sort, grouping, columns, pagination) | `DataTable`, `DataTableToolbar`, `useTableState` | `components/data-table/`, `components/data-list-table.tsx` |
|
|
15
|
+
| **Properties drawer** (display, columns, filters, sort, view type tiles) | `TablePropertiesDrawer` from `@/components/table-properties` | `DrawerToolbar` / list–board shells in `data-list-table.tsx` |
|
|
16
|
+
| **Board / list** | `PlacementsBoardView`, `PlacementsListView` + same `useTableState` | `DataListTable` |
|
|
17
|
+
| **Page header** (primary CTA + More ⋯ + export) | `PlacementsPageHeader` or `TeamPageHeader` | `components/placements-page-header.tsx`, `components/team-page-header.tsx` |
|
|
18
|
+
| **Team page (primary template)** | `TeamClient` = `ListPageTemplate` + `KeyMetrics` + `TeamPageHeader` + `TeamTable` (same composition as `DataListClient`) | `components/team-client.tsx`, `lib/mock/team-kpi.ts` |
|
|
19
|
+
| **Team roster** | `TeamTable` — `DataTable` + `useTableState` + `TablePropertiesDrawer`; list/board/dashboard read **`tableState.rows`** | `components/team-table.tsx` |
|
|
20
|
+
| **Dashboard view (list tab)** | **`KeyMetrics`** (`variant="flat"` or `"card"`) — same KPI system as the template metrics strip; **do not** add ad-hoc `Card` grids for entity summaries | `TeamTable` dashboard branch, `lib/mock/team-kpi.ts` |
|
|
21
|
+
| **Export** | `ExportDrawer` | `ListPageTemplate` export props; `DataListClient` |
|
|
22
|
+
| **View body layout** (gutter + centered max-width for folder / icon / panel-style content) | **`ListPageViewFrame`** (`components/data-views/list-page-view-frame.tsx`, re-exported from `components/data-views`) | **`FolderGridView`** (uses the frame); **`QuestionBankOsFolderView`** — see **`AGENTS.md` §4.5** |
|
|
23
|
+
|
|
24
|
+
**Rules:** (1) Import and compose these components; pass **props** and **column defs** for your entity. (2) If something is missing, **extend the shared component** under `components/` (e.g. a new optional slot on `DataTableToolbar`) rather than copying markup into a single page. (3) Card-only or lightweight pages may use a smaller **Properties** sheet only when there is **no** table—otherwise use `TablePropertiesDrawer` with `DataTable` (see Team).
|
|
25
|
+
|
|
26
|
+
### Table pages must use view tabs
|
|
27
|
+
|
|
28
|
+
If the page uses a **`DataTable`** (or equivalent data grid) as the main surface, it **must** sit under **`ListPageTemplate`** so users get the **views toolbar** (tabs, add view, per-tab settings). The default `ViewTab` entries should include at least one tab whose `viewType` is **`table`** when the primary experience is tabular data. **Do not** render a `DataTable` alone under only `PageHeader` + body.
|
|
29
|
+
|
|
30
|
+
- **Reference:** `DataListClient` (Placements) and `TeamClient` (Team).
|
|
31
|
+
- **Rationale:** Consistent navigation, saved views, per-tab view type (table / list / board), and export at the template level.
|
|
32
|
+
|
|
33
|
+
## View layout frame (centered, reusable)
|
|
34
|
+
|
|
35
|
+
Non-table view branches (e.g. **folder** icon grid, **panel** finder, OS-style folder explorer) **should** use **`ListPageViewFrame`** for the same horizontal gutter and optional **`max-w-*`** centering as other hubs — **not** hand-copied `mx-4 lg:mx-6` + `mx-auto max-w-6xl` in each `*-table.tsx`. New view types belong in **`components/data-views/`** as **generic** components with render props; hub tables only wire **`tableState.rows`** and callbacks.
|
|
36
|
+
|
|
37
|
+
**Handbook:** **`AGENTS.md` §4.5**. **Cursor:** **`.cursor/rules/exxat-list-page-view-shells.mdc`**. **Skill:** **`.cursor/skills/exxat-list-page-view-shells/SKILL.md`**. **Do not** wrap **`DataTable`** in the frame if that stacks padding with the table toolbar (**`AGENTS.md` §5**).
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
- **Page shell** — `ListPageTemplate` owns the views toolbar (tabs), optional metrics, and export drawer. Content for the active tab is rendered via `renderContent`.
|
|
42
|
+
- **Per–lifecycle (or category) data** — `DataListTable` swaps **columns** and **row sets** based on `lifecycleTabId` (e.g. All / Upcoming / Ongoing / Completed). Each lifecycle tab gets its **own** `DataListTable` instance (`key={tab.filterId}`) so `useTableState` resets correctly when columns change.
|
|
43
|
+
- **Shared table state** — `useTableState` holds sort, search, filters, grouping, column order/visibility/pins/widths, row height, gridlines, and pagination flags. **Table**, **list**, **board**, and **dashboard** all read the same state so switching view type keeps behavior aligned.
|
|
44
|
+
|
|
45
|
+
## Mock data and connected views
|
|
46
|
+
|
|
47
|
+
1. **Put entity rows in `lib/mock/<entity>.ts`** — Export a typed array (e.g. `TeamMember[]`, `Placement[]`) and reuse it from the page client and from KPI helpers.
|
|
48
|
+
2. **KPI / summary helpers** — Add `lib/mock/<entity>-kpi.ts` (or next to the mock file) with pure functions **`entityKpiMetrics(rows: T[])`** and **`entityKpiInsight(rows: T[])`** returning `MetricItem[]` and `MetricInsight` from `@/components/key-metrics`. Drive **both** the template **`metrics`** slot and the **dashboard view** from the **same helpers**, passing **`tableState.rows`** in the table component so filters/search apply everywhere.
|
|
49
|
+
3. **Single table component** — One component (e.g. `TeamTable`) receives **`members`** (full mock) + **`view`**. It calls **`useTableState(members, columns, …)`** once. Branch on `view`:
|
|
50
|
+
- **`table`** → `DataTable` with that state.
|
|
51
|
+
- **`list` / `board`** → `DataTableToolbar` + list/board UI with **`tableState.rows`**.
|
|
52
|
+
- **`dashboard`** → `DataTableToolbar` + **`KeyMetrics`** (and/or other **existing** dashboard building blocks — `ChartsOverview` only when charts are product-appropriate) fed by **`teamKpiMetrics(tableState.rows)`** / **`teamKpiInsight(tableState.rows)`** — **not** bespoke `Card` grids duplicating KPIs.
|
|
53
|
+
4. **Client wiring** — `renderContent` always renders the table component with **`view={tab.viewType}`** and **`key={tab.id}`** (not `viewType`) so switching views does not remount state. **Do not** show “not wired” placeholders for list/board/dashboard when the table stack supports those views.
|
|
54
|
+
5. **Full-route dashboards** — The **`/dashboard`** page uses **`DashboardTabs`**, **`KeyMetrics`**, and **`ChartsOverview`** with `lib/mock/dashboard.ts`. List-page **dashboard view** is a **narrower** slice: reuse **`KeyMetrics`** (+ entity KPI helpers) first; add charts only when they match the entity and reuse **`ChartsOverview`** patterns from `components/charts-overview.tsx`.
|
|
55
|
+
|
|
56
|
+
## Table vs list vs board vs dashboard
|
|
57
|
+
|
|
58
|
+
| Concern | Table | List | Board | Dashboard (view tab) |
|
|
59
|
+
| --- | --- | --- | --- | --- |
|
|
60
|
+
| Primary surface | `DataTable` | `PlacementsListView` / entity list | `PlacementsBoardView` / entity board | **`KeyMetrics`** (+ optional charts via shared dashboard components) |
|
|
61
|
+
| Data source | `useTableState` | **`tableState.rows`** | **`tableState.rows`** | **`tableState.rows`** via KPI helpers |
|
|
62
|
+
| Column headers / labels | `showColumnLabels` | Same source columns, list layout | Phase columns + optional board column menu | N/A (metrics from same columns/filters) |
|
|
63
|
+
| Row click / navigation | From `DataListTable` | From list shell | Card `onOpen` | N/A |
|
|
64
|
+
| Pagination | Optional `PaginationBar` + `CountSyncer` | Same pattern | N/A (board uses phase columns) | N/A |
|
|
65
|
+
|
|
66
|
+
## Toolbar and properties
|
|
67
|
+
|
|
68
|
+
- **Search** — Global quick search lives in table state (`search` / `showToolbarSearch` from display options). Board phase columns can add local search in the board column header.
|
|
69
|
+
- **Filters** — Built from `ColumnDef.filter` and `filterFields` passed to `TablePropertiesDrawer`. Connectors between filters (`and` / `or`) are part of table state.
|
|
70
|
+
- **Sort / group** — Sort rules and `groupBy` are in table state; board menu proxies the same actions when `boardColumnMenu` is wired.
|
|
71
|
+
- **Properties drawer** — `TablePropertiesDrawer` is **generic**: supply `filterFields`, `fieldDefinitionsForDrawer`, `resolveColumnLabel`, `activeFilters`, sort/column handlers, `displayOptions`, and optional view-type tiles. Domain-specific defaults (e.g. `FILTER_FIELDS` in `types.ts`) are optional; new pages can pass their own definitions.
|
|
72
|
+
|
|
73
|
+
**Import:** `@/components/table-properties` re-exports the drawer and types.
|
|
74
|
+
|
|
75
|
+
## Board UI reuse
|
|
76
|
+
|
|
77
|
+
**Handbook:** **`AGENTS.md` §4.4** — board card shell, badge row, shared status maps, and MUST/MUST NOT. **Cursor:** **`.cursor/rules/exxat-board-cards.mdc`**, skill **`.cursor/skills/exxat-board-cards/SKILL.md`**.
|
|
78
|
+
|
|
79
|
+
- **Card shell** — `components/data-views/list-page-board-card.tsx` — **`ListPageBoardCard`**, **`ListPageBoardCardHeader`**, **`ListPageBoardCardTitleRow`**, **`ListPageBoardCardAvatar`**, **`ListPageBoardCardBadgeRow`**, **`ListPageBoardCardBody`**, **`ListPageBoardCardSecondary`**. All product board cards on list hubs **should** use this shell (same **`Card` `size="sm"`** pattern as Placements).
|
|
80
|
+
- **Primitives** — `components/data-views/board-card-primitives.tsx` — `BoardCardIconRow`, `BoardCardTwoLineBlock` (optional `line2`), `BoardNewCardPlaceholder`, `lineClampClass`.
|
|
81
|
+
- **Status (Team & Compliance)** — `lib/list-status-badges.ts` — single source for label strings + badge `className` tails for **table, list, and board**. Do **not** pair with `uppercase` on the Badge (labels are sentence case, aligned with Placements `BoardStatusBadge`).
|
|
82
|
+
- **Owner initials** — `lib/initials-from-name.ts` when mock rows have a display name but no `initials` field.
|
|
83
|
+
- **Shared column shell** — `components/data-views/list-page-board-template.tsx` — `ListPageBoardTemplate` + `ListPageBoardColumnDef<T>`: define columns with `filter` predicates, `renderCard`, `getRowKey`. Used by **Team** and **Compliance** boards; new hubs should start here before custom chrome.
|
|
84
|
+
- **Placement card** — `components/data-views/placement-board-card.tsx` — `BoardPlacementCard` composes **`ListPageBoardCard`** parts with `ColumnDef<Placement>` and lifecycle layout helpers from `lib/placement-board-card-layout.ts`. Placements keeps richer column headers (search, menus); still uses the same primitives.
|
|
85
|
+
- New entities should add their own card component that composes **`ListPageBoardCard`** + primitives rather than duplicating column scroll/layout or ad-hoc card chrome.
|
|
86
|
+
|
|
87
|
+
## Dashboard view (list pages)
|
|
88
|
+
|
|
89
|
+
- **Reuse the dashboard chart bundle** — `components/dashboard-report-charts.tsx` — `DashboardReportCharts`: flat `KeyMetrics` + middle chart section + period comparison `KeyMetrics` card. **`ChartsOverview`** (placement-themed demo gallery) is the default middle section for `/dashboard` and **Placements**. **Team** passes **`chartsSection={<TeamDashboardChartsSection members={tableState.rows} />}`** so charts reflect roster data, not placements. List hubs use `ListPageDashboardCharts` (`metricsSingleRow`). Chart **style** can follow `ChartVariantProvider` when using `ChartsOverview`.
|
|
90
|
+
- **Data tab canvas charts** (`data-view-dashboard-charts*.tsx`) share **`ChartFigure`**, **`ChartCard`**, and **`ChartDataTable`** with `charts-overview.tsx`. **Layout** for Placements / Team / Compliance is stored in one place: **`lib/data-view-dashboard-storage.ts`** (see `AGENTS.md` §4.3). **Keyboard-selected bars/slices** must use **`lib/chart-keyboard-selection.ts`** (`activeBar` / `activeShape`) so behavior matches the gallery — not opacity-only `Cell` dimming.
|
|
91
|
+
|
|
92
|
+
## Persistence
|
|
93
|
+
|
|
94
|
+
- **Page-level** (`lib/data-list-persistence.ts`, key `exxat-ds:data-list:page:v1`): `displayOptions`, `showMetrics`, `tabs`, `activeTabId`. Loaded in `DataListClient` with `useLayoutEffect`, saved debounced on change. `ListPageTemplate` runs in **controlled** mode when `tabs` / `onTabsChange` / `activeTabId` / `onActiveTabChange` are passed.
|
|
95
|
+
- **Per–lifecycle tab** (key `exxat-ds:data-list:lifecycle:v1:<filterId>`): sort, search, filters, group by, column order/hidden/widths/pins/wrap, column menu search map, row height, gridlines, filter bar visibility, search popover, conditional rules, pagination toggle and page/size. Hydrated in `DataListTable` with `useLayoutEffect`; saved debounced when those fields change.
|
|
96
|
+
|
|
97
|
+
To add a new page: copy the `DataListClient` pattern (controlled `ListPageTemplate` + storage key namespace) or call `schedulePageSave` / `scheduleLifecycleSave` with your own keys.
|
|
98
|
+
|
|
99
|
+
## Rules of thumb
|
|
100
|
+
|
|
101
|
+
1. **One `useTableState` per logical table** — Remount with `key` when the column set or entity context changes.
|
|
102
|
+
2. **Don’t fork filter/sort UX** — Reuse `TablePropertiesDrawer` and `DataTableToolbar` patterns so accessibility and behavior stay consistent.
|
|
103
|
+
3. **Boards derive from the same columns** — `boardColumns` / `hiddenColKeys` should reflect table `displayCols` so “hide column” and “properties” stay in sync.
|
|
104
|
+
4. **Persist stable JSON** — Version objects with `v: 1` and keep keys namespaced to allow migrations later.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Required UX for dense lists (10+ items)
|
|
109
|
+
|
|
110
|
+
When a page shows a **list**, **table**, or **card grid** with **more than 10 items**, it must expose:
|
|
111
|
+
|
|
112
|
+
| Capability | Table / list / board | Card-only pages (no `DataTable`) |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| **Search** | Toolbar search + column quick-search where applicable | At least one search control filtering the visible set |
|
|
115
|
+
| **Filter** | Filter bar + `TablePropertiesDrawer` filters | Filters in toolbar or in Properties, as appropriate to the content |
|
|
116
|
+
| **Sorting** | Column sort / sort rules in drawer | User-visible sort (e.g. name, date, role) |
|
|
117
|
+
| **Properties** | `TablePropertiesDrawer` (columns, display, filters, sort) | A **Properties** entry point (sheet or drawer) for view options — same *role* as table properties, even if the UI is simpler |
|
|
118
|
+
|
|
119
|
+
Below the threshold, these may be omitted unless the page is a primary data hub (see below).
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Data pages: primary CTA + More + Export
|
|
124
|
+
|
|
125
|
+
If the page **has exportable data** (rows, members, placements, etc.), follow the **Placements** header pattern:
|
|
126
|
+
|
|
127
|
+
1. **Primary action** — One default (filled) button for the main task (e.g. **New placement**, **Invite member**). Do **not** use `variant="outline"` for that primary action.
|
|
128
|
+
2. **More (⋯)** — An outline **icon** button opening a menu that includes **Export** (and other overflow actions). Wire **Export** to `ExportDrawer` (or equivalent).
|
|
129
|
+
3. **Subtitle** — Prefer a short line with **count + freshness** (e.g. `24 records · Last updated now`), matching `PlacementsPageHeader`.
|
|
130
|
+
|
|
131
|
+
Reference: `components/placements-page-header.tsx`, `components/team-page-header.tsx`, `components/team-client.tsx`.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Page vs drawer (actions and auxiliary views)
|
|
136
|
+
|
|
137
|
+
**When to use a drawer or sheet:** The user needs **the surrounding page** (list, hub, or parent task) to stay **visible for context** *and* they need a **quick view**, **quick actions**, or a **short auxiliary step** — e.g. table properties, export, a brief row summary, or “change one setting and dismiss.”
|
|
138
|
+
|
|
139
|
+
**When to use a new page (route):** The flow is **primary**, **long-form**, **multi-step**, or should have its **own URL** / bookmark / history **without** the parent page behind it — e.g. full create/edit, wizards, or detail that *is* the task.
|
|
140
|
+
|
|
141
|
+
**Rule of thumb:** **Context + quick** → **drawer**; **otherwise** → **new page**.
|
|
142
|
+
|
|
143
|
+
Canonical rules: **`AGENTS.md` §6.4**, root **`.cursor/rules/exxat-page-vs-drawer.mdc`**.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Primary pages with large datasets
|
|
148
|
+
|
|
149
|
+
When a route is a **primary** destination in nav (main hub for an entity) **and** the dataset is **large** or **highly interactive**:
|
|
150
|
+
|
|
151
|
+
- Use the **primary page template**: `ListPageTemplate` (tabs, metrics strip, export drawer at template level) + data client pattern as in **`DataListClient`** / **`DataListTable`** — not a minimal `PageHeader`-only layout.
|
|
152
|
+
- Smaller satellite pages may use **`PageHeader` + section content**; once the dataset grows past the dense-list threshold, add the toolbar rules above and consider promoting to the full template if the page becomes a main hub.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Summary checklist
|
|
157
|
+
|
|
158
|
+
- [ ] **Reuse** — Tabs, search, filters, and property UI come from **`ListPageTemplate`**, **`DataTable` / `useTableState`**, **`TablePropertiesDrawer`**, and related Placements modules—not ad-hoc duplicates.
|
|
159
|
+
- [ ] **Table + tabs** — Any **`DataTable`** is wrapped in **`ListPageTemplate`** (view tabs), not only `PageHeader` + body.
|
|
160
|
+
- [ ] **>10 items** → search, filter, sort, properties (per surface type above).
|
|
161
|
+
- [ ] **Has data to export** → **More** menu with **Export** + shared `ExportDrawer` pattern.
|
|
162
|
+
- [ ] **Primary + large / main hub** → `ListPageTemplate`-style shell where applicable.
|
|
163
|
+
- [ ] **Page vs drawer (§6.4)** — Quick actions with parent **context** → drawer/sheet; primary or long flows → **new route**.
|
|
164
|
+
- [ ] **Primary button** → `Button` default variant (`size="lg"` for parity with Placements), not outline.
|
|
165
|
+
- [ ] **Dashboard view tab** → `KeyMetrics` + shared KPI helpers from **`tableState.rows`**; no duplicate one-off metric cards.
|
|
166
|
+
- [ ] **Data view charts** → `ChartFigure` + `chart-keyboard-selection`; layout persistence via **`data-view-dashboard-storage`** (see `AGENTS.md` §4.3).
|
|
167
|
+
- [ ] **Mock + KPIs** → Entity rows in **`lib/mock/`**; **`entityKpiMetrics` / `entityKpiInsight`** consumed by template metrics and dashboard view.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exxatdesignux/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.7",
|
|
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
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
"main": "./src/index.ts",
|
|
31
31
|
"types": "./src/index.ts",
|
|
32
32
|
"bin": {
|
|
33
|
+
"exxat-ui": "./bin/cli.mjs",
|
|
33
34
|
"create-exxat-app": "./bin/init.mjs"
|
|
34
35
|
},
|
|
35
36
|
"files": [
|
|
36
37
|
"src",
|
|
37
38
|
"bin",
|
|
38
|
-
"template"
|
|
39
|
+
"template",
|
|
40
|
+
"consumer-extras"
|
|
39
41
|
],
|
|
40
42
|
"dependencies": {
|
|
41
43
|
"@hookform/resolvers": "^5.2.2",
|
|
@@ -72,6 +74,8 @@
|
|
|
72
74
|
},
|
|
73
75
|
"scripts": {
|
|
74
76
|
"typecheck": "tsc --noEmit",
|
|
75
|
-
"lint": "eslint src/"
|
|
77
|
+
"lint": "eslint src/",
|
|
78
|
+
"sync-template": "node ./scripts/sync-template-from-web.mjs",
|
|
79
|
+
"vendor:consumer-extras": "node ./scripts/vendor-consumer-extras.mjs"
|
|
76
80
|
}
|
|
77
81
|
}
|
|
@@ -469,14 +469,19 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
|
469
469
|
<li
|
|
470
470
|
data-slot="sidebar-menu-item"
|
|
471
471
|
data-sidebar="menu-item"
|
|
472
|
-
className={cn(
|
|
472
|
+
className={cn(
|
|
473
|
+
"group/menu-item relative",
|
|
474
|
+
/* Icon rail: center the square menu control in the column (footer + primary). */
|
|
475
|
+
"group-data-[collapsible=icon]:flex group-data-[collapsible=icon]:justify-center",
|
|
476
|
+
className,
|
|
477
|
+
)}
|
|
473
478
|
{...props}
|
|
474
479
|
/>
|
|
475
480
|
)
|
|
476
481
|
}
|
|
477
482
|
|
|
478
483
|
const sidebarMenuButtonVariants = cva(
|
|
479
|
-
"peer/menu-button group/menu-button flex w-full cursor-pointer select-none items-center gap-2 overflow-hidden rounded-md p-2 text-start text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pe-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-background data-active:font-medium data-active:text-foreground data-active:shadow-sm data-active:ring-1 data-active:ring-sidebar-border data-active:hover:bg-background data-active:hover:text-foreground data-active:hc:border data-active:hc:border-foreground data-active:forced-colors:border data-active:forced-colors:border-[Highlight] [&_svg:not([data-product-logo]):not([data-product-logo-mark])]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
|
|
484
|
+
"peer/menu-button group/menu-button flex w-full group-data-[collapsible=icon]:w-8 cursor-pointer select-none items-center gap-2 overflow-hidden rounded-md p-2 text-start text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pe-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-background data-active:font-medium data-active:text-foreground data-active:shadow-sm data-active:ring-1 data-active:ring-sidebar-border data-active:hover:bg-background data-active:hover:text-foreground data-active:hc:border data-active:hc:border-foreground data-active:forced-colors:border data-active:forced-colors:border-[Highlight] [&_svg:not([data-product-logo]):not([data-product-logo-mark])]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:gap-0 group-data-[collapsible=icon]:[&>*:not(:first-child)]:hidden",
|
|
480
485
|
{
|
|
481
486
|
variants: {
|
|
482
487
|
variant: {
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shadcn
|
|
3
|
+
description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
|
|
4
|
+
user-invocable: false
|
|
5
|
+
allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# shadcn/ui
|
|
9
|
+
|
|
10
|
+
A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.
|
|
11
|
+
|
|
12
|
+
> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
|
|
13
|
+
|
|
14
|
+
## Current Project Context
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
!`npx shadcn@latest info --json`
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.
|
|
21
|
+
|
|
22
|
+
## Principles
|
|
23
|
+
|
|
24
|
+
1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.
|
|
25
|
+
2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.
|
|
26
|
+
3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc.
|
|
27
|
+
4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.
|
|
28
|
+
|
|
29
|
+
## Critical Rules
|
|
30
|
+
|
|
31
|
+
These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.
|
|
32
|
+
|
|
33
|
+
### Styling & Tailwind → [styling.md](./rules/styling.md)
|
|
34
|
+
|
|
35
|
+
- **`className` for layout, not styling.** Never override component colors or typography.
|
|
36
|
+
- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.
|
|
37
|
+
- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.
|
|
38
|
+
- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
|
39
|
+
- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).
|
|
40
|
+
- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.
|
|
41
|
+
- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.
|
|
42
|
+
|
|
43
|
+
### Forms & Inputs → [forms.md](./rules/forms.md)
|
|
44
|
+
|
|
45
|
+
- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
|
|
46
|
+
- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.
|
|
47
|
+
- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**
|
|
48
|
+
- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.
|
|
49
|
+
- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.
|
|
50
|
+
- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.
|
|
51
|
+
|
|
52
|
+
### Component Structure → [composition.md](./rules/composition.md)
|
|
53
|
+
|
|
54
|
+
- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.
|
|
55
|
+
- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)
|
|
56
|
+
- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden.
|
|
57
|
+
- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.
|
|
58
|
+
- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.
|
|
59
|
+
- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.
|
|
60
|
+
- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.
|
|
61
|
+
|
|
62
|
+
### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)
|
|
63
|
+
|
|
64
|
+
- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
|
|
65
|
+
- **Callouts use `Alert`.** Don't build custom styled divs.
|
|
66
|
+
- **Empty states use `Empty`.** Don't build custom empty state markup.
|
|
67
|
+
- **Toast via `sonner`.** Use `toast()` from `sonner`.
|
|
68
|
+
- **Use `Separator`** instead of `<hr>` or `<div className="border-t">`.
|
|
69
|
+
- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
|
|
70
|
+
- **Use `Badge`** instead of custom styled spans.
|
|
71
|
+
|
|
72
|
+
### Icons → [icons.md](./rules/icons.md)
|
|
73
|
+
|
|
74
|
+
- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon.
|
|
75
|
+
- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.
|
|
76
|
+
- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.
|
|
77
|
+
|
|
78
|
+
### CLI
|
|
79
|
+
|
|
80
|
+
- **Never decode or fetch preset codes manually.** Pass them directly to `npx shadcn@latest init --preset <code>`.
|
|
81
|
+
|
|
82
|
+
## Key Patterns
|
|
83
|
+
|
|
84
|
+
These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
// Form layout: FieldGroup + Field, not div + Label.
|
|
88
|
+
<FieldGroup>
|
|
89
|
+
<Field>
|
|
90
|
+
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
91
|
+
<Input id="email" />
|
|
92
|
+
</Field>
|
|
93
|
+
</FieldGroup>
|
|
94
|
+
|
|
95
|
+
// Validation: data-invalid on Field, aria-invalid on the control.
|
|
96
|
+
<Field data-invalid>
|
|
97
|
+
<FieldLabel>Email</FieldLabel>
|
|
98
|
+
<Input aria-invalid />
|
|
99
|
+
<FieldDescription>Invalid email.</FieldDescription>
|
|
100
|
+
</Field>
|
|
101
|
+
|
|
102
|
+
// Icons in buttons: data-icon, no sizing classes.
|
|
103
|
+
<Button>
|
|
104
|
+
<SearchIcon data-icon="inline-start" />
|
|
105
|
+
Search
|
|
106
|
+
</Button>
|
|
107
|
+
|
|
108
|
+
// Spacing: gap-*, not space-y-*.
|
|
109
|
+
<div className="flex flex-col gap-4"> // correct
|
|
110
|
+
<div className="space-y-4"> // wrong
|
|
111
|
+
|
|
112
|
+
// Equal dimensions: size-*, not w-* h-*.
|
|
113
|
+
<Avatar className="size-10"> // correct
|
|
114
|
+
<Avatar className="w-10 h-10"> // wrong
|
|
115
|
+
|
|
116
|
+
// Status colors: Badge variants or semantic tokens, not raw colors.
|
|
117
|
+
<Badge variant="secondary">+20.1%</Badge> // correct
|
|
118
|
+
<span className="text-emerald-600">+20.1%</span> // wrong
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Component Selection
|
|
122
|
+
|
|
123
|
+
| Need | Use |
|
|
124
|
+
| -------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
125
|
+
| Button/action | `Button` with appropriate variant |
|
|
126
|
+
| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |
|
|
127
|
+
| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |
|
|
128
|
+
| Data display | `Table`, `Card`, `Badge`, `Avatar` |
|
|
129
|
+
| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
|
|
130
|
+
| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
|
|
131
|
+
| Feedback | `sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` |
|
|
132
|
+
| Command palette | `Command` inside `Dialog` |
|
|
133
|
+
| Charts | `Chart` (wraps Recharts) |
|
|
134
|
+
| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
|
|
135
|
+
| Empty states | `Empty` |
|
|
136
|
+
| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |
|
|
137
|
+
| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |
|
|
138
|
+
|
|
139
|
+
## Key Fields
|
|
140
|
+
|
|
141
|
+
The injected project context contains these key fields:
|
|
142
|
+
|
|
143
|
+
- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.
|
|
144
|
+
- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive.
|
|
145
|
+
- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`.
|
|
146
|
+
- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.
|
|
147
|
+
- **`style`** → component visual treatment (e.g. `nova`, `vega`).
|
|
148
|
+
- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.
|
|
149
|
+
- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.
|
|
150
|
+
- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.
|
|
151
|
+
- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).
|
|
152
|
+
- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).
|
|
153
|
+
|
|
154
|
+
See [cli.md — `info` command](./cli.md) for the full field reference.
|
|
155
|
+
|
|
156
|
+
## Component Docs, Examples, and Usage
|
|
157
|
+
|
|
158
|
+
Run `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npx shadcn@latest docs button dialog select
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.
|
|
165
|
+
|
|
166
|
+
## Workflow
|
|
167
|
+
|
|
168
|
+
1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.
|
|
169
|
+
2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.
|
|
170
|
+
3. **Find components** — `npx shadcn@latest search`.
|
|
171
|
+
4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.
|
|
172
|
+
5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).
|
|
173
|
+
6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
|
|
174
|
+
7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.
|
|
175
|
+
8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, etc.), ask which registry to use. Never default to a registry on behalf of the user.
|
|
176
|
+
9. **Switching presets** — Ask the user first: **reinstall**, **merge**, or **skip**?
|
|
177
|
+
- **Reinstall**: `npx shadcn@latest init --preset <code> --force --reinstall`. Overwrites all components.
|
|
178
|
+
- **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.
|
|
179
|
+
- **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
|
|
180
|
+
- **Important**: Always run preset commands inside the user's project directory. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
|
|
181
|
+
|
|
182
|
+
## Updating Components
|
|
183
|
+
|
|
184
|
+
When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
|
|
185
|
+
|
|
186
|
+
1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.
|
|
187
|
+
2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.
|
|
188
|
+
3. Decide per file based on the diff:
|
|
189
|
+
- No local changes → safe to overwrite.
|
|
190
|
+
- Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.
|
|
191
|
+
- User says "just update everything" → use `--overwrite`, but confirm first.
|
|
192
|
+
4. **Never use `--overwrite` without the user's explicit approval.**
|
|
193
|
+
|
|
194
|
+
## Quick Reference
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Create a new project.
|
|
198
|
+
npx shadcn@latest init --name my-app --preset base-nova
|
|
199
|
+
npx shadcn@latest init --name my-app --preset a2r6bw --template vite
|
|
200
|
+
|
|
201
|
+
# Create a monorepo project.
|
|
202
|
+
npx shadcn@latest init --name my-app --preset base-nova --monorepo
|
|
203
|
+
npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
|
|
204
|
+
|
|
205
|
+
# Initialize existing project.
|
|
206
|
+
npx shadcn@latest init --preset base-nova
|
|
207
|
+
npx shadcn@latest init --defaults # shortcut: --template=next --preset=base-nova
|
|
208
|
+
|
|
209
|
+
# Add components.
|
|
210
|
+
npx shadcn@latest add button card dialog
|
|
211
|
+
npx shadcn@latest add @magicui/shimmer-button
|
|
212
|
+
npx shadcn@latest add --all
|
|
213
|
+
|
|
214
|
+
# Preview changes before adding/updating.
|
|
215
|
+
npx shadcn@latest add button --dry-run
|
|
216
|
+
npx shadcn@latest add button --diff button.tsx
|
|
217
|
+
npx shadcn@latest add @acme/form --view button.tsx
|
|
218
|
+
|
|
219
|
+
# Search registries.
|
|
220
|
+
npx shadcn@latest search @shadcn -q "sidebar"
|
|
221
|
+
npx shadcn@latest search @tailark -q "stats"
|
|
222
|
+
|
|
223
|
+
# Get component docs and example URLs.
|
|
224
|
+
npx shadcn@latest docs button dialog select
|
|
225
|
+
|
|
226
|
+
# View registry item details (for items not yet installed).
|
|
227
|
+
npx shadcn@latest view @shadcn/button
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Named presets:** `base-nova`, `radix-nova`
|
|
231
|
+
**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo)
|
|
232
|
+
**Preset codes:** Base62 strings starting with `a` (e.g. `a2r6bw`), from [ui.shadcn.com](https://ui.shadcn.com).
|
|
233
|
+
|
|
234
|
+
## Detailed References
|
|
235
|
+
|
|
236
|
+
- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states
|
|
237
|
+
- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading
|
|
238
|
+
- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects
|
|
239
|
+
- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index
|
|
240
|
+
- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion
|
|
241
|
+
- [cli.md](./cli.md) — Commands, flags, presets, templates
|
|
242
|
+
- [customization.md](./customization.md) — Theming, CSS variables, extending components
|
|
Binary file
|