@exxatdesignux/ui 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +701 -6
- package/README.md +138 -0
- package/bin/init.mjs +134 -31
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +2 -2
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +2 -0
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +3 -3
- package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +6 -6
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -1
- package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -3
- package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +7 -7
- package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +8 -8
- package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
- package/consumer-extras/handbook/HANDBOOK.md +2 -0
- package/consumer-extras/handbook/glossary.md +2 -1
- package/consumer-extras/handbook/reference-implementations.md +31 -4
- package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
- package/consumer-extras/patterns/data-views-pattern.md +18 -16
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
- package/dist/components/data-table/index.js +2 -2
- package/dist/components/data-table/index.js.map +1 -1
- package/dist/components/data-table/pagination.js +3 -3
- package/dist/components/data-table/pagination.js.map +1 -1
- package/dist/components/data-table/use-table-state.d.ts +1 -1
- package/dist/components/data-table/use-table-state.js.map +1 -1
- package/dist/components/data-views/data-row-list.js.map +1 -1
- package/dist/components/data-views/finder-panel-view.d.ts +1 -1
- package/dist/components/data-views/finder-panel-view.js.map +1 -1
- package/dist/components/data-views/hub-table.d.ts +9 -3
- package/dist/components/data-views/hub-table.js +262 -40
- package/dist/components/data-views/hub-table.js.map +1 -1
- package/dist/components/data-views/index.js +262 -40
- package/dist/components/data-views/index.js.map +1 -1
- package/dist/components/data-views/list-page-split-hub-tokens.d.ts +2 -2
- package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -1
- package/dist/components/data-views/list-page-tree-column-header.d.ts +1 -1
- package/dist/components/data-views/list-page-tree-column-header.js.map +1 -1
- package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -1
- package/dist/components/data-views/os-folder-glyph.d.ts +1 -1
- package/dist/components/data-views/os-folder-glyph.js.map +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/key-metrics.js.map +1 -1
- package/dist/index.js +136 -39
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/components/data-table/index.tsx +2 -2
- package/src/components/data-table/pagination.tsx +5 -1
- package/src/components/data-table/use-table-state.ts +1 -1
- package/src/components/data-views/data-row-list.tsx +1 -1
- package/src/components/data-views/finder-panel-view.tsx +2 -2
- package/src/components/data-views/hub-table.tsx +149 -41
- package/src/components/data-views/list-page-split-hub-tokens.ts +2 -2
- package/src/components/data-views/list-page-tree-column-header.tsx +1 -1
- package/src/components/data-views/os-folder-glyph.tsx +1 -1
- package/src/components/ui/key-metrics.tsx +1 -1
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
- package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
- package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +6 -6
- package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +5 -5
- package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
- package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
- package/template/AGENTS.md +43 -37
- package/template/app/(app)/columns/page.tsx +11 -0
- package/template/app/(app)/library/all/page.tsx +11 -0
- package/template/app/(app)/library/find/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/layout.tsx +16 -16
- package/template/app/(app)/library/list/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/new/page.tsx +10 -10
- package/template/app/(app)/library/page.tsx +11 -0
- package/template/app/(app)/tokens-themes/page.tsx +11 -0
- package/template/components/ask-leo-composer.tsx +2 -2
- package/template/components/columns-client.tsx +158 -0
- package/template/components/columns-showcase.tsx +541 -0
- package/template/components/data-views/index.ts +32 -6
- package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
- package/template/components/data-views/table-cells.tsx +673 -0
- package/template/components/folder-details-shell.tsx +11 -11
- package/template/components/hub-tree-panel-view.tsx +24 -24
- package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
- package/template/components/{question-bank-client.tsx → library-client.tsx} +82 -82
- package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
- package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
- package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +43 -43
- package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +14 -14
- package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
- package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
- package/template/components/library-panel-activator.tsx +8 -0
- package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +60 -60
- package/template/components/{question-bank-table.tsx → library-table.tsx} +97 -97
- package/template/components/list-hub-status-badge.tsx +2 -2
- package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +37 -37
- package/template/components/sidebar/app-sidebar.tsx +61 -5
- package/template/components/sidebar/secondary-panel.tsx +109 -56
- package/template/components/sidebar/sidebar-auto-collapse.tsx +2 -2
- package/template/components/sidebar/sidebar-auto-open.tsx +2 -1
- package/template/components/table-properties/types.ts +1 -1
- package/template/components/templates/discovery-hub-template.tsx +1 -1
- package/template/components/templates/new-focus-template.tsx +2 -2
- package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
- package/template/components/tokens-secondary-nav.tsx +192 -0
- package/template/components/tokens-themes-client.tsx +476 -0
- package/template/components/tokens-themes-section.tsx +386 -0
- package/template/docs/HANDBOOK.md +187 -0
- package/template/docs/blueprints/README.md +1 -1
- package/template/docs/blueprints/board-card.md +1 -1
- package/template/docs/blueprints/data-table.md +2 -2
- package/template/docs/blueprints/list-page-template.md +3 -3
- package/template/docs/blueprints/page-header.md +4 -4
- package/template/docs/collaboration-access-pattern.md +7 -7
- package/template/docs/component-selection-guide.md +1 -1
- package/template/docs/data-views-pattern.md +18 -16
- package/template/docs/glossary.md +58 -0
- package/template/docs/kpi-flat-band-pattern.md +3 -3
- package/template/docs/kpi-trend-pattern.md +18 -3
- package/template/docs/large-dataset-strategy.md +155 -0
- package/template/docs/library-hub-header-pattern.md +25 -0
- package/template/docs/migrations/_template.md +1 -1
- package/template/docs/reference-implementations.md +151 -0
- package/template/docs/token-taxonomy.md +1 -1
- package/template/docs/voice-and-tone.md +262 -0
- package/template/eslint.config.mjs +9 -39
- package/template/hooks/use-secondary-panel-hub-nav.ts +10 -10
- package/template/lib/ask-leo-route-context.ts +6 -18
- package/template/lib/coach-mark-registry.ts +0 -16
- package/template/lib/command-menu-config.ts +5 -12
- package/template/lib/command-menu-search-data.ts +8 -39
- package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
- package/template/lib/library-dedicated-search.ts +19 -0
- package/template/lib/library-hub-search.ts +90 -0
- package/template/lib/library-nav.ts +477 -0
- package/template/lib/library-recent-searches.ts +22 -0
- package/template/lib/{placements-supported-views.ts → library-supported-views.ts} +2 -2
- package/template/lib/list-status-badges.ts +16 -104
- package/template/lib/mock/dashboard.ts +1 -1
- package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
- package/template/lib/mock/library-header-collaborators.ts +54 -0
- package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
- package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
- package/template/lib/mock/library.ts +249 -0
- package/template/lib/mock/navigation.tsx +32 -26
- package/template/lib/table-state-lifecycle.ts +1 -1
- package/template/next.config.mjs +7 -4
- package/template/package.json +0 -1
- package/tokens/hooks-index.json +2874 -0
- package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +0 -28
- package/template/app/(app)/examples/page.tsx +0 -41
- package/template/app/(app)/question-bank/find/page.tsx +0 -12
- package/template/app/(app)/question-bank/library/page.tsx +0 -11
- package/template/app/(app)/question-bank/list/page.tsx +0 -12
- package/template/app/(app)/question-bank/page.tsx +0 -11
- package/template/components/compliance-board-view.tsx +0 -142
- package/template/components/compliance-client.tsx +0 -92
- package/template/components/compliance-page-header.tsx +0 -89
- package/template/components/compliance-table.tsx +0 -468
- package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
- package/template/components/data-view-dashboard-charts-team.tsx +0 -971
- package/template/components/data-view-dashboard-charts.tsx +0 -1503
- package/template/components/new-placement-back-btn.tsx +0 -28
- package/template/components/new-placement-form.tsx +0 -942
- package/template/components/placement-board-card.tsx +0 -250
- package/template/components/placement-detail.tsx +0 -438
- package/template/components/placements-board-view.tsx +0 -397
- package/template/components/placements-client.tsx +0 -220
- package/template/components/placements-list-view.tsx +0 -124
- package/template/components/placements-page-header.tsx +0 -166
- package/template/components/placements-table-cells.test.tsx +0 -22
- package/template/components/placements-table-cells.tsx +0 -173
- package/template/components/placements-table-columns.tsx +0 -210
- package/template/components/placements-table.tsx +0 -934
- package/template/components/question-bank-panel-activator.tsx +0 -8
- package/template/components/rotations-empty-state.tsx +0 -50
- package/template/components/rotations-panel-activator.tsx +0 -8
- package/template/components/sites-board-view.tsx +0 -67
- package/template/components/sites-client.tsx +0 -154
- package/template/components/sites-table.tsx +0 -249
- package/template/components/team-board-view.tsx +0 -122
- package/template/components/team-client.tsx +0 -100
- package/template/components/team-page-header.tsx +0 -92
- package/template/components/team-table.tsx +0 -553
- package/template/docs/question-bank-hub-header-pattern.md +0 -25
- package/template/lib/compliance-supported-views.ts +0 -10
- package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
- package/template/lib/mock/compliance-kpi.ts +0 -61
- package/template/lib/mock/compliance.ts +0 -146
- package/template/lib/mock/placements-kpi.ts +0 -134
- package/template/lib/mock/placements.ts +0 -176
- package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
- package/template/lib/mock/question-bank.ts +0 -249
- package/template/lib/mock/sites-directory.ts +0 -16
- package/template/lib/mock/sites-kpi.ts +0 -25
- package/template/lib/mock/team-kpi.ts +0 -60
- package/template/lib/mock/team.ts +0 -118
- package/template/lib/placement-board-card-layout.ts +0 -79
- package/template/lib/question-bank-dedicated-search.ts +0 -19
- package/template/lib/question-bank-hub-search.ts +0 -90
- package/template/lib/question-bank-nav.ts +0 -477
- package/template/lib/question-bank-recent-searches.ts +0 -22
- package/template/lib/question-bank-supported-views.ts +0 -12
- package/template/lib/sites-supported-views.ts +0 -10
- package/template/lib/team-supported-views.ts +0 -10
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @exxatdesignux/ui
|
|
2
|
+
|
|
3
|
+
The Exxat Design System — a Next.js + React 19 + Tailwind v4 component
|
|
4
|
+
library plus the full hub-page stack (`ListPageTemplate`, `DataTable`,
|
|
5
|
+
`HubTable`, `TablePropertiesDrawer`, `KeyMetrics`, `PageHeader`, board
|
|
6
|
+
cards, charts, …) wrapped in one publishable package.
|
|
7
|
+
|
|
8
|
+
## 60-second start
|
|
9
|
+
|
|
10
|
+
Spin up a brand-new app pre-wired with the DS:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm create exxat-app my-app
|
|
14
|
+
cd my-app
|
|
15
|
+
pnpm dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Works with every modern package manager — the scaffolder auto-detects
|
|
19
|
+
the runner and uses the matching install:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm create exxat-app my-app
|
|
23
|
+
yarn create exxat-app my-app
|
|
24
|
+
bun create exxat-app my-app
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The scaffolder ships a complete Next.js 16 starter: the reference
|
|
28
|
+
**Library** hub at `/library/all`, the `/columns` cell-catalog showcase,
|
|
29
|
+
typed mock data, the full Cursor skill + pattern doc set under
|
|
30
|
+
`cursor-rules/`, `cursor-skills/`, `patterns/`, and `handbook/`.
|
|
31
|
+
|
|
32
|
+
## Adding to an existing Next.js app
|
|
33
|
+
|
|
34
|
+
If you already have a Next 15/16 app and just want the components:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add @exxatdesignux/ui
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then in your global CSS (e.g. `app/globals.css`):
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
@import "@exxatdesignux/ui/globals.css";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
And import primitives or full patterns from the umbrella or a subpath:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { DataTable, HubTable, ListPageTemplate, KeyMetrics } from "@exxatdesignux/ui"
|
|
50
|
+
import { Button } from "@exxatdesignux/ui/components/button"
|
|
51
|
+
import { useTableState } from "@exxatdesignux/ui/components/data-table/use-table-state"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Mount `KeyMetricsProvider` near the root if you want the "Ask Leo about
|
|
55
|
+
these metrics" CTA wired to your AI surface (otherwise the strip just
|
|
56
|
+
hides that button automatically).
|
|
57
|
+
|
|
58
|
+
## Refresh Cursor skills + pattern docs in any consumer
|
|
59
|
+
|
|
60
|
+
The package ships a parallel set of binding rules, skills, and pattern
|
|
61
|
+
docs so AI agents in your own repo can read the same guidance the source
|
|
62
|
+
repo uses. To install or refresh them in your repo:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm dlx --package=@exxatdesignux/ui@latest exxat-ui sync-extras
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This copies `cursor-rules/`, `cursor-skills/`, `patterns/`, and
|
|
69
|
+
`handbook/` into your repo root. The next time Cursor opens, every
|
|
70
|
+
binding rule + skill is live.
|
|
71
|
+
|
|
72
|
+
## What's inside
|
|
73
|
+
|
|
74
|
+
- **Primitives** (`@exxatdesignux/ui/components/<name>`) — Button, Input,
|
|
75
|
+
Select, Dialog, AlertDialog, Sheet, Drawer, Popover, DropdownMenu,
|
|
76
|
+
ContextMenu, Tabs, Accordion, Tooltip, Toast-free Banner, ScrollArea,
|
|
77
|
+
Slider, HoverCard, Avatar, Badge, StatusBadge, Calendar, Kbd, …
|
|
78
|
+
- **Hub stack** — `DataTable<TRow>`, `useTableState<TRow>`,
|
|
79
|
+
`TablePropertiesDrawer`, `HubTable<TRow>`, `ListPageTemplate`,
|
|
80
|
+
`PageHeader`, `KeyMetrics`, `ListPageBoardCard`,
|
|
81
|
+
`ListPageBoardTemplate`, `DataRowList`, `FinderPanelView`,
|
|
82
|
+
`FolderGridView`, `OutlineTreeMenu`.
|
|
83
|
+
- **Data-list registry** — `DataListViewType`, `DATA_LIST_VIEW_TILES`,
|
|
84
|
+
`dataListViewIcon`, `dataListViewAddShortcut`, `usesDataTableComponent`,
|
|
85
|
+
`usesDashboardSurface`.
|
|
86
|
+
- **Templates** — `ListPageTemplate`, `NestedSecondaryPanelShell`,
|
|
87
|
+
`DedicatedSearchLandingTemplate`, `DedicatedSearchResultsTemplate`.
|
|
88
|
+
- **Tokens** — `tokens/hooks-index.json` (197 tokens · 42 namespaces)
|
|
89
|
+
feeds the workspace ESLint plugin's `no-deprecated-tokens` rule.
|
|
90
|
+
- **Tailwind v4 token bridge** — `globals.css` imports the L0
|
|
91
|
+
`--exxat-color-*` / `--exxat-radius-*` / `--exxat-spacing-*` family
|
|
92
|
+
and wires Tailwind shorthand (`bg-surface-1`, `text-ink-1`,
|
|
93
|
+
`rounded-2`, …) via `@theme inline`.
|
|
94
|
+
|
|
95
|
+
## CLI commands
|
|
96
|
+
|
|
97
|
+
After `pnpm add @exxatdesignux/ui` (or globally `npm i -g @exxatdesignux/ui`):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# In an empty directory (or new sub-folder), scaffold an app:
|
|
101
|
+
npx --package=@exxatdesignux/ui create-exxat-app my-app
|
|
102
|
+
|
|
103
|
+
# Sync Cursor skills + binding rules + pattern docs into your repo:
|
|
104
|
+
npx --package=@exxatdesignux/ui exxat-ui sync-extras
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The standalone [`create-exxat-app`](https://www.npmjs.com/package/create-exxat-app)
|
|
108
|
+
package wraps the first command so `<pm> create exxat-app …` resolves
|
|
109
|
+
natively.
|
|
110
|
+
|
|
111
|
+
## Docs
|
|
112
|
+
|
|
113
|
+
- Design-system **HANDBOOK** — read-this-first orientation, the 6-step
|
|
114
|
+
"how to build a hub" walkthrough, the §13 PR checklist.
|
|
115
|
+
- **Blueprints** — framework-agnostic specs for each major composition
|
|
116
|
+
(`PageHeader`, `DataTable`, `ListPageTemplate`, `BoardCard`,
|
|
117
|
+
`KeyMetrics`, …).
|
|
118
|
+
- **Component selection guide** — top-of-funnel decision tree for
|
|
119
|
+
picking the right composition.
|
|
120
|
+
- **Token taxonomy** — naming + deprecation policy for every CSS
|
|
121
|
+
custom property the DS ships.
|
|
122
|
+
|
|
123
|
+
The full doc set is mirrored under `handbook/` and `patterns/` in this
|
|
124
|
+
package; `exxat-ui sync-extras` drops them into your repo so they sit
|
|
125
|
+
alongside your source.
|
|
126
|
+
|
|
127
|
+
## Compatibility
|
|
128
|
+
|
|
129
|
+
| Peer | Range |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| React | `>=19.0.0` |
|
|
132
|
+
| Next.js | `>=15.0.0` (App Router) |
|
|
133
|
+
| Tailwind CSS | `>=4.0.0` |
|
|
134
|
+
| Node | `>=22.0.0` (build / dev) |
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
UNLICENSED — proprietary to Exxat Design.
|
package/bin/init.mjs
CHANGED
|
@@ -1,43 +1,146 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Scaffold a new Exxat DS app from the bundled template.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx create-exxat-app # scaffold into current directory
|
|
7
|
+
* npx create-exxat-app my-app # scaffold into ./my-app (created if missing)
|
|
8
|
+
* npx create-exxat-app . --force # overwrite-merge into current directory
|
|
9
|
+
* npx create-exxat-app my-app --no-install # skip the package install step
|
|
10
|
+
*
|
|
11
|
+
* Also runs as `npx --package=@exxatdesignux/ui create-exxat-app …` and as
|
|
12
|
+
* `pnpm create exxat-app …` when invoked via the standalone create-exxat-app
|
|
13
|
+
* discovery shim.
|
|
14
|
+
*/
|
|
15
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"
|
|
16
|
+
import { resolve, dirname, basename, join } from "node:path"
|
|
17
|
+
import { fileURLToPath } from "node:url"
|
|
18
|
+
import { execSync } from "node:child_process"
|
|
6
19
|
|
|
7
20
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
-
const templateDir = resolve(__dirname,
|
|
9
|
-
const selfPkgPath = resolve(__dirname,
|
|
10
|
-
const
|
|
21
|
+
const templateDir = resolve(__dirname, "../template")
|
|
22
|
+
const selfPkgPath = resolve(__dirname, "../package.json")
|
|
23
|
+
const selfPkg = JSON.parse(readFileSync(selfPkgPath, "utf8"))
|
|
11
24
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
25
|
+
const args = process.argv.slice(2)
|
|
26
|
+
const flags = new Set(args.filter((a) => a.startsWith("--")))
|
|
27
|
+
const positional = args.filter((a) => !a.startsWith("--"))
|
|
28
|
+
const targetArg = positional[0] ?? "."
|
|
29
|
+
const targetDir = resolve(process.cwd(), targetArg)
|
|
30
|
+
const projectName = targetArg === "." ? basename(targetDir) : targetArg
|
|
31
|
+
const force = flags.has("--force")
|
|
32
|
+
const skipInstall = flags.has("--no-install") || flags.has("--skip-install")
|
|
14
33
|
|
|
15
|
-
if (
|
|
16
|
-
console.
|
|
34
|
+
if (flags.has("--help") || flags.has("-h")) {
|
|
35
|
+
console.log(`
|
|
36
|
+
create-exxat-app — scaffold a Next.js app on @exxatdesignux/ui
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
create-exxat-app [target-directory] [options]
|
|
40
|
+
|
|
41
|
+
Arguments:
|
|
42
|
+
target-directory Where to create the app (default: ".")
|
|
43
|
+
A relative or absolute path. Created if it does not exist.
|
|
44
|
+
|
|
45
|
+
Options:
|
|
46
|
+
--force Allow scaffolding into a non-empty directory (existing
|
|
47
|
+
files are NOT overwritten unless template ships the
|
|
48
|
+
same path).
|
|
49
|
+
--no-install Skip the dependency install step.
|
|
50
|
+
-h, --help Show this message.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
create-exxat-app my-app
|
|
54
|
+
create-exxat-app .
|
|
55
|
+
create-exxat-app ../sandbox --force --no-install
|
|
56
|
+
`)
|
|
57
|
+
process.exit(0)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!existsSync(targetDir)) {
|
|
61
|
+
mkdirSync(targetDir, { recursive: true })
|
|
62
|
+
console.log(`📁 Created ${projectName}/`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const targetStat = statSync(targetDir)
|
|
66
|
+
if (!targetStat.isDirectory()) {
|
|
67
|
+
console.error(`❌ Target ${targetDir} exists but is not a directory.`)
|
|
17
68
|
process.exit(1)
|
|
18
69
|
}
|
|
19
70
|
|
|
20
|
-
|
|
71
|
+
const existing = readdirSync(targetDir)
|
|
72
|
+
const ignorable = new Set([".git", ".gitignore", ".DS_Store"])
|
|
73
|
+
const blockers = existing.filter((name) => !ignorable.has(name))
|
|
74
|
+
|
|
75
|
+
if (blockers.length > 0 && !force) {
|
|
76
|
+
console.error(`❌ Target directory is not empty (${targetDir}).`)
|
|
77
|
+
console.error("")
|
|
78
|
+
console.error(" Found:")
|
|
79
|
+
for (const name of blockers.slice(0, 8)) console.error(` - ${name}`)
|
|
80
|
+
if (blockers.length > 8) console.error(` … and ${blockers.length - 8} more`)
|
|
81
|
+
console.error("")
|
|
82
|
+
console.error(" Options:")
|
|
83
|
+
console.error(` 1. Scaffold into a new folder: create-exxat-app my-app`)
|
|
84
|
+
console.error(` 2. Allow merging existing files: create-exxat-app . --force`)
|
|
85
|
+
console.error("")
|
|
86
|
+
process.exit(1)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(`📦 Copying Exxat DS starter app into ${projectName}/ …`)
|
|
21
90
|
cpSync(templateDir, targetDir, { recursive: true })
|
|
22
91
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const targetPkgPath = resolve(targetDir,
|
|
26
|
-
const targetPkg = JSON.parse(readFileSync(targetPkgPath,
|
|
27
|
-
if (targetPkg.dependencies?.[
|
|
28
|
-
targetPkg.dependencies[
|
|
29
|
-
writeFileSync(targetPkgPath, `${JSON.stringify(targetPkg, null, 2)}\n`)
|
|
30
|
-
console.log(`📌 Pinned @exxatdesignux/ui to ^${selfVersion} (matches this scaffold).`)
|
|
92
|
+
// Pin DS to this CLI's published version so `latest` cache drift cannot
|
|
93
|
+
// trick the consumer into installing an older dist.
|
|
94
|
+
const targetPkgPath = resolve(targetDir, "package.json")
|
|
95
|
+
const targetPkg = JSON.parse(readFileSync(targetPkgPath, "utf8"))
|
|
96
|
+
if (targetPkg.dependencies?.["@exxatdesignux/ui"]) {
|
|
97
|
+
targetPkg.dependencies["@exxatdesignux/ui"] = `^${selfPkg.version}`
|
|
31
98
|
}
|
|
99
|
+
// Name the project after the target dir for nicer dev-server / process titles.
|
|
100
|
+
targetPkg.name = projectName.replace(/[^a-z0-9-_]/gi, "-").toLowerCase() || "my-exxat-app"
|
|
101
|
+
writeFileSync(targetPkgPath, `${JSON.stringify(targetPkg, null, 2)}\n`)
|
|
102
|
+
console.log(`📌 Pinned @exxatdesignux/ui to ^${selfPkg.version} and named the project "${targetPkg.name}".`)
|
|
32
103
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
console.log(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
console.
|
|
42
|
-
console.
|
|
43
|
-
console.
|
|
104
|
+
const pm = detectPackageManager()
|
|
105
|
+
if (skipInstall) {
|
|
106
|
+
console.log(`⏭ Skipped install (--no-install).`)
|
|
107
|
+
} else {
|
|
108
|
+
console.log(`📥 Installing dependencies with ${pm.label} …`)
|
|
109
|
+
try {
|
|
110
|
+
execSync(pm.installCmd, { stdio: "inherit", cwd: targetDir })
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error("")
|
|
113
|
+
console.error(`❌ ${pm.label} install failed. You can retry manually:`)
|
|
114
|
+
console.error(` cd ${projectName} && ${pm.installCmd}`)
|
|
115
|
+
process.exit(1)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log("")
|
|
120
|
+
console.log(`✅ Done! Your Exxat DS app is ready in ./${projectName}/`)
|
|
121
|
+
console.log("")
|
|
122
|
+
console.log(` cd ${projectName}`)
|
|
123
|
+
console.log(` ${pm.runCmd} dev # start dev server`)
|
|
124
|
+
console.log("")
|
|
125
|
+
console.log(" Optional — refresh Cursor skills + pattern docs:")
|
|
126
|
+
console.log(` ${pm.dlxCmd} --package=@exxatdesignux/ui@${selfPkg.version} exxat-ui sync-extras`)
|
|
127
|
+
console.log("")
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Detect the package manager that invoked us — npm_config_user_agent is set
|
|
131
|
+
* by every modern package-runner (npm, pnpm, yarn, bun) and looks like
|
|
132
|
+
* "pnpm/10.33.0 npm/? node/v22.0.0 darwin arm64". We fall back to npm.
|
|
133
|
+
*/
|
|
134
|
+
function detectPackageManager() {
|
|
135
|
+
const ua = process.env.npm_config_user_agent ?? ""
|
|
136
|
+
if (ua.startsWith("pnpm")) {
|
|
137
|
+
return { label: "pnpm", installCmd: "pnpm install", runCmd: "pnpm", dlxCmd: "pnpm dlx" }
|
|
138
|
+
}
|
|
139
|
+
if (ua.startsWith("yarn")) {
|
|
140
|
+
return { label: "yarn", installCmd: "yarn install", runCmd: "yarn", dlxCmd: "yarn dlx" }
|
|
141
|
+
}
|
|
142
|
+
if (ua.startsWith("bun")) {
|
|
143
|
+
return { label: "bun", installCmd: "bun install", runCmd: "bun", dlxCmd: "bunx" }
|
|
144
|
+
}
|
|
145
|
+
return { label: "npm", installCmd: "npm install", runCmd: "npm run", dlxCmd: "npx" }
|
|
146
|
+
}
|
|
@@ -11,7 +11,7 @@ alwaysApply: true
|
|
|
11
11
|
|
|
12
12
|
1. **Shell** — Use **`ListPageBoardCard`** from **`components/data-views/list-page-board-card.tsx`** for product board cards (same **`Card` `size="sm"`** treatment as Placements).
|
|
13
13
|
2. **Hierarchy** — **Title** (`ListPageBoardCardTitleRow`) → optional **avatar** (`ListPageBoardCardAvatar` on `trailing`) → **status row** (`ListPageBoardCardBadgeRow` + **`ListHubStatusBadge`** **`surface="board"`**) when the entity has status → **body** (`ListPageBoardCardBody`) with **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`** (**`components/data-views/board-card-primitives.tsx`**).
|
|
14
|
-
3. **Status** — **Placements** (**`PLACEMENT_STATUS_*`** + **`StatusBadge`** in **`placements-table-cells.tsx`**), **Team / Compliance /
|
|
14
|
+
3. **Status** — **Placements** (**`PLACEMENT_STATUS_*`** + **`StatusBadge`** in **`placements-table-cells.tsx`**), **Team / Compliance / Library** (**`ListHubStatusBadge`** + entity maps): all labels/tints/icons live in **`lib/list-status-badges.ts`**. **`surface="table"`** in grid/list rows, **`surface="board"`** on kanban cards. Prefer semantic **`LIST_HUB_STATUS_TINT_*`**. **MUST NOT** use **`uppercase`** on those chips.
|
|
15
15
|
4. **Simple column boards** — **`ListPageBoardTemplate`** + **`renderCard`**; compose **`ListPageBoardCard`** inside **`renderCard`**.
|
|
16
16
|
|
|
17
17
|
## MUST NOT
|
|
@@ -27,14 +27,14 @@ alwaysApply: true
|
|
|
27
27
|
|
|
28
28
|
8. **`ListPageViewFrame`** — Non-**`DataTable`** view bodies (**folder**, **panel**, icon grids, comparable dashboard sections) **MUST** use **`ListPageViewFrame`** (and exported max-width constants) instead of copy-pasted **`mx-*` / `max-w-*`** per hub (**`exxat-list-page-view-shells.mdc`**).
|
|
29
29
|
|
|
30
|
-
9. **`components/data-views/`** — New **record-bearing** view layouts (**grids**, **OS folder**, **finder split**) **MUST** land as **generic** building blocks under **`data-views/`** with **`rows`**, **`getRowId`**, render props — hub **`TeamTable`** / **`
|
|
30
|
+
9. **`components/data-views/`** — New **record-bearing** view layouts (**grids**, **OS folder**, **finder split**) **MUST** land as **generic** building blocks under **`data-views/`** with **`rows`**, **`getRowId`**, render props — hub **`TeamTable`** / **`LibraryTable`** **only** wires props and branch logic (**`AGENTS.md` §4.5**).
|
|
31
31
|
|
|
32
32
|
10. **Hub client composition** — One **`*-client.tsx`** owns **`useTableState`**, passes **`tableState.rows`** into every **`viewType`** branch, and mounts **template** slots (**metrics**, **export**, **`beforeSiteHeader`**) — **MUST NOT** split the same hub across multiple clients with different row sources.
|
|
33
33
|
|
|
34
34
|
## MUST NOT
|
|
35
35
|
|
|
36
36
|
- Ship alternate mock datasets per **`DataListViewType`** for the same hub without documenting them as **computed derivatives** of one canonical list.
|
|
37
|
-
- Duplicate entity fields in inspector-only types that contradict **`
|
|
37
|
+
- Duplicate entity fields in inspector-only types that contradict **`LibraryItem`** / **`Placement`** / etc.; extend the shared interface **once**.
|
|
38
38
|
|
|
39
39
|
## See also
|
|
40
40
|
|
|
@@ -29,4 +29,4 @@ alwaysApply: false
|
|
|
29
29
|
|
|
30
30
|
- **`exxat-page-vs-drawer.mdc`** — invite is a **sheet**, not a new route.
|
|
31
31
|
- **`exxat-kbd-shortcuts.mdc`** — workflow **Cancel** / **Send invite** shortcuts on the sheet.
|
|
32
|
-
- **`exxat-
|
|
32
|
+
- **`exxat-library-hub-header.mdc`** — Library library: when URL is **folder-scoped**, **⋯ More** also includes **Customize folder** (hub client hosts **`LibraryNewFolderSheet`**).
|
|
@@ -16,6 +16,7 @@ For **any app screen that shows a browsable, filterable grid of records** (lists
|
|
|
16
16
|
5. **Filters:** Configure per-column `filter:` blocks in `ColumnDef` (text / select / date / number) — `HubTable` turns those into chips automatically via `columnsToFilterFields`. **MUST NOT** ship one-off filter inputs above the table that duplicate this.
|
|
17
17
|
6. **Table properties:** Always reachable. `HubTable` mounts `TablePropertiesDrawerButton` in `toolbarSlot`; on **`ListPageTemplate`** pages with **table / list / board / dashboard** tabs it **MUST** also receive **`currentView`** and **`onViewChange`** (see **`apps/web/AGENTS.md` §4.2** and **`.cursor/rules/exxat-table-properties-drawer.mdc`**) so Properties matches the selected view.
|
|
18
18
|
7. **Dropdown menus:** `DropdownMenuContent` uses the shared **`@exxatdesignux/ui`** default (**intrinsic `w-max`**, **`min-w-52`**, capped **`max-w`**) for view settings, row ⋯, column menus, and filter pickers — **pure CSS**, no **`ResizeObserver`**. Override only for deliberate narrow/wide rails (e.g. pagination **`w-20`**, account trigger-width, school switcher **`!w-max min-w-72 …`**). See **`docs/data-views-pattern.md`** (“Dropdown menus”).
|
|
19
|
+
8. **Cell renderers MUST come from `@/components/data-views` (`table-cells.tsx`).** The DS ships **`ProgressCell`**, **`CurrencyCell`**, **`NumericCell`**, **`RatingCell`**, **`SignalBarsCell`**, **`BooleanToggleCell`**, **`AttachmentCountCell`**, **`ExternalLinkCell`**, **`RelativeTimeCell`**, **`PeopleAvatarRailCell`**, **`PillCell`**, **`TagListCell`**, and a generic **`RowActionsCell<TRow>`**. A `ColumnDef['cell']` for these patterns is a **one-liner** that calls the named cell. **MUST NOT** inline `Intl.NumberFormat`, raw `<a target="_blank">`, `[1,2,3,4,5].map(s => …)` star loops, paperclip + count chips, custom face-rail `AvatarGroup`s, or per-hub `DropdownMenu` overflow menus inside `cell:` — those are signals you're re-deriving a shipped primitive. Catalog: `apps/web/components/columns-showcase.tsx` (`/columns`). Skill: `.cursor/skills/exxat-token-economy/SKILL.md` §3.
|
|
19
20
|
|
|
20
21
|
**Reference implementations:**
|
|
21
22
|
|
|
@@ -30,6 +31,7 @@ For **any app screen that shows a browsable, filterable grid of records** (lists
|
|
|
30
31
|
- Do **not** build product list pages with `@/components/ui/table` alone, raw `<table>` markup, or third-party data grids.
|
|
31
32
|
- Do **not** mount raw `<DataTable>` inside `ListPageTemplate.renderContent` — use `HubTable`. Raw `<DataTable>` does not ship the Properties drawer or filter chips; users lose discoverability.
|
|
32
33
|
- Do **not** introduce a second “table component” pattern for the same product surfaces (splitting search/filters/properties across incompatible implementations).
|
|
34
|
+
- Do **not** inline-implement progress bars, currency formatters, rating stars, relative-time helpers, attachment chips, external-link wrappers, face rails, type pills, tag lists, or row-action dropdowns inside a `ColumnDef['cell']`. Import the named cell from `@/components/data-views` instead. New hub with novel cell needs MUST extend `table-cells.tsx` (and ship the catalog entry in `columns-showcase.tsx`), not fork inline JSX.
|
|
33
35
|
|
|
34
36
|
## Exceptions
|
|
35
37
|
|
|
@@ -18,7 +18,7 @@ Shared building blocks use **generic** `DedicatedSearch*` names under `component
|
|
|
18
18
|
|
|
19
19
|
## MUST NOT
|
|
20
20
|
|
|
21
|
-
- Introduce parallel `*
|
|
21
|
+
- Introduce parallel `*LibrarySearchLanding*` (or similar) components for another entity — extend the generic layer and compose in the hub client.
|
|
22
22
|
|
|
23
23
|
## See also
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@ Before implementing or reviewing **list / table / board / dashboard / data-heavy
|
|
|
26
26
|
11. **Centralized hub dataset + presentation** — **`apps/web/AGENTS.md` §4.1** / **§4.5** + **`.cursor/rules/exxat-centralized-list-dataset.mdc`** + **`.cursor/skills/exxat-centralized-list-dataset/SKILL.md`** — one **`useTableState`** row bag for **all** views + **`TablePropertiesDrawer`**; **`ListPageViewFrame`** + **`data-views/`** for shared chrome; **no** parallel mock arrays per **`DataListViewType`**.
|
|
27
27
|
12. **Font Awesome (product icons)** — **`.cursor/rules/exxat-fontawesome-icons.mdc`** — Kit in **`app/layout.tsx`**, **`fa-light` / `fa-solid`**, subset audit, **`aria-hidden`** on decorative **`<i>`**; pair with **`exxat-accessibility.mdc`** for icon-only controls.
|
|
28
28
|
13. **Monospace system IDs** — **`apps/web/AGENTS.md` §1 (item 9)** + **`.cursor/rules/exxat-mono-ids.mdc`** + **`.cursor/skills/exxat-mono-ids/SKILL.md`** — **`font-mono tabular-nums`** on question/record keys; mono **only** the ID token in mixed lines.
|
|
29
|
-
14. **Primary nav → secondary panel** (
|
|
29
|
+
14. **Primary nav → secondary panel** (Library) — **`apps/web/AGENTS.md` §4.6** + **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`** — **`secondaryPanel`** + **`PANELS`** + **`useAutoPanel`**; **`--secondary-panel-bg`** brand elevation (**`apps/web/docs/shell-surface-elevation-pattern.md`**); **folder URL scope** → **`exxat-library-hub-header.mdc`** + **`apps/web/docs/library-hub-header-pattern.md`**.
|
|
30
30
|
15. **Collaboration & access** (shared hubs) — **`apps/web/AGENTS.md` §4.7** + **`.cursor/rules/exxat-collaboration-access.mdc`** + **`.cursor/skills/exxat-collaboration-access/SKILL.md`** — face rail, **`InviteCollaboratorsDrawer`**, **`lib/collaborator-access.ts`**.
|
|
31
31
|
16. **Dedicated search** (landing vs results, `DedicatedSearch*`) — **`apps/web/AGENTS.md` §4.8** + **`.cursor/rules/exxat-dedicated-search-surfaces.mdc`** + **`.cursor/skills/exxat-dedicated-search-surfaces/SKILL.md`**.
|
|
32
32
|
17. **No toast** → **`apps/web/AGENTS.md` §6.5** + **`exxat-no-toast.mdc`** — do not use **`toast()`** / Sonner / snackbars for product messaging; use banners, inline status, or dialogs.
|
|
@@ -50,7 +50,7 @@ Before implementing or reviewing **list / table / board / dashboard / data-heavy
|
|
|
50
50
|
- **`apps/web/docs/blueprints/`** — framework-agnostic specs (start: `page-header.md`, `data-table.md`).
|
|
51
51
|
- **`apps/web/docs/migrations/`** — token rename + removal history.
|
|
52
52
|
- **`packages/ui/tokens/hooks-index.json`** — machine-readable token index (regenerate via `pnpm --filter @exxatdesignux/ui tokens:index`).
|
|
53
|
-
- `.cursor/rules/exxat-data-tables.mdc`, `exxat-list-page-connected-views.mdc`, **`exxat-centralized-list-dataset.mdc`**, **`exxat-list-page-view-shells.mdc`**, **`exxat-fontawesome-icons.mdc`**, **`exxat-mono-ids.mdc`**, **`exxat-primary-nav-secondary-panel.mdc`**, **`exxat-
|
|
53
|
+
- `.cursor/rules/exxat-data-tables.mdc`, `exxat-list-page-connected-views.mdc`, **`exxat-centralized-list-dataset.mdc`**, **`exxat-list-page-view-shells.mdc`**, **`exxat-fontawesome-icons.mdc`**, **`exxat-mono-ids.mdc`**, **`exxat-primary-nav-secondary-panel.mdc`**, **`exxat-library-hub-header.mdc`**, **`exxat-collaboration-access.mdc`**, **`exxat-dedicated-search-surfaces.mdc`**, **`exxat-kpi-trends.mdc`**, **`exxat-kpi-flat-band.mdc`**, **`exxat-drawer-vs-dialog.mdc`**, **`exxat-card-vs-list-rows.mdc`**, **`exxat-kpi-max-four.mdc`**, **`exxat-reuse-before-custom.mdc`**, `exxat-table-properties-drawer.mdc`, **`exxat-board-cards.mdc`**, **`exxat-page-vs-drawer.mdc`**, **`exxat-no-toast.mdc`**, **`exxat-command-menu.mdc`**, `exxat-kbd-shortcuts.mdc`, `exxat-accessibility.mdc` at repo root; **`apps/web/.cursor/rules/exxat-dashboard-view-charts.mdc`** for Data tab charts.
|
|
54
54
|
- **`apps/web/docs/kpi-flat-band-pattern.md`**, **`apps/web/docs/shell-surface-elevation-pattern.md`** — flat KPI strip + sidebar/secondary/page OKLCH stack.
|
|
55
|
-
- **`apps/web/docs/
|
|
55
|
+
- **`apps/web/docs/library-hub-header-pattern.md`** — folder-scoped library header **Customize folder**.
|
|
56
56
|
- **`apps/web/docs/collaboration-access-pattern.md`** — shared hub face rail + invite sheet.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Library library — folder-scoped hub header More menu must expose Customize folder; sheet on hub client
|
|
3
|
+
globs: apps/web/components/library-*.tsx, packages/ui/template/components/library-*.tsx
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exxat DS — Library hub header (folder scope)
|
|
8
|
+
|
|
9
|
+
When the library library URL is **scoped to a folder** (`parseLibraryNav` → **`scope === "folder"`** and **`folderId`** set), users are effectively on a **“folder page”**: the hub title matches that folder, and **global** actions belong in the **`LibraryPageHeader`** **⋯ More** menu — not only on per-row or per-tile overflow menus inside a single view tab.
|
|
10
|
+
|
|
11
|
+
**Pattern doc:** **`apps/web/docs/library-hub-header-pattern.md`**. **Handbook:** **`apps/web/AGENTS.md` §4.6** (folder-scoped hub chrome).
|
|
12
|
+
|
|
13
|
+
## MUST
|
|
14
|
+
|
|
15
|
+
1. **`LibraryPageHeader`** — When **`navState.scope === "folder"`** and **`navState.folderId`** resolves to a row in **`folders`**, pass **`onCustomizeFolder`** so **⋯ More** includes **Customize folder** ( **`fa-wand-magic-sparkles`** + label **Customize folder** ), placed after **Invite people** (collaboration variant) and before **Export**.
|
|
16
|
+
2. **Hub client** — Mount **`LibraryNewFolderSheet`** on the **hub client** (e.g. **`LibraryClient`**) next to **`ListPageTemplate`**, driven by local **`open` / `customizingFolder`** state opened from **`onCustomizeFolder`**. **MUST NOT** rely on **`LibraryTable`** alone to host the sheet when some view branches (**table**, **list**, **board**, **dashboard**) do not render that sheet — users would lose **Customize folder** on those tabs.
|
|
17
|
+
3. **`onCreated`** — On save, **`setFolders`** (or equivalent) **maps** the scoped folder **`id`** to updated **`name`**, **`icon`**, **`colorKey`** — same contract as **`LibraryTable`** panel/tree customize handlers.
|
|
18
|
+
|
|
19
|
+
## MUST NOT
|
|
20
|
+
|
|
21
|
+
- Omit **Customize folder** from the header **⋯** when the URL is folder-scoped, expecting users to find it only on secondary-nav tree rows or OS-folder tiles.
|
|
22
|
+
- Mount **only** one customize sheet inside **`LibraryTable`** without a **client-level** sheet when the hub uses **`ListPageTemplate`** view tabs that omit that table subtree.
|
|
23
|
+
|
|
24
|
+
## See also
|
|
25
|
+
|
|
26
|
+
- **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`** — URL scope + secondary panel.
|
|
27
|
+
- **`.cursor/rules/exxat-collaboration-access.mdc`** — **`variant="collaboration"`** header + **⋯** invite pattern.
|
|
28
|
+
- **`lib/library-nav.ts`** — **`parseLibraryNav`**, **`LibraryNavState`**.
|
|
@@ -16,7 +16,7 @@ Use this when rendering **system identifiers** — values a user copies, searche
|
|
|
16
16
|
|
|
17
17
|
## SHOULD
|
|
18
18
|
|
|
19
|
-
- Match existing hubs: **`
|
|
19
|
+
- Match existing hubs: **`library-table.tsx`**, **`library-list-view.tsx`**, **`new-library-item-form.tsx`** (header subtitle), **`sites-table.tsx`** (`row.id`).
|
|
20
20
|
- Prefer **`truncate`** / **`min-w-0`** on mono IDs in tight layouts so long tokens do not blow out columns.
|
|
21
21
|
|
|
22
22
|
## MUST NOT
|
|
@@ -24,7 +24,7 @@ Dense or width-constrained surfaces **MUST NOT** squeeze email if it harms scana
|
|
|
24
24
|
## Table / list / drawer / inspector
|
|
25
25
|
|
|
26
26
|
- **Dedicated person column** or **profile / invite / access** surfaces — show **avatar + name + email** (stacked or name with email below), consistent with **`InviteCollaboratorsDrawer`** and **`PageHeader`** collaboration variant.
|
|
27
|
-
- **Example:** `
|
|
27
|
+
- **Example:** `LibraryTable` **Author** — `AvatarInitials`, **primary name** (`text-sm font-medium`), **muted `text-xs` email** with optional `mailto:` (row click does not navigate when the link is used).
|
|
28
28
|
|
|
29
29
|
## Avatar rows — **never overlapping**
|
|
30
30
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Primary sidebar item opens nested SecondaryPanel (
|
|
2
|
+
description: Primary sidebar item opens nested SecondaryPanel (Library pattern)
|
|
3
3
|
globs: apps/web/components/**/*.tsx
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
@@ -8,13 +8,13 @@ alwaysApply: false
|
|
|
8
8
|
|
|
9
9
|
Some hubs expose **scoped navigation** (All / My / tree / filters) in a **nested panel** between the **icon rail** and **main content** — not as **primary nav child rows**.
|
|
10
10
|
|
|
11
|
-
**Reference:** **
|
|
11
|
+
**Reference:** **Library** — **`lib/mock/navigation.tsx`** (`secondaryPanel: "library"`), **`components/app-sidebar.tsx`**, **`components/secondary-panel.tsx`**, **`components/library-panel-activator.tsx`**, **`components/library-secondary-nav.tsx`**, **`lib/library-nav.ts`** (URL scope).
|
|
12
12
|
|
|
13
13
|
## MUST (wiring)
|
|
14
14
|
|
|
15
|
-
1. **`NavLinkItem`** — On the **primary** row that should drive the panel, set **`secondaryPanel`** to a **stable string id** (e.g. **`"
|
|
15
|
+
1. **`NavLinkItem`** — On the **primary** row that should drive the panel, set **`secondaryPanel`** to a **stable string id** (e.g. **`"library"`**). Keep **`url`** pointing at the **hub route**.
|
|
16
16
|
2. **`SecondaryPanel` registry** — In **`components/secondary-panel.tsx`**, add **`PANELS[id]`** → component that renders **panel chrome** (title, optional search) + **your secondary nav** list. **MUST** keep ids in sync with **`NAV_PRIMARY`**.
|
|
17
|
-
3. **Auto-open on route** — The hub **`client`** (or layout slot) **MUST** mount **`*PanelActivator`** that calls **`useAutoPanel(id)`** with the **same id**, so deep links and first visit open the panel while the route is mounted (e.g. **`
|
|
17
|
+
3. **Auto-open on route** — The hub **`client`** (or layout slot) **MUST** mount **`*PanelActivator`** that calls **`useAutoPanel(id)`** with the **same id**, so deep links and first visit open the panel while the route is mounted (e.g. **`LibraryPanelActivator`** + **`beforeSiteHeader`** on **`LibraryClient`**).
|
|
18
18
|
4. **Same-route reopen** — **`AppSidebar`** already calls **`openPanel(secondaryPanel)`** when the user clicks the primary item **again** while already on that **`url`** (prevents no-op navigation). Secondary nav rows that stay on the same path **SHOULD** call **`openPanel`** on click where **Next.js `Link`** would not fire (same **`href`**).
|
|
19
19
|
|
|
20
20
|
## Surface elevation (brand chrome)
|
|
@@ -35,7 +35,7 @@ The nested panel sits **between** the icon rail and main content. **MUST** use t
|
|
|
35
35
|
|
|
36
36
|
## SHOULD
|
|
37
37
|
|
|
38
|
-
- Drive **hub scope** from the **URL** (**`useSearchParams`** + helpers like **`
|
|
38
|
+
- Drive **hub scope** from the **URL** (**`useSearchParams`** + helpers like **`parseLibraryNav`**) so **refresh**, **share link**, and **breadcrumbs** match the secondary list.
|
|
39
39
|
- Keep **panel content** **Font Awesome**-aligned with the rest of the app (**`.cursor/rules/exxat-fontawesome-icons.mdc`**).
|
|
40
40
|
|
|
41
41
|
## MUST NOT
|
|
@@ -47,6 +47,6 @@ The nested panel sits **between** the icon rail and main content. **MUST** use t
|
|
|
47
47
|
|
|
48
48
|
- **`apps/web/AGENTS.md` §4.6** — handbook summary.
|
|
49
49
|
- **`apps/web/docs/shell-surface-elevation-pattern.md`** — OKLCH tokens + product theme.
|
|
50
|
-
- **`.cursor/rules/exxat-
|
|
50
|
+
- **`.cursor/rules/exxat-library-hub-header.mdc`** — folder-scoped library header **⋯** → **Customize folder** + client-mounted sheet.
|
|
51
51
|
- **`.cursor/rules/exxat-page-vs-drawer.mdc`** — when **drawer** vs **route**; secondary panel is **nav chrome**, not a workflow drawer.
|
|
52
52
|
- **`.cursor/rules/exxat-kpi-flat-band.mdc`** — flat KPI strip (separate from panel fill).
|
|
@@ -8,7 +8,7 @@ alwaysApply: true
|
|
|
8
8
|
## MUST
|
|
9
9
|
|
|
10
10
|
1. **Compose first** — Use existing **`components/ui/`**, **`components/data-views/`**, **`components/templates/`**, **`PageHeader`**, **`ListPageTemplate`**, **`DataTable`**, **`KeyMetrics`**, and patterns in **`AGENTS.md` §9** before writing new layout or interaction chrome.
|
|
11
|
-
2. **Search the codebase** — Grep or open the nearest hub (Placements, Team,
|
|
11
|
+
2. **Search the codebase** — Grep or open the nearest hub (Placements, Team, Library) for the same UX (toolbar, drawer, metrics, board card, `ListPageViewFrame`, etc.).
|
|
12
12
|
3. **Extend in place** — Prefer adding a variant, slot, or prop to a shared component over a one-off duplicate under a single route.
|
|
13
13
|
|
|
14
14
|
## When the tool must ask the user
|
|
@@ -39,13 +39,13 @@ Import from **`@/components/data-views/list-page-board-card`**:
|
|
|
39
39
|
|
|
40
40
|
Prefer **two-line blocks** for stacked **primary / secondary** facts so cards match Placements’ visual rhythm.
|
|
41
41
|
|
|
42
|
-
## Status badges (list hubs: Team, Compliance,
|
|
42
|
+
## Status badges (list hubs: Team, Compliance, Library, …)
|
|
43
43
|
|
|
44
44
|
- **Maps (single source):** **`lib/list-status-badges.ts`** — per-entity `*_STATUS_LABEL`, `*_STATUS_BADGE_CLASS`, `*_STATUS_ICON`, plus semantic **`LIST_HUB_STATUS_TINT_*`** (success / warning / neutral / danger) for new domains.
|
|
45
45
|
- **Component:** **`ListHubStatusBadge`** from **`@/components/list-hub-status-badge`** — **`surface="table"`** for **DataTable** cells and **list** rows; **`surface="board"`** inside **`ListPageBoardCardBadgeRow`**. Do not duplicate the shell classes on each page.
|
|
46
46
|
- Use the **same** maps everywhere for that entity so copy and colors never drift.
|
|
47
47
|
- **Do not** add **`uppercase`** or **`tracking-wide`** — **sentence / title case**, consistent with **`BoardStatusBadge`** on Placements (`placement-board-card.tsx`).
|
|
48
|
-
- **Placements** lifecycle uses **`StatusBadge`** in **`placements-table-cells.tsx`** — thin wrapper over **`ListHubStatusBadge`** + **`PLACEMENT_STATUS_*`** in **`list-status-badges.ts`** (same visuals as Team /
|
|
48
|
+
- **Placements** lifecycle uses **`StatusBadge`** in **`placements-table-cells.tsx`** — thin wrapper over **`ListHubStatusBadge`** + **`PLACEMENT_STATUS_*`** in **`list-status-badges.ts`** (same visuals as Team / Library).
|
|
49
49
|
|
|
50
50
|
## Avatar when mock has no `initials`
|
|
51
51
|
|
|
@@ -76,7 +76,7 @@ Goal: **one row model**, **one filtered bag** (`tableState.rows`), **one place f
|
|
|
76
76
|
|
|
77
77
|
- **`components/placements-client.tsx`** + **`placements-table.tsx`** — Placements pattern.
|
|
78
78
|
- **`components/team-client.tsx`** + **`team-table.tsx`**.
|
|
79
|
-
- **`components/
|
|
79
|
+
- **`components/library-table.tsx`** — multiple **`DataListViewType`** branches sharing **`tableState`** / **`folders`** / **`items`**.
|
|
80
80
|
|
|
81
81
|
---
|
|
82
82
|
|
|
@@ -9,7 +9,7 @@ user-invocable: true
|
|
|
9
9
|
**Handbook:** `apps/web/AGENTS.md` §4.7
|
|
10
10
|
**Narrative:** `apps/web/docs/collaboration-access-pattern.md`
|
|
11
11
|
**Cursor rule:** `.cursor/rules/exxat-collaboration-access.mdc`
|
|
12
|
-
**Related (
|
|
12
|
+
**Related (Library folder scope + ⋯ Customize folder):** `.cursor/rules/exxat-library-hub-header.mdc` · `docs/library-hub-header-pattern.md`
|
|
13
13
|
|
|
14
14
|
## Wiring checklist
|
|
15
15
|
|
|
@@ -20,7 +20,7 @@ user-invocable: true
|
|
|
20
20
|
5. **Access maps** — `lib/collaborator-access.ts` for Owner / Editor / Commenter / Viewer, invite options, and **`COLLABORATION_HEADER_ADD_LABEL`**.
|
|
21
21
|
6. **Header** — empty roster → outline **Add collaborator**; non-empty → face rail; both open the invite sheet.
|
|
22
22
|
7. **Invite sheet** — `InviteCollaboratorsDrawer`: export-style **`Sheet`**, combined email + access menu, grouped roster (name → email → role tags → access badge).
|
|
23
|
-
8. **
|
|
23
|
+
8. **Library — folder URL scope** — When **`?scope=folder`**, **`LibraryPageHeader`** **⋯** also lists **Customize folder**; **`LibraryNewFolderSheet`** on **`LibraryClient`** — **`.cursor/rules/exxat-library-hub-header.mdc`**, **`docs/library-hub-header-pattern.md`**.
|
|
24
24
|
|
|
25
25
|
## MUST NOT
|
|
26
26
|
|
|
@@ -31,5 +31,5 @@ user-invocable: true
|
|
|
31
31
|
|
|
32
32
|
## Reference
|
|
33
33
|
|
|
34
|
-
- `components/collaboration-access-flow.tsx`, `components/
|
|
34
|
+
- `components/collaboration-access-flow.tsx`, `components/library-page-header.tsx`, `components/library-client.tsx`
|
|
35
35
|
- `components/invite-collaborators-drawer.tsx`, `components/export-drawer.tsx`
|
|
@@ -37,8 +37,8 @@ description: >-
|
|
|
37
37
|
| Recents list | `components/dedicated-search-recents.tsx` |
|
|
38
38
|
| Recents storage factory | `lib/dedicated-search-recents.ts` |
|
|
39
39
|
| Optional default `q` patcher | `lib/dedicated-search-url.ts` |
|
|
40
|
-
|
|
|
41
|
-
|
|
|
40
|
+
| Library adapter (placeholders + patch) | `lib/library-dedicated-search.ts` |
|
|
41
|
+
| Library wiring | `components/library-client.tsx` |
|
|
42
42
|
|
|
43
43
|
## Cursor rule
|
|
44
44
|
|