@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.
- package/CHANGELOG.md +6 -0
- package/README.md +10 -10
- package/cli/cli.integration.test.ts +115 -0
- package/cli/index.mjs +260 -5
- package/cli/registry.json +84 -0
- package/dist/{chunk-N3FNN47Q.mjs → chunk-3IE4IELX.mjs} +32 -32
- package/dist/chunk-3IE4IELX.mjs.map +1 -0
- package/dist/{chunk-JEZUMCJ6.mjs → chunk-3N575QVC.mjs} +3 -3
- package/dist/{chunk-JEZUMCJ6.mjs.map → chunk-3N575QVC.mjs.map} +1 -1
- package/dist/{chunk-RRUVHDN6.js → chunk-5TVBPPS6.js} +11 -11
- package/dist/{chunk-RRUVHDN6.js.map → chunk-5TVBPPS6.js.map} +1 -1
- package/dist/{chunk-YNTUZCLI.js → chunk-642JVK45.js} +16 -16
- package/dist/{chunk-YNTUZCLI.js.map → chunk-642JVK45.js.map} +1 -1
- package/dist/{chunk-AZ26NZJV.mjs → chunk-6ERBVFDA.mjs} +35 -35
- package/dist/chunk-6ERBVFDA.mjs.map +1 -0
- package/dist/{chunk-SRJCGSNX.js → chunk-6Q4EOLKN.js} +23 -23
- package/dist/{chunk-SRJCGSNX.js.map → chunk-6Q4EOLKN.js.map} +1 -1
- package/dist/{chunk-TCEGQIGW.mjs → chunk-7N2UYQBJ.mjs} +9 -7
- package/dist/{chunk-TCEGQIGW.mjs.map → chunk-7N2UYQBJ.mjs.map} +1 -1
- package/dist/{chunk-VKEDTQY6.js → chunk-APCN6NUH.js} +8 -8
- package/dist/{chunk-VKEDTQY6.js.map → chunk-APCN6NUH.js.map} +1 -1
- package/dist/{chunk-X7HK6RTF.js → chunk-BIQZC26Q.js} +23 -23
- package/dist/{chunk-X7HK6RTF.js.map → chunk-BIQZC26Q.js.map} +1 -1
- package/dist/{chunk-K2H6LIPQ.mjs → chunk-GOH2THVW.mjs} +3 -3
- package/dist/{chunk-K2H6LIPQ.mjs.map → chunk-GOH2THVW.mjs.map} +1 -1
- package/dist/chunk-GUB3RSPR.mjs +211 -0
- package/dist/chunk-GUB3RSPR.mjs.map +1 -0
- package/dist/{chunk-GENYOZN4.mjs → chunk-HVMYPW3Q.mjs} +3 -3
- package/dist/{chunk-GENYOZN4.mjs.map → chunk-HVMYPW3Q.mjs.map} +1 -1
- package/dist/chunk-J5NTJVKX.js +229 -0
- package/dist/chunk-J5NTJVKX.js.map +1 -0
- package/dist/{chunk-XUW42JAP.js → chunk-PZ25OHJE.js} +32 -32
- package/dist/chunk-PZ25OHJE.js.map +1 -0
- package/dist/{chunk-UFDJ5NIY.mjs → chunk-PZI2UVPL.mjs} +3 -3
- package/dist/{chunk-UFDJ5NIY.mjs.map → chunk-PZI2UVPL.mjs.map} +1 -1
- package/dist/chunk-R4D5V7NT.js +19 -0
- package/dist/{chunk-4TX7EQ5Y.js.map → chunk-R4D5V7NT.js.map} +1 -1
- package/dist/{chunk-BGIWVTAU.mjs → chunk-RJ7L45SA.mjs} +3 -3
- package/dist/{chunk-BGIWVTAU.mjs.map → chunk-RJ7L45SA.mjs.map} +1 -1
- package/dist/{chunk-C6K2SWHC.mjs → chunk-TVEK6PKH.mjs} +23 -23
- package/dist/{chunk-C6K2SWHC.mjs.map → chunk-TVEK6PKH.mjs.map} +1 -1
- package/dist/{chunk-BJRS5RXR.mjs → chunk-TYBQZO6Y.mjs} +3 -3
- package/dist/{chunk-BJRS5RXR.mjs.map → chunk-TYBQZO6Y.mjs.map} +1 -1
- package/dist/{chunk-AOVZY2A3.js → chunk-UQQHB3OI.js} +35 -35
- package/dist/chunk-UQQHB3OI.js.map +1 -0
- package/dist/{chunk-NEFDIJ5N.js → chunk-V2LI5QZD.js} +54 -52
- package/dist/chunk-V2LI5QZD.js.map +1 -0
- package/dist/{chunk-KCXTYTOY.js → chunk-VHYUH5OH.js} +6 -6
- package/dist/{chunk-KCXTYTOY.js.map → chunk-VHYUH5OH.js.map} +1 -1
- package/dist/{chunk-BVTYDGLM.mjs → chunk-WIKNEHPJ.mjs} +23 -23
- package/dist/{chunk-BVTYDGLM.mjs.map → chunk-WIKNEHPJ.mjs.map} +1 -1
- package/dist/{chunk-XF3NHZZ3.js → chunk-Y6DFOQ5E.js} +7 -7
- package/dist/{chunk-XF3NHZZ3.js.map → chunk-Y6DFOQ5E.js.map} +1 -1
- package/dist/design-system/combobox.d.ts +120 -37
- package/dist/design-system/combobox.d.ts.map +1 -1
- package/dist/design-system/command.d.ts +33 -33
- package/dist/design-system/facade.js +7 -7
- package/dist/design-system/facade.mjs +6 -6
- package/dist/design-system/popover.d.ts +30 -30
- package/dist/design-system/tabs.d.ts +21 -21
- package/dist/design-system/toast.d.ts +21 -21
- package/dist/ui/buttons/animated.js +9 -9
- package/dist/ui/buttons/animated.mjs +7 -7
- package/dist/ui/buttons.js +10 -10
- package/dist/ui/buttons.mjs +8 -8
- package/dist/ui/combobox/combobox-base.d.ts.map +1 -1
- package/dist/ui/combobox/variants.d.ts +3 -3
- package/dist/ui/combobox.js +22 -20
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/combobox.mjs +6 -4
- package/dist/ui/combobox.mjs.map +1 -1
- package/dist/ui/command/animated.js +3 -3
- package/dist/ui/command/animated.mjs +2 -2
- package/dist/ui/command.js +16 -16
- package/dist/ui/command.mjs +3 -3
- package/dist/ui/dynamic-stepper.js +19 -19
- package/dist/ui/dynamic-stepper.mjs +8 -8
- package/dist/ui/pagination.js +15 -15
- package/dist/ui/pagination.mjs +7 -7
- package/dist/ui/popover/animated.js +5 -5
- package/dist/ui/popover/animated.mjs +2 -2
- package/dist/ui/popover.js +8 -8
- package/dist/ui/popover.mjs +2 -2
- package/dist/ui/tabs/animated.js +3 -3
- package/dist/ui/tabs/animated.mjs +2 -2
- package/dist/ui/tabs.js +10 -10
- package/dist/ui/tabs.mjs +2 -2
- package/dist/ui/toast/animated.js +8 -8
- package/dist/ui/toast/animated.mjs +2 -2
- package/dist/ui/toast.js +13 -13
- package/dist/ui/toast.mjs +2 -2
- package/package.json +3 -2
- package/src/design-system/combobox.ts +176 -46
- package/src/design-system/command.ts +33 -33
- package/src/design-system/popover.ts +30 -30
- package/src/design-system/tabs.ts +21 -21
- package/src/design-system/toast.ts +21 -21
- package/src/ui/combobox/combobox-base.tsx +5 -3
- package/src/ui/combobox/combobox.test.tsx +69 -0
- package/src/ui/command/command.test.tsx +15 -0
- package/src/ui/popover/popover.test.tsx +15 -0
- package/src/ui/tabs/tabs.test.tsx +15 -0
- package/src/ui/toast/toast.test.tsx +12 -0
- package/dist/chunk-4TX7EQ5Y.js +0 -19
- package/dist/chunk-AOVZY2A3.js.map +0 -1
- package/dist/chunk-AZ26NZJV.mjs.map +0 -1
- package/dist/chunk-K7PR3XXT.mjs +0 -128
- package/dist/chunk-K7PR3XXT.mjs.map +0 -1
- package/dist/chunk-N3FNN47Q.mjs.map +0 -1
- package/dist/chunk-NEFDIJ5N.js.map +0 -1
- package/dist/chunk-QNRJT7R4.js +0 -144
- package/dist/chunk-QNRJT7R4.js.map +0 -1
- package/dist/chunk-XUW42JAP.js.map +0 -1
package/CHANGELOG.md
CHANGED
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 |
|
|
33
|
+
| Tests | 713 passed (713) |
|
|
34
34
|
|
|
35
35
|
| Area | Test files | Tests |
|
|
36
36
|
| --------------------------- | ---------- | ----- |
|
|
37
|
-
| Components and UI utilities | 46 |
|
|
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 |
|
|
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` |
|
|
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/
|
|
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
|
|
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",
|