@exxatdesignux/ui 0.5.11 → 0.5.13
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 +45 -0
- package/bin/cli.mjs +70 -1
- package/bin/init.mjs +18 -4
- package/bin/sync-extras.mjs +28 -4
- package/consumer-extras/README.md +41 -5
- package/consumer-extras/cursor-rules/exxat-accessibility.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +5 -3
- package/consumer-extras/cursor-rules/exxat-breadcrumbs-no-back.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-card-vs-list-rows.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +4 -2
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-command-menu.mdc +3 -2
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +5 -3
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +7 -0
- package/consumer-extras/cursor-rules/exxat-drawer-vs-dialog.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-fontawesome-icons.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-hub-supported-views.mdc +6 -4
- package/consumer-extras/cursor-rules/exxat-kbd-shortcuts.mdc +6 -5
- package/consumer-extras/cursor-rules/exxat-kpi-flat-band.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-kpi-max-four.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-kpi-trends.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-list-page-connected-views.mdc +6 -2
- package/consumer-extras/cursor-rules/exxat-list-page-view-shells.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-nav-single-active.mdc +4 -3
- package/consumer-extras/cursor-rules/exxat-no-image-pixel-copy.mdc +25 -14
- package/consumer-extras/cursor-rules/exxat-no-slds-leakage.mdc +8 -2
- package/consumer-extras/cursor-rules/exxat-no-toast.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-no-vaul.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-page-header-actions.mdc +6 -4
- package/consumer-extras/cursor-rules/exxat-page-vs-drawer.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-sidebar-shell.mdc +13 -7
- package/consumer-extras/cursor-rules/exxat-table-properties-drawer.mdc +5 -3
- package/consumer-extras/cursor-rules/exxat-table-row-preview.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-tabs-chrome.mdc +6 -4
- package/consumer-extras/cursor-rules/exxat-token-discipline.mdc +6 -0
- package/consumer-extras/cursor-rules/exxat-ux-discovery-protocol.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-ux-principles.mdc +1 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-kpi-trends/SKILL.md +5 -3
- package/consumer-extras/patterns/command-menu-pattern.md +2 -2
- package/consumer-extras/patterns/consumer-upgrade-checklist.md +1 -1
- package/consumer-extras/patterns/jobs/README.md +1 -1
- package/consumer-extras/patterns/perf-memory-pattern.md +115 -150
- package/consumer-extras/scripts/dev-guard.mjs +156 -0
- package/consumer-extras/templates/README.md +23 -0
- package/consumer-extras/templates/handoff.md +190 -0
- package/package.json +2 -3
- package/{template → template-vite}/.claude/skills/exxat-ds-skill/SKILL.md +184 -23
- package/template-vite/.cursor/rules/exxat-accessibility.mdc +40 -0
- package/template-vite/.cursor/rules/exxat-board-cards.mdc +28 -0
- package/template-vite/.cursor/rules/exxat-breadcrumbs-no-back.mdc +22 -0
- package/template-vite/.cursor/rules/exxat-card-vs-list-rows.mdc +22 -0
- package/template-vite/.cursor/rules/exxat-centralized-list-dataset.mdc +46 -0
- package/template-vite/.cursor/rules/exxat-collaboration-access.mdc +33 -0
- package/{template → template-vite}/.cursor/rules/exxat-command-menu.mdc +5 -5
- package/template-vite/.cursor/rules/exxat-data-tables.mdc +47 -0
- package/template-vite/.cursor/rules/exxat-dedicated-search-surfaces.mdc +32 -0
- package/template-vite/.cursor/rules/exxat-drawer-vs-dialog.mdc +23 -0
- package/template-vite/.cursor/rules/exxat-ds-agents.mdc +87 -0
- package/template-vite/.cursor/rules/exxat-fontawesome-icons.mdc +32 -0
- package/template-vite/.cursor/rules/exxat-hub-supported-views.mdc +56 -0
- package/{template → template-vite}/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -0
- package/template-vite/.cursor/rules/exxat-kpi-flat-band.mdc +29 -0
- package/template-vite/.cursor/rules/exxat-kpi-max-four.mdc +22 -0
- package/template-vite/.cursor/rules/exxat-kpi-trends.mdc +32 -0
- package/template-vite/.cursor/rules/exxat-library-hub-header.mdc +29 -0
- package/template-vite/.cursor/rules/exxat-list-page-connected-views.mdc +28 -0
- package/template-vite/.cursor/rules/exxat-list-page-view-shells.mdc +32 -0
- package/{template → template-vite}/.cursor/rules/exxat-mono-ids.mdc +1 -0
- package/template-vite/.cursor/rules/exxat-nav-single-active.mdc +32 -0
- package/template-vite/.cursor/rules/exxat-no-image-pixel-copy.mdc +46 -0
- package/template-vite/.cursor/rules/exxat-no-slds-leakage.mdc +84 -0
- package/{template → template-vite}/.cursor/rules/exxat-no-toast.mdc +2 -2
- package/template-vite/.cursor/rules/exxat-no-vaul.mdc +26 -0
- package/template-vite/.cursor/rules/exxat-page-header-actions.mdc +33 -0
- package/{template → template-vite}/.cursor/rules/exxat-page-vs-drawer.mdc +5 -3
- package/template-vite/.cursor/rules/exxat-person-identity-display.mdc +48 -0
- package/template-vite/.cursor/rules/exxat-primary-nav-secondary-panel.mdc +53 -0
- package/template-vite/.cursor/rules/exxat-reuse-before-custom.mdc +37 -0
- package/template-vite/.cursor/rules/exxat-sidebar-shell.mdc +41 -0
- package/template-vite/.cursor/rules/exxat-table-properties-drawer.mdc +79 -0
- package/template-vite/.cursor/rules/exxat-table-row-preview.mdc +25 -0
- package/template-vite/.cursor/rules/exxat-tabs-chrome.mdc +33 -0
- package/template-vite/.cursor/rules/exxat-token-discipline.mdc +109 -0
- package/template-vite/.cursor/rules/exxat-ux-discovery-protocol.mdc +202 -0
- package/template-vite/.cursor/rules/exxat-ux-principles.mdc +187 -0
- package/template-vite/.cursor/skills/exxat-accessibility/SKILL.md +282 -0
- package/template-vite/.cursor/skills/exxat-board-cards/SKILL.md +68 -0
- package/template-vite/.cursor/skills/exxat-card-vs-list-rows/SKILL.md +20 -0
- package/template-vite/.cursor/skills/exxat-centralized-list-dataset/SKILL.md +99 -0
- package/template-vite/.cursor/skills/exxat-collaboration-access/SKILL.md +35 -0
- package/template-vite/.cursor/skills/exxat-dedicated-search-surfaces/SKILL.md +45 -0
- package/template-vite/.cursor/skills/exxat-drawer-vs-dialog/SKILL.md +20 -0
- package/template-vite/.cursor/skills/exxat-ds-skill/SKILL.md +893 -0
- package/template-vite/.cursor/skills/exxat-ds-skill/references/accessibility.md +142 -0
- package/template-vite/.cursor/skills/exxat-ds-skill/references/coach-marks.md +169 -0
- package/template-vite/.cursor/skills/exxat-ds-skill/references/data-table-pattern.md +392 -0
- package/template-vite/.cursor/skills/exxat-fontawesome-icons/SKILL.md +31 -0
- package/template-vite/.cursor/skills/exxat-kpi-flat-band/SKILL.md +38 -0
- package/template-vite/.cursor/skills/exxat-kpi-max-four/SKILL.md +19 -0
- package/template-vite/.cursor/skills/exxat-kpi-trends/SKILL.md +29 -0
- package/template-vite/.cursor/skills/exxat-list-page-view-shells/SKILL.md +36 -0
- package/template-vite/.cursor/skills/exxat-mono-ids/SKILL.md +56 -0
- package/template-vite/.cursor/skills/exxat-primary-nav-secondary-panel/SKILL.md +49 -0
- package/template-vite/.cursor/skills/exxat-senior-ux/SKILL.md +198 -0
- package/template-vite/.cursor/skills/exxat-token-economy/SKILL.md +287 -0
- package/template-vite/.cursor/skills/exxat-ux-audit/SKILL.md +303 -0
- package/{template → template-vite}/components/ask-leo-sidebar.tsx +10 -8
- package/{template → template-vite}/components/command-menu.tsx +1 -1
- package/{template → template-vite}/components/data-views/library-folder-tree-branch.tsx +1 -1
- package/{template → template-vite}/components/dedicated-search-recents.tsx +1 -1
- package/{template → template-vite}/components/dedicated-search-url-composer.tsx +1 -1
- package/{template → template-vite}/components/exxat-product-logo.tsx +3 -3
- package/{template → template-vite}/components/library-client.tsx +1 -1
- package/{template → template-vite}/components/library-hub-client.tsx +2 -2
- package/{template → template-vite}/components/library-secondary-nav.tsx +2 -2
- package/{template → template-vite}/components/library-table.tsx +35 -27
- package/{template → template-vite}/components/new-library-item-form.tsx +1 -1
- package/{template → template-vite}/components/page-breadcrumb-trail.tsx +1 -1
- package/{template → template-vite}/components/settings-client.tsx +1 -1
- package/{template → template-vite}/components/sidebar/app-sidebar.tsx +2 -2
- package/{template → template-vite}/components/sidebar/nav-main.tsx +1 -1
- package/{template → template-vite}/components/sidebar/nav-user.tsx +1 -1
- package/{template → template-vite}/components/sidebar/secondary-nav.tsx +1 -1
- package/{template → template-vite}/components/system-banner-slot.tsx +1 -1
- package/{template → template-vite}/components/templates/discovery-hub-template.tsx +2 -2
- package/{template → template-vite}/components/templates/new-focus-template.tsx +1 -1
- package/{template → template-vite}/components/tokens-secondary-nav.tsx +2 -2
- package/{template → template-vite}/components/tokens-themes-client.tsx +1 -1
- package/{template → template-vite}/hooks/use-secondary-panel-hub-nav.ts +1 -1
- package/template-vite/index.html +49 -0
- package/template-vite/lib/next-compat.tsx +98 -0
- package/{template → template-vite}/package.json +15 -27
- package/template-vite/scripts/port-next-imports.mjs +70 -0
- package/template-vite/src/App.tsx +103 -0
- package/template-vite/src/main.tsx +50 -0
- package/{template/app/(app)/error.tsx → template-vite/src/pages/_error.tsx} +12 -24
- package/{template/app/(app)/loading.tsx → template-vite/src/pages/_loading.tsx} +4 -2
- package/template-vite/src/pages/_not-found.tsx +17 -0
- package/template-vite/src/pages/dashboard.tsx +48 -0
- package/{template/app/(app)/help/page.tsx → template-vite/src/pages/help.tsx} +3 -2
- package/{template/app/(app)/library/layout.tsx → template-vite/src/pages/library/_layout.tsx} +18 -16
- package/{template/app/(app)/library/all/page.tsx → template-vite/src/pages/library/all.tsx} +1 -1
- package/{template/app/(app)/library/new/page.tsx → template-vite/src/pages/library/new.tsx} +12 -18
- package/template-vite/src/routes.tsx +72 -0
- package/template-vite/src/styles/globals.css +25 -0
- package/{template → template-vite}/tsconfig.json +5 -14
- package/template-vite/vite.config.ts +52 -0
- package/consumer-extras/cursor-rules/exxat-dashboard-view-charts.mdc +0 -53
- package/template/.agents/skills/shadcn/SKILL.md +0 -242
- package/template/.agents/skills/shadcn/agents/openai.yml +0 -5
- 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 +0 -257
- package/template/.agents/skills/shadcn/customization.md +0 -202
- package/template/.agents/skills/shadcn/evals/evals.json +0 -47
- package/template/.agents/skills/shadcn/mcp.md +0 -94
- package/template/.agents/skills/shadcn/rules/base-vs-radix.md +0 -306
- package/template/.agents/skills/shadcn/rules/composition.md +0 -195
- package/template/.agents/skills/shadcn/rules/forms.md +0 -192
- package/template/.agents/skills/shadcn/rules/icons.md +0 -101
- package/template/.agents/skills/shadcn/rules/styling.md +0 -162
- package/template/.cursor/rules/exxat-accessibility.mdc +0 -33
- package/template/.cursor/rules/exxat-data-tables.mdc +0 -32
- package/template/.cursor/rules/exxat-ds-agents.mdc +0 -26
- package/template/.cursor/rules/exxat-list-page-connected-views.mdc +0 -16
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +0 -40
- package/template/.nvmrc +0 -1
- package/template/.prettierignore +0 -7
- package/template/Logo/Exxat_Prism.svg +0 -39
- package/template/Logo/Exxat_one.svg +0 -36
- package/template/app/(app)/dashboard/loading.tsx +0 -18
- package/template/app/(app)/dashboard/page.tsx +0 -36
- package/template/app/(app)/layout.tsx +0 -77
- package/template/app/global-error.tsx +0 -63
- package/template/app/globals.css +0 -20
- package/template/app/layout.tsx +0 -133
- package/template/app/page.tsx +0 -9
- package/template/docs/HANDBOOK.md +0 -187
- package/template/docs/blueprints/README.md +0 -86
- package/template/docs/blueprints/_template.md +0 -91
- package/template/docs/blueprints/board-card.md +0 -123
- package/template/docs/blueprints/data-table.md +0 -139
- package/template/docs/blueprints/key-metrics.md +0 -128
- package/template/docs/blueprints/list-page-template.md +0 -123
- package/template/docs/blueprints/page-header.md +0 -130
- package/template/docs/card-vs-rows-pattern.md +0 -36
- package/template/docs/collaboration-access-pattern.md +0 -116
- package/template/docs/command-menu-pattern.md +0 -45
- package/template/docs/component-selection-guide.md +0 -224
- package/template/docs/components-audit-2026-05.md +0 -158
- package/template/docs/consumer-upgrade-checklist.md +0 -52
- package/template/docs/data-views-pattern.md +0 -185
- package/template/docs/drawer-vs-dialog-pattern.md +0 -50
- package/template/docs/glossary.md +0 -59
- package/template/docs/hub-supported-views-pattern.md +0 -53
- package/template/docs/jobs/README.md +0 -59
- package/template/docs/jobs/record-detail.md +0 -177
- package/template/docs/kpi-flat-band-pattern.md +0 -57
- package/template/docs/kpi-strip-max-four-pattern.md +0 -30
- package/template/docs/kpi-trend-pattern.md +0 -58
- package/template/docs/large-dataset-strategy.md +0 -155
- package/template/docs/library-hub-header-pattern.md +0 -25
- package/template/docs/migrations/0001-brand-deep-alias-stabilization.md +0 -95
- package/template/docs/migrations/0002-exxat-token-namespace.md +0 -154
- package/template/docs/migrations/0003-globals-css-canonical.md +0 -110
- package/template/docs/migrations/README.md +0 -100
- package/template/docs/migrations/_template.md +0 -64
- package/template/docs/modern-saas-patterns.md +0 -165
- package/template/docs/perf-memory-pattern.md +0 -206
- package/template/docs/reference-implementations.md +0 -153
- package/template/docs/shell-surface-elevation-pattern.md +0 -52
- package/template/docs/token-taxonomy.md +0 -416
- package/template/docs/voice-and-tone.md +0 -262
- package/template/ecosystem.config.cjs +0 -32
- package/template/next.config.mjs +0 -216
- package/template/postcss.config.mjs +0 -8
- package/template/public/favicon/favicon.ico +0 -0
- package/template/tests/setup.ts +0 -26
- package/template/vitest.config.ts +0 -18
- /package/{template → template-vite}/.cursor/rules/exxat-dashboard-view-charts.mdc +0 -0
- /package/{template → template-vite}/.prettierrc +0 -0
- /package/{template → template-vite}/AGENTS.md +0 -0
- /package/{template → template-vite}/README.md +0 -0
- /package/{template → template-vite}/components/.gitkeep +0 -0
- /package/{template → template-vite}/components/ask-leo-composer.tsx +0 -0
- /package/{template → template-vite}/components/brand-color-picker.tsx +0 -0
- /package/{template → template-vite}/components/chart-area-interactive.tsx +0 -0
- /package/{template → template-vite}/components/charts-overview.tsx +0 -0
- /package/{template → template-vite}/components/collaboration-access-flow.tsx +0 -0
- /package/{template → template-vite}/components/columns-client.tsx +0 -0
- /package/{template → template-vite}/components/columns-showcase.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-promo-banner.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-quota-progress-card.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-report-charts.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-section-heading.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-tabs.tsx +0 -0
- /package/{template → template-vite}/components/data-table/filter-date-calendar.tsx +0 -0
- /package/{template → template-vite}/components/data-table/filter-text-value-input.tsx +0 -0
- /package/{template → template-vite}/components/data-table/index.tsx +0 -0
- /package/{template → template-vite}/components/data-table/pagination.tsx +0 -0
- /package/{template → template-vite}/components/data-table/types.ts +0 -0
- /package/{template → template-vite}/components/data-table/use-table-state.test.ts +0 -0
- /package/{template → template-vite}/components/data-table/use-table-state.ts +0 -0
- /package/{template → template-vite}/components/data-views/board-card-primitives.tsx +0 -0
- /package/{template → template-vite}/components/data-views/data-row-list.tsx +0 -0
- /package/{template → template-vite}/components/data-views/finder-panel-view.tsx +0 -0
- /package/{template → template-vite}/components/data-views/folder-grid-view.tsx +0 -0
- /package/{template → template-vite}/components/data-views/hub-table.tsx +0 -0
- /package/{template → template-vite}/components/data-views/index.ts +0 -0
- /package/{template → template-vite}/components/data-views/list-page-board-card.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-board-template.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-connected-view-body.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-split-details-placeholder.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-split-hub-chrome.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-split-hub-tokens.ts +0 -0
- /package/{template → template-vite}/components/data-views/list-page-tree-column-header.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-tree-panel-shell.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-view-frame.tsx +0 -0
- /package/{template → template-vite}/components/data-views/os-folder-glyph.tsx +0 -0
- /package/{template → template-vite}/components/data-views/outline-tree-menu.tsx +0 -0
- /package/{template → template-vite}/components/data-views/table-cells.tsx +0 -0
- /package/{template → template-vite}/components/dev-chunk-load-recovery.tsx +0 -0
- /package/{template → template-vite}/components/export-drawer.test.tsx +0 -0
- /package/{template → template-vite}/components/export-drawer.tsx +0 -0
- /package/{template → template-vite}/components/folder-details-shell.tsx +0 -0
- /package/{template → template-vite}/components/form-layout-01.tsx +0 -0
- /package/{template → template-vite}/components/hub-tree-panel-view.tsx +0 -0
- /package/{template → template-vite}/components/invite-collaborators-drawer.tsx +0 -0
- /package/{template → template-vite}/components/key-metrics-ask-leo-bridge.tsx +0 -0
- /package/{template → template-vite}/components/key-metrics.tsx +0 -0
- /package/{template → template-vite}/components/leo-insight-indicator.tsx +0 -0
- /package/{template → template-vite}/components/leo-typing-dots.tsx +0 -0
- /package/{template → template-vite}/components/library-board-view.tsx +0 -0
- /package/{template → template-vite}/components/library-dashboard-charts.tsx +0 -0
- /package/{template → template-vite}/components/library-favorite-button.tsx +0 -0
- /package/{template → template-vite}/components/library-new-folder-sheet.tsx +0 -0
- /package/{template → template-vite}/components/library-os-folder-view.tsx +0 -0
- /package/{template → template-vite}/components/library-page-header.tsx +0 -0
- /package/{template → template-vite}/components/library-panel-activator.tsx +0 -0
- /package/{template → template-vite}/components/list-hub-status-badge.tsx +0 -0
- /package/{template → template-vite}/components/list-page-dashboard-charts.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/getting-started.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/index.ts +0 -0
- /package/{template → template-vite}/components/onboarding/onboarding-01.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/onboarding-02.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/onboarding-03.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/onboarding-04.tsx +0 -0
- /package/{template → template-vite}/components/page-header.tsx +0 -0
- /package/{template → template-vite}/components/product-switcher.tsx +0 -0
- /package/{template → template-vite}/components/product-wordmark.tsx +0 -0
- /package/{template → template-vite}/components/settings-appearance-card.tsx +0 -0
- /package/{template → template-vite}/components/settings-form-row.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/app-sidebar-dynamic.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/index.ts +0 -0
- /package/{template → template-vite}/components/sidebar/nav-documents.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/nav-secondary.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/secondary-panel.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/sidebar-auto-collapse.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/sidebar-auto-open.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/sidebar-shell.tsx +0 -0
- /package/{template → template-vite}/components/site-header.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/column-row.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/draggable-list.ts +0 -0
- /package/{template → template-vite}/components/table-properties/drawer-button.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/drawer.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/filter-card.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/index.ts +0 -0
- /package/{template → template-vite}/components/table-properties/sort-card.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/types.ts +0 -0
- /package/{template → template-vite}/components/task-list-panel.tsx +0 -0
- /package/{template → template-vite}/components/task-priority-badge.tsx +0 -0
- /package/{template → template-vite}/components/templates/dedicated-search-landing-template.tsx +0 -0
- /package/{template → template-vite}/components/templates/dedicated-search-results-template.tsx +0 -0
- /package/{template → template-vite}/components/templates/list-page.tsx +0 -0
- /package/{template → template-vite}/components/templates/nested-secondary-panel-shell.tsx +0 -0
- /package/{template → template-vite}/components/templates/primary-page-template.tsx +0 -0
- /package/{template → template-vite}/components/templates/secondary-panel-hub-template.tsx +0 -0
- /package/{template → template-vite}/components/theme-color-sync.tsx +0 -0
- /package/{template → template-vite}/components/theme-provider.tsx +0 -0
- /package/{template → template-vite}/components/tinted-icon-disc.tsx +0 -0
- /package/{template → template-vite}/components/tokens-hub-auxiliary-views.tsx +0 -0
- /package/{template → template-vite}/components/tokens-themes-section.tsx +0 -0
- /package/{template → template-vite}/components/ui/accordion.tsx +0 -0
- /package/{template → template-vite}/components/ui/ai-thinking-surface.tsx +0 -0
- /package/{template → template-vite}/components/ui/alert-dialog.tsx +0 -0
- /package/{template → template-vite}/components/ui/avatar.tsx +0 -0
- /package/{template → template-vite}/components/ui/badge.tsx +0 -0
- /package/{template → template-vite}/components/ui/banner.tsx +0 -0
- /package/{template → template-vite}/components/ui/breadcrumb.tsx +0 -0
- /package/{template → template-vite}/components/ui/button.tsx +0 -0
- /package/{template → template-vite}/components/ui/calendar.tsx +0 -0
- /package/{template → template-vite}/components/ui/card.tsx +0 -0
- /package/{template → template-vite}/components/ui/chart.tsx +0 -0
- /package/{template → template-vite}/components/ui/checkbox.tsx +0 -0
- /package/{template → template-vite}/components/ui/coach-mark.tsx +0 -0
- /package/{template → template-vite}/components/ui/collapsible.tsx +0 -0
- /package/{template → template-vite}/components/ui/command.tsx +0 -0
- /package/{template → template-vite}/components/ui/context-menu.tsx +0 -0
- /package/{template → template-vite}/components/ui/date-picker-field.tsx +0 -0
- /package/{template → template-vite}/components/ui/dialog.tsx +0 -0
- /package/{template → template-vite}/components/ui/dot-pattern.tsx +0 -0
- /package/{template → template-vite}/components/ui/drag-handle-grip.tsx +0 -0
- /package/{template → template-vite}/components/ui/dropdown-menu.tsx +0 -0
- /package/{template → template-vite}/components/ui/field.tsx +0 -0
- /package/{template → template-vite}/components/ui/form.tsx +0 -0
- /package/{template → template-vite}/components/ui/hover-card.tsx +0 -0
- /package/{template → template-vite}/components/ui/input-group.tsx +0 -0
- /package/{template → template-vite}/components/ui/input-mask.tsx +0 -0
- /package/{template → template-vite}/components/ui/input.tsx +0 -0
- /package/{template → template-vite}/components/ui/kbd.tsx +0 -0
- /package/{template → template-vite}/components/ui/label.tsx +0 -0
- /package/{template → template-vite}/components/ui/leo-icon.tsx +0 -0
- /package/{template → template-vite}/components/ui/payment-card-fields.tsx +0 -0
- /package/{template → template-vite}/components/ui/popover.tsx +0 -0
- /package/{template → template-vite}/components/ui/radio-group.tsx +0 -0
- /package/{template → template-vite}/components/ui/resizable.tsx +0 -0
- /package/{template → template-vite}/components/ui/scroll-area.tsx +0 -0
- /package/{template → template-vite}/components/ui/select.tsx +0 -0
- /package/{template → template-vite}/components/ui/selection-tile-grid.tsx +0 -0
- /package/{template → template-vite}/components/ui/separator.tsx +0 -0
- /package/{template → template-vite}/components/ui/sheet.tsx +0 -0
- /package/{template → template-vite}/components/ui/sidebar.tsx +0 -0
- /package/{template → template-vite}/components/ui/skeleton.tsx +0 -0
- /package/{template → template-vite}/components/ui/slider.tsx +0 -0
- /package/{template → template-vite}/components/ui/sonner.tsx +0 -0
- /package/{template → template-vite}/components/ui/status-badge.tsx +0 -0
- /package/{template → template-vite}/components/ui/table.tsx +0 -0
- /package/{template → template-vite}/components/ui/tabs.tsx +0 -0
- /package/{template → template-vite}/components/ui/textarea.tsx +0 -0
- /package/{template → template-vite}/components/ui/tip.tsx +0 -0
- /package/{template → template-vite}/components/ui/toggle-group.tsx +0 -0
- /package/{template → template-vite}/components/ui/toggle-switch.tsx +0 -0
- /package/{template → template-vite}/components/ui/toggle.tsx +0 -0
- /package/{template → template-vite}/components/ui/tooltip.tsx +0 -0
- /package/{template → template-vite}/components/ui/view-segmented-control.tsx +0 -0
- /package/{template → template-vite}/components.json +0 -0
- /package/{template → template-vite}/contexts/chart-variant-context.tsx +0 -0
- /package/{template → template-vite}/contexts/command-menu-context.tsx +0 -0
- /package/{template → template-vite}/contexts/dashboard-view-context.tsx +0 -0
- /package/{template → template-vite}/contexts/product-context.tsx +0 -0
- /package/{template → template-vite}/contexts/system-banner-context.tsx +0 -0
- /package/{template → template-vite}/eslint.config.mjs +0 -0
- /package/{template → template-vite}/fontawesome-subset.manifest.json +0 -0
- /package/{template → template-vite}/hooks/.gitkeep +0 -0
- /package/{template → template-vite}/hooks/use-app-theme.ts +0 -0
- /package/{template → template-vite}/hooks/use-coach-mark.ts +0 -0
- /package/{template → template-vite}/hooks/use-location-hash.ts +0 -0
- /package/{template → template-vite}/hooks/use-mobile.ts +0 -0
- /package/{template → template-vite}/hooks/use-mod-key-label.ts +0 -0
- /package/{template → template-vite}/hooks/use-sidebar-reflow-zoom.ts +0 -0
- /package/{template → template-vite}/lib/.gitkeep +0 -0
- /package/{template → template-vite}/lib/ask-leo-route-context.ts +0 -0
- /package/{template → template-vite}/lib/chart-keyboard-selection.test.ts +0 -0
- /package/{template → template-vite}/lib/chart-keyboard-selection.ts +0 -0
- /package/{template → template-vite}/lib/chart-line-dash.ts +0 -0
- /package/{template → template-vite}/lib/chunk-load-error.ts +0 -0
- /package/{template → template-vite}/lib/coach-mark-registry.ts +0 -0
- /package/{template → template-vite}/lib/collaborator-access.ts +0 -0
- /package/{template → template-vite}/lib/command-menu-config.ts +0 -0
- /package/{template → template-vite}/lib/command-menu-search-data.ts +0 -0
- /package/{template → template-vite}/lib/conditional-rule-match.ts +0 -0
- /package/{template → template-vite}/lib/dashboard-customize-coach-mark.ts +0 -0
- /package/{template → template-vite}/lib/dashboard-layout-merge.ts +0 -0
- /package/{template → template-vite}/lib/data-list-display-options.ts +0 -0
- /package/{template → template-vite}/lib/data-list-persistence.ts +0 -0
- /package/{template → template-vite}/lib/data-list-view-registry.ts +0 -0
- /package/{template → template-vite}/lib/data-list-view-surface.ts +0 -0
- /package/{template → template-vite}/lib/data-list-view.ts +0 -0
- /package/{template → template-vite}/lib/data-view-dashboard-storage.ts +0 -0
- /package/{template → template-vite}/lib/date-filter.ts +0 -0
- /package/{template → template-vite}/lib/dedicated-search-recents.ts +0 -0
- /package/{template → template-vite}/lib/dedicated-search-url.ts +0 -0
- /package/{template → template-vite}/lib/dev-log.test.ts +0 -0
- /package/{template → template-vite}/lib/dev-log.ts +0 -0
- /package/{template → template-vite}/lib/discovery-hub.ts +0 -0
- /package/{template → template-vite}/lib/editable-target.ts +0 -0
- /package/{template → template-vite}/lib/exxat-palette.json +0 -0
- /package/{template → template-vite}/lib/exxat-palette.ts +0 -0
- /package/{template → template-vite}/lib/floating-sheet-panel.ts +0 -0
- /package/{template → template-vite}/lib/full-hub-supported-views.ts +0 -0
- /package/{template → template-vite}/lib/hub-connected-view-renderers.ts +0 -0
- /package/{template → template-vite}/lib/initials-from-name.ts +0 -0
- /package/{template → template-vite}/lib/library-authoring.ts +0 -0
- /package/{template → template-vite}/lib/library-dedicated-search.ts +0 -0
- /package/{template → template-vite}/lib/library-hub-search.ts +0 -0
- /package/{template → template-vite}/lib/library-nav.ts +0 -0
- /package/{template → template-vite}/lib/library-recent-searches.ts +0 -0
- /package/{template → template-vite}/lib/library-supported-views.ts +0 -0
- /package/{template → template-vite}/lib/list-hub-supported-views.ts +0 -0
- /package/{template → template-vite}/lib/list-page-table-properties.ts +0 -0
- /package/{template → template-vite}/lib/list-status-badges.ts +0 -0
- /package/{template → template-vite}/lib/logo-dev.ts +0 -0
- /package/{template → template-vite}/lib/mailto.ts +0 -0
- /package/{template → template-vite}/lib/mock/dashboard.ts +0 -0
- /package/{template → template-vite}/lib/mock/library-folders.ts +0 -0
- /package/{template → template-vite}/lib/mock/library-header-collaborators.ts +0 -0
- /package/{template → template-vite}/lib/mock/library-inspector.ts +0 -0
- /package/{template → template-vite}/lib/mock/library-kpi.ts +0 -0
- /package/{template → template-vite}/lib/mock/library.ts +0 -0
- /package/{template → template-vite}/lib/mock/navigation.tsx +0 -0
- /package/{template → template-vite}/lib/motion-ui.ts +0 -0
- /package/{template → template-vite}/lib/product-brand.ts +0 -0
- /package/{template → template-vite}/lib/raf-throttle.ts +0 -0
- /package/{template → template-vite}/lib/row-height.ts +0 -0
- /package/{template → template-vite}/lib/sidebar-state-cookie.ts +0 -0
- /package/{template → template-vite}/lib/stock-portrait.ts +0 -0
- /package/{template → template-vite}/lib/table-state-lifecycle.ts +0 -0
- /package/{template → template-vite}/lib/utils.test.ts +0 -0
- /package/{template → template-vite}/lib/utils.ts +0 -0
- /package/{template → template-vite}/public/.gitkeep +0 -0
- /package/{template → template-vite}/public/Illustration/Rotation.svg +0 -0
- /package/{template → template-vite}/public/avatars/user.svg +0 -0
- /package/{template/public → template-vite/public/favicon}/favicon.ico +0 -0
- /package/{template/app → template-vite/public}/favicon.ico +0 -0
- /package/{template → template-vite}/public/folders/icons8-folder-windows-11.svg +0 -0
- /package/{template → template-vite}/public/logos/exxat-one.svg +0 -0
- /package/{template → template-vite}/public/logos/exxat-prism.svg +0 -0
- /package/{template → template-vite}/public/mock-schools/emory.svg +0 -0
- /package/{template → template-vite}/public/mock-schools/rush.svg +0 -0
- /package/{template → template-vite}/scripts/fontawesome-subset-audit.mjs +0 -0
- /package/{template → template-vite}/scripts/pm2-startup-macos.sh +0 -0
- /package/{template → template-vite}/skills-lock.json +0 -0
- /package/{template/app/(app)/columns/page.tsx → template-vite/src/pages/columns.tsx} +0 -0
- /package/{template/app/(app)/library/find/page.tsx → template-vite/src/pages/library/find.tsx} +0 -0
- /package/{template/app/(app)/library/page.tsx → template-vite/src/pages/library/index.tsx} +0 -0
- /package/{template/app/(app)/library/list/page.tsx → template-vite/src/pages/library/list.tsx} +0 -0
- /package/{template/app/(app)/settings/page.tsx → template-vite/src/pages/settings.tsx} +0 -0
- /package/{template/app/(app)/tokens-themes/page.tsx → template-vite/src/pages/tokens-themes.tsx} +0 -0
- /package/{template → template-vite}/stores/app-store.ts +0 -0
- /package/{template → template-vite}/types/react-payment-inputs.d.ts +0 -0
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
# Exxat DS — Token Taxonomy
|
|
2
|
-
|
|
3
|
-
**Status:** Authoritative · **Audience:** humans + AI agents · **Standard:** WCAG 2.1 AA
|
|
4
|
-
**Machine-readable index:** [`packages/ui/tokens/hooks-index.json`](../../packages/ui/tokens/hooks-index.json) (generated)
|
|
5
|
-
**Defining file (canonical):** [`packages/ui/src/globals.css`](../../packages/ui/src/globals.css). Consumer apps (`apps/web/app/globals.css`, the `create-exxat-app` starter at `packages/ui/template/app/globals.css`) are thin shells that `@import "@exxatdesignux/ui/globals.css"` and only declare their own `@source` directive. The dedicated `packages/ui/src/theme.css` file has been **retired** — see [`docs/migrations/0003-globals-css-canonical.md`](./migrations/0003-globals-css-canonical.md).
|
|
6
|
-
|
|
7
|
-
This document formalizes the **naming, layering, and ownership** of every CSS
|
|
8
|
-
custom property the design system ships. It is the answer to "what do I name a
|
|
9
|
-
new token?" and "is there already a token for this?". If something here
|
|
10
|
-
disagrees with the CSS, the CSS wins — and this file needs an edit.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 1. Layering — where a token lives
|
|
15
|
-
|
|
16
|
-
Tokens are declared in **four** layers. Higher layers may consume lower ones;
|
|
17
|
-
they MUST NOT redefine names from a higher layer with a different meaning.
|
|
18
|
-
|
|
19
|
-
| Layer | File | Purpose | Edit when |
|
|
20
|
-
|---|---|---|---|
|
|
21
|
-
| **L0 — Exxat canonical** | `packages/ui/src/globals.css` `:root` (block tagged `Exxat L0 — canonical namespace`) | SLDS-style flat namespace (`--exxat-color-surface-1`, `--exxat-radius-2`, …). The **official** names the DS scales on. Today these are `var()` aliases of L1; the canonical OKLCH will move here over time. | New product surface tokens, broader brand picker, anything you would otherwise put under "L1 with an Exxat brand prefix" |
|
|
22
|
-
| **L1 — shadcn semantic** | `packages/ui/src/globals.css` `:root`/`.dark` | OKLCH literals + back-compat vocabulary (`--background`, `--foreground`, `--brand-color`, …). Required for the upstream shadcn primitives in `components/ui/*`. **Frozen** — do not extend; new tokens go to L0. | Brand color changes, accessibility-driven contrast lifts, new chip/chart slot tied to shadcn names |
|
|
23
|
-
| **L2 — Tailwind bridge** | `packages/ui/src/globals.css` `@theme inline` block | Maps primitives to `--color-*` and `--radius-*` so Tailwind v4 emits utility classes (`bg-brand`, `bg-surface-1`, `text-chip-3`, `rounded-2`, …) | Whenever an L0/L1 token needs a Tailwind utility class |
|
|
24
|
-
| **L3 — Surface override** | Theme blocks (`.theme-one`, `.theme-prism`, `[data-contrast="high"]`, `[data-text-size]`) | Per-brand / per-mode rebindings of L1 names — never new names | New brand, new contrast mode, new density step |
|
|
25
|
-
|
|
26
|
-
> **Rules:**
|
|
27
|
-
> 1. A token is **introduced exactly once**. New product tokens land in **L0**.
|
|
28
|
-
> 2. L1 (shadcn names) is **frozen** — keep working, do not extend.
|
|
29
|
-
> 3. L2 bridges L0/L1 → Tailwind utilities, exactly once per name.
|
|
30
|
-
> 4. L3 re-binds existing L1 OKLCH values per theme. L0 inherits via `var(L1)` automatically.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## 2. Namespace map (all current prefixes)
|
|
35
|
-
|
|
36
|
-
Every prefix below has a clear role. **New tokens MUST land in one of these
|
|
37
|
-
prefixes** — if you cannot place a token, the design system is missing a
|
|
38
|
-
prefix; propose one in a PR and document it here first.
|
|
39
|
-
|
|
40
|
-
### 2.0 Exxat L0 — canonical namespace (`--exxat-*`)
|
|
41
|
-
|
|
42
|
-
The **official** name layer. Mirrors the SLDS `--slds-g-*` pattern but flat
|
|
43
|
-
(no `-g-` for "global" yet, since we have a single product namespace). Today
|
|
44
|
-
these resolve to L1 via `var(...)` so every existing theme override flows
|
|
45
|
-
through automatically. New components SHOULD prefer L0 names; the linter will
|
|
46
|
-
not yet *force* this, but `apps/web/docs/migrations/0002-exxat-token-namespace.md`
|
|
47
|
-
tracks the rollout.
|
|
48
|
-
|
|
49
|
-
| Category | L0 token | Resolves to L1 |
|
|
50
|
-
|---|---|---|
|
|
51
|
-
| Surface | `--exxat-color-surface-1` … `-3` | `--background` / `--card` / `--popover` |
|
|
52
|
-
| Surface | `--exxat-color-surface-muted` / `-accent` / `-secondary` / `-sidebar` / `-input` | `--muted` / `--accent` / `--secondary` / `--sidebar` / `--input-background` |
|
|
53
|
-
| Ink | `--exxat-color-ink-1` / `-2` | `--foreground` / `--muted-foreground` |
|
|
54
|
-
| Ink | `--exxat-color-ink-on-surface-2` / `-on-surface-3` | `--card-foreground` / `--popover-foreground` |
|
|
55
|
-
| Ink | `--exxat-color-ink-on-brand` / `-on-primary` / `-on-secondary` / `-on-accent` / `-on-destructive` | foreground tokens |
|
|
56
|
-
| Brand | `--exxat-color-brand-1` / `-2` / `-3` / `-deep` | `--brand-color` / `-dark` / `-light` / `-deep` |
|
|
57
|
-
| Brand | `--exxat-color-brand-tint-1` / `-2` / `-3` | `--brand-tint` / `-subtle` / `-light` |
|
|
58
|
-
| Action | `--exxat-color-action-primary` / `-secondary` / `-destructive` | `--primary` / `--secondary` / `--destructive` |
|
|
59
|
-
| Border | `--exxat-color-border-1` / `-control-subtle` / `-control-1` / `-control-2` | `--border` ladder (see §6) |
|
|
60
|
-
| Focus | `--exxat-color-focus-ring` | `--ring` |
|
|
61
|
-
| Overlay | `--exxat-color-overlay` | `--overlay` |
|
|
62
|
-
| Chart | `--exxat-color-chart-1` … `-5` | `--chart-1` … `-5` |
|
|
63
|
-
| Chip | `--exxat-color-chip-1` … `-5` / `-destructive` | `--chip-1` … `-5` / `-destructive` |
|
|
64
|
-
| Radius | `--exxat-radius-1` … `-6` | `4px` / `8px` / `12px` / `16px` / `20px` / `24px` |
|
|
65
|
-
| Spacing | `--exxat-spacing-1` / `-2` / `-3` / `-4` / `-5` / `-6` / `-8` / `-12` | Tailwind base scale |
|
|
66
|
-
| Control | `--exxat-control-height-1` / `-2` / `-3` | `--control-height-sm` / `-` / `-touch` |
|
|
67
|
-
|
|
68
|
-
**Tailwind utilities** are emitted via L2 bridges in `@theme inline`:
|
|
69
|
-
|
|
70
|
-
| L0 token | Tailwind utility |
|
|
71
|
-
|---|---|
|
|
72
|
-
| `--exxat-color-surface-1` | `bg-surface-1`, `text-surface-1`, `border-surface-1`, … |
|
|
73
|
-
| `--exxat-color-ink-1` / `-2` | `text-ink-1`, `text-ink-2` |
|
|
74
|
-
| `--exxat-color-brand-1` … `-3` | `bg-brand-1`, `bg-brand-2`, `bg-brand-3` |
|
|
75
|
-
| `--exxat-color-brand-tint-1` … `-3` | `bg-brand-tint-1`, … |
|
|
76
|
-
| `--exxat-color-border-1` | `border-1` |
|
|
77
|
-
| `--exxat-color-focus-ring` | `ring-focus-ring` |
|
|
78
|
-
| `--exxat-radius-1` … `-6` | `rounded-1` … `rounded-6` |
|
|
79
|
-
|
|
80
|
-
> The existing `bg-background`, `bg-brand`, `rounded-md`, etc. are not going
|
|
81
|
-
> away — they alias to the same OKLCH. Both forms work side-by-side; **new code**
|
|
82
|
-
> should reach for the L0 form for clarity and grep-ability (`grep --exxat-`
|
|
83
|
-
> finds every product token without false positives from shadcn names).
|
|
84
|
-
|
|
85
|
-
### 2.1 Semantic surface (shadcn core, L1 — frozen)
|
|
86
|
-
|
|
87
|
-
Inherited from shadcn / Radix and treated as the **stable** semantic vocabulary
|
|
88
|
-
across every shadcn component. Do not rename — downstream `components/ui/*`
|
|
89
|
-
expects these.
|
|
90
|
-
|
|
91
|
-
| Token | Role |
|
|
92
|
-
|---|---|
|
|
93
|
-
| `--background` / `--foreground` | Page canvas + ink |
|
|
94
|
-
| `--card` / `--card-foreground` | Raised card surfaces |
|
|
95
|
-
| `--popover` / `--popover-foreground` | Floating menus, tooltips, dropdowns |
|
|
96
|
-
| `--primary` / `--primary-foreground` | Default CTA color (neutral charcoal, **not** brand — see §3) |
|
|
97
|
-
| `--secondary` / `--secondary-foreground` | Secondary button + de-emphasized surface |
|
|
98
|
-
| `--muted` / `--muted-foreground` | Muted body copy, subtle backgrounds |
|
|
99
|
-
| `--accent` / `--accent-foreground` | Hovered list rows, low-contrast chips |
|
|
100
|
-
| `--destructive` / `--destructive-foreground` | Errors, delete confirms |
|
|
101
|
-
| `--border` | Decorative dividers (no AA requirement) |
|
|
102
|
-
| `--border-control` / `--border-control-3` / `--border-control-35` / `--control-border` | Form-field borders — see §6 for the contrast ladder |
|
|
103
|
-
| `--input` / `--input-background` | Input outline + fill |
|
|
104
|
-
| `--ring` | Focus ring (≥ 3:1, SC 2.4.11) |
|
|
105
|
-
| `--overlay` | Modal / sheet / drawer scrim |
|
|
106
|
-
|
|
107
|
-
### 2.2 Brand (`--brand-*`)
|
|
108
|
-
|
|
109
|
-
The **product accent** — different from `--primary` (which is neutral). Exxat
|
|
110
|
-
ships **two** brand themes (Exxat One = lavender 286.1, Exxat Prism = rose 343)
|
|
111
|
-
plus user-picked custom brand.
|
|
112
|
-
|
|
113
|
-
| Token | Role |
|
|
114
|
-
|---|---|
|
|
115
|
-
| `--brand-color` | Solid brand fill (used on chips, links, charts when product) |
|
|
116
|
-
| `--brand-color-light` / `--brand-color-dark` / `--brand-color-deep` | Brand scale (light / dark / deepest) |
|
|
117
|
-
| `--brand-foreground` | Ink on a solid brand fill |
|
|
118
|
-
| `--brand-tint` / `--brand-tint-light` / `--brand-tint-subtle` | Wash surfaces (sidebar, secondary panel) |
|
|
119
|
-
| `--brand-preview-one` / `--brand-preview-prism` | Fixed swatches for the brand picker — **never change with the active theme** |
|
|
120
|
-
|
|
121
|
-
### 2.3 Sidebar + secondary panel (`--sidebar-*`, `--secondary-panel-bg`)
|
|
122
|
-
|
|
123
|
-
Three-level brand chrome stack. See `docs/shell-surface-elevation-pattern.md`.
|
|
124
|
-
|
|
125
|
-
| Token | Role |
|
|
126
|
-
|---|---|
|
|
127
|
-
| `--sidebar` / `--sidebar-foreground` | Primary sidebar (= `--brand-tint` on product themes) |
|
|
128
|
-
| `--sidebar-primary` / `--sidebar-primary-foreground` | Solid pill / active hint on sidebar |
|
|
129
|
-
| `--sidebar-accent` / `--sidebar-accent-foreground` | Hovered/active row in sidebar |
|
|
130
|
-
| `--sidebar-border` | Inner divider |
|
|
131
|
-
| `--sidebar-ring` | Focus ring inside sidebar |
|
|
132
|
-
| `--sidebar-section-label-foreground` | Section title — mixed against real `--sidebar`, not `--background` |
|
|
133
|
-
| `--secondary-panel-bg` | Nested panel (Library) — level 1 between sidebar and canvas |
|
|
134
|
-
|
|
135
|
-
### 2.4 Chips / badges (`--chip-*`)
|
|
136
|
-
|
|
137
|
-
A five-slot **AA-compliant** palette for tags, kanban badges, and small status
|
|
138
|
-
chips. All slots maintain ≥ 4.5:1 against `--background`.
|
|
139
|
-
|
|
140
|
-
| Token | Hue (light) |
|
|
141
|
-
|---|---|
|
|
142
|
-
| `--chip-1` | indigo (264) |
|
|
143
|
-
| `--chip-2` | teal (184) |
|
|
144
|
-
| `--chip-3` | slate (227) |
|
|
145
|
-
| `--chip-4` | amber (84) |
|
|
146
|
-
| `--chip-5` | orange (70) |
|
|
147
|
-
| `--chip-destructive` | red (25) |
|
|
148
|
-
|
|
149
|
-
Use through `lib/list-status-badges.ts` (`LIST_HUB_STATUS_TINT_*`). Do not pick a
|
|
150
|
-
chip slot to mean "this entity uses indigo" — pick by **semantic intent**
|
|
151
|
-
(success / warning / neutral / danger / info).
|
|
152
|
-
|
|
153
|
-
### 2.5 Charts (`--chart-*`)
|
|
154
|
-
|
|
155
|
-
Five slots scoped for `recharts` series. They are **darker** than chip slots
|
|
156
|
-
because chart areas are larger and need denser pigment.
|
|
157
|
-
|
|
158
|
-
| Token | Hue |
|
|
159
|
-
|---|---|
|
|
160
|
-
| `--chart-1` | blue (264) |
|
|
161
|
-
| `--chart-2` | green-teal (184) |
|
|
162
|
-
| `--chart-3` | slate (227) |
|
|
163
|
-
| `--chart-4` | amber (84) |
|
|
164
|
-
| `--chart-5` | orange (70) |
|
|
165
|
-
|
|
166
|
-
Insights derived from charts use `--insight-severity-*-bg` / `-fg` (KPI strip).
|
|
167
|
-
|
|
168
|
-
### 2.6 Interactive hover (`--interactive-hover-*`)
|
|
169
|
-
|
|
170
|
-
Single source for ghost-button hover, list-row hover, table chrome hover.
|
|
171
|
-
Replaces ad-hoc `hover:bg-muted` so theming + dark mode flip together.
|
|
172
|
-
|
|
173
|
-
| Token | Role |
|
|
174
|
-
|---|---|
|
|
175
|
-
| `--interactive-hover` | Default opaque hover fill |
|
|
176
|
-
| `--interactive-hover-foreground` | Ink on hover |
|
|
177
|
-
| `--interactive-hover-subtle` / `-soft` / `-medium` / `-strong` | Opacity ladder (50 / 40 / 60 / 70 mix) |
|
|
178
|
-
| `--interactive-hover-row` | List/table row hover (accent-mixed) |
|
|
179
|
-
|
|
180
|
-
### 2.7 DataTable (`--dt-*`)
|
|
181
|
-
|
|
182
|
-
Pinned cells must be **opaque** so they sit above scrolled rows. These tokens
|
|
183
|
-
exist because `var(--muted)` alone gives translucent surfaces in dark mode.
|
|
184
|
-
|
|
185
|
-
| Token | Role |
|
|
186
|
-
|---|---|
|
|
187
|
-
| `--dt-row-bg` | Base row fill (opaque) |
|
|
188
|
-
| `--dt-row-hover` | Hovered row |
|
|
189
|
-
| `--dt-row-selected` / `--dt-row-selected-fg` | Selection state |
|
|
190
|
-
| `--dt-header-bg` | Header row |
|
|
191
|
-
| `--dt-group-bg` | Group / divider row |
|
|
192
|
-
| `--dt-new-row-bg` / `--dt-new-row-border` | "Just created" highlight |
|
|
193
|
-
|
|
194
|
-
### 2.8 KPI strip (`--key-metrics-*`, `--insight-severity-*`)
|
|
195
|
-
|
|
196
|
-
Used by `KeyMetrics` (both `variant="flat"` and `variant="card"`). See
|
|
197
|
-
`docs/kpi-flat-band-pattern.md`.
|
|
198
|
-
|
|
199
|
-
| Token | Role |
|
|
200
|
-
|---|---|
|
|
201
|
-
| `--key-metrics-flat-cell-bg` | Transparent on `variant="flat"` |
|
|
202
|
-
| `--key-metrics-flat-divider` | Cell-border hairline |
|
|
203
|
-
| `--key-metrics-flat-band-radial` | OKLCH brand glow at bottom |
|
|
204
|
-
| `--key-metrics-flat-band-shadow` | `none` on flat |
|
|
205
|
-
| `--key-metrics-card-glow-radial` | Card-variant glow |
|
|
206
|
-
| `--insight-severity-warning-bg` / `-fg` | Yellow severity (chart-4 mix) |
|
|
207
|
-
| `--insight-severity-info-bg` / `-fg` | Blue severity (chart-1 mix) |
|
|
208
|
-
|
|
209
|
-
### 2.9 Conditional formatting (`--conditional-rule-*`)
|
|
210
|
-
|
|
211
|
-
Row backgrounds for table conditional-format rules. Already six slots — adding
|
|
212
|
-
more requires expanding the rule type in `lib/table-conditional-format.ts`.
|
|
213
|
-
|
|
214
|
-
### 2.10 Icon discs (`--icon-disc-*`)
|
|
215
|
-
|
|
216
|
-
Soft tinted backgrounds for icon "discs" (KPI cards, banner avatars).
|
|
217
|
-
Bg ≈ 14% mix, fg from `--chip-*` / `--brand-color-dark`.
|
|
218
|
-
|
|
219
|
-
### 2.11 Avatar (`--avatar-initials-*`)
|
|
220
|
-
|
|
221
|
-
Bg + fg for initials avatars in `DataTable` cells. Pair brand wash with deep
|
|
222
|
-
brand ink for ≥ 4.5:1 on white rows.
|
|
223
|
-
|
|
224
|
-
### 2.12 Leo (Ask Leo) (`--leo-*`)
|
|
225
|
-
|
|
226
|
-
| Token | Role |
|
|
227
|
-
|---|---|
|
|
228
|
-
| `--leo-surface-tint-a` / `-b` | Top / bottom of the AI panel wash |
|
|
229
|
-
| `--leo-surface-gradient` | Full linear wash |
|
|
230
|
-
|
|
231
|
-
### 2.13 Layout + density (`--header-height`, `--control-*`, `--table-row-height`, `--scaling`)
|
|
232
|
-
|
|
233
|
-
Sized in **px** so JS can `parseFloat` them. Multiply by `--scaling` for future
|
|
234
|
-
density modes.
|
|
235
|
-
|
|
236
|
-
### 2.14 Border radius (`--radius`, `--radius-sm`/`-md`/`-lg`/`-xl`/`-2xl`/`-3xl`)
|
|
237
|
-
|
|
238
|
-
`--radius` is the **base** (8px). The named scale lives at L2 (`@theme inline`)
|
|
239
|
-
so Tailwind emits `rounded-sm`, `rounded-md`, etc.
|
|
240
|
-
|
|
241
|
-
### 2.15 Shadows + transitions (`--shadow-*`, `--transition-*`)
|
|
242
|
-
|
|
243
|
-
| Token | Role |
|
|
244
|
-
|---|---|
|
|
245
|
-
| `--shadow-sm` / `-md` / `-lg` | Three-step elevation |
|
|
246
|
-
| `--transition-fast` / `-normal` / `-colors` | Standard durations |
|
|
247
|
-
| `--sticky-edge-fade` | Edge-of-pinned-column gradient |
|
|
248
|
-
|
|
249
|
-
### 2.16 Promo / banner (`--banner-prism-bg`)
|
|
250
|
-
|
|
251
|
-
Rose hue 343, used **universally** as the Exxat Prism promo highlight — does
|
|
252
|
-
not flip with the active theme.
|
|
253
|
-
|
|
254
|
-
### 2.17 OS chrome (`--theme-color-chrome`)
|
|
255
|
-
|
|
256
|
-
Mirrored into `<meta name="theme-color">` so the browser titlebar matches the
|
|
257
|
-
sidebar. Hex literal here is **intentional** — it is consumed by the browser
|
|
258
|
-
parser, not CSS.
|
|
259
|
-
|
|
260
|
-
---
|
|
261
|
-
|
|
262
|
-
## 3. Color identity rules
|
|
263
|
-
|
|
264
|
-
1. **`--primary` is neutral**, not brand. The product accent is `--brand-color`.
|
|
265
|
-
This matches Exxat's design language (neutral charcoal CTAs, lavender/rose
|
|
266
|
-
reserved for surfaces and chips). When a button **must** carry the brand,
|
|
267
|
-
use `bg-brand-color text-brand-foreground` — not `bg-primary`.
|
|
268
|
-
2. **Brand washes (`--brand-tint*`) are surfaces; brand colors
|
|
269
|
-
(`--brand-color*`) are ink/fills.** Mixing the roles produces low-contrast
|
|
270
|
-
chips and washed-out sidebars.
|
|
271
|
-
3. **Chip ≠ chart.** Chip tokens are sized for inline chips; chart tokens are
|
|
272
|
-
sized for filled SVG areas. Re-use a chip color on a chart fill (or vice
|
|
273
|
-
versa) only when the design explicitly calls for it.
|
|
274
|
-
4. **Every theme MUST keep `--brand-preview-one` and `--brand-preview-prism`
|
|
275
|
-
fixed.** They drive the brand picker swatch — flipping them with the
|
|
276
|
-
selected theme defeats the picker.
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
## 4. Naming a new token — checklist
|
|
281
|
-
|
|
282
|
-
Run this list **before** opening `globals.css`:
|
|
283
|
-
|
|
284
|
-
- [ ] **Does an L0 alias already exist?** (`--exxat-color-surface-1`,
|
|
285
|
-
`--exxat-color-brand-1`, `--exxat-radius-2`, …) — prefer L0 for new
|
|
286
|
-
consumers; the L1 shadcn name is the back-compat alias.
|
|
287
|
-
- [ ] If the token is new, **name it at L0 first** (`--exxat-<category>-<slot>`).
|
|
288
|
-
Add an L1 mirror **only** if a shadcn primitive in `components/ui/*`
|
|
289
|
-
needs it — that's the only place L1 is still required.
|
|
290
|
-
- [ ] Can it be expressed with an existing semantic token? (`--exxat-color-surface-muted`
|
|
291
|
-
instead of `--exxat-color-surface-4`, `--brand-color-dark` instead of
|
|
292
|
-
`--brand-deeper`)
|
|
293
|
-
- [ ] Does it belong to a **scoped surface** (`KeyMetrics`, `DataTable`, Leo,
|
|
294
|
-
sidebar, secondary panel)? If yes, **prefix with that surface**, e.g.
|
|
295
|
-
`--key-metrics-*`, `--dt-*`, `--leo-*`. Do **not** drop scoped tokens at
|
|
296
|
-
the top level.
|
|
297
|
-
- [ ] Is it a **decoration** or a **decision**? Decorations (gradients, fades,
|
|
298
|
-
shadows) live alongside the surface tokens they decorate. Decisions
|
|
299
|
-
(semantic intent: warning, info, success) live with chips / charts /
|
|
300
|
-
insight tokens.
|
|
301
|
-
- [ ] Does it need a Tailwind utility (`bg-*`, `text-*`, `border-*`)? If yes,
|
|
302
|
-
add the `--color-<name>` bridge in `@theme inline` in **both** globals
|
|
303
|
-
files.
|
|
304
|
-
- [ ] Does dark mode / high contrast / Prism need different OKLCH? Override at
|
|
305
|
-
L3 — never branch by emitting a new token name.
|
|
306
|
-
- [ ] Is it derived (e.g. 14% mix of `--brand-color`)? Use `color-mix(in oklch,
|
|
307
|
-
…)` so theme overrides cascade automatically.
|
|
308
|
-
- [ ] Add the bridge in `packages/ui/src/globals.css` `@theme inline`. App
|
|
309
|
-
consumers @import that file, so a single edit is enough — no manual
|
|
310
|
-
mirroring across `apps/web/app/globals.css` / `template/app/globals.css`.
|
|
311
|
-
|
|
312
|
-
If you tick all the boxes, also:
|
|
313
|
-
|
|
314
|
-
1. Add an entry to this file (the right §).
|
|
315
|
-
2. Re-run `pnpm --filter @exxatdesignux/ui tokens:index` (see §7) to refresh
|
|
316
|
-
`packages/ui/tokens/hooks-index.json`.
|
|
317
|
-
|
|
318
|
-
---
|
|
319
|
-
|
|
320
|
-
## 5. Deprecation policy
|
|
321
|
-
|
|
322
|
-
Tokens follow the same **add → mark deprecated → remove** lifecycle as code:
|
|
323
|
-
|
|
324
|
-
1. **Add** the new token; switch components to consume it.
|
|
325
|
-
2. **Mark deprecated** in `packages/ui/src/globals.css` with a comment:
|
|
326
|
-
```css
|
|
327
|
-
/* @deprecated v0.2.18 — use --brand-color-dark; remove in v0.4.0 */
|
|
328
|
-
--brand-deep: var(--brand-color-dark);
|
|
329
|
-
```
|
|
330
|
-
3. **Migrate** consumers (script + grep + manual review). Document in
|
|
331
|
-
`docs/migrations/<NNNN>-<slug>.md`.
|
|
332
|
-
4. **Remove** in the version named in the deprecation comment. Bump
|
|
333
|
-
`@exxatdesignux/ui` major or document in `CHANGELOG.md`.
|
|
334
|
-
|
|
335
|
-
Active deprecations are listed in `docs/migrations/README.md`.
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
## 6. Form-field border contrast ladder
|
|
340
|
-
|
|
341
|
-
Form-field borders are a recurring source of WCAG SC 1.4.11 (3:1 UI contrast)
|
|
342
|
-
failures. Use the right rung — do not guess:
|
|
343
|
-
|
|
344
|
-
| Token | OKLCH L | Contrast vs `oklch(1 0 0)` | When to use |
|
|
345
|
-
|---|---|---|---|
|
|
346
|
-
| `--border` | 0.92 | < 3:1 | **Decorative only** — card dividers, list separators |
|
|
347
|
-
| `--border-control` | 0.82 | < 3:1 | Layout chrome where contrast is not required |
|
|
348
|
-
| `--border-control-3` | 0.62 | ≈ 3:1 | Form-field border **minimum** — passes AA |
|
|
349
|
-
| `--border-control-35` | 0.25 | > 3.5:1 | Form-field border **recommended** — passes AA + 16% buffer |
|
|
350
|
-
| `--control-border` (alias of `--border-control-3`) | — | — | Default form-field border slot |
|
|
351
|
-
|
|
352
|
-
If you find yourself reaching for `--border` on an `<input>`, stop — use
|
|
353
|
-
`--control-border`.
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
357
|
-
## 7. Machine-readable hooks index
|
|
358
|
-
|
|
359
|
-
Tooling (linters, codegens, design-tool sync) needs to discover tokens
|
|
360
|
-
programmatically. Run:
|
|
361
|
-
|
|
362
|
-
```bash
|
|
363
|
-
pnpm --filter @exxatdesignux/ui tokens:index
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
This regenerates [`packages/ui/tokens/hooks-index.json`](../../packages/ui/tokens/hooks-index.json),
|
|
367
|
-
which mirrors SLDS's `hooks-index.json` shape. The current index contains
|
|
368
|
-
**195 tokens** across **41 namespaces** (including the 12 Exxat L0 sub-namespaces
|
|
369
|
-
`exxat-surface`, `exxat-ink`, `exxat-brand`, `exxat-action`, `exxat-border`,
|
|
370
|
-
`exxat-focus`, `exxat-overlay`, `exxat-chart`, `exxat-chip`, `exxat-radius`,
|
|
371
|
-
`exxat-spacing`, `exxat-control`):
|
|
372
|
-
|
|
373
|
-
```jsonc
|
|
374
|
-
{
|
|
375
|
-
"version": "0.2.18",
|
|
376
|
-
"source": "packages/ui/src/globals.css",
|
|
377
|
-
"generatedAt": "ISO-8601",
|
|
378
|
-
"tokens": {
|
|
379
|
-
"--exxat-color-brand-1": {
|
|
380
|
-
"namespace": "exxat-brand",
|
|
381
|
-
"category": "color",
|
|
382
|
-
"values": { "light": "var(--brand-color)" },
|
|
383
|
-
"tailwindUtilities": ["bg-brand-1", "text-brand-1", "border-brand-1", "ring-brand-1"],
|
|
384
|
-
"deprecated": false
|
|
385
|
-
},
|
|
386
|
-
"--brand-color": {
|
|
387
|
-
"namespace": "brand",
|
|
388
|
-
"category": "color",
|
|
389
|
-
"values": {
|
|
390
|
-
"light": "oklch(0.50 0.14 286.1)",
|
|
391
|
-
"dark": "oklch(0.50 0.14 286.1)",
|
|
392
|
-
"theme-one": "oklch(0.50 0.14 286.1)",
|
|
393
|
-
"theme-prism": "oklch(0.57 0.24 342)",
|
|
394
|
-
"high-contrast": "oklch(0.06 0 0)"
|
|
395
|
-
},
|
|
396
|
-
"tailwindUtilities": ["bg-brand", "text-brand", "border-brand", "ring-brand"],
|
|
397
|
-
"deprecated": false
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
CI runs `tokens:index` and fails if the committed JSON is stale.
|
|
404
|
-
|
|
405
|
-
---
|
|
406
|
-
|
|
407
|
-
## 8. References
|
|
408
|
-
|
|
409
|
-
- `packages/ui/src/globals.css` — **single source of truth** for L1 / L2 / L3 primitives + Tailwind bridges
|
|
410
|
-
- `apps/web/app/globals.css`, `packages/ui/template/app/globals.css` — thin shells (`@import` + `@source`)
|
|
411
|
-
- `apps/web/docs/shell-surface-elevation-pattern.md` — sidebar / secondary panel
|
|
412
|
-
- `apps/web/docs/kpi-flat-band-pattern.md` — `--key-metrics-flat-*`
|
|
413
|
-
- `apps/web/docs/kpi-trend-pattern.md` — `--insight-severity-*` polarity
|
|
414
|
-
- `.cursor/rules/exxat-token-discipline.mdc` — enforcement rule (don't ship hex
|
|
415
|
-
literals, don't use deprecated tokens)
|
|
416
|
-
- `docs/migrations/` — token rename + removal history
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
# Exxat DS — Voice and tone
|
|
2
|
-
|
|
3
|
-
> The single biggest driver of "this feels like one product" — or "this feels random" — is **copy**. Empty states, errors, buttons, banners, validation, labels. This doc is the binding reference for all of it.
|
|
4
|
-
>
|
|
5
|
-
> **Audience:** designers writing copy, engineers shipping strings, AI agents drafting UI text. Reviewed in PR alongside `.cursor/rules/exxat-accessibility.mdc`.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. Voice (the constant)
|
|
10
|
-
|
|
11
|
-
Exxat copy is **clear, direct, and respectful**. We talk to grown professionals (faculty, admins) and to students. We never:
|
|
12
|
-
|
|
13
|
-
- talk down (no "Oops!" / "Whoops" / "Uh-oh"),
|
|
14
|
-
- over-celebrate ("Awesome!" / "Great job!" / 🎉),
|
|
15
|
-
- guilt-trip ("You haven't… yet" / "Don't forget!"),
|
|
16
|
-
- hide the cause behind metaphor ("Something went wrong" with no detail),
|
|
17
|
-
- shout (sentence case everywhere; ALL CAPS only for system status acronyms).
|
|
18
|
-
|
|
19
|
-
We **always**:
|
|
20
|
-
|
|
21
|
-
- name the **thing**: the entity (placement, site, student), the action (Save, Invite, Export), the outcome (saved, sent, downloaded),
|
|
22
|
-
- say what **changed** or what **the user can do next**,
|
|
23
|
-
- use **present tense** (`Saving` / `Saved` / `Try again`), not past-aspirational (`We've saved your changes!`),
|
|
24
|
-
- use **active voice** (`Exxat couldn't save the placement.` not `The placement could not be saved.`).
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## 2. Tone (the variable)
|
|
29
|
-
|
|
30
|
-
Tone shifts by surface. Same voice, different temperature.
|
|
31
|
-
|
|
32
|
-
| Surface | Tone | Example |
|
|
33
|
-
|---|---|---|
|
|
34
|
-
| Primary action label | Imperative, 1–3 words | "Save", "Invite people", "Export" |
|
|
35
|
-
| Empty state (filtered) | Helpful, names the filter | "No placements match the current filters. Clear them to see all 247." |
|
|
36
|
-
| Empty state (no data ever) | Welcoming, names the next step | "No placements yet. Add your first one to get started." |
|
|
37
|
-
| Inline validation | Neutral, says the rule | "Use the format MM/DD/YYYY." |
|
|
38
|
-
| Banner (informational) | Calm, explains why | "Read-only — this term has ended." |
|
|
39
|
-
| Banner (warning) | Direct, says the consequence | "Saving will overwrite 3 students' assignments." |
|
|
40
|
-
| Banner (error) | Honest, names the failure + next step | "Exxat couldn't reach the placement service. Try again, or check your network." |
|
|
41
|
-
| Dialog (destructive confirm) | Specific, names the object | "Delete placement P-2026-014? This cannot be undone." |
|
|
42
|
-
| Microcopy (helper, hints) | Brief, format-only | "Out of 4.0", "MM/DD/YYYY" |
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## 3. Buttons
|
|
47
|
-
|
|
48
|
-
| Pattern | Use | Example |
|
|
49
|
-
|---|---|---|
|
|
50
|
-
| **Sentence case** | Always | `Save`, `Invite people`, `Export selection` |
|
|
51
|
-
| **Title case** | Never | ~~`Save Changes`~~ → `Save changes` |
|
|
52
|
-
| **Verb-first imperative** | Primary actions | `Save`, `Send invite`, `Create placement`, `Download CSV` |
|
|
53
|
-
| **No "Please"** | The button is the request | ~~`Please save`~~ → `Save` |
|
|
54
|
-
| **Don't repeat the field name** | Inline context is enough | ~~`Save changes to placement`~~ → `Save` |
|
|
55
|
-
| **"Cancel" not "Discard"** | Cancel returns to a safe state; Discard is only for destructive draft loss | dialog primary = `Save changes`; cancel = `Cancel` |
|
|
56
|
-
| **Loading verb** | When in-flight, swap to `…` form, preserve width | `Save` → `Saving…` |
|
|
57
|
-
| **Disabled tooltip** | Always say why | "Add at least one collaborator first." |
|
|
58
|
-
|
|
59
|
-
### Don't
|
|
60
|
-
|
|
61
|
-
| ❌ Don't | ✅ Do |
|
|
62
|
-
|---|---|
|
|
63
|
-
| `Click here` | `Open placement` |
|
|
64
|
-
| `OK` | `Save`, `Got it`, `Continue` (the actual outcome) |
|
|
65
|
-
| `Submit` | `Send invite`, `Save changes`, `Create placement` |
|
|
66
|
-
| `Yes` / `No` in destructive dialogs | `Delete placement`, `Cancel` |
|
|
67
|
-
| `Loading…` for >2 s with no detail | `Loading placements…` (name the thing) |
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## 4. Empty states
|
|
72
|
-
|
|
73
|
-
There are **three** kinds. The copy is different for each.
|
|
74
|
-
|
|
75
|
-
### 4a. Filter-empty ("zero matches")
|
|
76
|
-
|
|
77
|
-
The dataset has rows; the active filters hide all of them. Tell the user the filters are responsible and offer to clear them.
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
No placements match the current filters.
|
|
81
|
-
[Clear filters]
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Rules:
|
|
85
|
-
|
|
86
|
-
- Name the entity ("placements", not "items").
|
|
87
|
-
- Mention "the current filters" — not just "no results".
|
|
88
|
-
- Surface a **Clear filters** action if the toolbar has any filters applied.
|
|
89
|
-
- If the search box is the only filter, the message becomes `No placements match "<query>".` (quote the query exactly).
|
|
90
|
-
|
|
91
|
-
### 4b. True-empty ("no data ever")
|
|
92
|
-
|
|
93
|
-
The dataset is empty for this user / scope. Tell them what to do next.
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
No placements yet.
|
|
97
|
-
Add your first one to start tracking student rotations.
|
|
98
|
-
[New placement]
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Rules:
|
|
102
|
-
|
|
103
|
-
- One-line headline + one-line context.
|
|
104
|
-
- The CTA is the same imperative used elsewhere in the app — don't invent "Start now" for an empty state.
|
|
105
|
-
- If creation requires a precursor ("Add at least one site first"), say so and link to the precursor.
|
|
106
|
-
|
|
107
|
-
### 4c. Permission-empty ("not allowed to see")
|
|
108
|
-
|
|
109
|
-
The user can't see anything because of access. Don't show counts; don't suggest creating.
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
You don't have access to this hub.
|
|
113
|
-
Ask a coordinator to invite you with Viewer or higher access.
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Rules:
|
|
117
|
-
|
|
118
|
-
- Name the access level required.
|
|
119
|
-
- Don't show row counts the user can't see.
|
|
120
|
-
- Don't expose the existence of restricted records by mentioning "0 of N hidden".
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## 5. Errors
|
|
125
|
-
|
|
126
|
-
Errors must answer three questions: **What happened? Why? What can the user do?**
|
|
127
|
-
|
|
128
|
-
| Element | Required? | Example |
|
|
129
|
-
|---|---|---|
|
|
130
|
-
| Subject | required | `Exxat couldn't save the placement.` |
|
|
131
|
-
| Cause | when knowable | `The site was deleted while you were editing.` |
|
|
132
|
-
| Next step | required | `Choose another site or refresh.` |
|
|
133
|
-
|
|
134
|
-
### Don't
|
|
135
|
-
|
|
136
|
-
- `Something went wrong.` — every word is empty. Replace with the subject + cause.
|
|
137
|
-
- `Error 500: Internal Server Error.` — leak the trace ID to logs, not the user. User-facing: "Exxat had a problem. Try again in a moment."
|
|
138
|
-
- Toasts for errors. Use `SystemBanner` (route-level) or inline `FormMessage` (field-level). See [`exxat-no-toast.mdc`](../../../.cursor/rules/exxat-no-toast.mdc).
|
|
139
|
-
|
|
140
|
-
### Field-level (`FormMessage`)
|
|
141
|
-
|
|
142
|
-
Match the rule, not the field name. Keep under 60 chars.
|
|
143
|
-
|
|
144
|
-
| Rule | Message |
|
|
145
|
-
|---|---|
|
|
146
|
-
| Required | `This is required.` |
|
|
147
|
-
| Format (date) | `Use MM/DD/YYYY.` |
|
|
148
|
-
| Format (phone) | `Use +1 (555) 555-0100.` |
|
|
149
|
-
| Range (number) | `Enter a number between 0 and 4.0.` |
|
|
150
|
-
| Unique | `That ID is already in use.` |
|
|
151
|
-
| Server | `Exxat couldn't save this field. Try again.` |
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
## 6. Banners
|
|
156
|
-
|
|
157
|
-
We use **persistent banners** (not toasts) for all product feedback. See [`exxat-no-toast.mdc`](../../../.cursor/rules/exxat-no-toast.mdc) and `LocalBanner` / `SystemBanner` in `packages/ui`.
|
|
158
|
-
|
|
159
|
-
| Variant | Use | Lead with |
|
|
160
|
-
|---|---|---|
|
|
161
|
-
| `info` | Read-only / scope notice / "you're viewing X" | The fact: "Read-only — this term has ended." |
|
|
162
|
-
| `success` | Confirmed state change (long-lived; toasts forbidden) | The outcome: "Invite sent to 3 collaborators." |
|
|
163
|
-
| `warning` | Action will cause consequence | The consequence: "Saving will overwrite 3 students' assignments." |
|
|
164
|
-
| `error` | Recoverable failure | The subject + next step: "Exxat couldn't reach the placement service. Try again." |
|
|
165
|
-
| `destructive` | Pending destructive change | The object + reversibility: "This term will be archived in 7 days. Restore now to keep it." |
|
|
166
|
-
|
|
167
|
-
Rules:
|
|
168
|
-
|
|
169
|
-
- One banner per region. If two compete, the higher-severity wins.
|
|
170
|
-
- Pair a banner with an **action** when there's something the user can do (`[Restore]`, `[Try again]`, `[Open log]`).
|
|
171
|
-
- No exclamation marks. The variant color carries the tone.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## 7. Status badges
|
|
176
|
-
|
|
177
|
-
Status labels live in [`lib/list-status-badges.ts`](../lib/list-status-badges.ts) and are shared across every surface (table, board card, list row). Don't fork labels per page.
|
|
178
|
-
|
|
179
|
-
| Type | Conventions | Example |
|
|
180
|
-
|---|---|---|
|
|
181
|
-
| Lifecycle | Title case, single word where possible | `Active`, `Draft`, `Archived`, `Pending` |
|
|
182
|
-
| Outcome | Past-tense verb when an action completed | `Approved`, `Rejected`, `Sent`, `Skipped` |
|
|
183
|
-
| Risk | Plain language, no jargon | `On track`, `At risk`, `Blocked` |
|
|
184
|
-
| Mandatory pairing | Color **AND** icon (WCAG 1.4.1 — never color alone) | green check, amber triangle, red circle-x |
|
|
185
|
-
|
|
186
|
-
Never: `OK`, `N/A`, `?` as a status label. Show the actual lifecycle state or `Unknown` with a tooltip describing how to resolve it.
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## 8. KPI copy (the highest-stakes copy in the product)
|
|
191
|
-
|
|
192
|
-
KPIs read top-down: **label → value → trend chip → description**. The reader is scanning, not reading. Each line earns its place.
|
|
193
|
-
|
|
194
|
-
| Field | Rule | Example |
|
|
195
|
-
|---|---|---|
|
|
196
|
-
| `label` | Sentence case noun phrase. ≤ 24 chars. No "Total" prefix (the value is total by default). | `Active placements`, `New invites`, `Compliance issues` |
|
|
197
|
-
| `value` | The number. Use `tabular-nums`. Round to a sensible precision. Suffix unit (`%`, `hrs`) without space when natural. | `247`, `12 %`, `38 hrs` |
|
|
198
|
-
| `delta` | The **count** of change. Empty (`""` or `0`) hides the chip entirely. Never prose. | `+12`, `-3`, `+8 %` |
|
|
199
|
-
| `description` | The caption beneath the value row. Prose explaining what the number is or how it splits. Never the delta. | `vs last week`, `across 4 sites`, `left + right` |
|
|
200
|
-
|
|
201
|
-
See [`exxat-kpi-trends.mdc`](../../../.cursor/rules/exxat-kpi-trends.mdc) for the trend-polarity rules (when up means bad, set `trendPolarity: "lower_is_better"`).
|
|
202
|
-
|
|
203
|
-
### Don't
|
|
204
|
-
|
|
205
|
-
- `Total: 247` — drop "Total".
|
|
206
|
-
- `247 placements` in `value` — put `247` in `value`, `placements` is in `label`.
|
|
207
|
-
- `+5 (up 12 % from last week)` in `delta` — `delta = "+5"`, `description = "vs last week"`.
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
## 9. Form labels and helper text
|
|
212
|
-
|
|
213
|
-
| Element | Rule | Example |
|
|
214
|
-
|---|---|---|
|
|
215
|
-
| Label | Sentence case, no colon, no "*" — pair `*` decoration with `aria-required` only. | `Date of birth` |
|
|
216
|
-
| Required marker | Visible `*` (`aria-hidden`), programmatic `aria-required="true"`. | `Date of birth *` |
|
|
217
|
-
| Helper text | Persistent, format-first. Use `FormDescription`. Never placeholder-only. | `MM/DD/YYYY` |
|
|
218
|
-
| Placeholder | May mirror the format. Never the sole carrier. | `MM/DD/YYYY` |
|
|
219
|
-
| Inline error | Replaces helper while active. Match the rule, not the field name. | `Use MM/DD/YYYY.` |
|
|
220
|
-
| Unit in label vs description | Units go in description when context-dependent (`Out of 4.0` under "GPA"), in label when fixed (`Hours per week`). | — |
|
|
221
|
-
|
|
222
|
-
See [`exxat-accessibility.mdc`](../../../.cursor/rules/exxat-accessibility.mdc) §"Form fields — format hints MUST be persistent".
|
|
223
|
-
|
|
224
|
-
---
|
|
225
|
-
|
|
226
|
-
## 10. Dates, times, numbers
|
|
227
|
-
|
|
228
|
-
| Type | Format | Example |
|
|
229
|
-
|---|---|---|
|
|
230
|
-
| Date (UI) | Locale-aware, defaults to `MM/DD/YYYY` for en-US | `12/14/2025` |
|
|
231
|
-
| Date range | Same format, en-dash with hair-thin spaces (or hyphen-minus in mono) | `12/14/2025 – 12/20/2025` |
|
|
232
|
-
| Time | 12-hour with am/pm in lowercase | `2:30 pm` |
|
|
233
|
-
| Relative time | Use sparingly; pair with absolute on hover/tooltip | `Updated 3 hours ago` (tip: `12/15/2025, 11:14 am`) |
|
|
234
|
-
| Numbers | `tabular-nums`. Use thousands separator (`12,547`) for ≥ 4 digits | `12,547` |
|
|
235
|
-
| Currency | Symbol-first, no space (`$1,200.00`) | `$1,200.00` |
|
|
236
|
-
| Percent | No space (`12 %` is wrong, use `12%`) | `12%` |
|
|
237
|
-
| Duration | Short form (`38 hrs`, `2 wks`) | `38 hrs` |
|
|
238
|
-
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
## 11. Reviewer checklist (paste into PRs that touch copy)
|
|
242
|
-
|
|
243
|
-
- [ ] Sentence case (not title case) everywhere.
|
|
244
|
-
- [ ] No `Click here`, `Submit`, `OK`, `Loading…` without a noun.
|
|
245
|
-
- [ ] No toasts; banners or inline status instead.
|
|
246
|
-
- [ ] Empty state names the entity and surfaces a next action or "Clear filters".
|
|
247
|
-
- [ ] Errors answer: what / why / what now.
|
|
248
|
-
- [ ] Status labels come from `lib/list-status-badges.ts`, not new strings.
|
|
249
|
-
- [ ] KPI `delta` is a count; KPI `description` is prose. Polarity matches the metric.
|
|
250
|
-
- [ ] Form helper text is `FormDescription`, not placeholder-only.
|
|
251
|
-
- [ ] No exclamation marks outside `success` banners (and even those rarely).
|
|
252
|
-
- [ ] No emoji unless explicitly approved for that surface.
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## See also
|
|
257
|
-
|
|
258
|
-
- [`HANDBOOK.md`](./HANDBOOK.md) — where this fits in the docs map
|
|
259
|
-
- [`glossary.md`](./glossary.md) — vocabulary
|
|
260
|
-
- [`.cursor/rules/exxat-no-toast.mdc`](../../../.cursor/rules/exxat-no-toast.mdc) — why banners, not toasts
|
|
261
|
-
- [`.cursor/rules/exxat-kpi-trends.mdc`](../../../.cursor/rules/exxat-kpi-trends.mdc) — KPI delta vs description
|
|
262
|
-
- [`.cursor/rules/exxat-accessibility.mdc`](../../../.cursor/rules/exxat-accessibility.mdc) — format-hint persistence rule
|