cooterlabs 1.0.2 → 2.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/README.md +228 -45
- package/bin/index.js +376 -22
- package/package.json +12 -4
- package/templates/_lib/utils.ts +28 -0
- package/templates/action-sheet/action-sheet.tsx +108 -0
- package/templates/alert/alert.tsx +146 -0
- package/templates/avatar/avatar.tsx +20 -8
- package/templates/badge/badge.tsx +114 -25
- package/templates/breadcrumb/breadcrumb.tsx +108 -0
- package/templates/button/button.tsx +39 -6
- package/templates/button-group/button-group.tsx +88 -0
- package/templates/card/card.tsx +31 -24
- package/templates/checkbox/checkbox.tsx +5 -9
- package/templates/context-menu/context-menu.tsx +172 -0
- package/templates/dialog/dialog.tsx +122 -0
- package/templates/dropdown/dropdown.tsx +175 -0
- package/templates/input/input.tsx +80 -14
- package/templates/label/label.tsx +2 -7
- package/templates/list/list.tsx +91 -0
- package/templates/loader/loader.tsx +33 -0
- package/templates/navbar-bottom/navbar-bottom.tsx +71 -0
- package/templates/navbar-top/navbar-top.tsx +79 -0
- package/templates/page-control/page-control.tsx +50 -0
- package/templates/pagination/pagination.tsx +104 -0
- package/templates/progress/progress.tsx +64 -0
- package/templates/radio/radio.tsx +36 -0
- package/templates/select/select.tsx +113 -0
- package/templates/stepper/stepper.tsx +88 -0
- package/templates/tabs/tabs.tsx +52 -0
- package/templates/textarea/textarea.tsx +2 -7
- package/templates/toggle/toggle.tsx +22 -0
- package/templates/tooltip/tooltip.tsx +32 -0
package/bin/index.js
CHANGED
|
@@ -14,6 +14,116 @@ const __dirname = path.dirname(__filename);
|
|
|
14
14
|
const cwd = process.cwd();
|
|
15
15
|
|
|
16
16
|
const REGISTRY = {
|
|
17
|
+
alert: {
|
|
18
|
+
name: "Alert",
|
|
19
|
+
dependencies: ["lucide-react", "clsx", "tailwind-merge"],
|
|
20
|
+
dev_dependencies: [],
|
|
21
|
+
},
|
|
22
|
+
"action-sheet": {
|
|
23
|
+
name: "ActionSheet",
|
|
24
|
+
dependencies: ["@radix-ui/react-dialog", "clsx", "tailwind-merge"],
|
|
25
|
+
dev_dependencies: [],
|
|
26
|
+
},
|
|
27
|
+
breadcrumb: {
|
|
28
|
+
name: "Breadcrumb",
|
|
29
|
+
dependencies: ["lucide-react", "clsx", "tailwind-merge"],
|
|
30
|
+
dev_dependencies: [],
|
|
31
|
+
},
|
|
32
|
+
"button-group": {
|
|
33
|
+
name: "ButtonGroup",
|
|
34
|
+
dependencies: ["clsx", "tailwind-merge"],
|
|
35
|
+
dev_dependencies: [],
|
|
36
|
+
},
|
|
37
|
+
"context-menu": {
|
|
38
|
+
name: "ContextMenu",
|
|
39
|
+
dependencies: [
|
|
40
|
+
"@radix-ui/react-context-menu",
|
|
41
|
+
"lucide-react",
|
|
42
|
+
"clsx",
|
|
43
|
+
"tailwind-merge",
|
|
44
|
+
],
|
|
45
|
+
dev_dependencies: [],
|
|
46
|
+
},
|
|
47
|
+
dialog: {
|
|
48
|
+
name: "Dialog",
|
|
49
|
+
dependencies: [
|
|
50
|
+
"@radix-ui/react-dialog",
|
|
51
|
+
"lucide-react",
|
|
52
|
+
"clsx",
|
|
53
|
+
"tailwind-merge",
|
|
54
|
+
],
|
|
55
|
+
dev_dependencies: [],
|
|
56
|
+
},
|
|
57
|
+
dropdown: {
|
|
58
|
+
name: "Dropdown",
|
|
59
|
+
dependencies: [
|
|
60
|
+
"@radix-ui/react-dropdown-menu",
|
|
61
|
+
"lucide-react",
|
|
62
|
+
"clsx",
|
|
63
|
+
"tailwind-merge",
|
|
64
|
+
],
|
|
65
|
+
dev_dependencies: [],
|
|
66
|
+
},
|
|
67
|
+
list: {
|
|
68
|
+
name: "List",
|
|
69
|
+
dependencies: ["lucide-react", "clsx", "tailwind-merge"],
|
|
70
|
+
dev_dependencies: [],
|
|
71
|
+
},
|
|
72
|
+
loader: {
|
|
73
|
+
name: "Loader",
|
|
74
|
+
dependencies: ["clsx", "tailwind-merge"],
|
|
75
|
+
dev_dependencies: [],
|
|
76
|
+
},
|
|
77
|
+
"navbar-bottom": {
|
|
78
|
+
name: "NavbarBottom",
|
|
79
|
+
dependencies: ["lucide-react", "clsx", "tailwind-merge"],
|
|
80
|
+
dev_dependencies: [],
|
|
81
|
+
},
|
|
82
|
+
"navbar-top": {
|
|
83
|
+
name: "NavbarTop",
|
|
84
|
+
dependencies: ["lucide-react", "clsx", "tailwind-merge"],
|
|
85
|
+
dev_dependencies: [],
|
|
86
|
+
},
|
|
87
|
+
"page-control": {
|
|
88
|
+
name: "PageControl",
|
|
89
|
+
dependencies: ["clsx", "tailwind-merge"],
|
|
90
|
+
dev_dependencies: [],
|
|
91
|
+
},
|
|
92
|
+
pagination: {
|
|
93
|
+
name: "Pagination",
|
|
94
|
+
dependencies: ["lucide-react", "clsx", "tailwind-merge"],
|
|
95
|
+
dev_dependencies: [],
|
|
96
|
+
},
|
|
97
|
+
progress: {
|
|
98
|
+
name: "Progress",
|
|
99
|
+
dependencies: ["@radix-ui/react-progress", "clsx", "tailwind-merge"],
|
|
100
|
+
dev_dependencies: [],
|
|
101
|
+
},
|
|
102
|
+
radio: {
|
|
103
|
+
name: "Radio",
|
|
104
|
+
dependencies: ["@radix-ui/react-radio-group", "clsx", "tailwind-merge"],
|
|
105
|
+
dev_dependencies: [],
|
|
106
|
+
},
|
|
107
|
+
stepper: {
|
|
108
|
+
name: "Stepper",
|
|
109
|
+
dependencies: ["lucide-react", "clsx", "tailwind-merge"],
|
|
110
|
+
dev_dependencies: [],
|
|
111
|
+
},
|
|
112
|
+
tabs: {
|
|
113
|
+
name: "Tabs",
|
|
114
|
+
dependencies: ["@radix-ui/react-tabs", "clsx", "tailwind-merge"],
|
|
115
|
+
dev_dependencies: [],
|
|
116
|
+
},
|
|
117
|
+
toggle: {
|
|
118
|
+
name: "Toggle",
|
|
119
|
+
dependencies: ["@radix-ui/react-switch", "clsx", "tailwind-merge"],
|
|
120
|
+
dev_dependencies: [],
|
|
121
|
+
},
|
|
122
|
+
tooltip: {
|
|
123
|
+
name: "Tooltip",
|
|
124
|
+
dependencies: ["@radix-ui/react-tooltip", "clsx", "tailwind-merge"],
|
|
125
|
+
dev_dependencies: [],
|
|
126
|
+
},
|
|
17
127
|
button: {
|
|
18
128
|
name: "Button",
|
|
19
129
|
dependencies: [
|
|
@@ -56,12 +166,219 @@ const REGISTRY = {
|
|
|
56
166
|
},
|
|
57
167
|
checkbox: {
|
|
58
168
|
name: "Checkbox",
|
|
59
|
-
dependencies: [
|
|
169
|
+
dependencies: [
|
|
170
|
+
"@radix-ui/react-checkbox",
|
|
171
|
+
"lucide-react",
|
|
172
|
+
"clsx",
|
|
173
|
+
"tailwind-merge",
|
|
174
|
+
],
|
|
175
|
+
dev_dependencies: [],
|
|
176
|
+
},
|
|
177
|
+
select: {
|
|
178
|
+
name: "Select",
|
|
179
|
+
dependencies: [
|
|
180
|
+
"@radix-ui/react-select",
|
|
181
|
+
"lucide-react",
|
|
182
|
+
"clsx",
|
|
183
|
+
"tailwind-merge",
|
|
184
|
+
],
|
|
60
185
|
dev_dependencies: [],
|
|
186
|
+
requires: ["input"], // select.tsx imports shared variants from ./input
|
|
61
187
|
},
|
|
62
188
|
};
|
|
63
189
|
|
|
190
|
+
// Shared cn() helper scaffolded to src/lib/utils — components import it
|
|
191
|
+
// from "../../lib/utils" instead of inlining it per file.
|
|
192
|
+
const UTILS_TEMPLATE = fs.readFileSync(
|
|
193
|
+
path.join(__dirname, "../templates/_lib/utils.ts"),
|
|
194
|
+
"utf-8",
|
|
195
|
+
);
|
|
196
|
+
|
|
64
197
|
const THEMES = {
|
|
198
|
+
aeon: `@theme static {
|
|
199
|
+
/* ——— Aeon Product Design System ——— */
|
|
200
|
+
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
201
|
+
|
|
202
|
+
/* Semantic colors */
|
|
203
|
+
--color-background: #ffffff;
|
|
204
|
+
--color-foreground: #1c222a;
|
|
205
|
+
--color-card: #ffffff;
|
|
206
|
+
--color-card-foreground: #1c222a;
|
|
207
|
+
--color-popover: #ffffff;
|
|
208
|
+
--color-popover-foreground: #1c222a;
|
|
209
|
+
--color-primary: #0d3435;
|
|
210
|
+
--color-primary-foreground: #ffffff;
|
|
211
|
+
--color-secondary: #f4f5f6;
|
|
212
|
+
--color-secondary-foreground: #1c222a;
|
|
213
|
+
--color-muted: #e7e9ec;
|
|
214
|
+
--color-muted-foreground: #475569;
|
|
215
|
+
--color-accent: #f1f3f3;
|
|
216
|
+
--color-accent-foreground: #0a2b2b;
|
|
217
|
+
--color-destructive: #821717;
|
|
218
|
+
--color-destructive-foreground: #ffffff;
|
|
219
|
+
--color-border: #cbcfd5;
|
|
220
|
+
--color-input: #cbcfd5;
|
|
221
|
+
--color-ring: #0d3435;
|
|
222
|
+
--color-success: #14532d;
|
|
223
|
+
--color-success-foreground: #ffffff;
|
|
224
|
+
--color-info: #075985;
|
|
225
|
+
--color-info-foreground: #ffffff;
|
|
226
|
+
--color-warning: #713f12;
|
|
227
|
+
--color-warning-foreground: #ffffff;
|
|
228
|
+
--color-error: #991b1b;
|
|
229
|
+
--color-error-foreground: #ffffff;
|
|
230
|
+
|
|
231
|
+
/* Primitive scales */
|
|
232
|
+
--color-primary-50: #f1f3f3;
|
|
233
|
+
--color-primary-100: #e0e6e6;
|
|
234
|
+
--color-primary-200: #bcc9c9;
|
|
235
|
+
--color-primary-300: #93a8a8;
|
|
236
|
+
--color-primary-400: #577778;
|
|
237
|
+
--color-primary-500: #0f3d3e;
|
|
238
|
+
--color-primary-600: #0d3435;
|
|
239
|
+
--color-primary-700: #0a2b2b;
|
|
240
|
+
--color-primary-800: #082222;
|
|
241
|
+
--color-primary-900: #061819;
|
|
242
|
+
--color-grey-50: #f4f5f6;
|
|
243
|
+
--color-grey-100: #e7e9ec;
|
|
244
|
+
--color-grey-200: #cbcfd5;
|
|
245
|
+
--color-grey-300: #acb2bc;
|
|
246
|
+
--color-grey-400: #7e8896;
|
|
247
|
+
--color-grey-500: #475569;
|
|
248
|
+
--color-grey-600: #3c4859;
|
|
249
|
+
--color-grey-700: #323c4a;
|
|
250
|
+
--color-grey-800: #272f3a;
|
|
251
|
+
--color-grey-900: #1c222a;
|
|
252
|
+
--color-success-50: #f1f5f2;
|
|
253
|
+
--color-success-100: #e0e9e4;
|
|
254
|
+
--color-success-200: #bdcfc4;
|
|
255
|
+
--color-success-300: #95b2a0;
|
|
256
|
+
--color-success-400: #5a876c;
|
|
257
|
+
--color-success-500: #14532d;
|
|
258
|
+
--color-success-600: #114726;
|
|
259
|
+
--color-success-700: #0e3a20;
|
|
260
|
+
--color-success-800: #0b2e19;
|
|
261
|
+
--color-success-900: #082112;
|
|
262
|
+
--color-error-50: #f9f1f1;
|
|
263
|
+
--color-error-100: #f2e1e1;
|
|
264
|
+
--color-error-200: #e2bfbf;
|
|
265
|
+
--color-error-300: #d19898;
|
|
266
|
+
--color-error-400: #b85f5f;
|
|
267
|
+
--color-error-500: #991b1b;
|
|
268
|
+
--color-error-600: #821717;
|
|
269
|
+
--color-error-700: #6b1313;
|
|
270
|
+
--color-error-800: #540f0f;
|
|
271
|
+
--color-error-900: #3d0b0b;
|
|
272
|
+
--color-warning-50: #f6f3f1;
|
|
273
|
+
--color-warning-100: #ede6e0;
|
|
274
|
+
--color-warning-200: #d7c9bd;
|
|
275
|
+
--color-warning-300: #bfa994;
|
|
276
|
+
--color-warning-400: #9c7959;
|
|
277
|
+
--color-warning-500: #713f12;
|
|
278
|
+
--color-warning-600: #60360f;
|
|
279
|
+
--color-warning-700: #4f2c0d;
|
|
280
|
+
--color-warning-800: #3e230a;
|
|
281
|
+
--color-warning-900: #2d1907;
|
|
282
|
+
--color-info-50: #f0f5f8;
|
|
283
|
+
--color-info-100: #dfe9ef;
|
|
284
|
+
--color-info-200: #bad1dd;
|
|
285
|
+
--color-info-300: #8fb4c8;
|
|
286
|
+
--color-info-400: #518baa;
|
|
287
|
+
--color-info-500: #075985;
|
|
288
|
+
--color-info-600: #064c71;
|
|
289
|
+
--color-info-700: #053e5d;
|
|
290
|
+
--color-info-800: #04314a;
|
|
291
|
+
--color-info-900: #032435;
|
|
292
|
+
|
|
293
|
+
/* Typography — Aeon text ramp (text-h1 … text-c3, text-label, text-btn-*) */
|
|
294
|
+
--text-h1: 3rem;
|
|
295
|
+
--text-h1--line-height: 3.625rem;
|
|
296
|
+
--text-h1--font-weight: 600;
|
|
297
|
+
--text-h2: 2.5rem;
|
|
298
|
+
--text-h2--line-height: 3rem;
|
|
299
|
+
--text-h2--font-weight: 600;
|
|
300
|
+
--text-h3: 2rem;
|
|
301
|
+
--text-h3--line-height: 2.375rem;
|
|
302
|
+
--text-h3--font-weight: 600;
|
|
303
|
+
--text-h4: 1.75rem;
|
|
304
|
+
--text-h4--line-height: 2.125rem;
|
|
305
|
+
--text-h4--font-weight: 600;
|
|
306
|
+
--text-h5: 1.5rem;
|
|
307
|
+
--text-h5--line-height: 1.75rem;
|
|
308
|
+
--text-h5--font-weight: 600;
|
|
309
|
+
--text-s1: 1.125rem;
|
|
310
|
+
--text-s1--line-height: 1.75rem;
|
|
311
|
+
--text-s1--font-weight: 600;
|
|
312
|
+
--text-s2: 1rem;
|
|
313
|
+
--text-s2--line-height: 1.5rem;
|
|
314
|
+
--text-s2--font-weight: 600;
|
|
315
|
+
--text-b1: 1rem;
|
|
316
|
+
--text-b1--line-height: 1.5rem;
|
|
317
|
+
--text-b1--font-weight: 400;
|
|
318
|
+
--text-b2: 1rem;
|
|
319
|
+
--text-b2--line-height: 1.5rem;
|
|
320
|
+
--text-b2--font-weight: 500;
|
|
321
|
+
--text-b3: 0.875rem;
|
|
322
|
+
--text-b3--line-height: 1.25rem;
|
|
323
|
+
--text-b3--font-weight: 400;
|
|
324
|
+
--text-b4: 0.875rem;
|
|
325
|
+
--text-b4--line-height: 1.25rem;
|
|
326
|
+
--text-b4--font-weight: 500;
|
|
327
|
+
--text-c1: 0.75rem;
|
|
328
|
+
--text-c1--line-height: 1rem;
|
|
329
|
+
--text-c1--font-weight: 400;
|
|
330
|
+
--text-c2: 0.75rem;
|
|
331
|
+
--text-c2--line-height: 1rem;
|
|
332
|
+
--text-c2--font-weight: 500;
|
|
333
|
+
--text-c3: 0.625rem;
|
|
334
|
+
--text-c3--line-height: 0.875rem;
|
|
335
|
+
--text-c3--font-weight: 500;
|
|
336
|
+
--text-label: 0.75rem;
|
|
337
|
+
--text-label--line-height: 1rem;
|
|
338
|
+
--text-label--font-weight: 500;
|
|
339
|
+
--text-btn-giant: 1.125rem;
|
|
340
|
+
--text-btn-giant--line-height: 1.5rem;
|
|
341
|
+
--text-btn-giant--font-weight: 600;
|
|
342
|
+
--text-btn-large: 1rem;
|
|
343
|
+
--text-btn-large--line-height: 1.25rem;
|
|
344
|
+
--text-btn-large--font-weight: 600;
|
|
345
|
+
--text-btn-medium: 0.875rem;
|
|
346
|
+
--text-btn-medium--line-height: 1rem;
|
|
347
|
+
--text-btn-medium--font-weight: 600;
|
|
348
|
+
--text-btn-small: 0.75rem;
|
|
349
|
+
--text-btn-small--line-height: 1rem;
|
|
350
|
+
--text-btn-small--font-weight: 600;
|
|
351
|
+
--text-btn-tiny: 0.625rem;
|
|
352
|
+
--text-btn-tiny--line-height: 0.75rem;
|
|
353
|
+
--text-btn-tiny--font-weight: 600;
|
|
354
|
+
|
|
355
|
+
/* Radius scale — rounded-xxs … rounded-xl */
|
|
356
|
+
--radius-xxs: 0.25rem;
|
|
357
|
+
--radius-xs: 0.5rem;
|
|
358
|
+
--radius-sm: 0.75rem;
|
|
359
|
+
--radius-md: 1rem;
|
|
360
|
+
--radius-lg: 1.25rem;
|
|
361
|
+
--radius-xl: 1.5rem;
|
|
362
|
+
|
|
363
|
+
/* Elevation — shadow-100 … shadow-800 */
|
|
364
|
+
--shadow-100: 0 4px 4px -2px #13192714, 0 2px 4px -2px #1319271f;
|
|
365
|
+
--shadow-200: 0 8px 8px -4px #13192714, 0 4px 6px -4px #1319271f;
|
|
366
|
+
--shadow-300: 0 8px 16px -6px #13192714, 0 6px 8px -6px #1319271f;
|
|
367
|
+
--shadow-400: 0 8px 24px -4px #13192714, 0 6px 12px -6px #1319271f;
|
|
368
|
+
--shadow-500: 0 10px 32px -4px #1319271a, 0 6px 14px -6px #1319271f;
|
|
369
|
+
--shadow-600: 0 12px 42px -4px #1319271f, 0 8px 18px -6px #1319271f;
|
|
370
|
+
--shadow-700: 0 14px 64px -4px #1319271f, 0 8px 22px -6px #1319271f;
|
|
371
|
+
--shadow-800: 0 18px 88px -4px #13192724, 0 8px 28px -6px #1319271f;
|
|
372
|
+
|
|
373
|
+
/* Focus ring — shared by every interactive component (focus-visible:shadow-focus) */
|
|
374
|
+
--shadow-focus: 0px 0px 0px 3px #c2f5f6;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* Alert auto-dismiss timeline */
|
|
378
|
+
@keyframes alert-timer {
|
|
379
|
+
from { width: 100%; }
|
|
380
|
+
to { width: 0%; }
|
|
381
|
+
}`,
|
|
65
382
|
zinc: `:root {
|
|
66
383
|
--background: 0 0% 100%;
|
|
67
384
|
--foreground: 240 10% 3.9%;
|
|
@@ -226,24 +543,35 @@ const main = async () => {
|
|
|
226
543
|
if (fs.existsSync(templatesDir)) {
|
|
227
544
|
const dirs = fs
|
|
228
545
|
.readdirSync(templatesDir)
|
|
546
|
+
.filter((f) => !f.startsWith("_")) // _lib etc. are support files, not components
|
|
229
547
|
.filter((f) => fs.statSync(path.join(templatesDir, f)).isDirectory());
|
|
230
548
|
if (dirs.length > 0) availableComponents = dirs;
|
|
231
549
|
}
|
|
232
550
|
|
|
233
551
|
if (command === "init") {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
552
|
+
let theme = componentName; // support `cooterlabs init aeon`
|
|
553
|
+
if (!THEMES[theme]) {
|
|
554
|
+
const response = await prompts({
|
|
555
|
+
type: "select",
|
|
556
|
+
name: "theme",
|
|
557
|
+
message: "Which theme would you like to use?",
|
|
558
|
+
choices: Object.keys(THEMES).map((t) => ({
|
|
559
|
+
title: t === "aeon" ? "aeon (default)" : t,
|
|
560
|
+
value: t,
|
|
561
|
+
})),
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
if (!response.theme) {
|
|
565
|
+
console.log(chalk.red("❌ Theme selection cancelled."));
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
theme = response.theme;
|
|
244
569
|
}
|
|
245
570
|
|
|
246
|
-
|
|
571
|
+
// aeon is a Tailwind v4 @theme block appended at top level;
|
|
572
|
+
// legacy themes are :root vars wrapped in @layer base.
|
|
573
|
+
const isV4Theme = theme === "aeon";
|
|
574
|
+
const themeCSS = THEMES[theme];
|
|
247
575
|
let cssPath = path.join(cwd, "src", "index.css");
|
|
248
576
|
|
|
249
577
|
if (env.framework === "next") {
|
|
@@ -277,24 +605,29 @@ const main = async () => {
|
|
|
277
605
|
cssContent.includes("@tailwind base") ||
|
|
278
606
|
cssContent.includes('@import "tailwindcss');
|
|
279
607
|
if (!hasTailwindDirectives) {
|
|
280
|
-
cssContent =
|
|
608
|
+
cssContent = isV4Theme
|
|
609
|
+
? `@import "tailwindcss";\n\n${cssContent}`
|
|
610
|
+
: `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n${cssContent}`;
|
|
281
611
|
}
|
|
282
612
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
613
|
+
const themeBlock = isV4Theme
|
|
614
|
+
? `\n${themeCSS}\n`
|
|
615
|
+
: `\n@layer base {\n${themeCSS}\n}\n`;
|
|
616
|
+
|
|
617
|
+
const hasExistingVars = isV4Theme
|
|
618
|
+
? cssContent.includes("@theme")
|
|
619
|
+
: cssContent.includes(":root {") || cssContent.includes("--background:");
|
|
620
|
+
|
|
621
|
+
if (hasExistingVars) {
|
|
287
622
|
console.log(
|
|
288
623
|
chalk.yellow(
|
|
289
|
-
`⚠️
|
|
624
|
+
`⚠️ Theme variables may already exist in ${cssPath}. Please append them manually if needed:\n${themeBlock}`,
|
|
290
625
|
),
|
|
291
626
|
);
|
|
292
627
|
} else {
|
|
293
|
-
cssContent = `${cssContent}
|
|
628
|
+
cssContent = `${cssContent}${themeBlock}`;
|
|
294
629
|
fs.writeFileSync(cssPath, cssContent);
|
|
295
|
-
console.log(
|
|
296
|
-
chalk.green(`✅ Theme '${response.theme}' initialized in ${cssPath}`),
|
|
297
|
-
);
|
|
630
|
+
console.log(chalk.green(`✅ Theme '${theme}' initialized in ${cssPath}`));
|
|
298
631
|
}
|
|
299
632
|
} else if (command === "add") {
|
|
300
633
|
let componentsToAdd = [];
|
|
@@ -328,12 +661,33 @@ const main = async () => {
|
|
|
328
661
|
process.exit(1);
|
|
329
662
|
}
|
|
330
663
|
|
|
664
|
+
// Pull in components required by selected ones (e.g. select imports from ./input)
|
|
665
|
+
for (const comp of [...componentsToAdd]) {
|
|
666
|
+
for (const req of REGISTRY[comp]?.requires || []) {
|
|
667
|
+
if (!componentsToAdd.includes(req)) componentsToAdd.push(req);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
331
671
|
// Create target dir
|
|
332
672
|
const targetDir = path.join(cwd, env.componentsDir);
|
|
333
673
|
if (!fs.existsSync(targetDir)) {
|
|
334
674
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
335
675
|
}
|
|
336
676
|
|
|
677
|
+
// Components import cn() from "../../lib/utils" — scaffold it once
|
|
678
|
+
const utilsPath = path.join(
|
|
679
|
+
targetDir,
|
|
680
|
+
"../../lib",
|
|
681
|
+
`utils.${env.typescript ? "ts" : "js"}`,
|
|
682
|
+
);
|
|
683
|
+
if (!fs.existsSync(utilsPath)) {
|
|
684
|
+
fs.mkdirSync(path.dirname(utilsPath), { recursive: true });
|
|
685
|
+
fs.writeFileSync(utilsPath, UTILS_TEMPLATE, "utf-8");
|
|
686
|
+
console.log(
|
|
687
|
+
chalk.green(`✅ Created ${path.relative(cwd, utilsPath)}`),
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
337
691
|
let allDependencies = new Set();
|
|
338
692
|
|
|
339
693
|
for (const comp of componentsToAdd) {
|
package/package.json
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cooterlabs",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Component library CLI for CooterLabs apps",
|
|
5
5
|
"files": [
|
|
6
6
|
"bin",
|
|
7
7
|
"templates"
|
|
8
8
|
],
|
|
9
|
-
"main": "index.js",
|
|
10
9
|
"scripts": {
|
|
11
10
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
11
|
},
|
|
13
12
|
"bin": {
|
|
14
|
-
"cooterlabs": "
|
|
13
|
+
"cooterlabs": "bin/index.js"
|
|
15
14
|
},
|
|
16
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"aeon",
|
|
17
|
+
"react",
|
|
18
|
+
"typescript",
|
|
19
|
+
"cli",
|
|
20
|
+
"component-library"
|
|
21
|
+
],
|
|
17
22
|
"author": "",
|
|
18
23
|
"license": "ISC",
|
|
19
24
|
"type": "module",
|
|
@@ -25,5 +30,8 @@
|
|
|
25
30
|
"prettier": "^3.8.1",
|
|
26
31
|
"prompts": "^2.4.2",
|
|
27
32
|
"tailwind-merge": "^3.5.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/react": "^19.2.14"
|
|
28
36
|
}
|
|
29
37
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx"
|
|
2
|
+
import { extendTailwindMerge } from "tailwind-merge"
|
|
3
|
+
|
|
4
|
+
// The custom font-size ramp from index.css (--text-h1 … --text-btn-tiny) is
|
|
5
|
+
// unknown to tailwind-merge, so it treats text-b4 etc. as text COLORS and
|
|
6
|
+
// silently drops real color classes like text-white. Teach it the ramp.
|
|
7
|
+
const twMerge = extendTailwindMerge({
|
|
8
|
+
extend: {
|
|
9
|
+
classGroups: {
|
|
10
|
+
"font-size": [
|
|
11
|
+
{
|
|
12
|
+
text: [
|
|
13
|
+
"h1", "h2", "h3", "h4", "h5",
|
|
14
|
+
"s1", "s2",
|
|
15
|
+
"b1", "b2", "b3", "b4",
|
|
16
|
+
"c1", "c2", "c3",
|
|
17
|
+
"label",
|
|
18
|
+
"btn-giant", "btn-large", "btn-medium", "btn-small", "btn-tiny",
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export function cn(...inputs: ClassValue[]) {
|
|
27
|
+
return twMerge(clsx(inputs))
|
|
28
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
const ActionSheet = DialogPrimitive.Root
|
|
6
|
+
|
|
7
|
+
const ActionSheetTrigger = DialogPrimitive.Trigger
|
|
8
|
+
|
|
9
|
+
const ActionSheetPortal = DialogPrimitive.Portal
|
|
10
|
+
|
|
11
|
+
const ActionSheetOverlay = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
14
|
+
>(({ className, ...props }, ref) => (
|
|
15
|
+
<DialogPrimitive.Overlay
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={cn(
|
|
18
|
+
"fixed inset-0 z-50 bg-black/50 transition-opacity data-[state=closed]:opacity-0",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
))
|
|
24
|
+
ActionSheetOverlay.displayName = "ActionSheetOverlay"
|
|
25
|
+
|
|
26
|
+
const ActionSheetContent = React.forwardRef<
|
|
27
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
28
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
29
|
+
>(({ className, children, ...props }, ref) => (
|
|
30
|
+
<ActionSheetPortal>
|
|
31
|
+
<ActionSheetOverlay />
|
|
32
|
+
<DialogPrimitive.Content
|
|
33
|
+
ref={ref}
|
|
34
|
+
className={cn(
|
|
35
|
+
"fixed inset-x-0 bottom-0 z-50 flex w-full flex-col bg-background rounded-t-lg px-4 pb-4 pt-2 transition-transform data-[state=closed]:translate-y-full",
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
>
|
|
40
|
+
<div className="mx-auto mb-2 h-1 w-10 rounded-full bg-grey-200" />
|
|
41
|
+
{children}
|
|
42
|
+
</DialogPrimitive.Content>
|
|
43
|
+
</ActionSheetPortal>
|
|
44
|
+
))
|
|
45
|
+
ActionSheetContent.displayName = "ActionSheetContent"
|
|
46
|
+
|
|
47
|
+
const ActionSheetHeader = ({
|
|
48
|
+
className,
|
|
49
|
+
...props
|
|
50
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
51
|
+
<div
|
|
52
|
+
className={cn(
|
|
53
|
+
"flex flex-col gap-1 px-4 py-3 text-center text-c1 text-muted-foreground",
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
ActionSheetHeader.displayName = "ActionSheetHeader"
|
|
60
|
+
|
|
61
|
+
export interface ActionSheetItemProps
|
|
62
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
63
|
+
variant?: "default" | "destructive"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const ActionSheetItem = React.forwardRef<
|
|
67
|
+
HTMLButtonElement,
|
|
68
|
+
ActionSheetItemProps
|
|
69
|
+
>(({ className, variant = "default", ...props }, ref) => (
|
|
70
|
+
<button
|
|
71
|
+
ref={ref}
|
|
72
|
+
className={cn(
|
|
73
|
+
"flex h-12 w-full items-center justify-center gap-2 text-b2 transition-colors hover:bg-secondary active:bg-muted focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-5 [&_svg]:shrink-0",
|
|
74
|
+
variant === "destructive" ? "text-error" : "text-foreground",
|
|
75
|
+
className
|
|
76
|
+
)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
))
|
|
80
|
+
ActionSheetItem.displayName = "ActionSheetItem"
|
|
81
|
+
|
|
82
|
+
const ActionSheetCancel = React.forwardRef<
|
|
83
|
+
HTMLButtonElement,
|
|
84
|
+
React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
85
|
+
>(({ className, ...props }, ref) => (
|
|
86
|
+
<DialogPrimitive.Close asChild>
|
|
87
|
+
<button
|
|
88
|
+
ref={ref}
|
|
89
|
+
className={cn(
|
|
90
|
+
"mt-2 flex h-12 w-full items-center justify-center rounded-xs border border-border bg-background text-b2 text-foreground transition-colors hover:bg-secondary active:bg-muted focus-visible:outline-none focus-visible:shadow-focus",
|
|
91
|
+
className
|
|
92
|
+
)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
</DialogPrimitive.Close>
|
|
96
|
+
))
|
|
97
|
+
ActionSheetCancel.displayName = "ActionSheetCancel"
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
ActionSheet,
|
|
101
|
+
ActionSheetTrigger,
|
|
102
|
+
ActionSheetPortal,
|
|
103
|
+
ActionSheetOverlay,
|
|
104
|
+
ActionSheetContent,
|
|
105
|
+
ActionSheetHeader,
|
|
106
|
+
ActionSheetItem,
|
|
107
|
+
ActionSheetCancel,
|
|
108
|
+
}
|