cooterlabs 1.0.1 → 1.0.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/README.md +23 -4
- package/bin/index.js +211 -81
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,15 +17,26 @@ It brings components directly to you. We do not provide a generic NPM library pa
|
|
|
17
17
|
|
|
18
18
|
1. Open your terminal at the root of your project directory.
|
|
19
19
|
2. Ensure you have `tailwind-merge` and `clsx` properly configured in a central utility path or installed manually (some components will auto-detect and install these).
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
### Initialization (Themes & CSS Base)
|
|
22
|
+
|
|
23
|
+
To swiftly set up a cohesive Tailwind theme and properly configure your base CSS file (e.g. `index.css` or `globals.css`), run:
|
|
21
24
|
|
|
22
25
|
```bash
|
|
23
|
-
npx cooterlabs
|
|
26
|
+
npx cooterlabs init
|
|
24
27
|
```
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
This will give you a list of carefully curated designer themes (like `zinc`, `slate`) to apply global default variables (background, foreground, primary borders, etc.) out-of-the-box.
|
|
30
|
+
|
|
31
|
+
### Adding Components
|
|
32
|
+
|
|
33
|
+
Run the CLI tool without arguments to get an interactive autocomplete prompt:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx cooterlabs add
|
|
37
|
+
```
|
|
27
38
|
|
|
28
|
-
If you know exactly what component you want,
|
|
39
|
+
If you know exactly what component you want, bypass the interactive wizard and supply the name:
|
|
29
40
|
|
|
30
41
|
```bash
|
|
31
42
|
# Example: Adding a Button
|
|
@@ -35,6 +46,14 @@ npx cooterlabs add button
|
|
|
35
46
|
npx cooterlabs add avatar
|
|
36
47
|
```
|
|
37
48
|
|
|
49
|
+
### Install All Components At Once
|
|
50
|
+
|
|
51
|
+
Need a complete starting block? Add the `--all` flag to scaffold the entire library in one go:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx cooterlabs add --all
|
|
55
|
+
```
|
|
56
|
+
|
|
38
57
|
## Available Components
|
|
39
58
|
|
|
40
59
|
Currently, the CooterLabs Library ships with the following carefully designed boilerplate components:
|
package/bin/index.js
CHANGED
|
@@ -56,16 +56,58 @@ const REGISTRY = {
|
|
|
56
56
|
},
|
|
57
57
|
checkbox: {
|
|
58
58
|
name: "Checkbox",
|
|
59
|
-
dependencies: [
|
|
60
|
-
"lucide-react",
|
|
61
|
-
"clsx",
|
|
62
|
-
"tailwind-merge",
|
|
63
|
-
"@radix-ui/react-checkbox",
|
|
64
|
-
],
|
|
59
|
+
dependencies: ["lucide-react", "clsx", "tailwind-merge"],
|
|
65
60
|
dev_dependencies: [],
|
|
66
61
|
},
|
|
67
62
|
};
|
|
68
63
|
|
|
64
|
+
const THEMES = {
|
|
65
|
+
zinc: `:root {
|
|
66
|
+
--background: 0 0% 100%;
|
|
67
|
+
--foreground: 240 10% 3.9%;
|
|
68
|
+
--card: 0 0% 100%;
|
|
69
|
+
--card-foreground: 240 10% 3.9%;
|
|
70
|
+
--popover: 0 0% 100%;
|
|
71
|
+
--popover-foreground: 240 10% 3.9%;
|
|
72
|
+
--primary: 240 5.9% 10%;
|
|
73
|
+
--primary-foreground: 0 0% 98%;
|
|
74
|
+
--secondary: 240 4.8% 95.9%;
|
|
75
|
+
--secondary-foreground: 240 5.9% 10%;
|
|
76
|
+
--muted: 240 4.8% 95.9%;
|
|
77
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
78
|
+
--accent: 240 4.8% 95.9%;
|
|
79
|
+
--accent-foreground: 240 5.9% 10%;
|
|
80
|
+
--destructive: 0 84.2% 60.2%;
|
|
81
|
+
--destructive-foreground: 0 0% 98%;
|
|
82
|
+
--border: 240 5.9% 90%;
|
|
83
|
+
--input: 240 5.9% 90%;
|
|
84
|
+
--ring: 240 10% 3.9%;
|
|
85
|
+
--radius: 0.5rem;
|
|
86
|
+
}`,
|
|
87
|
+
slate: `:root {
|
|
88
|
+
--background: 0 0% 100%;
|
|
89
|
+
--foreground: 222.2 84% 4.9%;
|
|
90
|
+
--card: 0 0% 100%;
|
|
91
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
92
|
+
--popover: 0 0% 100%;
|
|
93
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
94
|
+
--primary: 222.2 47.4% 11.2%;
|
|
95
|
+
--primary-foreground: 210 40% 98%;
|
|
96
|
+
--secondary: 210 40% 96.1%;
|
|
97
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
98
|
+
--muted: 210 40% 96.1%;
|
|
99
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
100
|
+
--accent: 210 40% 96.1%;
|
|
101
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
102
|
+
--destructive: 0 84.2% 60.2%;
|
|
103
|
+
--destructive-foreground: 210 40% 98%;
|
|
104
|
+
--border: 214.3 31.8% 91.4%;
|
|
105
|
+
--input: 214.3 31.8% 91.4%;
|
|
106
|
+
--ring: 222.2 84% 4.9%;
|
|
107
|
+
--radius: 0.5rem;
|
|
108
|
+
}`,
|
|
109
|
+
};
|
|
110
|
+
|
|
69
111
|
const templatesDir = path.join(__dirname, "../templates");
|
|
70
112
|
|
|
71
113
|
const detectEnvironment = () => {
|
|
@@ -164,13 +206,20 @@ const main = async () => {
|
|
|
164
206
|
}
|
|
165
207
|
|
|
166
208
|
const args = process.argv.slice(2);
|
|
167
|
-
|
|
168
|
-
let
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
209
|
+
const isAll = args.includes("--all");
|
|
210
|
+
let command = "add";
|
|
211
|
+
let componentName = "";
|
|
212
|
+
|
|
213
|
+
// Parse command & componentName from args (ignoring flags)
|
|
214
|
+
const positionalArgs = args.filter((a) => !a.startsWith("--"));
|
|
215
|
+
if (positionalArgs.length > 0) {
|
|
216
|
+
if (positionalArgs[0] === "init" || positionalArgs[0] === "add") {
|
|
217
|
+
command = positionalArgs[0];
|
|
218
|
+
componentName = positionalArgs[1] || "";
|
|
219
|
+
} else {
|
|
220
|
+
command = "add";
|
|
221
|
+
componentName = positionalArgs[0];
|
|
222
|
+
}
|
|
174
223
|
}
|
|
175
224
|
|
|
176
225
|
let availableComponents = Object.keys(REGISTRY);
|
|
@@ -181,102 +230,183 @@ const main = async () => {
|
|
|
181
230
|
if (dirs.length > 0) availableComponents = dirs;
|
|
182
231
|
}
|
|
183
232
|
|
|
184
|
-
if (command === "
|
|
185
|
-
|
|
233
|
+
if (command === "init") {
|
|
234
|
+
const response = await prompts({
|
|
235
|
+
type: "select",
|
|
236
|
+
name: "theme",
|
|
237
|
+
message: "Which theme would you like to use?",
|
|
238
|
+
choices: Object.keys(THEMES).map((t) => ({ title: t, value: t })),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (!response.theme) {
|
|
242
|
+
console.log(chalk.red("❌ Theme selection cancelled."));
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const themeCSS = THEMES[response.theme];
|
|
247
|
+
let cssPath = path.join(cwd, "src", "index.css");
|
|
248
|
+
|
|
249
|
+
if (env.framework === "next") {
|
|
250
|
+
if (env.isAppDir) {
|
|
251
|
+
cssPath = fs.existsSync(path.join(cwd, "src", "app"))
|
|
252
|
+
? path.join(cwd, "src", "app", "globals.css")
|
|
253
|
+
: path.join(cwd, "app", "globals.css");
|
|
254
|
+
} else {
|
|
255
|
+
cssPath = fs.existsSync(path.join(cwd, "src", "styles", "globals.css"))
|
|
256
|
+
? path.join(cwd, "src", "styles", "globals.css")
|
|
257
|
+
: path.join(cwd, "styles", "globals.css");
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
cssPath = fs.existsSync(path.join(cwd, "src", "index.css"))
|
|
261
|
+
? path.join(cwd, "src", "index.css")
|
|
262
|
+
: path.join(cwd, "src", "globals.css");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Ensure dir exists
|
|
266
|
+
const cssDir = path.dirname(cssPath);
|
|
267
|
+
if (!fs.existsSync(cssDir)) {
|
|
268
|
+
fs.mkdirSync(cssDir, { recursive: true });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let cssContent = "";
|
|
272
|
+
if (fs.existsSync(cssPath)) {
|
|
273
|
+
cssContent = fs.readFileSync(cssPath, "utf-8");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const hasTailwindDirectives =
|
|
277
|
+
cssContent.includes("@tailwind base") ||
|
|
278
|
+
cssContent.includes('@import "tailwindcss');
|
|
279
|
+
if (!hasTailwindDirectives) {
|
|
280
|
+
cssContent = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n${cssContent}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
cssContent.includes(":root {") ||
|
|
285
|
+
cssContent.includes("--background:")
|
|
286
|
+
) {
|
|
287
|
+
console.log(
|
|
288
|
+
chalk.yellow(
|
|
289
|
+
`⚠️ CSS variables may already exist in ${cssPath}. Please append them manually if needed:\n\n@layer base {\n${themeCSS}\n}\n`,
|
|
290
|
+
),
|
|
291
|
+
);
|
|
292
|
+
} else {
|
|
293
|
+
cssContent = `${cssContent}\n@layer base {\n${themeCSS}\n}\n`;
|
|
294
|
+
fs.writeFileSync(cssPath, cssContent);
|
|
295
|
+
console.log(
|
|
296
|
+
chalk.green(`✅ Theme '${response.theme}' initialized in ${cssPath}`),
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
} else if (command === "add") {
|
|
300
|
+
let componentsToAdd = [];
|
|
301
|
+
|
|
302
|
+
if (isAll) {
|
|
303
|
+
componentsToAdd = availableComponents;
|
|
304
|
+
} else if (componentName) {
|
|
305
|
+
componentsToAdd = [componentName];
|
|
306
|
+
} else {
|
|
186
307
|
const response = await prompts({
|
|
187
|
-
type: "
|
|
188
|
-
name: "
|
|
189
|
-
message:
|
|
308
|
+
type: "autocompleteMultiselect",
|
|
309
|
+
name: "components",
|
|
310
|
+
message:
|
|
311
|
+
"Which components would you like to add? (Space to select, Enter to submit, or use --all flag)",
|
|
190
312
|
choices: availableComponents.map((c) => ({ title: c, value: c })),
|
|
191
313
|
});
|
|
192
|
-
|
|
314
|
+
componentsToAdd = response.components || [];
|
|
193
315
|
}
|
|
194
316
|
|
|
195
|
-
if (
|
|
196
|
-
console.log(chalk.red("❌ Operation cancelled."));
|
|
317
|
+
if (componentsToAdd.length === 0) {
|
|
318
|
+
console.log(chalk.red("❌ No components selected. Operation cancelled."));
|
|
197
319
|
process.exit(1);
|
|
198
320
|
}
|
|
199
321
|
|
|
200
|
-
|
|
201
|
-
|
|
322
|
+
// Check availability
|
|
323
|
+
const notFound = componentsToAdd.filter(
|
|
324
|
+
(c) => !availableComponents.includes(c),
|
|
325
|
+
);
|
|
326
|
+
if (notFound.length > 0) {
|
|
327
|
+
console.log(chalk.red(`❌ Components not found: ${notFound.join(", ")}`));
|
|
202
328
|
process.exit(1);
|
|
203
329
|
}
|
|
204
330
|
|
|
205
|
-
const templatePath = path.join(templatesDir, componentName);
|
|
206
|
-
|
|
207
331
|
// Create target dir
|
|
208
332
|
const targetDir = path.join(cwd, env.componentsDir);
|
|
209
333
|
if (!fs.existsSync(targetDir)) {
|
|
210
334
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
211
335
|
}
|
|
212
336
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
path.join(templatePath, srcFile),
|
|
230
|
-
"utf-8",
|
|
337
|
+
let allDependencies = new Set();
|
|
338
|
+
|
|
339
|
+
for (const comp of componentsToAdd) {
|
|
340
|
+
const templatePath = path.join(templatesDir, comp);
|
|
341
|
+
const extension = env.typescript ? ".tsx" : ".jsx";
|
|
342
|
+
const componentFile = `${comp}${extension}`;
|
|
343
|
+
const targetPath = path.join(targetDir, componentFile);
|
|
344
|
+
|
|
345
|
+
const spinner = ora(`Installing ${comp}...`).start();
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
let sourceContent = "";
|
|
349
|
+
if (fs.existsSync(templatePath)) {
|
|
350
|
+
const files = fs.readdirSync(templatePath);
|
|
351
|
+
const srcFile = files.find(
|
|
352
|
+
(f) => f.endsWith(".tsx") || f.endsWith(".jsx"),
|
|
231
353
|
);
|
|
354
|
+
if (srcFile) {
|
|
355
|
+
sourceContent = fs.readFileSync(
|
|
356
|
+
path.join(templatePath, srcFile),
|
|
357
|
+
"utf-8",
|
|
358
|
+
);
|
|
359
|
+
} else if (files.length > 0) {
|
|
360
|
+
sourceContent = fs.readFileSync(
|
|
361
|
+
path.join(templatePath, files[0]),
|
|
362
|
+
"utf-8",
|
|
363
|
+
);
|
|
364
|
+
}
|
|
232
365
|
} else {
|
|
233
|
-
sourceContent =
|
|
234
|
-
|
|
235
|
-
|
|
366
|
+
sourceContent = `import React from 'react';\n\nexport const ${REGISTRY[comp]?.name || "Component"} = () => { return <div>${comp}</div>; };\n`;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Format code
|
|
370
|
+
let formattedContent = sourceContent;
|
|
371
|
+
try {
|
|
372
|
+
formattedContent = await formatFile(sourceContent, targetPath);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
console.log(
|
|
375
|
+
chalk.yellow(
|
|
376
|
+
`\n⚠️ Formatting failed for ${comp}. Using original formatting.`,
|
|
377
|
+
),
|
|
236
378
|
);
|
|
237
379
|
}
|
|
238
|
-
} else {
|
|
239
|
-
// Fallback or generic content simply to show it works
|
|
240
|
-
sourceContent = `import React from 'react';\n\nexport const ${REGISTRY[componentName]?.name || "Component"} = () => { return <div>${componentName}</div>; };\n`;
|
|
241
|
-
}
|
|
242
380
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// Simple type stripping if needed, but omitted for stability.
|
|
246
|
-
// It's recommended to publish compiled JS if targeting non-TS,
|
|
247
|
-
// or let the user fix up the .jsx file.
|
|
248
|
-
}
|
|
381
|
+
fs.writeFileSync(targetPath, formattedContent, "utf-8");
|
|
382
|
+
spinner.succeed(`Created ${componentFile} in ${env.componentsDir}`);
|
|
249
383
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
384
|
+
// Track dependencies
|
|
385
|
+
const registryEntry = REGISTRY[comp];
|
|
386
|
+
if (registryEntry && registryEntry.dependencies) {
|
|
387
|
+
registryEntry.dependencies.forEach((d) => allDependencies.add(d));
|
|
388
|
+
}
|
|
254
389
|
} catch (err) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
);
|
|
390
|
+
spinner.fail(`Failed to add ${comp}`);
|
|
391
|
+
console.error(chalk.red(err.message));
|
|
258
392
|
}
|
|
393
|
+
}
|
|
259
394
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
installDependencies(registryEntry.dependencies, env.pkgCommand);
|
|
272
|
-
depSpinner.succeed(`Installed dependencies.`);
|
|
395
|
+
if (allDependencies.size > 0) {
|
|
396
|
+
const depsArray = Array.from(allDependencies);
|
|
397
|
+
const depSpinner = ora(
|
|
398
|
+
`Installing collective dependencies: ${depsArray.join(", ")}...`,
|
|
399
|
+
).start();
|
|
400
|
+
try {
|
|
401
|
+
installDependencies(depsArray, env.pkgCommand);
|
|
402
|
+
depSpinner.succeed(`Dependencies installed successfully.`);
|
|
403
|
+
} catch (err) {
|
|
404
|
+
depSpinner.fail(`Failed to install dependencies.`);
|
|
405
|
+
console.error(chalk.red(err.message));
|
|
273
406
|
}
|
|
274
|
-
|
|
275
|
-
console.log(chalk.green(`\n✅ ${componentName} added successfully!`));
|
|
276
|
-
} catch (err) {
|
|
277
|
-
spinner.fail(`Failed to add ${componentName}`);
|
|
278
|
-
console.error(chalk.red(err.message));
|
|
279
407
|
}
|
|
408
|
+
|
|
409
|
+
console.log(chalk.green(`\n✅ Finished adding components!`));
|
|
280
410
|
} else {
|
|
281
411
|
console.log(chalk.red(`❌ Unknown command: ${command}`));
|
|
282
412
|
}
|