@zentauri-ui/zentauri-components 2.1.1 → 2.1.3

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.
Files changed (76) hide show
  1. package/README.md +17 -16
  2. package/cli/cli.integration.test.ts +115 -0
  3. package/cli/index.mjs +254 -5
  4. package/cli/registry.json +84 -0
  5. package/dist/{chunk-TYBQZO6Y.mjs → chunk-2P4WPYC3.mjs} +3 -3
  6. package/dist/{chunk-TYBQZO6Y.mjs.map → chunk-2P4WPYC3.mjs.map} +1 -1
  7. package/dist/{chunk-ZMFRJHO6.mjs → chunk-3BL5PO3K.mjs} +3 -3
  8. package/dist/{chunk-ZMFRJHO6.mjs.map → chunk-3BL5PO3K.mjs.map} +1 -1
  9. package/dist/{chunk-TT33BIIT.js → chunk-BJDT7P2I.js} +35 -35
  10. package/dist/{chunk-TT33BIIT.js.map → chunk-BJDT7P2I.js.map} +1 -1
  11. package/dist/{chunk-7N2UYQBJ.mjs → chunk-BOTDQV6U.mjs} +4 -4
  12. package/dist/{chunk-7N2UYQBJ.mjs.map → chunk-BOTDQV6U.mjs.map} +1 -1
  13. package/dist/{chunk-3N575QVC.mjs → chunk-BPSW3SRE.mjs} +3 -3
  14. package/dist/{chunk-3N575QVC.mjs.map → chunk-BPSW3SRE.mjs.map} +1 -1
  15. package/dist/{chunk-5TVBPPS6.js → chunk-DKBMNS6F.js} +11 -11
  16. package/dist/{chunk-5TVBPPS6.js.map → chunk-DKBMNS6F.js.map} +1 -1
  17. package/dist/{chunk-4LCH4OJ5.js → chunk-E7P4O3ET.js} +10 -10
  18. package/dist/{chunk-4LCH4OJ5.js.map → chunk-E7P4O3ET.js.map} +1 -1
  19. package/dist/{chunk-VHYUH5OH.js → chunk-GNQH247A.js} +6 -6
  20. package/dist/{chunk-VHYUH5OH.js.map → chunk-GNQH247A.js.map} +1 -1
  21. package/dist/{chunk-42ZSQNDF.mjs → chunk-NIVJFG5Z.mjs} +35 -35
  22. package/dist/{chunk-42ZSQNDF.mjs.map → chunk-NIVJFG5Z.mjs.map} +1 -1
  23. package/dist/{chunk-BIQZC26Q.js → chunk-OH5VOGNW.js} +4 -4
  24. package/dist/chunk-OH5VOGNW.js.map +1 -0
  25. package/dist/chunk-SOYCLBHK.js +19 -0
  26. package/dist/{chunk-R4D5V7NT.js.map → chunk-SOYCLBHK.js.map} +1 -1
  27. package/dist/{chunk-V2LI5QZD.js → chunk-UJPB5NHW.js} +18 -18
  28. package/dist/{chunk-V2LI5QZD.js.map → chunk-UJPB5NHW.js.map} +1 -1
  29. package/dist/{chunk-TVEK6PKH.mjs → chunk-XY3TKIIH.mjs} +4 -4
  30. package/dist/chunk-XY3TKIIH.mjs.map +1 -0
  31. package/dist/{chunk-GOH2THVW.mjs → chunk-ZNI3AB3W.mjs} +3 -3
  32. package/dist/{chunk-GOH2THVW.mjs.map → chunk-ZNI3AB3W.mjs.map} +1 -1
  33. package/dist/design-system/alert.d.ts +33 -33
  34. package/dist/design-system/facade.js +4 -4
  35. package/dist/design-system/facade.mjs +3 -3
  36. package/dist/design-system/tabs.d.ts +2 -2
  37. package/dist/ui/alert/animated.js +3 -3
  38. package/dist/ui/alert/animated.mjs +2 -2
  39. package/dist/ui/alert.js +11 -11
  40. package/dist/ui/alert.mjs +3 -3
  41. package/dist/ui/buttons/animated.js +6 -6
  42. package/dist/ui/buttons/animated.mjs +4 -4
  43. package/dist/ui/buttons.js +7 -7
  44. package/dist/ui/buttons.mjs +5 -5
  45. package/dist/ui/context-menu/context-menu.d.ts.map +1 -1
  46. package/dist/ui/context-menu/types.d.ts +3 -1
  47. package/dist/ui/context-menu/types.d.ts.map +1 -1
  48. package/dist/ui/context-menu.js +17 -0
  49. package/dist/ui/context-menu.js.map +1 -1
  50. package/dist/ui/context-menu.mjs +17 -0
  51. package/dist/ui/context-menu.mjs.map +1 -1
  52. package/dist/ui/dropdown/dropdown.d.ts +1 -1
  53. package/dist/ui/dropdown/dropdown.d.ts.map +1 -1
  54. package/dist/ui/dropdown.js +43 -2
  55. package/dist/ui/dropdown.js.map +1 -1
  56. package/dist/ui/dropdown.mjs +44 -3
  57. package/dist/ui/dropdown.mjs.map +1 -1
  58. package/dist/ui/dynamic-stepper.js +16 -16
  59. package/dist/ui/dynamic-stepper.mjs +5 -5
  60. package/dist/ui/pagination.js +12 -12
  61. package/dist/ui/pagination.mjs +4 -4
  62. package/dist/ui/tabs/animated.js +3 -3
  63. package/dist/ui/tabs/animated.mjs +2 -2
  64. package/dist/ui/tabs.js +10 -10
  65. package/dist/ui/tabs.mjs +2 -2
  66. package/package.json +10 -3
  67. package/src/design-system/alert.ts +33 -33
  68. package/src/design-system/tabs.ts +2 -2
  69. package/src/hooks/useTableFilter/useTableFilter.test.ts +12 -2
  70. package/src/ui/context-menu/context-menu.test.tsx +38 -0
  71. package/src/ui/context-menu/context-menu.tsx +17 -1
  72. package/src/ui/context-menu/types.ts +3 -0
  73. package/src/ui/dropdown/dropdown.tsx +59 -2
  74. package/dist/chunk-BIQZC26Q.js.map +0 -1
  75. package/dist/chunk-R4D5V7NT.js +0 -19
  76. package/dist/chunk-TVEK6PKH.mjs.map +0 -1
package/README.md CHANGED
@@ -29,17 +29,17 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
29
29
 
30
30
  | Metric | Result |
31
31
  | ---------- | ---------------- |
32
- | Test files | 92 passed (92) |
33
- | Tests | 707 passed (707) |
34
-
35
- | Area | Test files | Tests |
36
- | --------------------------- | ---------- | ----- |
37
- | Components and UI utilities | 46 | 453 |
38
- | Standalone animations | 1 | 45 |
39
- | React hooks | 41 | 174 |
40
- | Design system facade | 1 | 11 |
41
- | CLI and import rewriting | 2 | 18 |
42
- | Axe core test cases | 1 | 6 |
32
+ | Test files | 93 passed (93) |
33
+ | Tests | 751 passed (751) |
34
+
35
+ | Area | Test files | Tests |
36
+ | ------------------------------ | ---------- | ----- |
37
+ | Components and UI utilities | 46 | 455 |
38
+ | Standalone animations | 1 | 45 |
39
+ | React hooks | 41 | 174 |
40
+ | Design system facade | 1 | 11 |
41
+ | CLI and import rewriting | 2 | 24 |
42
+ | Accessibility (axe + keyboard) | 2 | 42 |
43
43
 
44
44
  ### Per-suite snapshot
45
45
 
@@ -49,17 +49,19 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
49
49
  | `src/ui/buttons/button.test.tsx` | 44 |
50
50
  | `src/ui/inputs/input.test.tsx` | 40 |
51
51
  | `src/ui/peer-isolation.test.ts` | 29 |
52
+ | `src/accessibility/axe-core.test.tsx` | 24 |
52
53
  | `src/ui/combobox/combobox.test.tsx` | 24 |
54
+ | `cli/cli.integration.test.ts` | 19 |
55
+ | `src/accessibility/keyboard-interaction.test.tsx` | 18 |
53
56
  | `src/ui/pagination/pagination.test.tsx` | 15 |
54
57
  | `src/ui/timeline/timeline.test.tsx` | 14 |
55
- | `cli/cli.integration.test.ts` | 13 |
58
+ | `src/ui/context-menu/context-menu.test.tsx` | 12 |
56
59
  | `src/lib/facade.test.ts` | 11 |
57
60
  | `src/ui/alert/alert.test.tsx` | 11 |
58
61
  | `src/ui/rating/rating.test.tsx` | 11 |
59
62
  | `src/ui/select/select.test.tsx` | 11 |
60
63
  | `src/ui/table/table.test.tsx` | 11 |
61
64
  | `src/hooks/usePagination/usePagination.test.ts` | 10 |
62
- | `src/ui/context-menu/context-menu.test.tsx` | 10 |
63
65
  | `src/ui/marquee/marquee.test.tsx` | 10 |
64
66
  | `src/ui/modal/modal.test.tsx` | 10 |
65
67
  | `src/ui/otp-input/otp-input.test.tsx` | 10 |
@@ -83,7 +85,6 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
83
85
  | `src/ui/drawer/drawer.test.tsx` | 7 |
84
86
  | `src/ui/kbd/kbd.test.tsx` | 7 |
85
87
  | `src/ui/typography/typography.test.tsx` | 7 |
86
- | `src/accessibility/axe-core.test.tsx` | 6 |
87
88
  | `src/charts/charts.test.tsx` | 6 |
88
89
  | `src/hooks/useClipboard/useClipboard.test.ts` | 6 |
89
90
  | `src/hooks/useCountdown/useCountdown.test.ts` | 6 |
@@ -846,8 +847,8 @@ From this package directory in the monorepo:
846
847
 
847
848
  - `pnpm build` (or `npm run build`) — production bundle via `tsup` (Rollup treeshake + `scripts/prepend-use-client.mjs` via `onSuccess` so each UI entry under `dist/ui/`, animation entry under `dist/animations/`, chart entry under `dist/charts/`, and `dist/ui/<name>/animated.*` starts with `"use client"` where needed)
848
849
  - `pnpm dev` — `tsup` watch mode (same `onSuccess` hook after each rebuild)
849
- - `pnpm test` / `pnpm test:watch` — **Vitest** and **Testing Library** unit tests // currently covered 707 test cases in total
850
- - `pnpm test:a11y` — focused **axe-core** accessibility smoke coverage for package-level UI primitives and compound components
850
+ - `pnpm test` / `pnpm test:watch` — **Vitest** and **Testing Library** unit tests // currently covered 751 test cases in total
851
+ - `pnpm test:a11y` — focused accessibility coverage for package-level UI primitives and compound components: **axe-core** audits for every interactive component plus **keyboard-interaction** tests (focus order, arrow-key nav, Home/End, Escape/Enter) for the compound components
851
852
  - `pnpm check:tokens` — enforce the `--zui-*` token contract across design-system, variant, and local custom-property usage without generating a large checked-in token catalog
852
853
  - **`pnpm run generate:registry`** — runs `scripts/generate-registry.mjs`, which reads **`uiComponentNames`**, **`uiAnimatedComponentNames`**, **`animationEntryNames`**, **`chartEntryNames`**, and **`hooksEntryNames`** from `tsup.config.ts`, applies fixed **`nameAliases`**, scans each component/chart source to build **`peerHints`**, and writes **`cli/registry.json`** (`components` + `animations` + `hooks` + `peerHints`). Run this after adding or renaming UI, animation, chart, or hook entries so the CLI stays in sync (the script prints counts).
853
854
 
@@ -17,7 +17,122 @@ function runCli(cwd: string, args: string[]): string {
17
17
  });
18
18
  }
19
19
 
20
+ function runCliError(cwd: string, args: string[]): string {
21
+ try {
22
+ runCli(cwd, args);
23
+ throw new Error("Expected CLI command to fail");
24
+ } catch (error) {
25
+ if (error && typeof error === "object" && "stderr" in error) {
26
+ return Buffer.isBuffer(error.stderr)
27
+ ? error.stderr.toString("utf8")
28
+ : String(error.stderr);
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+
20
34
  describe("zentauri-ui CLI", () => {
35
+ it("should init with framework-aware Tailwind source guidance", () => {
36
+ const dir = mkdtempSync(join(tmpdir(), "zentauri-cli-init-next-"));
37
+ try {
38
+ const packageJsonPath = join(dir, "package.json");
39
+ execFileSync(
40
+ process.execPath,
41
+ [
42
+ "-e",
43
+ `require("node:fs").writeFileSync(${JSON.stringify(
44
+ packageJsonPath,
45
+ )}, JSON.stringify({ dependencies: { next: "16.0.0" } }))`,
46
+ ],
47
+ { cwd: dir },
48
+ );
49
+ const out = runCli(dir, ["init"]);
50
+ expect(out).toContain("Detected framework: Next.js");
51
+ expect(out).toContain('@source "./src/components/ui";');
52
+ expect(out).toContain("pnpm add react react-dom");
53
+ expect(existsSync(join(dir, "components.json"))).toBe(true);
54
+ } finally {
55
+ rmSync(dir, { recursive: true, force: true });
56
+ }
57
+ });
58
+
59
+ it("should detect Remix over Vite when both dependencies are present", () => {
60
+ const dir = mkdtempSync(join(tmpdir(), "zentauri-cli-init-remix-"));
61
+ try {
62
+ const packageJsonPath = join(dir, "package.json");
63
+ execFileSync(
64
+ process.execPath,
65
+ [
66
+ "-e",
67
+ `require("node:fs").writeFileSync(${JSON.stringify(
68
+ packageJsonPath,
69
+ )}, JSON.stringify({ dependencies: { "@remix-run/react": "2.0.0" }, devDependencies: { vite: "5.0.0" } }))`,
70
+ ],
71
+ { cwd: dir },
72
+ );
73
+ const out = runCli(dir, ["init"]);
74
+ expect(out).toContain("Detected framework: Remix");
75
+ expect(out).toContain('@source "./app/components/ui";');
76
+ } finally {
77
+ rmSync(dir, { recursive: true, force: true });
78
+ }
79
+ });
80
+
81
+ it("should list addable UI, chart, animation, and hook entries", () => {
82
+ const dir = mkdtempSync(join(tmpdir(), "zentauri-cli-list-"));
83
+ try {
84
+ const out = runCli(dir, ["list"]);
85
+ expect(out).toContain("UI components");
86
+ expect(out).toContain("buttons");
87
+ expect(out).toContain("charts/line");
88
+ expect(out).toContain("animations/fade-in");
89
+ expect(out).toContain("Hooks");
90
+ expect(out).toContain("useWindowSize");
91
+ } finally {
92
+ rmSync(dir, { recursive: true, force: true });
93
+ }
94
+ });
95
+
96
+ it("should print component info with install and import commands", () => {
97
+ const dir = mkdtempSync(join(tmpdir(), "zentauri-cli-info-"));
98
+ try {
99
+ const out = runCli(dir, ["info", "button"]);
100
+ expect(out).toContain("Name: buttons");
101
+ expect(out).toContain("npx zentauri-ui add button");
102
+ expect(out).toContain("@zentauri-ui/zentauri-components/ui/buttons");
103
+ expect(out).toContain("--animated");
104
+ } finally {
105
+ rmSync(dir, { recursive: true, force: true });
106
+ }
107
+ });
108
+
109
+ it("should add an animated component explicitly and report missing peers", () => {
110
+ const dir = mkdtempSync(join(tmpdir(), "zentauri-cli-add-animated-"));
111
+ try {
112
+ runCli(dir, ["init"]);
113
+ const out = runCli(dir, ["add", "--animated", "button"]);
114
+ expect(
115
+ existsSync(join(dir, "src/components/ui/buttons/animated/index.ts")),
116
+ ).toBe(true);
117
+ expect(out).toContain("Including animated entry for buttons");
118
+ expect(out).toContain("Missing peer dependencies in this project");
119
+ expect(out).toContain("framer-motion");
120
+ } finally {
121
+ rmSync(dir, { recursive: true, force: true });
122
+ }
123
+ });
124
+
125
+ it("should reject --animated for components without an animated entry", () => {
126
+ const dir = mkdtempSync(join(tmpdir(), "zentauri-cli-add-static-only-"));
127
+ try {
128
+ runCli(dir, ["init"]);
129
+ const stderr = runCliError(dir, ["add", "--animated", "pagination"]);
130
+ expect(stderr).toContain('Component "pagination" has no animated entry');
131
+ } finally {
132
+ rmSync(dir, { recursive: true, force: true });
133
+ }
134
+ });
135
+
21
136
  it("should init, add accordion, and rewrite internal imports", () => {
22
137
  const dir = mkdtempSync(join(tmpdir(), "zentauri-cli-int-"));
23
138
  try {
package/cli/index.mjs CHANGED
@@ -136,7 +136,7 @@ function loadRegistry() {
136
136
  */
137
137
  function printHelp() {
138
138
  const reg = loadRegistry();
139
- const componentsList = (reg.components ?? []).join("\n");
139
+ const componentsList = (reg.uiComponents ?? reg.components ?? []).join("\n");
140
140
  const animationsList = (reg.animations ?? []).join("\n");
141
141
  const hooksList = (reg.hooks ?? []).join("\n");
142
142
 
@@ -145,7 +145,10 @@ function printHelp() {
145
145
  Usage:
146
146
  zentauri-components init [options] Create components.json with defaults
147
147
  zentauri-components add <component> [...] Copy UI components (and their hooks)
148
+ zentauri-components add --animated <component> Copy and validate an animated UI entry
148
149
  zentauri-components add hook <hook> [...] Copy hook source only (plus transitive hook deps)
150
+ zentauri-components list Show addable components, charts, animations, and hooks
151
+ zentauri-components info <name> Show install/import details for one entry
149
152
  zentauri-components theme <hex> Generate compact global --zui-* theme tokens
150
153
 
151
154
  List of components:
@@ -301,6 +304,132 @@ function defaultConfig() {
301
304
  };
302
305
  }
303
306
 
307
+ const CORE_PEERS = [
308
+ "react",
309
+ "react-dom",
310
+ "class-variance-authority",
311
+ "clsx",
312
+ "tailwind-merge",
313
+ ];
314
+
315
+ // Ordered by specificity: meta-frameworks first, generic Vite last. Vite-era
316
+ // Remix (and other Vite-based setups) also depend on `vite`, so a first-match
317
+ // scan with Vite earlier would misclassify them.
318
+ const FRAMEWORKS = [
319
+ {
320
+ name: "Next.js",
321
+ deps: ["next"],
322
+ files: ["next.config.js", "next.config.mjs", "next.config.ts"],
323
+ source: '@source "./src/components/ui";',
324
+ note: "Place the @source line in your global CSS file, often app/globals.css.",
325
+ },
326
+ {
327
+ name: "Remix",
328
+ deps: ["@remix-run/react", "@remix-run/node"],
329
+ files: ["remix.config.js", "remix.config.mjs"],
330
+ source: '@source "./app/components/ui";',
331
+ note: 'If components.json keeps the default src/ paths, use @source "./src/components/ui" instead.',
332
+ },
333
+ {
334
+ name: "Astro",
335
+ deps: ["astro"],
336
+ files: ["astro.config.js", "astro.config.mjs", "astro.config.ts"],
337
+ source: '@source "./src/components/ui";',
338
+ note: "Use this in the global stylesheet loaded by your Astro React integration.",
339
+ },
340
+ {
341
+ name: "Vite",
342
+ deps: ["vite"],
343
+ files: ["vite.config.js", "vite.config.mjs", "vite.config.ts"],
344
+ source: '@source "./src/components/ui";',
345
+ note: "Place the @source line in src/index.css or the CSS file imported by your app entry.",
346
+ },
347
+ ];
348
+
349
+ function readProjectPackageJson(cwd) {
350
+ const packagePath = join(cwd, "package.json");
351
+ if (!existsSync(packagePath)) {
352
+ return undefined;
353
+ }
354
+ try {
355
+ return JSON.parse(readFileSync(packagePath, "utf8"));
356
+ } catch {
357
+ return undefined;
358
+ }
359
+ }
360
+
361
+ function packageDependencyMap(pkg) {
362
+ return {
363
+ ...(pkg?.dependencies ?? {}),
364
+ ...(pkg?.devDependencies ?? {}),
365
+ ...(pkg?.peerDependencies ?? {}),
366
+ ...(pkg?.optionalDependencies ?? {}),
367
+ };
368
+ }
369
+
370
+ function detectFramework(cwd) {
371
+ const deps = packageDependencyMap(readProjectPackageJson(cwd));
372
+ return (
373
+ FRAMEWORKS.find((framework) => framework.deps.some((dep) => deps[dep])) ??
374
+ FRAMEWORKS.find((framework) =>
375
+ framework.files.some((file) => existsSync(join(cwd, file))),
376
+ )
377
+ );
378
+ }
379
+
380
+ function printInitGuidance(cwd) {
381
+ const framework = detectFramework(cwd);
382
+ const missingCorePeers = getMissingDependencies(cwd, CORE_PEERS);
383
+ console.log(
384
+ `Detected framework: ${framework?.name ?? "React app (framework not detected)"}`,
385
+ );
386
+ console.log("\nInstall core peer dependencies:");
387
+ console.log(` pnpm add ${CORE_PEERS.join(" ")}`);
388
+ console.log(` npm install ${CORE_PEERS.join(" ")}`);
389
+ if (missingCorePeers.length > 0) {
390
+ console.log(` Missing now: ${missingCorePeers.join(", ")}`);
391
+ }
392
+ console.log("\nTailwind v4 source scanning:");
393
+ console.log(` ${framework?.source ?? '@source "./src/components/ui";'}`);
394
+ console.log(
395
+ ` ${framework?.note ?? "Place the @source line in the global CSS file processed by Tailwind."}`,
396
+ );
397
+ console.log("\nOptional peers:");
398
+ console.log(" framer-motion # animated UI and animation entries");
399
+ console.log(" react-icons # icon-heavy components such as rating");
400
+ console.log(" recharts # chart entries");
401
+ }
402
+
403
+ function findPackageJson(startDir) {
404
+ let d = startDir;
405
+ for (;;) {
406
+ const p = join(d, "package.json");
407
+ if (existsSync(p)) {
408
+ return p;
409
+ }
410
+ const parent = dirname(d);
411
+ if (parent === d) {
412
+ return undefined;
413
+ }
414
+ d = parent;
415
+ }
416
+ }
417
+
418
+ function getMissingDependencies(cwd, deps) {
419
+ const packagePath = findPackageJson(cwd);
420
+ if (!packagePath) {
421
+ return deps;
422
+ }
423
+ try {
424
+ const installed = packageDependencyMap(
425
+ JSON.parse(readFileSync(packagePath, "utf8")),
426
+ );
427
+ return deps.filter((dep) => !installed[dep]);
428
+ } catch {
429
+ return deps;
430
+ }
431
+ }
432
+
304
433
  /**
305
434
  * Ensures `add` has everything it needs to compute destination paths and rewrite
306
435
  * imports. Throws a single clear error if the config is incomplete.
@@ -396,6 +525,85 @@ function resolveHookName(input, registry) {
396
525
  );
397
526
  }
398
527
 
528
+ function resolveAnyRegistryName(input, registry) {
529
+ try {
530
+ return { kind: "component", name: resolveComponentName(input, registry) };
531
+ } catch {
532
+ try {
533
+ return { kind: "hook", name: resolveHookName(input, registry) };
534
+ } catch {
535
+ throw new Error(`Unknown entry "${input}". Run: zentauri-ui list`);
536
+ }
537
+ }
538
+ }
539
+
540
+ function isAnimatedComponent(name, registry) {
541
+ return (registry.animatedComponents ?? []).includes(name);
542
+ }
543
+
544
+ function printList(registry) {
545
+ const ui = Array.from(
546
+ new Set([
547
+ ...(registry.uiComponents ?? registry.components ?? []),
548
+ ...(registry.animatedComponents ?? []).filter(
549
+ (name) => !(registry.uiComponents ?? []).includes(name),
550
+ ),
551
+ ]),
552
+ );
553
+ const charts = registry.charts ?? [];
554
+ const animations = registry.animations ?? [];
555
+ const hooks = registry.hooks ?? [];
556
+
557
+ console.log("UI components:");
558
+ console.log(ui.join("\n"));
559
+ console.log("\nCharts:");
560
+ console.log(charts.join("\n"));
561
+ console.log("\nAnimations:");
562
+ console.log(animations.join("\n"));
563
+ console.log("\nHooks:");
564
+ console.log(hooks.join("\n"));
565
+ }
566
+
567
+ function importPathFor(name, kind, registry) {
568
+ const uiComponents = registry.uiComponents ?? [];
569
+
570
+ if (kind === "hook") {
571
+ return `@zentauri-ui/zentauri-components/hooks/${name}`;
572
+ }
573
+ if (name.startsWith("charts/")) {
574
+ return `@zentauri-ui/zentauri-components/${name}`;
575
+ }
576
+ if (name.startsWith("animations/")) {
577
+ return `@zentauri-ui/zentauri-components/${name}`;
578
+ }
579
+ if (isAnimatedComponent(name, registry) && !uiComponents.includes(name)) {
580
+ return `@zentauri-ui/zentauri-components/ui/${name}/animated`;
581
+ }
582
+ return `@zentauri-ui/zentauri-components/ui/${name}`;
583
+ }
584
+
585
+ function printInfo(input, registry) {
586
+ const { kind, name } = resolveAnyRegistryName(input, registry);
587
+ const peers = kind === "component" ? (registry.peerHints?.[name] ?? []) : [];
588
+
589
+ console.log(`Name: ${name}`);
590
+ console.log(`Type: ${kind === "hook" ? "hook" : "addable entry"}`);
591
+ console.log(`Add command: npx zentauri-ui add ${input}`);
592
+ if (kind === "hook") {
593
+ console.log(`Hook-only command: npx zentauri-ui add hook ${name}`);
594
+ }
595
+ console.log(`Import: ${importPathFor(name, kind, registry)}`);
596
+ if (kind === "component" && isAnimatedComponent(name, registry)) {
597
+ console.log(
598
+ `Animated import: @zentauri-ui/zentauri-components/ui/${name}/animated`,
599
+ );
600
+ console.log(`Animated vendoring: npx zentauri-ui add --animated ${input}`);
601
+ }
602
+ if (peers.length > 0) {
603
+ console.log(`Peer hints: ${peers.join(", ")}`);
604
+ }
605
+ }
606
+
399
607
  const THEME_COLOR_NAMES = [
400
608
  "blue",
401
609
  "cyan",
@@ -901,7 +1109,7 @@ const PEER_HINT_REASONS = {
901
1109
  * @param {object} registry — from `loadRegistry()`
902
1110
  * @param {object} config — validated `components.json` (for resolvedPaths.ui)
903
1111
  */
904
- function printAdoptionHints(resolvedNames, registry, config) {
1112
+ function printAdoptionHints(resolvedNames, registry, config, configDir) {
905
1113
  const peerHints = registry.peerHints ?? {};
906
1114
  /** @type {Map<string, string[]>} peer -> component names that need it */
907
1115
  const needed = new Map();
@@ -923,6 +1131,14 @@ function printAdoptionHints(resolvedNames, registry, config) {
923
1131
  );
924
1132
  }
925
1133
  console.log(` Install with: npm i ${[...needed.keys()].join(" ")}`);
1134
+ const missing = getMissingDependencies(configDir, [...needed.keys()]);
1135
+ if (missing.length > 0) {
1136
+ console.log("\nMissing peer dependencies in this project:");
1137
+ for (const peer of missing) {
1138
+ console.log(` - ${peer}`);
1139
+ }
1140
+ console.log(` Install with: pnpm add ${missing.join(" ")}`);
1141
+ }
926
1142
  }
927
1143
 
928
1144
  const uiPath = config?.resolvedPaths?.ui ?? "your components directory";
@@ -962,6 +1178,7 @@ async function cmdInit(cwd) {
962
1178
  const body = `${JSON.stringify(defaultConfig(), null, 2)}\n`;
963
1179
  await writeFile(target, body, "utf8");
964
1180
  console.log(`Wrote ${target}`);
1181
+ printInitGuidance(cwd);
965
1182
  }
966
1183
 
967
1184
  /**
@@ -983,7 +1200,7 @@ async function cmdInit(cwd) {
983
1200
  * // No components.json in cwd or parents — stderr:
984
1201
  * // No components.json found. Run: zentauri-components init (or: zentauri-ui init)
985
1202
  */
986
- async function cmdAdd(names, cwd) {
1203
+ async function cmdAdd(names, cwd, options = {}) {
987
1204
  const configPath = await findComponentsJson(cwd);
988
1205
  if (!configPath) {
989
1206
  console.error(
@@ -997,6 +1214,7 @@ async function cmdAdd(names, cwd) {
997
1214
  validateConfig(config);
998
1215
 
999
1216
  const registry = loadRegistry();
1217
+ const animated = Boolean(options.animated);
1000
1218
  const hookMode = names.length > 0 && names[0].toLowerCase() === "hook";
1001
1219
  const payload = hookMode ? names.slice(1) : names;
1002
1220
 
@@ -1009,6 +1227,11 @@ async function cmdAdd(names, cwd) {
1009
1227
  }
1010
1228
 
1011
1229
  if (hookMode) {
1230
+ if (animated) {
1231
+ console.error("--animated can only be used with UI component entries.");
1232
+ process.exitCode = 1;
1233
+ return;
1234
+ }
1012
1235
  await ensureUtilsFile(config, configDir, packageRoot);
1013
1236
  const resolvedHooks = payload.map((n) => resolveHookName(n, registry));
1014
1237
  const finalHooks = await collectHookTransitiveClosure(
@@ -1024,6 +1247,15 @@ async function cmdAdd(names, cwd) {
1024
1247
  }
1025
1248
 
1026
1249
  const resolvedNames = payload.map((n) => resolveComponentName(n, registry));
1250
+ if (animated) {
1251
+ for (const name of resolvedNames) {
1252
+ if (!isAnimatedComponent(name, registry)) {
1253
+ console.error(`Component "${name}" has no animated entry.`);
1254
+ process.exitCode = 1;
1255
+ return;
1256
+ }
1257
+ }
1258
+ }
1027
1259
 
1028
1260
  await ensureUtilsFile(config, configDir, packageRoot);
1029
1261
  await copyDesignSystemFolder(config, configDir, packageRoot);
@@ -1031,6 +1263,9 @@ async function cmdAdd(names, cwd) {
1031
1263
  const allHooks = new Set();
1032
1264
  for (const name of resolvedNames) {
1033
1265
  console.log(`Adding ${name}…`);
1266
+ if (animated) {
1267
+ console.log(`Including animated entry for ${name}…`);
1268
+ }
1034
1269
  const uh = await copyUiComponent(name, config, configDir, packageRoot);
1035
1270
  for (const h of uh) {
1036
1271
  allHooks.add(h);
@@ -1047,7 +1282,7 @@ async function cmdAdd(names, cwd) {
1047
1282
  }
1048
1283
 
1049
1284
  console.log("Done.");
1050
- printAdoptionHints(resolvedNames, registry, config);
1285
+ printAdoptionHints(resolvedNames, registry, config, configDir);
1051
1286
  }
1052
1287
 
1053
1288
  async function cmdTheme(hex, options, cwd) {
@@ -1102,6 +1337,7 @@ async function main() {
1102
1337
  out: { type: "string" },
1103
1338
  selector: { type: "string" },
1104
1339
  dark: { type: "string" },
1340
+ animated: { type: "boolean" },
1105
1341
  },
1106
1342
  });
1107
1343
 
@@ -1131,6 +1367,19 @@ async function main() {
1131
1367
  await cmdInit(cwd);
1132
1368
  return;
1133
1369
  }
1370
+ if (cmd === "list") {
1371
+ printList(loadRegistry());
1372
+ return;
1373
+ }
1374
+ if (cmd === "info") {
1375
+ if (rest.length === 0) {
1376
+ console.error("Usage: zentauri-components info <component|hook>");
1377
+ process.exitCode = 1;
1378
+ return;
1379
+ }
1380
+ printInfo(rest[0], loadRegistry());
1381
+ return;
1382
+ }
1134
1383
  if (cmd === "add") {
1135
1384
  if (rest.length === 0) {
1136
1385
  console.error(
@@ -1139,7 +1388,7 @@ async function main() {
1139
1388
  process.exitCode = 1;
1140
1389
  return;
1141
1390
  }
1142
- await cmdAdd(rest, cwd);
1391
+ await cmdAdd(rest, cwd, { animated: values.animated });
1143
1392
  return;
1144
1393
  }
1145
1394
  if (cmd === "theme") {
package/cli/registry.json CHANGED
@@ -1,6 +1,90 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "description": "Addable UI components (src/ui), animation entries (src/animations/*), chart entries (src/charts/*), and hooks (src/hooks). Generated by scripts/generate-registry.mjs.",
4
+ "uiComponents": [
5
+ "accordion",
6
+ "alert",
7
+ "animated-number",
8
+ "avatar",
9
+ "badge",
10
+ "breadcrumb",
11
+ "buttons",
12
+ "card",
13
+ "checkbox",
14
+ "combobox",
15
+ "command",
16
+ "context-menu",
17
+ "copy-button",
18
+ "divider",
19
+ "drawer",
20
+ "dropdown",
21
+ "dynamic-stepper",
22
+ "empty-state",
23
+ "file-upload",
24
+ "inputs",
25
+ "kbd",
26
+ "marquee",
27
+ "modal",
28
+ "otp-input",
29
+ "pagination",
30
+ "popover",
31
+ "progress",
32
+ "radio-group",
33
+ "rating",
34
+ "scroll-area",
35
+ "search",
36
+ "select",
37
+ "skeleton",
38
+ "slider",
39
+ "table",
40
+ "tabs",
41
+ "timeline",
42
+ "toast",
43
+ "toggle",
44
+ "tooltip",
45
+ "tree-view",
46
+ "typography"
47
+ ],
48
+ "animatedComponents": [
49
+ "accordion",
50
+ "alert",
51
+ "avatar",
52
+ "badge",
53
+ "buttons",
54
+ "card",
55
+ "checkbox",
56
+ "command",
57
+ "copy-button",
58
+ "divider",
59
+ "drawer",
60
+ "empty-state",
61
+ "inputs",
62
+ "kbd",
63
+ "modal",
64
+ "popover",
65
+ "progress",
66
+ "radio-group",
67
+ "skeleton",
68
+ "spinner",
69
+ "table",
70
+ "tabs",
71
+ "timeline",
72
+ "toast",
73
+ "toggle",
74
+ "tooltip",
75
+ "tree-view"
76
+ ],
77
+ "charts": [
78
+ "charts/area",
79
+ "charts/bar",
80
+ "charts/bubble",
81
+ "charts/funnel",
82
+ "charts/line",
83
+ "charts/pie",
84
+ "charts/radar",
85
+ "charts/scatter",
86
+ "charts/stacked-bar"
87
+ ],
4
88
  "components": [
5
89
  "accordion",
6
90
  "alert",
@@ -1,4 +1,4 @@
1
- import { buttonVariants } from './chunk-3N575QVC.mjs';
1
+ import { buttonVariants } from './chunk-BPSW3SRE.mjs';
2
2
  import { cn } from './chunk-4D54YOL6.mjs';
3
3
  import { isValidElement, cloneElement } from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
@@ -97,5 +97,5 @@ var Button = (props) => {
97
97
  Button.displayName = "Button";
98
98
 
99
99
  export { Button };
100
- //# sourceMappingURL=chunk-TYBQZO6Y.mjs.map
101
- //# sourceMappingURL=chunk-TYBQZO6Y.mjs.map
100
+ //# sourceMappingURL=chunk-2P4WPYC3.mjs.map
101
+ //# sourceMappingURL=chunk-2P4WPYC3.mjs.map