@zentauri-ui/zentauri-components 2.1.0 → 2.1.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.
Files changed (113) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +10 -10
  3. package/cli/cli.integration.test.ts +115 -0
  4. package/cli/index.mjs +260 -5
  5. package/cli/registry.json +84 -0
  6. package/dist/{chunk-N3FNN47Q.mjs → chunk-3IE4IELX.mjs} +32 -32
  7. package/dist/chunk-3IE4IELX.mjs.map +1 -0
  8. package/dist/{chunk-JEZUMCJ6.mjs → chunk-3N575QVC.mjs} +3 -3
  9. package/dist/{chunk-JEZUMCJ6.mjs.map → chunk-3N575QVC.mjs.map} +1 -1
  10. package/dist/{chunk-RRUVHDN6.js → chunk-5TVBPPS6.js} +11 -11
  11. package/dist/{chunk-RRUVHDN6.js.map → chunk-5TVBPPS6.js.map} +1 -1
  12. package/dist/{chunk-YNTUZCLI.js → chunk-642JVK45.js} +16 -16
  13. package/dist/{chunk-YNTUZCLI.js.map → chunk-642JVK45.js.map} +1 -1
  14. package/dist/{chunk-AZ26NZJV.mjs → chunk-6ERBVFDA.mjs} +35 -35
  15. package/dist/chunk-6ERBVFDA.mjs.map +1 -0
  16. package/dist/{chunk-SRJCGSNX.js → chunk-6Q4EOLKN.js} +23 -23
  17. package/dist/{chunk-SRJCGSNX.js.map → chunk-6Q4EOLKN.js.map} +1 -1
  18. package/dist/{chunk-TCEGQIGW.mjs → chunk-7N2UYQBJ.mjs} +9 -7
  19. package/dist/{chunk-TCEGQIGW.mjs.map → chunk-7N2UYQBJ.mjs.map} +1 -1
  20. package/dist/{chunk-VKEDTQY6.js → chunk-APCN6NUH.js} +8 -8
  21. package/dist/{chunk-VKEDTQY6.js.map → chunk-APCN6NUH.js.map} +1 -1
  22. package/dist/{chunk-X7HK6RTF.js → chunk-BIQZC26Q.js} +23 -23
  23. package/dist/{chunk-X7HK6RTF.js.map → chunk-BIQZC26Q.js.map} +1 -1
  24. package/dist/{chunk-K2H6LIPQ.mjs → chunk-GOH2THVW.mjs} +3 -3
  25. package/dist/{chunk-K2H6LIPQ.mjs.map → chunk-GOH2THVW.mjs.map} +1 -1
  26. package/dist/chunk-GUB3RSPR.mjs +211 -0
  27. package/dist/chunk-GUB3RSPR.mjs.map +1 -0
  28. package/dist/{chunk-GENYOZN4.mjs → chunk-HVMYPW3Q.mjs} +3 -3
  29. package/dist/{chunk-GENYOZN4.mjs.map → chunk-HVMYPW3Q.mjs.map} +1 -1
  30. package/dist/chunk-J5NTJVKX.js +229 -0
  31. package/dist/chunk-J5NTJVKX.js.map +1 -0
  32. package/dist/{chunk-XUW42JAP.js → chunk-PZ25OHJE.js} +32 -32
  33. package/dist/chunk-PZ25OHJE.js.map +1 -0
  34. package/dist/{chunk-UFDJ5NIY.mjs → chunk-PZI2UVPL.mjs} +3 -3
  35. package/dist/{chunk-UFDJ5NIY.mjs.map → chunk-PZI2UVPL.mjs.map} +1 -1
  36. package/dist/chunk-R4D5V7NT.js +19 -0
  37. package/dist/{chunk-4TX7EQ5Y.js.map → chunk-R4D5V7NT.js.map} +1 -1
  38. package/dist/{chunk-BGIWVTAU.mjs → chunk-RJ7L45SA.mjs} +3 -3
  39. package/dist/{chunk-BGIWVTAU.mjs.map → chunk-RJ7L45SA.mjs.map} +1 -1
  40. package/dist/{chunk-C6K2SWHC.mjs → chunk-TVEK6PKH.mjs} +23 -23
  41. package/dist/{chunk-C6K2SWHC.mjs.map → chunk-TVEK6PKH.mjs.map} +1 -1
  42. package/dist/{chunk-BJRS5RXR.mjs → chunk-TYBQZO6Y.mjs} +3 -3
  43. package/dist/{chunk-BJRS5RXR.mjs.map → chunk-TYBQZO6Y.mjs.map} +1 -1
  44. package/dist/{chunk-AOVZY2A3.js → chunk-UQQHB3OI.js} +35 -35
  45. package/dist/chunk-UQQHB3OI.js.map +1 -0
  46. package/dist/{chunk-NEFDIJ5N.js → chunk-V2LI5QZD.js} +54 -52
  47. package/dist/chunk-V2LI5QZD.js.map +1 -0
  48. package/dist/{chunk-KCXTYTOY.js → chunk-VHYUH5OH.js} +6 -6
  49. package/dist/{chunk-KCXTYTOY.js.map → chunk-VHYUH5OH.js.map} +1 -1
  50. package/dist/{chunk-BVTYDGLM.mjs → chunk-WIKNEHPJ.mjs} +23 -23
  51. package/dist/{chunk-BVTYDGLM.mjs.map → chunk-WIKNEHPJ.mjs.map} +1 -1
  52. package/dist/{chunk-XF3NHZZ3.js → chunk-Y6DFOQ5E.js} +7 -7
  53. package/dist/{chunk-XF3NHZZ3.js.map → chunk-Y6DFOQ5E.js.map} +1 -1
  54. package/dist/design-system/combobox.d.ts +120 -37
  55. package/dist/design-system/combobox.d.ts.map +1 -1
  56. package/dist/design-system/command.d.ts +33 -33
  57. package/dist/design-system/facade.js +7 -7
  58. package/dist/design-system/facade.mjs +6 -6
  59. package/dist/design-system/popover.d.ts +30 -30
  60. package/dist/design-system/tabs.d.ts +21 -21
  61. package/dist/design-system/toast.d.ts +21 -21
  62. package/dist/ui/buttons/animated.js +9 -9
  63. package/dist/ui/buttons/animated.mjs +7 -7
  64. package/dist/ui/buttons.js +10 -10
  65. package/dist/ui/buttons.mjs +8 -8
  66. package/dist/ui/combobox/combobox-base.d.ts.map +1 -1
  67. package/dist/ui/combobox/variants.d.ts +3 -3
  68. package/dist/ui/combobox.js +22 -20
  69. package/dist/ui/combobox.js.map +1 -1
  70. package/dist/ui/combobox.mjs +6 -4
  71. package/dist/ui/combobox.mjs.map +1 -1
  72. package/dist/ui/command/animated.js +3 -3
  73. package/dist/ui/command/animated.mjs +2 -2
  74. package/dist/ui/command.js +16 -16
  75. package/dist/ui/command.mjs +3 -3
  76. package/dist/ui/dynamic-stepper.js +19 -19
  77. package/dist/ui/dynamic-stepper.mjs +8 -8
  78. package/dist/ui/pagination.js +15 -15
  79. package/dist/ui/pagination.mjs +7 -7
  80. package/dist/ui/popover/animated.js +5 -5
  81. package/dist/ui/popover/animated.mjs +2 -2
  82. package/dist/ui/popover.js +8 -8
  83. package/dist/ui/popover.mjs +2 -2
  84. package/dist/ui/tabs/animated.js +3 -3
  85. package/dist/ui/tabs/animated.mjs +2 -2
  86. package/dist/ui/tabs.js +10 -10
  87. package/dist/ui/tabs.mjs +2 -2
  88. package/dist/ui/toast/animated.js +8 -8
  89. package/dist/ui/toast/animated.mjs +2 -2
  90. package/dist/ui/toast.js +13 -13
  91. package/dist/ui/toast.mjs +2 -2
  92. package/package.json +3 -2
  93. package/src/design-system/combobox.ts +176 -46
  94. package/src/design-system/command.ts +33 -33
  95. package/src/design-system/popover.ts +30 -30
  96. package/src/design-system/tabs.ts +21 -21
  97. package/src/design-system/toast.ts +21 -21
  98. package/src/ui/combobox/combobox-base.tsx +5 -3
  99. package/src/ui/combobox/combobox.test.tsx +69 -0
  100. package/src/ui/command/command.test.tsx +15 -0
  101. package/src/ui/popover/popover.test.tsx +15 -0
  102. package/src/ui/tabs/tabs.test.tsx +15 -0
  103. package/src/ui/toast/toast.test.tsx +12 -0
  104. package/dist/chunk-4TX7EQ5Y.js +0 -19
  105. package/dist/chunk-AOVZY2A3.js.map +0 -1
  106. package/dist/chunk-AZ26NZJV.mjs.map +0 -1
  107. package/dist/chunk-K7PR3XXT.mjs +0 -128
  108. package/dist/chunk-K7PR3XXT.mjs.map +0 -1
  109. package/dist/chunk-N3FNN47Q.mjs.map +0 -1
  110. package/dist/chunk-NEFDIJ5N.js.map +0 -1
  111. package/dist/chunk-QNRJT7R4.js +0 -144
  112. package/dist/chunk-QNRJT7R4.js.map +0 -1
  113. package/dist/chunk-XUW42JAP.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @zentauri-ui/zentauri-components Changelog
2
2
 
3
+ ## 2.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 398393d: Add compact global theme tokens and generated theme CSS support for the Zentauri UI token system.
8
+
3
9
  ## 2.0.0
4
10
 
5
11
  ### Major Changes
package/README.md CHANGED
@@ -30,15 +30,15 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
30
30
  | Metric | Result |
31
31
  | ---------- | ---------------- |
32
32
  | Test files | 92 passed (92) |
33
- | Tests | 700 passed (700) |
33
+ | Tests | 713 passed (713) |
34
34
 
35
35
  | Area | Test files | Tests |
36
36
  | --------------------------- | ---------- | ----- |
37
- | Components and UI utilities | 46 | 446 |
37
+ | Components and UI utilities | 46 | 453 |
38
38
  | Standalone animations | 1 | 45 |
39
39
  | React hooks | 41 | 174 |
40
40
  | Design system facade | 1 | 11 |
41
- | CLI and import rewriting | 2 | 18 |
41
+ | CLI and import rewriting | 2 | 24 |
42
42
  | Axe core test cases | 1 | 6 |
43
43
 
44
44
  ### Per-suite snapshot
@@ -49,10 +49,10 @@ 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/ui/combobox/combobox.test.tsx` | 21 |
52
+ | `src/ui/combobox/combobox.test.tsx` | 24 |
53
+ | `cli/cli.integration.test.ts` | 19 |
53
54
  | `src/ui/pagination/pagination.test.tsx` | 15 |
54
55
  | `src/ui/timeline/timeline.test.tsx` | 14 |
55
- | `cli/cli.integration.test.ts` | 13 |
56
56
  | `src/lib/facade.test.ts` | 11 |
57
57
  | `src/ui/alert/alert.test.tsx` | 11 |
58
58
  | `src/ui/rating/rating.test.tsx` | 11 |
@@ -68,6 +68,7 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
68
68
  | `src/hooks/useTableFilter/useTableFilter.test.ts` | 9 |
69
69
  | `src/ui/animated-number/animated-number.test.tsx` | 9 |
70
70
  | `src/ui/slider/slider.test.tsx` | 9 |
71
+ | `src/ui/command/command.test.tsx` | 8 |
71
72
  | `src/ui/copy-button/copy-button.test.tsx` | 8 |
72
73
  | `src/ui/dynamic-stepper/dynamic-stepper.test.tsx` | 8 |
73
74
  | `src/ui/progress/progress.test.tsx` | 8 |
@@ -79,7 +80,6 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
79
80
  | `src/hooks/useTableSort/useTableSort.test.ts` | 7 |
80
81
  | `src/ui/accordion/accordion.test.tsx` | 7 |
81
82
  | `src/ui/card/card.test.tsx` | 7 |
82
- | `src/ui/command/command.test.tsx` | 7 |
83
83
  | `src/ui/drawer/drawer.test.tsx` | 7 |
84
84
  | `src/ui/kbd/kbd.test.tsx` | 7 |
85
85
  | `src/ui/typography/typography.test.tsx` | 7 |
@@ -98,14 +98,15 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
98
98
  | `src/ui/dropdown/dropdown.test.tsx` | 6 |
99
99
  | `src/ui/empty-state/empty-state.test.tsx` | 6 |
100
100
  | `src/ui/search/filter-search-suggestions.test.ts` | 6 |
101
+ | `src/ui/toast/toast.test.tsx` | 6 |
101
102
  | `cli/rewrite-imports.test.ts` | 5 |
102
103
  | `src/hooks/useCookie/useCookie.test.ts` | 5 |
103
104
  | `src/hooks/useDisclosure/useDisclosure.test.ts` | 5 |
104
105
  | `src/hooks/useEventListener/useEventListener.test.ts` | 5 |
105
106
  | `src/hooks/useScrollPosition/useScrollPosition.test.ts` | 5 |
106
107
  | `src/hooks/useTimeout/useTimeout.test.ts` | 5 |
108
+ | `src/ui/popover/popover.test.tsx` | 5 |
107
109
  | `src/ui/radio-group/radio-group.test.tsx` | 5 |
108
- | `src/ui/toast/toast.test.tsx` | 5 |
109
110
  | `src/ui/toggle/toggle.test.tsx` | 5 |
110
111
  | `src/hooks/useBodyScrollLock/useBodyScrollLock.test.ts` | 4 |
111
112
  | `src/hooks/useControllableState/useControllableState.test.ts` | 4 |
@@ -116,7 +117,7 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
116
117
  | `src/ui/avatar/avatar.test.tsx` | 4 |
117
118
  | `src/ui/breadcrumb/breadcrumb.test.tsx` | 4 |
118
119
  | `src/ui/file-upload/file-upload.test.tsx` | 4 |
119
- | `src/ui/popover/popover.test.tsx` | 4 |
120
+ | `src/ui/tabs/tabs.test.tsx` | 4 |
120
121
  | `src/ui/tooltip/tooltip.test.tsx` | 4 |
121
122
  | `src/hooks/useClickOutside/useClickOutside.test.tsx` | 3 |
122
123
  | `src/hooks/useDocumentTitle/useDocumentTitle.test.ts` | 3 |
@@ -124,7 +125,6 @@ Generated from the component package Vitest JSON report via `pnpm --filter @zent
124
125
  | `src/hooks/useIsMounted/useIsMounted.test.ts` | 3 |
125
126
  | `src/hooks/usePrevious/usePrevious.test.ts` | 3 |
126
127
  | `src/hooks/useSessionStorage/useSessionStorage.test.ts` | 3 |
127
- | `src/ui/tabs/tabs.test.tsx` | 3 |
128
128
  | `src/hooks/useHover/useHover.test.ts` | 2 |
129
129
  | `src/hooks/useIntersectionObserver/useIntersectionObserver.test.ts` | 2 |
130
130
  | `src/hooks/useMediaQuery/useMediaQuery.test.ts` | 2 |
@@ -846,7 +846,7 @@ From this package directory in the monorepo:
846
846
 
847
847
  - `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
848
  - `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 700 test cases in total
849
+ - `pnpm test` / `pnpm test:watch` — **Vitest** and **Testing Library** unit tests // currently covered 713 test cases in total
850
850
  - `pnpm test:a11y` — focused **axe-core** accessibility smoke coverage for package-level UI primitives and compound components
851
851
  - `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
852
  - **`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).
@@ -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,136 @@ 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) =>
374
+ framework.deps.some((dep) => deps[dep]),
375
+ ) ??
376
+ FRAMEWORKS.find((framework) =>
377
+ framework.files.some((file) => existsSync(join(cwd, file))),
378
+ )
379
+ );
380
+ }
381
+
382
+ function printInitGuidance(cwd) {
383
+ const framework = detectFramework(cwd);
384
+ const missingCorePeers = getMissingDependencies(cwd, CORE_PEERS);
385
+ console.log(
386
+ `Detected framework: ${framework?.name ?? "React app (framework not detected)"}`,
387
+ );
388
+ console.log("\nInstall core peer dependencies:");
389
+ console.log(` pnpm add ${CORE_PEERS.join(" ")}`);
390
+ console.log(` npm install ${CORE_PEERS.join(" ")}`);
391
+ if (missingCorePeers.length > 0) {
392
+ console.log(
393
+ ` Missing now: ${missingCorePeers.join(", ")}`,
394
+ );
395
+ }
396
+ console.log("\nTailwind v4 source scanning:");
397
+ console.log(` ${framework?.source ?? '@source "./src/components/ui";'}`);
398
+ console.log(
399
+ ` ${framework?.note ?? "Place the @source line in the global CSS file processed by Tailwind."}`,
400
+ );
401
+ console.log("\nOptional peers:");
402
+ console.log(" framer-motion # animated UI and animation entries");
403
+ console.log(" react-icons # icon-heavy components such as rating");
404
+ console.log(" recharts # chart entries");
405
+ }
406
+
407
+ function findPackageJson(startDir) {
408
+ let d = startDir;
409
+ for (;;) {
410
+ const p = join(d, "package.json");
411
+ if (existsSync(p)) {
412
+ return p;
413
+ }
414
+ const parent = dirname(d);
415
+ if (parent === d) {
416
+ return undefined;
417
+ }
418
+ d = parent;
419
+ }
420
+ }
421
+
422
+ function getMissingDependencies(cwd, deps) {
423
+ const packagePath = findPackageJson(cwd);
424
+ if (!packagePath) {
425
+ return deps;
426
+ }
427
+ try {
428
+ const installed = packageDependencyMap(
429
+ JSON.parse(readFileSync(packagePath, "utf8")),
430
+ );
431
+ return deps.filter((dep) => !installed[dep]);
432
+ } catch {
433
+ return deps;
434
+ }
435
+ }
436
+
304
437
  /**
305
438
  * Ensures `add` has everything it needs to compute destination paths and rewrite
306
439
  * imports. Throws a single clear error if the config is incomplete.
@@ -396,6 +529,87 @@ function resolveHookName(input, registry) {
396
529
  );
397
530
  }
398
531
 
532
+ function resolveAnyRegistryName(input, registry) {
533
+ try {
534
+ return { kind: "component", name: resolveComponentName(input, registry) };
535
+ } catch {
536
+ try {
537
+ return { kind: "hook", name: resolveHookName(input, registry) };
538
+ } catch {
539
+ throw new Error(
540
+ `Unknown entry "${input}". Run: zentauri-ui list`,
541
+ );
542
+ }
543
+ }
544
+ }
545
+
546
+ function isAnimatedComponent(name, registry) {
547
+ return (registry.animatedComponents ?? []).includes(name);
548
+ }
549
+
550
+ function printList(registry) {
551
+ const ui = Array.from(
552
+ new Set([
553
+ ...(registry.uiComponents ?? registry.components ?? []),
554
+ ...(registry.animatedComponents ?? []).filter(
555
+ (name) => !(registry.uiComponents ?? []).includes(name),
556
+ ),
557
+ ]),
558
+ );
559
+ const charts = registry.charts ?? [];
560
+ const animations = registry.animations ?? [];
561
+ const hooks = registry.hooks ?? [];
562
+
563
+ console.log("UI components:");
564
+ console.log(ui.join("\n"));
565
+ console.log("\nCharts:");
566
+ console.log(charts.join("\n"));
567
+ console.log("\nAnimations:");
568
+ console.log(animations.join("\n"));
569
+ console.log("\nHooks:");
570
+ console.log(hooks.join("\n"));
571
+ }
572
+
573
+ function importPathFor(name, kind, registry) {
574
+ const uiComponents = registry.uiComponents ?? [];
575
+
576
+ if (kind === "hook") {
577
+ return `@zentauri-ui/zentauri-components/hooks/${name}`;
578
+ }
579
+ if (name.startsWith("charts/")) {
580
+ return `@zentauri-ui/zentauri-components/${name}`;
581
+ }
582
+ if (name.startsWith("animations/")) {
583
+ return `@zentauri-ui/zentauri-components/${name}`;
584
+ }
585
+ if (isAnimatedComponent(name, registry) && !uiComponents.includes(name)) {
586
+ return `@zentauri-ui/zentauri-components/ui/${name}/animated`;
587
+ }
588
+ return `@zentauri-ui/zentauri-components/ui/${name}`;
589
+ }
590
+
591
+ function printInfo(input, registry) {
592
+ const { kind, name } = resolveAnyRegistryName(input, registry);
593
+ const peers = kind === "component" ? (registry.peerHints?.[name] ?? []) : [];
594
+
595
+ console.log(`Name: ${name}`);
596
+ console.log(`Type: ${kind === "hook" ? "hook" : "addable entry"}`);
597
+ console.log(`Add command: npx zentauri-ui add ${input}`);
598
+ if (kind === "hook") {
599
+ console.log(`Hook-only command: npx zentauri-ui add hook ${name}`);
600
+ }
601
+ console.log(`Import: ${importPathFor(name, kind, registry)}`);
602
+ if (kind === "component" && isAnimatedComponent(name, registry)) {
603
+ console.log(
604
+ `Animated import: @zentauri-ui/zentauri-components/ui/${name}/animated`,
605
+ );
606
+ console.log(`Animated vendoring: npx zentauri-ui add --animated ${input}`);
607
+ }
608
+ if (peers.length > 0) {
609
+ console.log(`Peer hints: ${peers.join(", ")}`);
610
+ }
611
+ }
612
+
399
613
  const THEME_COLOR_NAMES = [
400
614
  "blue",
401
615
  "cyan",
@@ -901,7 +1115,7 @@ const PEER_HINT_REASONS = {
901
1115
  * @param {object} registry — from `loadRegistry()`
902
1116
  * @param {object} config — validated `components.json` (for resolvedPaths.ui)
903
1117
  */
904
- function printAdoptionHints(resolvedNames, registry, config) {
1118
+ function printAdoptionHints(resolvedNames, registry, config, configDir) {
905
1119
  const peerHints = registry.peerHints ?? {};
906
1120
  /** @type {Map<string, string[]>} peer -> component names that need it */
907
1121
  const needed = new Map();
@@ -923,6 +1137,14 @@ function printAdoptionHints(resolvedNames, registry, config) {
923
1137
  );
924
1138
  }
925
1139
  console.log(` Install with: npm i ${[...needed.keys()].join(" ")}`);
1140
+ const missing = getMissingDependencies(configDir, [...needed.keys()]);
1141
+ if (missing.length > 0) {
1142
+ console.log("\nMissing peer dependencies in this project:");
1143
+ for (const peer of missing) {
1144
+ console.log(` - ${peer}`);
1145
+ }
1146
+ console.log(` Install with: pnpm add ${missing.join(" ")}`);
1147
+ }
926
1148
  }
927
1149
 
928
1150
  const uiPath = config?.resolvedPaths?.ui ?? "your components directory";
@@ -962,6 +1184,7 @@ async function cmdInit(cwd) {
962
1184
  const body = `${JSON.stringify(defaultConfig(), null, 2)}\n`;
963
1185
  await writeFile(target, body, "utf8");
964
1186
  console.log(`Wrote ${target}`);
1187
+ printInitGuidance(cwd);
965
1188
  }
966
1189
 
967
1190
  /**
@@ -983,7 +1206,7 @@ async function cmdInit(cwd) {
983
1206
  * // No components.json in cwd or parents — stderr:
984
1207
  * // No components.json found. Run: zentauri-components init (or: zentauri-ui init)
985
1208
  */
986
- async function cmdAdd(names, cwd) {
1209
+ async function cmdAdd(names, cwd, options = {}) {
987
1210
  const configPath = await findComponentsJson(cwd);
988
1211
  if (!configPath) {
989
1212
  console.error(
@@ -997,6 +1220,7 @@ async function cmdAdd(names, cwd) {
997
1220
  validateConfig(config);
998
1221
 
999
1222
  const registry = loadRegistry();
1223
+ const animated = Boolean(options.animated);
1000
1224
  const hookMode = names.length > 0 && names[0].toLowerCase() === "hook";
1001
1225
  const payload = hookMode ? names.slice(1) : names;
1002
1226
 
@@ -1009,6 +1233,11 @@ async function cmdAdd(names, cwd) {
1009
1233
  }
1010
1234
 
1011
1235
  if (hookMode) {
1236
+ if (animated) {
1237
+ console.error("--animated can only be used with UI component entries.");
1238
+ process.exitCode = 1;
1239
+ return;
1240
+ }
1012
1241
  await ensureUtilsFile(config, configDir, packageRoot);
1013
1242
  const resolvedHooks = payload.map((n) => resolveHookName(n, registry));
1014
1243
  const finalHooks = await collectHookTransitiveClosure(
@@ -1024,6 +1253,15 @@ async function cmdAdd(names, cwd) {
1024
1253
  }
1025
1254
 
1026
1255
  const resolvedNames = payload.map((n) => resolveComponentName(n, registry));
1256
+ if (animated) {
1257
+ for (const name of resolvedNames) {
1258
+ if (!isAnimatedComponent(name, registry)) {
1259
+ console.error(`Component "${name}" has no animated entry.`);
1260
+ process.exitCode = 1;
1261
+ return;
1262
+ }
1263
+ }
1264
+ }
1027
1265
 
1028
1266
  await ensureUtilsFile(config, configDir, packageRoot);
1029
1267
  await copyDesignSystemFolder(config, configDir, packageRoot);
@@ -1031,6 +1269,9 @@ async function cmdAdd(names, cwd) {
1031
1269
  const allHooks = new Set();
1032
1270
  for (const name of resolvedNames) {
1033
1271
  console.log(`Adding ${name}…`);
1272
+ if (animated) {
1273
+ console.log(`Including animated entry for ${name}…`);
1274
+ }
1034
1275
  const uh = await copyUiComponent(name, config, configDir, packageRoot);
1035
1276
  for (const h of uh) {
1036
1277
  allHooks.add(h);
@@ -1047,7 +1288,7 @@ async function cmdAdd(names, cwd) {
1047
1288
  }
1048
1289
 
1049
1290
  console.log("Done.");
1050
- printAdoptionHints(resolvedNames, registry, config);
1291
+ printAdoptionHints(resolvedNames, registry, config, configDir);
1051
1292
  }
1052
1293
 
1053
1294
  async function cmdTheme(hex, options, cwd) {
@@ -1102,6 +1343,7 @@ async function main() {
1102
1343
  out: { type: "string" },
1103
1344
  selector: { type: "string" },
1104
1345
  dark: { type: "string" },
1346
+ animated: { type: "boolean" },
1105
1347
  },
1106
1348
  });
1107
1349
 
@@ -1131,6 +1373,19 @@ async function main() {
1131
1373
  await cmdInit(cwd);
1132
1374
  return;
1133
1375
  }
1376
+ if (cmd === "list") {
1377
+ printList(loadRegistry());
1378
+ return;
1379
+ }
1380
+ if (cmd === "info") {
1381
+ if (rest.length === 0) {
1382
+ console.error("Usage: zentauri-components info <component|hook>");
1383
+ process.exitCode = 1;
1384
+ return;
1385
+ }
1386
+ printInfo(rest[0], loadRegistry());
1387
+ return;
1388
+ }
1134
1389
  if (cmd === "add") {
1135
1390
  if (rest.length === 0) {
1136
1391
  console.error(
@@ -1139,7 +1394,7 @@ async function main() {
1139
1394
  process.exitCode = 1;
1140
1395
  return;
1141
1396
  }
1142
- await cmdAdd(rest, cwd);
1397
+ await cmdAdd(rest, cwd, { animated: values.animated });
1143
1398
  return;
1144
1399
  }
1145
1400
  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",