@annondeveloper/ui-kit 0.1.0
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/LICENSE +21 -0
- package/README.md +215 -0
- package/dist/chunk-5OKSXPWK.js +270 -0
- package/dist/chunk-5OKSXPWK.js.map +1 -0
- package/dist/cli/index.js +430 -0
- package/dist/form.d.ts +65 -0
- package/dist/form.js +148 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +942 -0
- package/dist/index.js +2812 -0
- package/dist/index.js.map +1 -0
- package/dist/select-nnBJUO8U.d.ts +26 -0
- package/package.json +114 -0
- package/src/components/animated-counter.stories.tsx +68 -0
- package/src/components/animated-counter.tsx +85 -0
- package/src/components/avatar.tsx +106 -0
- package/src/components/badge.stories.tsx +70 -0
- package/src/components/badge.tsx +97 -0
- package/src/components/button.stories.tsx +101 -0
- package/src/components/button.tsx +67 -0
- package/src/components/card.tsx +128 -0
- package/src/components/checkbox.stories.tsx +64 -0
- package/src/components/checkbox.tsx +58 -0
- package/src/components/confirm-dialog.stories.tsx +96 -0
- package/src/components/confirm-dialog.tsx +145 -0
- package/src/components/data-table.stories.tsx +125 -0
- package/src/components/data-table.tsx +791 -0
- package/src/components/dropdown-menu.tsx +111 -0
- package/src/components/empty-state.stories.tsx +42 -0
- package/src/components/empty-state.tsx +43 -0
- package/src/components/filter-pill.stories.tsx +71 -0
- package/src/components/filter-pill.tsx +45 -0
- package/src/components/form-input.stories.tsx +91 -0
- package/src/components/form-input.tsx +77 -0
- package/src/components/log-viewer.tsx +212 -0
- package/src/components/metric-card.tsx +141 -0
- package/src/components/pipeline-stage.tsx +134 -0
- package/src/components/popover.tsx +72 -0
- package/src/components/port-status-grid.tsx +102 -0
- package/src/components/progress.tsx +128 -0
- package/src/components/radio-group.tsx +162 -0
- package/src/components/select.stories.tsx +52 -0
- package/src/components/select.tsx +92 -0
- package/src/components/severity-timeline.tsx +125 -0
- package/src/components/sheet.tsx +164 -0
- package/src/components/skeleton.stories.tsx +64 -0
- package/src/components/skeleton.tsx +62 -0
- package/src/components/slider.tsx +208 -0
- package/src/components/sparkline.tsx +104 -0
- package/src/components/status-badge.stories.tsx +84 -0
- package/src/components/status-badge.tsx +71 -0
- package/src/components/status-pulse.stories.tsx +56 -0
- package/src/components/status-pulse.tsx +78 -0
- package/src/components/success-checkmark.stories.tsx +67 -0
- package/src/components/success-checkmark.tsx +53 -0
- package/src/components/tabs.tsx +177 -0
- package/src/components/threshold-gauge.tsx +149 -0
- package/src/components/time-range-selector.tsx +86 -0
- package/src/components/toast.stories.tsx +70 -0
- package/src/components/toast.tsx +48 -0
- package/src/components/toggle-switch.stories.tsx +66 -0
- package/src/components/toggle-switch.tsx +51 -0
- package/src/components/tooltip.tsx +62 -0
- package/src/components/truncated-text.stories.tsx +56 -0
- package/src/components/truncated-text.tsx +80 -0
- package/src/components/uptime-tracker.tsx +138 -0
- package/src/components/utilization-bar.tsx +103 -0
- package/src/theme.css +178 -0
- package/src/utils.ts +123 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs2 from 'fs';
|
|
3
|
+
import * as path2 from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
// src/cli/add.ts
|
|
7
|
+
|
|
8
|
+
// src/cli/registry.ts
|
|
9
|
+
var registry = [
|
|
10
|
+
{
|
|
11
|
+
name: "animated-counter",
|
|
12
|
+
description: "Smoothly animates between numeric values with easing.",
|
|
13
|
+
files: ["components/animated-counter.tsx"],
|
|
14
|
+
dependencies: ["framer-motion"],
|
|
15
|
+
internalDeps: ["utils"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "badge",
|
|
19
|
+
description: "Themed badge with color presets, optional icon, and custom variant support.",
|
|
20
|
+
files: ["components/badge.tsx"],
|
|
21
|
+
dependencies: ["lucide-react"],
|
|
22
|
+
internalDeps: ["utils"]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "button",
|
|
26
|
+
description: "Polymorphic button with variant, size, loading, and icon support.",
|
|
27
|
+
files: ["components/button.tsx"],
|
|
28
|
+
dependencies: ["lucide-react"],
|
|
29
|
+
internalDeps: ["utils"]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "checkbox",
|
|
33
|
+
description: "Themed checkbox with indeterminate state support.",
|
|
34
|
+
files: ["components/checkbox.tsx"],
|
|
35
|
+
dependencies: ["lucide-react"],
|
|
36
|
+
internalDeps: ["utils"]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "confirm-dialog",
|
|
40
|
+
description: "Accessible confirmation dialog built on Radix AlertDialog.",
|
|
41
|
+
files: ["components/confirm-dialog.tsx"],
|
|
42
|
+
dependencies: ["@radix-ui/react-alert-dialog", "framer-motion", "lucide-react"],
|
|
43
|
+
internalDeps: ["utils"]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "data-table",
|
|
47
|
+
description: "Full-featured data table with sorting, filtering, pagination, and density control.",
|
|
48
|
+
files: ["components/data-table.tsx"],
|
|
49
|
+
dependencies: ["@tanstack/react-table", "framer-motion", "lucide-react"],
|
|
50
|
+
internalDeps: ["utils", "truncated-text", "empty-state", "skeleton"]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "dropdown-menu",
|
|
54
|
+
description: "Animated dropdown menu built on Radix DropdownMenu.",
|
|
55
|
+
files: ["components/dropdown-menu.tsx"],
|
|
56
|
+
dependencies: ["@radix-ui/react-dropdown-menu", "framer-motion", "lucide-react"],
|
|
57
|
+
internalDeps: ["utils"]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "empty-state",
|
|
61
|
+
description: "Decorative empty-state placeholder with icon, title, and optional actions.",
|
|
62
|
+
files: ["components/empty-state.tsx"],
|
|
63
|
+
dependencies: ["lucide-react"],
|
|
64
|
+
internalDeps: ["utils"]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "filter-pill",
|
|
68
|
+
description: "Rounded pill-style filter toggle with active state and optional count.",
|
|
69
|
+
files: ["components/filter-pill.tsx"],
|
|
70
|
+
dependencies: [],
|
|
71
|
+
internalDeps: ["utils"]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "form-input",
|
|
75
|
+
description: "Themed text input with label, error, and helper text. Exports shared class constants.",
|
|
76
|
+
files: ["components/form-input.tsx"],
|
|
77
|
+
dependencies: [],
|
|
78
|
+
internalDeps: ["utils"]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "metric-card",
|
|
82
|
+
description: "KPI card with animated counter, sparkline, and trend indicator.",
|
|
83
|
+
files: ["components/metric-card.tsx"],
|
|
84
|
+
dependencies: ["framer-motion", "lucide-react"],
|
|
85
|
+
internalDeps: ["utils", "animated-counter", "sparkline"]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "select",
|
|
89
|
+
description: "Themed select dropdown built on Radix Select.",
|
|
90
|
+
files: ["components/select.tsx"],
|
|
91
|
+
dependencies: ["@radix-ui/react-select", "lucide-react"],
|
|
92
|
+
internalDeps: ["utils"]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "severity-timeline",
|
|
96
|
+
description: "Vertical timeline with severity-colored dots and expandable detail.",
|
|
97
|
+
files: ["components/severity-timeline.tsx"],
|
|
98
|
+
dependencies: ["framer-motion"],
|
|
99
|
+
internalDeps: ["utils"]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "sheet",
|
|
103
|
+
description: "Slide-over panel from the right edge with backdrop and keyboard dismiss.",
|
|
104
|
+
files: ["components/sheet.tsx"],
|
|
105
|
+
dependencies: ["framer-motion", "lucide-react"],
|
|
106
|
+
internalDeps: ["utils"]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "skeleton",
|
|
110
|
+
description: "Shimmer skeleton loaders (block, text lines, card).",
|
|
111
|
+
files: ["components/skeleton.tsx"],
|
|
112
|
+
dependencies: [],
|
|
113
|
+
internalDeps: ["utils"]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "sparkline",
|
|
117
|
+
description: "Minimal SVG sparkline chart for inline metric trends.",
|
|
118
|
+
files: ["components/sparkline.tsx"],
|
|
119
|
+
dependencies: [],
|
|
120
|
+
internalDeps: ["utils"]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "status-badge",
|
|
124
|
+
description: "Status indicator with colored dot, label, and configurable status map.",
|
|
125
|
+
files: ["components/status-badge.tsx"],
|
|
126
|
+
dependencies: [],
|
|
127
|
+
internalDeps: ["utils"]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "status-pulse",
|
|
131
|
+
description: "Animated pulsing status dot with configurable colors.",
|
|
132
|
+
files: ["components/status-pulse.tsx"],
|
|
133
|
+
dependencies: ["framer-motion"],
|
|
134
|
+
internalDeps: ["utils"]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "success-checkmark",
|
|
138
|
+
description: "Animated SVG checkmark for success confirmations.",
|
|
139
|
+
files: ["components/success-checkmark.tsx"],
|
|
140
|
+
dependencies: ["framer-motion"],
|
|
141
|
+
internalDeps: ["utils"]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "tabs",
|
|
145
|
+
description: "Animated tab bar with optional icons and keyboard navigation.",
|
|
146
|
+
files: ["components/tabs.tsx"],
|
|
147
|
+
dependencies: ["framer-motion", "lucide-react"],
|
|
148
|
+
internalDeps: ["utils"]
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "threshold-gauge",
|
|
152
|
+
description: "SVG arc gauge with warning/critical threshold coloring.",
|
|
153
|
+
files: ["components/threshold-gauge.tsx"],
|
|
154
|
+
dependencies: ["framer-motion"],
|
|
155
|
+
internalDeps: ["utils"]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "toast",
|
|
159
|
+
description: "Themed toast notifications powered by Sonner.",
|
|
160
|
+
files: ["components/toast.tsx"],
|
|
161
|
+
dependencies: ["sonner"],
|
|
162
|
+
internalDeps: []
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "toggle-switch",
|
|
166
|
+
description: "Toggle switch with on/off icons and themed styling.",
|
|
167
|
+
files: ["components/toggle-switch.tsx"],
|
|
168
|
+
dependencies: ["lucide-react"],
|
|
169
|
+
internalDeps: ["utils"]
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "truncated-text",
|
|
173
|
+
description: "Text that truncates with ellipsis and shows full content in a tooltip.",
|
|
174
|
+
files: ["components/truncated-text.tsx"],
|
|
175
|
+
dependencies: ["@radix-ui/react-tooltip", "lucide-react"],
|
|
176
|
+
internalDeps: []
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "utilization-bar",
|
|
180
|
+
description: "Horizontal bar showing utilization percentage with threshold coloring.",
|
|
181
|
+
files: ["components/utilization-bar.tsx"],
|
|
182
|
+
dependencies: ["framer-motion"],
|
|
183
|
+
internalDeps: ["utils"]
|
|
184
|
+
}
|
|
185
|
+
];
|
|
186
|
+
var UTILS_ENTRY = {
|
|
187
|
+
name: "utils",
|
|
188
|
+
description: "Shared utility functions (cn, formatters, color helpers).",
|
|
189
|
+
files: ["utils.ts"],
|
|
190
|
+
dependencies: ["clsx", "tailwind-merge"],
|
|
191
|
+
internalDeps: []
|
|
192
|
+
};
|
|
193
|
+
function findComponent(name) {
|
|
194
|
+
if (name === "utils") return UTILS_ENTRY;
|
|
195
|
+
return registry.find((c) => c.name === name);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/cli/add.ts
|
|
199
|
+
var __dirname$1 = path2.dirname(fileURLToPath(import.meta.url));
|
|
200
|
+
function pkgSrc(rel) {
|
|
201
|
+
return path2.resolve(__dirname$1, "..", "..", "src", rel);
|
|
202
|
+
}
|
|
203
|
+
function readConfig() {
|
|
204
|
+
const configPath = path2.join(process.cwd(), ".ui-kit.json");
|
|
205
|
+
if (!fs2.existsSync(configPath)) return null;
|
|
206
|
+
return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
207
|
+
}
|
|
208
|
+
function writeConfig(config) {
|
|
209
|
+
const configPath = path2.join(process.cwd(), ".ui-kit.json");
|
|
210
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
211
|
+
}
|
|
212
|
+
function rewriteImports(content) {
|
|
213
|
+
return content.replace(/from\s+['"]\.\.\/utils['"]/g, "from './utils'");
|
|
214
|
+
}
|
|
215
|
+
function collectDeps(name, seen = /* @__PURE__ */ new Set()) {
|
|
216
|
+
if (seen.has(name)) return [];
|
|
217
|
+
seen.add(name);
|
|
218
|
+
const comp = findComponent(name);
|
|
219
|
+
if (!comp) return [];
|
|
220
|
+
const deps = [];
|
|
221
|
+
for (const dep of comp.internalDeps) {
|
|
222
|
+
deps.push(...collectDeps(dep, seen));
|
|
223
|
+
deps.push(dep);
|
|
224
|
+
}
|
|
225
|
+
return deps;
|
|
226
|
+
}
|
|
227
|
+
function add(componentName, opts) {
|
|
228
|
+
const comp = findComponent(componentName);
|
|
229
|
+
if (!comp) {
|
|
230
|
+
console.error(` Error: Unknown component "${componentName}".`);
|
|
231
|
+
console.error(` Run "npx @annondeveloper/ui-kit list" to see available components.`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
const projectRoot = process.cwd();
|
|
235
|
+
const config = readConfig() || { componentDir: opts.dir, installed: [] };
|
|
236
|
+
const targetDir = path2.resolve(projectRoot, config.componentDir || opts.dir);
|
|
237
|
+
fs2.mkdirSync(targetDir, { recursive: true });
|
|
238
|
+
const allDeps = collectDeps(componentName);
|
|
239
|
+
const toInstall = [];
|
|
240
|
+
for (const depName of allDeps) {
|
|
241
|
+
if (!config.installed.includes(depName)) {
|
|
242
|
+
const dep = findComponent(depName);
|
|
243
|
+
if (dep) toInstall.push(dep);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (!config.installed.includes(componentName) || opts.overwrite) {
|
|
247
|
+
toInstall.push(comp);
|
|
248
|
+
} else {
|
|
249
|
+
console.log(` [skip] ${componentName} already installed (use --overwrite)`);
|
|
250
|
+
if (toInstall.length === 0) return;
|
|
251
|
+
}
|
|
252
|
+
const allNpmDeps = /* @__PURE__ */ new Set();
|
|
253
|
+
for (const entry of toInstall) {
|
|
254
|
+
for (const file of entry.files) {
|
|
255
|
+
const src = pkgSrc(file);
|
|
256
|
+
const dst = path2.join(targetDir, path2.basename(file));
|
|
257
|
+
if (fs2.existsSync(dst) && !opts.overwrite && config.installed.includes(entry.name)) {
|
|
258
|
+
console.log(` [skip] ${path2.relative(projectRoot, dst)} (already exists)`);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!fs2.existsSync(src)) {
|
|
262
|
+
console.error(` [error] Source file not found: ${src}`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
let content = fs2.readFileSync(src, "utf-8");
|
|
266
|
+
content = rewriteImports(content);
|
|
267
|
+
fs2.writeFileSync(dst, content);
|
|
268
|
+
const label = entry.name === componentName ? "copy" : "dep ";
|
|
269
|
+
console.log(` [${label}] ${path2.relative(projectRoot, dst)}`);
|
|
270
|
+
}
|
|
271
|
+
for (const dep of entry.dependencies) {
|
|
272
|
+
allNpmDeps.add(dep);
|
|
273
|
+
}
|
|
274
|
+
if (!config.installed.includes(entry.name)) {
|
|
275
|
+
config.installed.push(entry.name);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
writeConfig(config);
|
|
279
|
+
if (allNpmDeps.size > 0) {
|
|
280
|
+
console.log("");
|
|
281
|
+
console.log(" Required npm dependencies:");
|
|
282
|
+
console.log("");
|
|
283
|
+
console.log(` npm install ${[...allNpmDeps].join(" ")}`);
|
|
284
|
+
console.log("");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
var __dirname2 = path2.dirname(fileURLToPath(import.meta.url));
|
|
288
|
+
function pkgSrc2(rel) {
|
|
289
|
+
return path2.resolve(__dirname2, "..", "..", "src", rel);
|
|
290
|
+
}
|
|
291
|
+
function init(opts) {
|
|
292
|
+
const projectRoot = process.cwd();
|
|
293
|
+
const targetDir = path2.resolve(projectRoot, opts.dir);
|
|
294
|
+
const stylesDir = path2.resolve(projectRoot, "styles");
|
|
295
|
+
fs2.mkdirSync(targetDir, { recursive: true });
|
|
296
|
+
fs2.mkdirSync(stylesDir, { recursive: true });
|
|
297
|
+
const themeSrc = pkgSrc2("theme.css");
|
|
298
|
+
const themeDst = path2.join(stylesDir, "ui-kit-theme.css");
|
|
299
|
+
if (fs2.existsSync(themeDst) && !opts.overwrite) {
|
|
300
|
+
console.log(` [skip] ${path2.relative(projectRoot, themeDst)} already exists (use --overwrite)`);
|
|
301
|
+
} else {
|
|
302
|
+
fs2.copyFileSync(themeSrc, themeDst);
|
|
303
|
+
console.log(` [copy] ${path2.relative(projectRoot, themeDst)}`);
|
|
304
|
+
}
|
|
305
|
+
const utilsSrc = pkgSrc2("utils.ts");
|
|
306
|
+
const utilsDst = path2.join(targetDir, "utils.ts");
|
|
307
|
+
if (fs2.existsSync(utilsDst) && !opts.overwrite) {
|
|
308
|
+
console.log(` [skip] ${path2.relative(projectRoot, utilsDst)} already exists (use --overwrite)`);
|
|
309
|
+
} else {
|
|
310
|
+
fs2.copyFileSync(utilsSrc, utilsDst);
|
|
311
|
+
console.log(` [copy] ${path2.relative(projectRoot, utilsDst)}`);
|
|
312
|
+
}
|
|
313
|
+
const configPath = path2.join(projectRoot, ".ui-kit.json");
|
|
314
|
+
const config = fs2.existsSync(configPath) ? JSON.parse(fs2.readFileSync(configPath, "utf-8")) : { componentDir: opts.dir, installed: [] };
|
|
315
|
+
config.componentDir = opts.dir;
|
|
316
|
+
if (!config.installed.includes("utils")) {
|
|
317
|
+
config.installed.push("utils");
|
|
318
|
+
}
|
|
319
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
320
|
+
console.log(` [write] .ui-kit.json`);
|
|
321
|
+
console.log("");
|
|
322
|
+
console.log(" Setup complete! Next steps:");
|
|
323
|
+
console.log("");
|
|
324
|
+
console.log(" 1. Import the theme in your root layout or global CSS:");
|
|
325
|
+
console.log("");
|
|
326
|
+
console.log(" @import '../styles/ui-kit-theme.css';");
|
|
327
|
+
console.log("");
|
|
328
|
+
console.log(" 2. Install required peer dependencies:");
|
|
329
|
+
console.log("");
|
|
330
|
+
console.log(" npm install clsx tailwind-merge");
|
|
331
|
+
console.log("");
|
|
332
|
+
console.log(" 3. Add components:");
|
|
333
|
+
console.log("");
|
|
334
|
+
console.log(" npx @annondeveloper/ui-kit add button");
|
|
335
|
+
console.log(" npx @annondeveloper/ui-kit add data-table");
|
|
336
|
+
console.log("");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/cli/list.ts
|
|
340
|
+
function listComponents() {
|
|
341
|
+
console.log("");
|
|
342
|
+
console.log(" Available components");
|
|
343
|
+
console.log(" " + "=".repeat(70));
|
|
344
|
+
console.log("");
|
|
345
|
+
const maxName = Math.max(...registry.map((c) => c.name.length));
|
|
346
|
+
for (const comp of registry) {
|
|
347
|
+
const name = comp.name.padEnd(maxName + 2);
|
|
348
|
+
const deps = comp.internalDeps.length > 0 ? ` [deps: ${comp.internalDeps.join(", ")}]` : "";
|
|
349
|
+
console.log(` ${name} ${comp.description}${deps}`);
|
|
350
|
+
}
|
|
351
|
+
console.log("");
|
|
352
|
+
console.log(` ${registry.length} components available`);
|
|
353
|
+
console.log("");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/cli/index.ts
|
|
357
|
+
var HELP = `
|
|
358
|
+
@annondeveloper/ui-kit CLI
|
|
359
|
+
|
|
360
|
+
Usage: npx @annondeveloper/ui-kit <command> [options]
|
|
361
|
+
|
|
362
|
+
Commands:
|
|
363
|
+
init Set up ui-kit in your project (copies theme.css + utils.ts)
|
|
364
|
+
add <component> Add a component to your project
|
|
365
|
+
list List all available components
|
|
366
|
+
|
|
367
|
+
Options:
|
|
368
|
+
--dir <path> Target directory (default: ./components/ui)
|
|
369
|
+
--overwrite Overwrite existing files
|
|
370
|
+
--help, -h Show this help message
|
|
371
|
+
`;
|
|
372
|
+
function parseArgs(argv) {
|
|
373
|
+
const args = argv.slice(2);
|
|
374
|
+
const command = args[0] || "help";
|
|
375
|
+
const positional = [];
|
|
376
|
+
const flags = {};
|
|
377
|
+
for (let i = 1; i < args.length; i++) {
|
|
378
|
+
const arg = args[i];
|
|
379
|
+
if (arg === "--help" || arg === "-h") {
|
|
380
|
+
flags.help = true;
|
|
381
|
+
} else if (arg === "--overwrite") {
|
|
382
|
+
flags.overwrite = true;
|
|
383
|
+
} else if (arg === "--dir" && i + 1 < args.length) {
|
|
384
|
+
flags.dir = args[++i];
|
|
385
|
+
} else if (!arg.startsWith("-")) {
|
|
386
|
+
positional.push(arg);
|
|
387
|
+
} else {
|
|
388
|
+
console.error(` Unknown option: ${arg}`);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return { command, positional, flags };
|
|
393
|
+
}
|
|
394
|
+
function main() {
|
|
395
|
+
const { command, positional, flags } = parseArgs(process.argv);
|
|
396
|
+
if (flags.help || command === "help" || command === "--help" || command === "-h") {
|
|
397
|
+
console.log(HELP);
|
|
398
|
+
process.exit(0);
|
|
399
|
+
}
|
|
400
|
+
const dir = flags.dir || "./components/ui";
|
|
401
|
+
const overwrite = !!flags.overwrite;
|
|
402
|
+
switch (command) {
|
|
403
|
+
case "init":
|
|
404
|
+
console.log("");
|
|
405
|
+
console.log(" Initializing @annondeveloper/ui-kit...");
|
|
406
|
+
console.log("");
|
|
407
|
+
init({ dir, overwrite });
|
|
408
|
+
break;
|
|
409
|
+
case "add": {
|
|
410
|
+
const name = positional[0];
|
|
411
|
+
if (!name) {
|
|
412
|
+
console.error(" Error: Please specify a component name.");
|
|
413
|
+
console.error(" Usage: npx @annondeveloper/ui-kit add <component>");
|
|
414
|
+
console.error(' Run "npx @annondeveloper/ui-kit list" to see available components.');
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
console.log("");
|
|
418
|
+
add(name, { dir, overwrite });
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case "list":
|
|
422
|
+
listComponents();
|
|
423
|
+
break;
|
|
424
|
+
default:
|
|
425
|
+
console.error(` Unknown command: ${command}`);
|
|
426
|
+
console.log(HELP);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
main();
|
package/dist/form.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { FieldValues, Control, Path, RegisterOptions } from 'react-hook-form';
|
|
3
|
+
import { S as SelectOption } from './select-nnBJUO8U.js';
|
|
4
|
+
|
|
5
|
+
interface RHFFormInputProps<T extends FieldValues> {
|
|
6
|
+
control: Control<T>;
|
|
7
|
+
name: Path<T>;
|
|
8
|
+
rules?: RegisterOptions<T, Path<T>>;
|
|
9
|
+
label: string;
|
|
10
|
+
type?: string;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
hint?: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
autoComplete?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* FormInput wrapper for react-hook-form.
|
|
20
|
+
* Shows validation errors from fieldState.error automatically.
|
|
21
|
+
*/
|
|
22
|
+
declare function RHFFormInput<T extends FieldValues>({ control, name, rules, label, type, placeholder, required, disabled, hint, className, autoComplete, }: RHFFormInputProps<T>): react_jsx_runtime.JSX.Element;
|
|
23
|
+
interface RHFSelectProps<T extends FieldValues> {
|
|
24
|
+
control: Control<T>;
|
|
25
|
+
name: Path<T>;
|
|
26
|
+
rules?: RegisterOptions<T, Path<T>>;
|
|
27
|
+
options: SelectOption[];
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
className?: string;
|
|
31
|
+
label?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Select wrapper for react-hook-form.
|
|
35
|
+
* Shows validation errors from fieldState.error automatically.
|
|
36
|
+
*/
|
|
37
|
+
declare function RHFSelect<T extends FieldValues>({ control, name, rules, options, placeholder, disabled, className, label, }: RHFSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
38
|
+
interface RHFCheckboxProps<T extends FieldValues> {
|
|
39
|
+
control: Control<T>;
|
|
40
|
+
name: Path<T>;
|
|
41
|
+
rules?: RegisterOptions<T, Path<T>>;
|
|
42
|
+
label?: string;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Checkbox wrapper for react-hook-form.
|
|
48
|
+
* Shows validation errors from fieldState.error automatically.
|
|
49
|
+
*/
|
|
50
|
+
declare function RHFCheckbox<T extends FieldValues>({ control, name, rules, label, disabled, className, }: RHFCheckboxProps<T>): react_jsx_runtime.JSX.Element;
|
|
51
|
+
interface RHFToggleSwitchProps<T extends FieldValues> {
|
|
52
|
+
control: Control<T>;
|
|
53
|
+
name: Path<T>;
|
|
54
|
+
rules?: RegisterOptions<T, Path<T>>;
|
|
55
|
+
label?: string;
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
className?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* ToggleSwitch wrapper for react-hook-form.
|
|
61
|
+
* Shows validation errors from fieldState.error automatically.
|
|
62
|
+
*/
|
|
63
|
+
declare function RHFToggleSwitch<T extends FieldValues>({ control, name, rules, label, disabled, className, }: RHFToggleSwitchProps<T>): react_jsx_runtime.JSX.Element;
|
|
64
|
+
|
|
65
|
+
export { RHFCheckbox, type RHFCheckboxProps, RHFFormInput, type RHFFormInputProps, RHFSelect, type RHFSelectProps, RHFToggleSwitch, type RHFToggleSwitchProps };
|
package/dist/form.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { FormInput, cn, Select, Checkbox, ToggleSwitch } from './chunk-5OKSXPWK.js';
|
|
2
|
+
import { Controller } from 'react-hook-form';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
function FieldError({ message }) {
|
|
6
|
+
if (!message) return null;
|
|
7
|
+
return /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-[hsl(var(--status-critical))]", children: message });
|
|
8
|
+
}
|
|
9
|
+
function RHFFormInput({
|
|
10
|
+
control,
|
|
11
|
+
name,
|
|
12
|
+
rules,
|
|
13
|
+
label,
|
|
14
|
+
type = "text",
|
|
15
|
+
placeholder,
|
|
16
|
+
required,
|
|
17
|
+
disabled,
|
|
18
|
+
hint,
|
|
19
|
+
className,
|
|
20
|
+
autoComplete
|
|
21
|
+
}) {
|
|
22
|
+
return /* @__PURE__ */ jsx(
|
|
23
|
+
Controller,
|
|
24
|
+
{
|
|
25
|
+
control,
|
|
26
|
+
name,
|
|
27
|
+
rules,
|
|
28
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs("div", { className: cn("space-y-1.5", className), children: [
|
|
29
|
+
/* @__PURE__ */ jsx(
|
|
30
|
+
FormInput,
|
|
31
|
+
{
|
|
32
|
+
label,
|
|
33
|
+
value: field.value ?? "",
|
|
34
|
+
onChange: field.onChange,
|
|
35
|
+
type,
|
|
36
|
+
placeholder,
|
|
37
|
+
required,
|
|
38
|
+
disabled,
|
|
39
|
+
hint: fieldState.error ? void 0 : hint,
|
|
40
|
+
autoComplete
|
|
41
|
+
}
|
|
42
|
+
),
|
|
43
|
+
/* @__PURE__ */ jsx(FieldError, { message: fieldState.error?.message })
|
|
44
|
+
] })
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
function RHFSelect({
|
|
49
|
+
control,
|
|
50
|
+
name,
|
|
51
|
+
rules,
|
|
52
|
+
options,
|
|
53
|
+
placeholder,
|
|
54
|
+
disabled,
|
|
55
|
+
className,
|
|
56
|
+
label
|
|
57
|
+
}) {
|
|
58
|
+
return /* @__PURE__ */ jsx(
|
|
59
|
+
Controller,
|
|
60
|
+
{
|
|
61
|
+
control,
|
|
62
|
+
name,
|
|
63
|
+
rules,
|
|
64
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs("div", { className: cn("space-y-1.5", className), children: [
|
|
65
|
+
label && /* @__PURE__ */ jsx("label", { className: "mb-1.5 block text-xs font-medium uppercase tracking-wider text-[hsl(var(--text-secondary))]", children: label }),
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
Select,
|
|
68
|
+
{
|
|
69
|
+
value: field.value ?? "",
|
|
70
|
+
onValueChange: field.onChange,
|
|
71
|
+
options,
|
|
72
|
+
placeholder,
|
|
73
|
+
disabled,
|
|
74
|
+
className: fieldState.error ? "border-[hsl(var(--status-critical))]" : void 0
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
/* @__PURE__ */ jsx(FieldError, { message: fieldState.error?.message })
|
|
78
|
+
] })
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
function RHFCheckbox({
|
|
83
|
+
control,
|
|
84
|
+
name,
|
|
85
|
+
rules,
|
|
86
|
+
label,
|
|
87
|
+
disabled,
|
|
88
|
+
className
|
|
89
|
+
}) {
|
|
90
|
+
return /* @__PURE__ */ jsx(
|
|
91
|
+
Controller,
|
|
92
|
+
{
|
|
93
|
+
control,
|
|
94
|
+
name,
|
|
95
|
+
rules,
|
|
96
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs("div", { className: cn("space-y-1", className), children: [
|
|
97
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
98
|
+
/* @__PURE__ */ jsx(
|
|
99
|
+
Checkbox,
|
|
100
|
+
{
|
|
101
|
+
checked: !!field.value,
|
|
102
|
+
onChange: (e) => field.onChange(e.target.checked),
|
|
103
|
+
disabled
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
label && /* @__PURE__ */ jsx("span", { className: "text-sm text-[hsl(var(--text-primary))]", children: label })
|
|
107
|
+
] }),
|
|
108
|
+
/* @__PURE__ */ jsx(FieldError, { message: fieldState.error?.message })
|
|
109
|
+
] })
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
function RHFToggleSwitch({
|
|
114
|
+
control,
|
|
115
|
+
name,
|
|
116
|
+
rules,
|
|
117
|
+
label,
|
|
118
|
+
disabled,
|
|
119
|
+
className
|
|
120
|
+
}) {
|
|
121
|
+
return /* @__PURE__ */ jsx(
|
|
122
|
+
Controller,
|
|
123
|
+
{
|
|
124
|
+
control,
|
|
125
|
+
name,
|
|
126
|
+
rules,
|
|
127
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs("div", { className: cn("space-y-1", className), children: [
|
|
128
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
129
|
+
/* @__PURE__ */ jsx(
|
|
130
|
+
ToggleSwitch,
|
|
131
|
+
{
|
|
132
|
+
enabled: !!field.value,
|
|
133
|
+
onChange: field.onChange,
|
|
134
|
+
disabled,
|
|
135
|
+
label
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
label && /* @__PURE__ */ jsx("span", { className: "text-sm text-[hsl(var(--text-primary))]", children: label })
|
|
139
|
+
] }),
|
|
140
|
+
/* @__PURE__ */ jsx(FieldError, { message: fieldState.error?.message })
|
|
141
|
+
] })
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { RHFCheckbox, RHFFormInput, RHFSelect, RHFToggleSwitch };
|
|
147
|
+
//# sourceMappingURL=form.js.map
|
|
148
|
+
//# sourceMappingURL=form.js.map
|
package/dist/form.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/form-integration.tsx"],"names":[],"mappings":"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","file":"form.js","sourcesContent":["'use client'\n\nimport { Controller, type Control, type FieldValues, type Path, type RegisterOptions } from 'react-hook-form'\nimport { FormInput, INPUT_CLS } from './components/form-input'\nimport { Select, type SelectOption } from './components/select'\nimport { Checkbox } from './components/checkbox'\nimport { ToggleSwitch } from './components/toggle-switch'\nimport { cn } from './utils'\n\n// ── Shared error display ──────────────────────────────────────────────────\n\nfunction FieldError({ message }: { message?: string }) {\n if (!message) return null\n return (\n <p className=\"mt-1 text-xs text-[hsl(var(--status-critical))]\">{message}</p>\n )\n}\n\n// ── RHFFormInput ──────────────────────────────────────────────────────────\n\nexport interface RHFFormInputProps<T extends FieldValues> {\n control: Control<T>\n name: Path<T>\n rules?: RegisterOptions<T, Path<T>>\n label: string\n type?: string\n placeholder?: string\n required?: boolean\n disabled?: boolean\n hint?: string\n className?: string\n autoComplete?: string\n}\n\n/**\n * FormInput wrapper for react-hook-form.\n * Shows validation errors from fieldState.error automatically.\n */\nexport function RHFFormInput<T extends FieldValues>({\n control, name, rules, label, type = 'text',\n placeholder, required, disabled, hint, className, autoComplete,\n}: RHFFormInputProps<T>) {\n return (\n <Controller\n control={control}\n name={name}\n rules={rules}\n render={({ field, fieldState }) => (\n <div className={cn('space-y-1.5', className)}>\n <FormInput\n label={label}\n value={field.value ?? ''}\n onChange={field.onChange}\n type={type}\n placeholder={placeholder}\n required={required}\n disabled={disabled}\n hint={fieldState.error ? undefined : hint}\n autoComplete={autoComplete}\n />\n <FieldError message={fieldState.error?.message} />\n </div>\n )}\n />\n )\n}\n\n// ── RHFSelect ─────────────────────────────────────────────────────────────\n\nexport interface RHFSelectProps<T extends FieldValues> {\n control: Control<T>\n name: Path<T>\n rules?: RegisterOptions<T, Path<T>>\n options: SelectOption[]\n placeholder?: string\n disabled?: boolean\n className?: string\n label?: string\n}\n\n/**\n * Select wrapper for react-hook-form.\n * Shows validation errors from fieldState.error automatically.\n */\nexport function RHFSelect<T extends FieldValues>({\n control, name, rules, options, placeholder, disabled, className, label,\n}: RHFSelectProps<T>) {\n return (\n <Controller\n control={control}\n name={name}\n rules={rules}\n render={({ field, fieldState }) => (\n <div className={cn('space-y-1.5', className)}>\n {label && (\n <label className=\"mb-1.5 block text-xs font-medium uppercase tracking-wider text-[hsl(var(--text-secondary))]\">\n {label}\n </label>\n )}\n <Select\n value={field.value ?? ''}\n onValueChange={field.onChange}\n options={options}\n placeholder={placeholder}\n disabled={disabled}\n className={fieldState.error ? 'border-[hsl(var(--status-critical))]' : undefined}\n />\n <FieldError message={fieldState.error?.message} />\n </div>\n )}\n />\n )\n}\n\n// ── RHFCheckbox ───────────────────────────────────────────────────────────\n\nexport interface RHFCheckboxProps<T extends FieldValues> {\n control: Control<T>\n name: Path<T>\n rules?: RegisterOptions<T, Path<T>>\n label?: string\n disabled?: boolean\n className?: string\n}\n\n/**\n * Checkbox wrapper for react-hook-form.\n * Shows validation errors from fieldState.error automatically.\n */\nexport function RHFCheckbox<T extends FieldValues>({\n control, name, rules, label, disabled, className,\n}: RHFCheckboxProps<T>) {\n return (\n <Controller\n control={control}\n name={name}\n rules={rules}\n render={({ field, fieldState }) => (\n <div className={cn('space-y-1', className)}>\n <div className=\"flex items-center gap-2\">\n <Checkbox\n checked={!!field.value}\n onChange={(e) => field.onChange(e.target.checked)}\n disabled={disabled}\n />\n {label && (\n <span className=\"text-sm text-[hsl(var(--text-primary))]\">{label}</span>\n )}\n </div>\n <FieldError message={fieldState.error?.message} />\n </div>\n )}\n />\n )\n}\n\n// ── RHFToggleSwitch ───────────────────────────────────────────────────────\n\nexport interface RHFToggleSwitchProps<T extends FieldValues> {\n control: Control<T>\n name: Path<T>\n rules?: RegisterOptions<T, Path<T>>\n label?: string\n disabled?: boolean\n className?: string\n}\n\n/**\n * ToggleSwitch wrapper for react-hook-form.\n * Shows validation errors from fieldState.error automatically.\n */\nexport function RHFToggleSwitch<T extends FieldValues>({\n control, name, rules, label, disabled, className,\n}: RHFToggleSwitchProps<T>) {\n return (\n <Controller\n control={control}\n name={name}\n rules={rules}\n render={({ field, fieldState }) => (\n <div className={cn('space-y-1', className)}>\n <div className=\"flex items-center gap-2\">\n <ToggleSwitch\n enabled={!!field.value}\n onChange={field.onChange}\n disabled={disabled}\n label={label}\n />\n {label && (\n <span className=\"text-sm text-[hsl(var(--text-primary))]\">{label}</span>\n )}\n </div>\n <FieldError message={fieldState.error?.message} />\n </div>\n )}\n />\n )\n}\n"]}
|