@exxatdesignux/ui 0.4.0 → 0.4.2

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 CHANGED
@@ -1,5 +1,124 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - ### `HubTable` — stop warning when the centralized default covers a supported view
8
+
9
+ The dev-time `Missing renderer for supported view …` warning in
10
+ `HubTable` only treated `data-table` as having a built-in fallback,
11
+ even though the same component synthesises centralized defaults for
12
+ `list-with-toolbar` (when the consumer passes `renderListRow`) and
13
+ `board-with-toolbar` (when the consumer passes `renderBoardCard` plus
14
+ `boardGroups`). Every consumer that wired list/board the happy-path
15
+ way saw a spurious console warning every time their hub mounted —
16
+ including the freshly-scaffolded Library hub in `create-exxat-app`,
17
+ where the first thing a new user saw was a repeating
18
+ `[HubTable: Library] Missing renderer for supported view "list"
19
+ (list-with-toolbar)` line, even though the view renders correctly.
20
+
21
+ The warning loop now also skips the two centralized-default cases
22
+ (it still fires for exotic surfaces that genuinely need an explicit
23
+ renderer entry, e.g. Placements' per-phase column-search board, or a
24
+ custom finder body the package doesn't synthesise).
25
+
26
+ No behavior change in production builds (the warning is already gated
27
+ on `process.env.NODE_ENV !== "production"`).
28
+
29
+ ## 0.4.1
30
+
31
+ ### Patch Changes
32
+
33
+ - ### `@exxatdesignux/ui` — publish hygiene + smarter scaffolder
34
+
35
+ Three real bugs in the `0.4.0` tarball made first-run consumer installs
36
+ fail. All three are now fixed.
37
+ - **`tokens/hooks-index.json` ships.** The `exports` map declared
38
+ `"./tokens/hooks-index.json"` but the `files` array did not include
39
+ `tokens`, so the file was silently omitted from the published tarball.
40
+ Any `import "@exxatdesignux/ui/tokens/hooks-index.json"` (used by
41
+ `@exxatdesignux/eslint-plugin`'s `no-deprecated-tokens` rule and by
42
+ workspaces that read the token registry) blew up with
43
+ `MODULE_NOT_FOUND`. The `files` array now includes `tokens`.
44
+ - **No more `workspace:*` leaking into the published template.** The
45
+ scaffolder template was sync'd from `apps/web/package.json`, which
46
+ uses `workspace:*` for both `@exxatdesignux/ui` (correct, gets
47
+ rewritten to a real semver pin) and a handful of monorepo-internal
48
+ dev tools (`@exxatdesignux/eslint-plugin`, …) which are not published
49
+ on npm. The previous sync rewrote only the design-system line, so
50
+ every consumer install (`npm` / `pnpm` / `yarn`) crashed on the
51
+ leaked entries. The sync script now walks every dep map
52
+ (`dependencies` / `devDependencies` / `peerDependencies` /
53
+ `optionalDependencies`), pins `@exxatdesignux/ui`, deletes any
54
+ remaining `workspace:` value, and fails the build if any leaks
55
+ survive — so the bug surfaces here, not on a consumer's terminal.
56
+ - **Consumer-safe `eslint.config.mjs` in the template.** The repo's
57
+ own `apps/web/eslint.config.mjs` imports
58
+ `@exxatdesignux/eslint-plugin` for DS guardrails (no hex literals,
59
+ no deprecated tokens, no SLDS leakage, no Sonner toasts). Because
60
+ that plugin isn't published, `pnpm lint` in a freshly-scaffolded app
61
+ crashed with `Cannot find package '@exxatdesignux/eslint-plugin'`.
62
+ The sync script now overwrites `template/eslint.config.mjs` with a
63
+ clean `eslint-config-next + globalIgnores + unused-vars` setup that
64
+ every consumer can run. The DS guardrails graduate to this template
65
+ whenever the plugin lands on npm.
66
+
67
+ ### Smarter `bin/init.mjs` scaffolder
68
+
69
+ The `create-exxat-app` binary now matches the conventions of
70
+ `create-next-app` and `create-vite`:
71
+ - **Positional target directory.** `create-exxat-app my-app` creates
72
+ `./my-app` and scaffolds into it. `create-exxat-app .` scaffolds
73
+ into the current directory. Missing directories are auto-created.
74
+ - **Package-manager auto-detection.** Reads `npm_config_user_agent`
75
+ and runs the matching install (`pnpm install` / `yarn install` /
76
+ `bun install` / `npm install`). The post-install hint prints the
77
+ matching `<pm> run dev` command.
78
+ - **`--force` flag.** Allow scaffolding into a non-empty directory
79
+ (existing files are not overwritten unless the template ships the
80
+ same path). The previous "directory must be empty" hard-error
81
+ blocked every "install the DS first, then scaffold" sequence.
82
+ - **`--no-install` flag.** Skip the install step (useful in CI or
83
+ sandboxed setups).
84
+ - **`--help` / `-h`.** Print usage.
85
+ - **Clearer blocked-directory error.** When the target is non-empty
86
+ without `--force`, the CLI lists which files are blocking and
87
+ suggests both options (`create-exxat-app my-new-app` or
88
+ `create-exxat-app . --force`) instead of failing silently.
89
+
90
+ ### Real README on the npm package page
91
+
92
+ `packages/ui/` had no `README.md` — the npm landing page at
93
+ [npmjs.com/package/@exxatdesignux/ui](https://www.npmjs.com/package/@exxatdesignux/ui)
94
+ showed only the bare package.json description. The new README leads
95
+ with the 60-second start (`pnpm create exxat-app my-app`), covers
96
+ "adding to an existing Next.js app", documents `exxat-ui sync-extras`
97
+ for Cursor skill installation, and links the full doc set.
98
+
99
+ ### `create-exxat-app@0.0.x` → new package
100
+
101
+ A new `create-exxat-app` discovery package now ships on npm so
102
+ `pnpm create exxat-app my-app` (and the npm / yarn / bun variants)
103
+ resolve natively without a `--package=` flag. The shim is intentionally
104
+ tiny — it depends on `@exxatdesignux/ui` (via `workspace:^`, pinned to
105
+ `^<version>` at publish) and delegates to
106
+ `@exxatdesignux/ui/bin/init.mjs`. All template, version-pinning, and
107
+ package-manager-detection logic stays in `@exxatdesignux/ui` so there
108
+ is exactly one source of truth.
109
+
110
+ ### Migration
111
+
112
+ Consumers on `0.4.0` should bump to `0.4.1`:
113
+
114
+ ```bash
115
+ pnpm up @exxatdesignux/ui@latest
116
+ ```
117
+
118
+ If you scaffolded with `0.4.0` and edited `eslint.config.mjs` /
119
+ `package.json` to work around the workspace leak, you can either keep
120
+ your manual edits or re-scaffold with `0.4.1` for a clean baseline.
121
+
3
122
  ## 0.4.0
4
123
 
5
124
  ### Minor Changes
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
- import { cpSync, readFileSync, readdirSync, writeFileSync } from 'fs'
3
- import { resolve, dirname } from 'path'
4
- import { fileURLToPath } from 'url'
5
- import { execSync } from 'child_process'
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, '../template')
9
- const selfPkgPath = resolve(__dirname, '../package.json')
10
- const targetDir = process.cwd()
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 files = readdirSync(targetDir)
13
- const isEmpty = files.length === 0 || (files.length === 1 && files[0] === '.git')
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 (!isEmpty) {
16
- console.error('❌ Target directory is not empty. Run this in a new empty folder.')
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
- console.log('📦 Copying Exxat DS starter app...')
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
- /** Pin DS to this CLI tarball version so `npm install` does not resolve a stale `latest` from cache. */
24
- const selfVersion = JSON.parse(readFileSync(selfPkgPath, 'utf8')).version
25
- const targetPkgPath = resolve(targetDir, 'package.json')
26
- const targetPkg = JSON.parse(readFileSync(targetPkgPath, 'utf8'))
27
- if (targetPkg.dependencies?.['@exxatdesignux/ui']) {
28
- targetPkg.dependencies['@exxatdesignux/ui'] = `^${selfVersion}`
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
- console.log('📥 Installing dependencies...')
34
- execSync('npm install', { stdio: 'inherit', cwd: targetDir })
35
-
36
- console.log('')
37
- console.log('✅ Done! Your Exxat DS app is ready.')
38
- console.log('')
39
- console.log(' npm run dev → start dev server')
40
- console.log('')
41
- console.log(' Optional — refresh Cursor skills + pattern docs from the same package:')
42
- console.log(` npx --package=@exxatdesignux/ui@${selfVersion} exxat-ui sync-extras`)
43
- console.log('')
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
+ }
@@ -5687,11 +5687,12 @@ function HubTable({
5687
5687
  for (const v of supportedViewTypes) {
5688
5688
  const kind = getDataListViewRenderKind(v);
5689
5689
  if (kind === "data-table") continue;
5690
- if (renderers[kind] == null) {
5691
- console.warn(
5692
- `[Exxat DS][HubTable: ${hubLabel}] Missing renderer for supported view "${v}" (${kind}). Add a renderer entry, or remove the view from supportedViewTypes.`
5693
- );
5694
- }
5690
+ if (renderers[kind] != null) continue;
5691
+ if (kind === "list-with-toolbar" && renderListRow != null) continue;
5692
+ if (kind === "board-with-toolbar" && renderBoardCard != null && boardGroups != null) continue;
5693
+ console.warn(
5694
+ `[Exxat DS][HubTable: ${hubLabel}] Missing renderer for supported view "${v}" (${kind}). Add a renderer entry, or remove the view from supportedViewTypes.`
5695
+ );
5695
5696
  }
5696
5697
  }
5697
5698
  const composed = {