@srcroot/ui 0.0.64 → 1.0.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/dist/index.js +187 -97
- package/package.json +2 -2
- package/src/registry/analytics/google-analytics.vite.tsx +35 -0
- package/src/registry/analytics/google-tag-manager.vite.tsx +50 -0
- package/src/registry/analytics/meta-pixel.vite.tsx +38 -0
- package/src/registry/analytics/microsoft-clarity.vite.tsx +36 -0
- package/src/registry/analytics/tiktok-pixel.vite.tsx +39 -0
- package/src/registry/ui/theme-switcher.vite.tsx +73 -0
package/dist/index.js
CHANGED
|
@@ -5,18 +5,43 @@ import { Command } from "commander";
|
|
|
5
5
|
import chalk3 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/cli/services/project-initializer.ts
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import fs5 from "fs-extra";
|
|
9
|
+
import path5 from "path";
|
|
10
10
|
import ora from "ora";
|
|
11
11
|
import prompts from "prompts";
|
|
12
|
-
import { fileURLToPath as
|
|
12
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13
13
|
import { execa } from "execa";
|
|
14
14
|
|
|
15
15
|
// src/cli/services/theme-service.ts
|
|
16
|
-
import
|
|
16
|
+
import fs2 from "fs-extra";
|
|
17
|
+
import path2 from "path";
|
|
18
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
19
|
+
|
|
20
|
+
// src/cli/utils/get-registry-path.ts
|
|
17
21
|
import path from "path";
|
|
22
|
+
import fs from "fs-extra";
|
|
18
23
|
import { fileURLToPath } from "url";
|
|
19
|
-
|
|
24
|
+
function getRegistryPath() {
|
|
25
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname5 = path.dirname(__filename2);
|
|
27
|
+
const pathsToCheck = [
|
|
28
|
+
path.resolve(__dirname5, "..", "src", "registry"),
|
|
29
|
+
// Production case
|
|
30
|
+
path.resolve(__dirname5, "..", "..", "registry"),
|
|
31
|
+
// Development case
|
|
32
|
+
path.resolve(process.cwd(), "src", "registry")
|
|
33
|
+
// Fallback/CWD case
|
|
34
|
+
];
|
|
35
|
+
for (const p of pathsToCheck) {
|
|
36
|
+
if (fs.existsSync(p)) {
|
|
37
|
+
return p;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return path.resolve(__dirname5, "..", "src", "registry");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/cli/services/theme-service.ts
|
|
44
|
+
var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
20
45
|
var THEME_METADATA = {
|
|
21
46
|
slate: { name: "Slate", description: "Cool gray with strong blue undertones (default)" },
|
|
22
47
|
neutral: { name: "Neutral", description: "Pure gray, no undertones" },
|
|
@@ -33,18 +58,18 @@ var THEME_METADATA = {
|
|
|
33
58
|
var ThemeService = class {
|
|
34
59
|
registryThemesPath;
|
|
35
60
|
constructor() {
|
|
36
|
-
this.registryThemesPath =
|
|
61
|
+
this.registryThemesPath = path2.join(getRegistryPath(), "themes");
|
|
37
62
|
}
|
|
38
63
|
/**
|
|
39
64
|
* Get list of available themes from registry/themes/v3/*.css (v3 as primary source)
|
|
40
65
|
*/
|
|
41
66
|
getAvailableThemes() {
|
|
42
67
|
const themes = [];
|
|
43
|
-
const v3Path =
|
|
44
|
-
if (!
|
|
68
|
+
const v3Path = path2.join(this.registryThemesPath, "v3");
|
|
69
|
+
if (!fs2.existsSync(v3Path)) {
|
|
45
70
|
return themes;
|
|
46
71
|
}
|
|
47
|
-
const files =
|
|
72
|
+
const files = fs2.readdirSync(v3Path);
|
|
48
73
|
for (const file of files) {
|
|
49
74
|
if (file.endsWith(".css")) {
|
|
50
75
|
const themeName = file.replace(".css", "");
|
|
@@ -66,25 +91,23 @@ var ThemeService = class {
|
|
|
66
91
|
*/
|
|
67
92
|
async getThemeCss(themeName, isTailwind4) {
|
|
68
93
|
const versionFolder = isTailwind4 ? "v4" : "v3";
|
|
69
|
-
const themeFilePath =
|
|
70
|
-
if (!
|
|
94
|
+
const themeFilePath = path2.join(this.registryThemesPath, versionFolder, `${themeName}.css`);
|
|
95
|
+
if (!fs2.existsSync(themeFilePath)) {
|
|
71
96
|
throw new Error(`Theme file not found: ${themeFilePath}`);
|
|
72
97
|
}
|
|
73
|
-
const content = await
|
|
98
|
+
const content = await fs2.readFile(themeFilePath, "utf-8");
|
|
74
99
|
return content;
|
|
75
100
|
}
|
|
76
101
|
};
|
|
77
102
|
|
|
78
103
|
// src/cli/utils/templates.ts
|
|
79
|
-
|
|
104
|
+
function getTailwindConfig(framework) {
|
|
105
|
+
const contentPaths = framework === "vite" ? ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"] : ["./src/**/*.{js,ts,jsx,tsx,mdx}", "./app/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}"];
|
|
106
|
+
return `import type { Config } from "tailwindcss"
|
|
80
107
|
|
|
81
108
|
const config: Config = {
|
|
82
109
|
darkMode: ["class"],
|
|
83
|
-
content: [
|
|
84
|
-
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
|
85
|
-
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
86
|
-
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
87
|
-
],
|
|
110
|
+
content: ${JSON.stringify(contentPaths, null, 6).replace(/\n/g, "\n ").replace(/\[$/, " [").replace(/\]$/, " ]")},
|
|
88
111
|
theme: {
|
|
89
112
|
extend: {
|
|
90
113
|
colors: {
|
|
@@ -148,16 +171,18 @@ const config: Config = {
|
|
|
148
171
|
|
|
149
172
|
export default config
|
|
150
173
|
`;
|
|
174
|
+
}
|
|
175
|
+
var TAILWIND_CONFIG = getTailwindConfig("nextjs");
|
|
151
176
|
|
|
152
177
|
// src/cli/utils/get-package-manager.ts
|
|
153
|
-
import
|
|
154
|
-
import
|
|
178
|
+
import fs3 from "fs";
|
|
179
|
+
import path3 from "path";
|
|
155
180
|
function getPackageManager(cwd) {
|
|
156
181
|
const dir = cwd || process.cwd();
|
|
157
|
-
if (
|
|
158
|
-
if (
|
|
159
|
-
if (
|
|
160
|
-
if (
|
|
182
|
+
if (fs3.existsSync(path3.join(dir, "bun.lockb"))) return "bun";
|
|
183
|
+
if (fs3.existsSync(path3.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
184
|
+
if (fs3.existsSync(path3.join(dir, "yarn.lock"))) return "yarn";
|
|
185
|
+
if (fs3.existsSync(path3.join(dir, "package-lock.json"))) return "npm";
|
|
161
186
|
const userAgent = process.env.npm_config_user_agent;
|
|
162
187
|
if (userAgent) {
|
|
163
188
|
if (userAgent.startsWith("yarn")) return "yarn";
|
|
@@ -168,19 +193,19 @@ function getPackageManager(cwd) {
|
|
|
168
193
|
}
|
|
169
194
|
|
|
170
195
|
// src/cli/utils/get-package-info.ts
|
|
171
|
-
import
|
|
172
|
-
import
|
|
173
|
-
import { fileURLToPath as
|
|
196
|
+
import path4 from "path";
|
|
197
|
+
import fs4 from "fs-extra";
|
|
198
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
174
199
|
function getPackageInfo() {
|
|
175
|
-
const __filename2 =
|
|
176
|
-
const __dirname5 =
|
|
200
|
+
const __filename2 = fileURLToPath3(import.meta.url);
|
|
201
|
+
const __dirname5 = path4.dirname(__filename2);
|
|
177
202
|
const pathsToCheck = [
|
|
178
|
-
|
|
179
|
-
|
|
203
|
+
path4.resolve(__dirname5, "..", "package.json"),
|
|
204
|
+
path4.resolve(__dirname5, "..", "..", "..", "package.json")
|
|
180
205
|
];
|
|
181
206
|
for (const pkgPath of pathsToCheck) {
|
|
182
|
-
if (
|
|
183
|
-
return
|
|
207
|
+
if (fs4.existsSync(pkgPath)) {
|
|
208
|
+
return fs4.readJSONSync(pkgPath);
|
|
184
209
|
}
|
|
185
210
|
}
|
|
186
211
|
return { version: "0.0.0" };
|
|
@@ -204,7 +229,7 @@ var logger = {
|
|
|
204
229
|
};
|
|
205
230
|
|
|
206
231
|
// src/cli/services/project-initializer.ts
|
|
207
|
-
var __dirname3 =
|
|
232
|
+
var __dirname3 = path5.dirname(fileURLToPath4(import.meta.url));
|
|
208
233
|
var ProjectInitializer = class {
|
|
209
234
|
options;
|
|
210
235
|
config = {};
|
|
@@ -223,47 +248,60 @@ var ProjectInitializer = class {
|
|
|
223
248
|
this.printSuccess();
|
|
224
249
|
}
|
|
225
250
|
async validateEnvironment() {
|
|
226
|
-
const cwd =
|
|
227
|
-
const packageJsonPath =
|
|
228
|
-
if (!
|
|
251
|
+
const cwd = path5.resolve(this.options.cwd);
|
|
252
|
+
const packageJsonPath = path5.join(cwd, "package.json");
|
|
253
|
+
if (!fs5.existsSync(packageJsonPath)) {
|
|
229
254
|
logger.error("Error: No package.json found. Please run this in a project directory.");
|
|
230
255
|
process.exit(1);
|
|
231
256
|
}
|
|
232
|
-
const pkg = await
|
|
257
|
+
const pkg = await fs5.readJson(packageJsonPath);
|
|
233
258
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
234
259
|
if (!allDeps["react"]) {
|
|
235
260
|
logger.error("Error: React not found in dependencies. Please initialize this in a React project.");
|
|
236
261
|
process.exit(1);
|
|
237
262
|
}
|
|
238
263
|
}
|
|
264
|
+
detectFramework(cwd) {
|
|
265
|
+
if (fs5.existsSync(path5.join(cwd, "next.config.ts")) || fs5.existsSync(path5.join(cwd, "next.config.js")) || fs5.existsSync(path5.join(cwd, "next.config.mjs"))) {
|
|
266
|
+
return "nextjs";
|
|
267
|
+
}
|
|
268
|
+
if (fs5.existsSync(path5.join(cwd, "vite.config.ts")) || fs5.existsSync(path5.join(cwd, "vite.config.js")) || fs5.existsSync(path5.join(cwd, "vite.config.mjs"))) {
|
|
269
|
+
return "vite";
|
|
270
|
+
}
|
|
271
|
+
if (fs5.existsSync(path5.join(cwd, "src", "app"))) {
|
|
272
|
+
return "nextjs";
|
|
273
|
+
}
|
|
274
|
+
return "unknown";
|
|
275
|
+
}
|
|
239
276
|
async detectConfiguration() {
|
|
240
|
-
const cwd =
|
|
277
|
+
const cwd = path5.resolve(this.options.cwd);
|
|
241
278
|
const packageManager = getPackageManager(cwd);
|
|
242
279
|
const installCmd = packageManager === "npm" ? "install" : "add";
|
|
243
|
-
const
|
|
280
|
+
const framework = this.detectFramework(cwd);
|
|
281
|
+
const pkg = await fs5.readJson(path5.join(cwd, "package.json"));
|
|
244
282
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
245
283
|
const tailwindVersion = allDeps["tailwindcss"] || "";
|
|
246
284
|
const isTailwind4 = tailwindVersion.includes("^4") || tailwindVersion.startsWith("4") || allDeps["@tailwindcss/postcss"];
|
|
247
|
-
const hasSrc =
|
|
248
|
-
const srcPath = hasSrc ?
|
|
249
|
-
const appPath =
|
|
250
|
-
const pagesPath =
|
|
251
|
-
const hasAppDir =
|
|
252
|
-
const hasPagesDir =
|
|
253
|
-
const libDir =
|
|
254
|
-
const componentsDir =
|
|
285
|
+
const hasSrc = fs5.existsSync(path5.join(cwd, "src"));
|
|
286
|
+
const srcPath = hasSrc ? path5.join(cwd, "src") : cwd;
|
|
287
|
+
const appPath = path5.join(srcPath, "app");
|
|
288
|
+
const pagesPath = path5.join(srcPath, "pages");
|
|
289
|
+
const hasAppDir = fs5.existsSync(appPath);
|
|
290
|
+
const hasPagesDir = fs5.existsSync(pagesPath);
|
|
291
|
+
const libDir = path5.join(srcPath, "lib");
|
|
292
|
+
const componentsDir = path5.join(srcPath, "components", "ui");
|
|
255
293
|
let globalsPath = "";
|
|
256
294
|
if (hasAppDir) {
|
|
257
|
-
if (
|
|
258
|
-
else if (
|
|
259
|
-
else globalsPath =
|
|
295
|
+
if (fs5.existsSync(path5.join(appPath, "globals.css"))) globalsPath = path5.join(appPath, "globals.css");
|
|
296
|
+
else if (fs5.existsSync(path5.join(appPath, "global.css"))) globalsPath = path5.join(appPath, "global.css");
|
|
297
|
+
else globalsPath = path5.join(appPath, "globals.css");
|
|
260
298
|
} else if (hasPagesDir) {
|
|
261
|
-
const stylesPath =
|
|
262
|
-
if (
|
|
263
|
-
else if (
|
|
264
|
-
else globalsPath =
|
|
299
|
+
const stylesPath = path5.join(srcPath, "styles");
|
|
300
|
+
if (fs5.existsSync(path5.join(stylesPath, "globals.css"))) globalsPath = path5.join(stylesPath, "globals.css");
|
|
301
|
+
else if (fs5.existsSync(path5.join(stylesPath, "global.css"))) globalsPath = path5.join(stylesPath, "global.css");
|
|
302
|
+
else globalsPath = path5.join(stylesPath, "globals.css");
|
|
265
303
|
} else {
|
|
266
|
-
globalsPath =
|
|
304
|
+
globalsPath = path5.join(srcPath, "globals.css");
|
|
267
305
|
}
|
|
268
306
|
this.config = {
|
|
269
307
|
cwd,
|
|
@@ -278,7 +316,8 @@ var ProjectInitializer = class {
|
|
|
278
316
|
hasPagesDir,
|
|
279
317
|
libDir,
|
|
280
318
|
componentsDir,
|
|
281
|
-
globalsPath
|
|
319
|
+
globalsPath,
|
|
320
|
+
framework
|
|
282
321
|
};
|
|
283
322
|
}
|
|
284
323
|
async promptUser() {
|
|
@@ -311,13 +350,13 @@ var ProjectInitializer = class {
|
|
|
311
350
|
const spinner = ora("Creating project structure...").start();
|
|
312
351
|
const cfg = this.config;
|
|
313
352
|
try {
|
|
314
|
-
await
|
|
315
|
-
await
|
|
316
|
-
const utilsPath =
|
|
317
|
-
const registryUtilsPath =
|
|
353
|
+
await fs5.ensureDir(cfg.libDir);
|
|
354
|
+
await fs5.ensureDir(cfg.componentsDir);
|
|
355
|
+
const utilsPath = path5.join(cfg.libDir, "utils.ts");
|
|
356
|
+
const registryUtilsPath = path5.join(getRegistryPath(), "lib", "utils.ts");
|
|
318
357
|
let utilsContent = "";
|
|
319
|
-
if (
|
|
320
|
-
utilsContent = await
|
|
358
|
+
if (fs5.existsSync(registryUtilsPath)) {
|
|
359
|
+
utilsContent = await fs5.readFile(registryUtilsPath, "utf-8");
|
|
321
360
|
} else {
|
|
322
361
|
utilsContent = `import { type ClassValue, clsx } from "clsx"
|
|
323
362
|
import { twMerge } from "tailwind-merge"
|
|
@@ -328,15 +367,15 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
328
367
|
`;
|
|
329
368
|
spinner.warn(`Could not find registry/utils.ts, using fallback content.`);
|
|
330
369
|
}
|
|
331
|
-
await
|
|
332
|
-
spinner.succeed(`Created ${
|
|
370
|
+
await fs5.writeFile(utilsPath, utilsContent);
|
|
371
|
+
spinner.succeed(`Created ${path5.relative(cfg.cwd, utilsPath)}`);
|
|
333
372
|
spinner.start(`Setting up ${cfg.selectedTheme} theme...`);
|
|
334
|
-
const stylesDir =
|
|
335
|
-
await
|
|
373
|
+
const stylesDir = path5.dirname(cfg.globalsPath);
|
|
374
|
+
await fs5.ensureDir(stylesDir);
|
|
336
375
|
try {
|
|
337
376
|
const cssContent = await this.themeService.getThemeCss(cfg.selectedTheme, cfg.isTailwind4);
|
|
338
|
-
await
|
|
339
|
-
spinner.succeed(`Updated ${
|
|
377
|
+
await fs5.writeFile(cfg.globalsPath, cssContent);
|
|
378
|
+
spinner.succeed(`Updated ${path5.relative(cfg.cwd, cfg.globalsPath)} with ${cfg.selectedTheme} theme (${cfg.isTailwind4 ? "Tailwind 4" : "Tailwind 3"})`);
|
|
340
379
|
} catch (error) {
|
|
341
380
|
spinner.fail(`Failed to load theme: ${cfg.selectedTheme}`);
|
|
342
381
|
console.error(error);
|
|
@@ -344,8 +383,8 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
344
383
|
}
|
|
345
384
|
if (!cfg.isTailwind4) {
|
|
346
385
|
spinner.start("Setting up Tailwind config...");
|
|
347
|
-
const tailwindConfigPath =
|
|
348
|
-
await
|
|
386
|
+
const tailwindConfigPath = path5.join(cfg.cwd, "tailwind.config.ts");
|
|
387
|
+
await fs5.writeFile(tailwindConfigPath, getTailwindConfig(cfg.framework));
|
|
349
388
|
spinner.succeed(`Created tailwind.config.ts`);
|
|
350
389
|
}
|
|
351
390
|
const packageInfo = getPackageInfo();
|
|
@@ -353,11 +392,11 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
353
392
|
version: packageInfo.version || "0.0.0",
|
|
354
393
|
theme: cfg.selectedTheme,
|
|
355
394
|
paths: {
|
|
356
|
-
components:
|
|
357
|
-
utils:
|
|
395
|
+
components: path5.relative(cfg.cwd, cfg.componentsDir),
|
|
396
|
+
utils: path5.relative(cfg.cwd, utilsPath)
|
|
358
397
|
}
|
|
359
398
|
};
|
|
360
|
-
await
|
|
399
|
+
await fs5.writeJSON(path5.join(cfg.cwd, "srcroot.config.json"), configObj, { spaces: 2 });
|
|
361
400
|
spinner.succeed("Created srcroot.config.json");
|
|
362
401
|
} catch (error) {
|
|
363
402
|
spinner.fail("Failed to initialize project");
|
|
@@ -378,8 +417,8 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
378
417
|
deps.push("tailwindcss-animate");
|
|
379
418
|
}
|
|
380
419
|
try {
|
|
381
|
-
const packageJsonPath =
|
|
382
|
-
const pkg = await
|
|
420
|
+
const packageJsonPath = path5.join(cfg.cwd, "package.json");
|
|
421
|
+
const pkg = await fs5.readJson(packageJsonPath);
|
|
383
422
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
384
423
|
const missingDeps = deps.filter((dep) => !allDeps[dep]);
|
|
385
424
|
if (missingDeps.length === 0) {
|
|
@@ -414,15 +453,15 @@ async function init(options) {
|
|
|
414
453
|
}
|
|
415
454
|
|
|
416
455
|
// src/cli/commands/add.ts
|
|
417
|
-
import
|
|
456
|
+
import path7 from "path";
|
|
418
457
|
|
|
419
458
|
// src/cli/services/component-adder.ts
|
|
420
|
-
import
|
|
421
|
-
import
|
|
459
|
+
import fs6 from "fs-extra";
|
|
460
|
+
import path6 from "path";
|
|
422
461
|
import ora2 from "ora";
|
|
423
462
|
import prompts2 from "prompts";
|
|
424
463
|
import { execa as execa2 } from "execa";
|
|
425
|
-
import { fileURLToPath as
|
|
464
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
426
465
|
|
|
427
466
|
// src/cli/registry.ts
|
|
428
467
|
var REGISTRY = {
|
|
@@ -898,15 +937,50 @@ var REGISTRY = {
|
|
|
898
937
|
};
|
|
899
938
|
|
|
900
939
|
// src/cli/services/component-adder.ts
|
|
901
|
-
var __dirname4 =
|
|
940
|
+
var __dirname4 = path6.dirname(fileURLToPath5(import.meta.url));
|
|
941
|
+
var FRAMEWORK_SPECIFIC_COMPONENTS = {
|
|
942
|
+
"google-analytics": "analytics/google-analytics.vite.tsx",
|
|
943
|
+
"google-tag-manager": "analytics/google-tag-manager.vite.tsx",
|
|
944
|
+
"meta-pixel": "analytics/meta-pixel.vite.tsx",
|
|
945
|
+
"microsoft-clarity": "analytics/microsoft-clarity.vite.tsx",
|
|
946
|
+
"tiktok-pixel": "analytics/tiktok-pixel.vite.tsx",
|
|
947
|
+
"theme-switcher": "ui/theme-switcher.vite.tsx"
|
|
948
|
+
};
|
|
902
949
|
var ComponentAdder = class {
|
|
903
950
|
cwd;
|
|
904
951
|
options;
|
|
952
|
+
framework = "unknown";
|
|
905
953
|
constructor(cwd, options) {
|
|
906
954
|
this.cwd = cwd;
|
|
907
955
|
this.options = options;
|
|
956
|
+
this.framework = this.detectFramework();
|
|
957
|
+
}
|
|
958
|
+
detectFramework() {
|
|
959
|
+
if (fs6.existsSync(path6.join(this.cwd, "next.config.ts")) || fs6.existsSync(path6.join(this.cwd, "next.config.js")) || fs6.existsSync(path6.join(this.cwd, "next.config.mjs"))) {
|
|
960
|
+
return "nextjs";
|
|
961
|
+
}
|
|
962
|
+
if (fs6.existsSync(path6.join(this.cwd, "vite.config.ts")) || fs6.existsSync(path6.join(this.cwd, "vite.config.js")) || fs6.existsSync(path6.join(this.cwd, "vite.config.mjs"))) {
|
|
963
|
+
return "vite";
|
|
964
|
+
}
|
|
965
|
+
if (fs6.existsSync(path6.join(this.cwd, "src", "app"))) {
|
|
966
|
+
return "nextjs";
|
|
967
|
+
}
|
|
968
|
+
return "unknown";
|
|
969
|
+
}
|
|
970
|
+
transformForVite(name, content) {
|
|
971
|
+
content = content.replace(/^"use client"[;\n\r]*/m, "");
|
|
972
|
+
content = content.replace(/import\s*{\s*useTheme\s*}\s*from\s*"next-themes"\s*;?\n?/g, "");
|
|
973
|
+
content = content.replace(/import\s*{\s*ThemeProvider\s*}\s*from\s*"next-themes"\s*;?\n?/g, "");
|
|
974
|
+
if (name === "theme-switcher") {
|
|
975
|
+
}
|
|
976
|
+
return content.trimStart();
|
|
908
977
|
}
|
|
909
978
|
async add(components) {
|
|
979
|
+
if (this.framework === "vite") {
|
|
980
|
+
logger.info("Detected Vite project - using Vite-compatible components");
|
|
981
|
+
} else if (this.framework === "nextjs") {
|
|
982
|
+
logger.info("Detected Next.js project - using Next.js components");
|
|
983
|
+
}
|
|
910
984
|
components = await this.resolveComponents(components);
|
|
911
985
|
const { valid, invalid } = this.validateComponents(components);
|
|
912
986
|
if (invalid.length > 0) {
|
|
@@ -1013,20 +1087,20 @@ Please manually install: ${packages.join(" ")}`);
|
|
|
1013
1087
|
}
|
|
1014
1088
|
async copyComponents(components) {
|
|
1015
1089
|
const spinner = ora2("Adding components...").start();
|
|
1016
|
-
const hasSrc =
|
|
1017
|
-
const srcPath = hasSrc ?
|
|
1018
|
-
const componentsDir =
|
|
1090
|
+
const hasSrc = fs6.existsSync(path6.join(this.cwd, "src"));
|
|
1091
|
+
const srcPath = hasSrc ? path6.join(this.cwd, "src") : this.cwd;
|
|
1092
|
+
const componentsDir = path6.join(srcPath, "components", "ui");
|
|
1019
1093
|
try {
|
|
1020
|
-
await
|
|
1094
|
+
await fs6.ensureDir(componentsDir);
|
|
1021
1095
|
let overwriteAll = false;
|
|
1022
1096
|
let skipAll = false;
|
|
1023
1097
|
if (!this.options.overwrite) {
|
|
1024
1098
|
const conflicts = [];
|
|
1025
1099
|
for (const name of components) {
|
|
1026
1100
|
const comp = REGISTRY[name];
|
|
1027
|
-
const fileName =
|
|
1028
|
-
const targetPath =
|
|
1029
|
-
if (
|
|
1101
|
+
const fileName = path6.basename(comp.file);
|
|
1102
|
+
const targetPath = path6.join(componentsDir, fileName);
|
|
1103
|
+
if (fs6.existsSync(targetPath)) {
|
|
1030
1104
|
conflicts.push(fileName);
|
|
1031
1105
|
}
|
|
1032
1106
|
}
|
|
@@ -1055,11 +1129,12 @@ Please manually install: ${packages.join(" ")}`);
|
|
|
1055
1129
|
}
|
|
1056
1130
|
spinner.start("Adding components...");
|
|
1057
1131
|
}
|
|
1132
|
+
let addedCount = 0;
|
|
1058
1133
|
for (const name of components) {
|
|
1059
1134
|
const comp = REGISTRY[name];
|
|
1060
|
-
const fileName =
|
|
1061
|
-
const targetPath =
|
|
1062
|
-
if (
|
|
1135
|
+
const fileName = path6.basename(comp.file);
|
|
1136
|
+
const targetPath = path6.join(componentsDir, fileName);
|
|
1137
|
+
if (fs6.existsSync(targetPath) && !this.options.overwrite && !overwriteAll) {
|
|
1063
1138
|
if (skipAll) {
|
|
1064
1139
|
spinner.info(`Skipped ${fileName}`);
|
|
1065
1140
|
continue;
|
|
@@ -1078,13 +1153,28 @@ Please manually install: ${packages.join(" ")}`);
|
|
|
1078
1153
|
}
|
|
1079
1154
|
spinner.start("Adding components...");
|
|
1080
1155
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1156
|
+
let registryPath = path6.resolve(getRegistryPath(), comp.file);
|
|
1157
|
+
let content = "";
|
|
1158
|
+
if (this.framework === "vite") {
|
|
1159
|
+
const viteVariant = FRAMEWORK_SPECIFIC_COMPONENTS[name];
|
|
1160
|
+
if (viteVariant) {
|
|
1161
|
+
const vitePath = path6.resolve(getRegistryPath(), viteVariant);
|
|
1162
|
+
if (fs6.existsSync(vitePath)) {
|
|
1163
|
+
registryPath = vitePath;
|
|
1164
|
+
spinner.info(`Using Vite-specific ${fileName}`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (!fs6.existsSync(registryPath)) {
|
|
1083
1169
|
spinner.warn(`Registry file not found for ${name}: ${registryPath}`);
|
|
1084
1170
|
continue;
|
|
1085
1171
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1172
|
+
content = await fs6.readFile(registryPath, "utf-8");
|
|
1173
|
+
if (this.framework === "vite") {
|
|
1174
|
+
content = this.transformForVite(name, content);
|
|
1175
|
+
}
|
|
1176
|
+
await fs6.writeFile(targetPath, content);
|
|
1177
|
+
addedCount++;
|
|
1088
1178
|
if (components.length > 10) {
|
|
1089
1179
|
spinner.text = `Adding ${fileName}...`;
|
|
1090
1180
|
} else {
|
|
@@ -1092,7 +1182,7 @@ Please manually install: ${packages.join(" ")}`);
|
|
|
1092
1182
|
}
|
|
1093
1183
|
}
|
|
1094
1184
|
if (components.length > 10) {
|
|
1095
|
-
spinner.succeed(`Added ${
|
|
1185
|
+
spinner.succeed(`Added ${addedCount} components`);
|
|
1096
1186
|
}
|
|
1097
1187
|
} catch (error) {
|
|
1098
1188
|
spinner.fail("Failed to add components");
|
|
@@ -1104,7 +1194,7 @@ Please manually install: ${packages.join(" ")}`);
|
|
|
1104
1194
|
|
|
1105
1195
|
// src/cli/commands/add.ts
|
|
1106
1196
|
async function add(components, options) {
|
|
1107
|
-
const cwd =
|
|
1197
|
+
const cwd = path7.resolve(options.cwd);
|
|
1108
1198
|
const adder = new ComponentAdder(cwd, options);
|
|
1109
1199
|
await adder.add(components);
|
|
1110
1200
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@srcroot/ui",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A UI library with polymorphic, accessible React components",
|
|
5
5
|
"author": "Shifaul Islam",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,4 +71,4 @@
|
|
|
71
71
|
"optional": true
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC } from "react"
|
|
3
|
+
|
|
4
|
+
interface GoogleAnalyticsProps {
|
|
5
|
+
gaIds: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const GoogleAnalytics: FC<GoogleAnalyticsProps> = ({ gaIds }) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (gaIds.length === 0) return
|
|
11
|
+
|
|
12
|
+
const gtmScript = document.createElement("script")
|
|
13
|
+
gtmScript.async = true
|
|
14
|
+
gtmScript.src = `https://www.googletagmanager.com/gtag/js?id=${gaIds[0]}`
|
|
15
|
+
document.head.appendChild(gtmScript)
|
|
16
|
+
|
|
17
|
+
const inlineScript = document.createElement("script")
|
|
18
|
+
inlineScript.innerHTML = `
|
|
19
|
+
window.dataLayer = window.dataLayer || [];
|
|
20
|
+
function gtag(){dataLayer.push(arguments);}
|
|
21
|
+
gtag('js', new Date());
|
|
22
|
+
${gaIds.map((id) => `gtag('config', '${id}', { page_path: window.location.pathname });`).join("\n")}
|
|
23
|
+
`
|
|
24
|
+
document.head.appendChild(inlineScript)
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
document.head.removeChild(gtmScript)
|
|
28
|
+
document.head.removeChild(inlineScript)
|
|
29
|
+
}
|
|
30
|
+
}, [gaIds])
|
|
31
|
+
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default GoogleAnalytics
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC, ReactNode } from "react"
|
|
3
|
+
|
|
4
|
+
interface GTMContainer {
|
|
5
|
+
gtmId: string;
|
|
6
|
+
tagServerUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface GoogleTagManagerProps {
|
|
10
|
+
containers: GTMContainer[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const GoogleTagManager: FC<GoogleTagManagerProps> = ({ containers }) => {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const defaultServer = "https://www.googletagmanager.com"
|
|
16
|
+
|
|
17
|
+
const scriptsMap = containers.reduce((map, container) => {
|
|
18
|
+
const server = container.tagServerUrl || defaultServer;
|
|
19
|
+
if (!map.has(server)) {
|
|
20
|
+
map.set(server, []);
|
|
21
|
+
}
|
|
22
|
+
map.get(server)!.push(container.gtmId);
|
|
23
|
+
return map;
|
|
24
|
+
}, new Map<string, string[]>()); const scriptElements: HTMLScriptElement[] = []
|
|
25
|
+
|
|
26
|
+
Array.from(scriptsMap.entries()).forEach(([server, ids]) => {
|
|
27
|
+
ids.forEach((id) => {
|
|
28
|
+
const script = document.createElement("script")
|
|
29
|
+
script.innerHTML = `
|
|
30
|
+
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=true;j.src="${server}/gtm.js?"+i;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${id}');
|
|
31
|
+
`
|
|
32
|
+
script.id = `gtm-script-${server}-${id}`
|
|
33
|
+
document.head.appendChild(script)
|
|
34
|
+
scriptElements.push(script)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
scriptElements.forEach((script) => {
|
|
40
|
+
if (document.head.contains(script)) {
|
|
41
|
+
document.head.removeChild(script)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}, [containers])
|
|
46
|
+
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default GoogleTagManager
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC } from "react"
|
|
3
|
+
|
|
4
|
+
interface MetaPixelProps {
|
|
5
|
+
pixelIds: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const MetaPixel: FC<MetaPixelProps> = ({ pixelIds }) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (pixelIds.length === 0) return
|
|
11
|
+
|
|
12
|
+
const script = document.createElement("script")
|
|
13
|
+
script.innerHTML = `
|
|
14
|
+
!function(f,b,e,v,n,t,s)
|
|
15
|
+
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
|
16
|
+
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
|
17
|
+
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
|
18
|
+
n.queue=[];t=b.createElement(e);t.async=!0;
|
|
19
|
+
t.src=v;s=b.getElementsByTagName(e)[0];
|
|
20
|
+
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
|
21
|
+
'https://connect.facebook.net/en_US/fbevents.js');
|
|
22
|
+
${pixelIds.map((id) => `fbq('init', '${id}');`).join("\n")}
|
|
23
|
+
fbq('track', 'PageView');
|
|
24
|
+
`
|
|
25
|
+
script.id = "fb-script-multi"
|
|
26
|
+
document.head.appendChild(script)
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
if (document.head.contains(script)) {
|
|
30
|
+
document.head.removeChild(script)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}, [pixelIds])
|
|
34
|
+
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default MetaPixel
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC } from "react"
|
|
3
|
+
|
|
4
|
+
interface MicrosoftClarityProps {
|
|
5
|
+
clarityIds: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const MicrosoftClarity: FC<MicrosoftClarityProps> = ({ clarityIds }) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
clarityIds.forEach((id) => {
|
|
11
|
+
const script = document.createElement("script")
|
|
12
|
+
script.innerHTML = `
|
|
13
|
+
(function(c,l,a,r,i,t,y){
|
|
14
|
+
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
|
15
|
+
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
|
16
|
+
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
|
17
|
+
})(window, document, "clarity", "script", "${id}");
|
|
18
|
+
`
|
|
19
|
+
script.id = `microsoft-clarity-init-${id}`
|
|
20
|
+
document.head.appendChild(script)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return () => {
|
|
24
|
+
clarityIds.forEach((id) => {
|
|
25
|
+
const script = document.getElementById(`microsoft-clarity-init-${id}`)
|
|
26
|
+
if (script && document.head.contains(script)) {
|
|
27
|
+
document.head.removeChild(script)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}, [clarityIds])
|
|
32
|
+
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default MicrosoftClarity
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC } from "react"
|
|
3
|
+
|
|
4
|
+
interface TikTokPixelProps {
|
|
5
|
+
pixelIds: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const TikTokPixel: FC<TikTokPixelProps> = ({ pixelIds }) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const script = document.createElement("script")
|
|
11
|
+
script.innerHTML = `
|
|
12
|
+
!function (w, d, t) {
|
|
13
|
+
w.TiktokAnalyticsObject=t;var ttq=w[t]=w[t]||[];
|
|
14
|
+
ttq.methods=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie","holdConsent","revokeConsent","grantConsent"],
|
|
15
|
+
ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}};
|
|
16
|
+
for(var i=0;i<ttq.methods.length;i++)ttq.setAndDefer(ttq,ttq.methods[i]);
|
|
17
|
+
ttq.instance=function(t){for(var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e},
|
|
18
|
+
ttq.load=function(e,n){var r="https://analytics.tiktok.com/i18n/pixel/events.js";
|
|
19
|
+
ttq._i=ttq._i||{},ttq._i[e]=[],ttq._i[e]._u=r,ttq._t=ttq._t||{},ttq._t[e]=+new Date,ttq._o=ttq._o||{},ttq._o[e]=n||{};
|
|
20
|
+
var s=document.createElement("script");s.type="text/javascript",s.async=!0,s.src=r+"?sdkid="+e+"&lib="+t;
|
|
21
|
+
var p=document.getElementsByTagName("script")[0];p.parentNode.insertBefore(s,p)};
|
|
22
|
+
${pixelIds.map((id) => `ttq.load('${id}');`).join("\n")}
|
|
23
|
+
ttq.page();
|
|
24
|
+
}(window, document, 'ttq');
|
|
25
|
+
`
|
|
26
|
+
script.id = "tiktok-script-multi"
|
|
27
|
+
document.head.appendChild(script)
|
|
28
|
+
|
|
29
|
+
return () => {
|
|
30
|
+
if (document.head.contains(script)) {
|
|
31
|
+
document.head.removeChild(script)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}, [pixelIds])
|
|
35
|
+
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default TikTokPixel
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { FiSun, FiMoon, FiMonitor } from "react-icons/fi"
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
|
|
5
|
+
interface ThemeSwitcherProps {
|
|
6
|
+
onThemeChange?: (theme: string) => void
|
|
7
|
+
className?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ThemeSwitcher({ onThemeChange, className }: ThemeSwitcherProps) {
|
|
11
|
+
const [theme, setThemeState] = React.useState<string>("light")
|
|
12
|
+
const [mounted, setMounted] = React.useState(false)
|
|
13
|
+
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
setMounted(true)
|
|
16
|
+
const stored = localStorage.getItem("theme") || "light"
|
|
17
|
+
setThemeState(stored)
|
|
18
|
+
}, [])
|
|
19
|
+
|
|
20
|
+
const setTheme = (newTheme: string) => {
|
|
21
|
+
setThemeState(newTheme)
|
|
22
|
+
localStorage.setItem("theme", newTheme)
|
|
23
|
+
if (newTheme === "dark") {
|
|
24
|
+
document.documentElement.classList.add("dark")
|
|
25
|
+
} else {
|
|
26
|
+
document.documentElement.classList.remove("dark")
|
|
27
|
+
}
|
|
28
|
+
onThemeChange?.(newTheme)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const toggleTheme = () => {
|
|
32
|
+
if (theme === "light") {
|
|
33
|
+
setTheme("dark")
|
|
34
|
+
} else if (theme === "dark") {
|
|
35
|
+
setTheme("system")
|
|
36
|
+
} else {
|
|
37
|
+
setTheme("light")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const getIcon = () => {
|
|
42
|
+
if (!mounted) {
|
|
43
|
+
return <FiSun className="mr-2 h-4 w-4" />
|
|
44
|
+
}
|
|
45
|
+
if (theme === "system") {
|
|
46
|
+
return <FiMonitor className="mr-2 h-4 w-4" />
|
|
47
|
+
}
|
|
48
|
+
if (theme === "dark") {
|
|
49
|
+
return <FiMoon className="mr-2 h-4 w-4" />
|
|
50
|
+
}
|
|
51
|
+
return <FiSun className="mr-2 h-4 w-4" />
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const getLabel = () => {
|
|
55
|
+
if (!mounted) return "Theme"
|
|
56
|
+
if (theme === "system") return "System"
|
|
57
|
+
if (theme === "dark") return "Dark"
|
|
58
|
+
return "Light"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<button
|
|
63
|
+
onClick={toggleTheme}
|
|
64
|
+
className={cn(
|
|
65
|
+
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
{getIcon()}
|
|
70
|
+
<span>Theme: {getLabel()}</span>
|
|
71
|
+
</button>
|
|
72
|
+
)
|
|
73
|
+
}
|