@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 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 fs4 from "fs-extra";
9
- import path4 from "path";
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 fileURLToPath3 } from "url";
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 fs from "fs-extra";
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
- var __dirname = path.dirname(fileURLToPath(import.meta.url));
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 = path.resolve(__dirname, "..", "src", "registry", "themes");
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 = path.join(this.registryThemesPath, "v3");
44
- if (!fs.existsSync(v3Path)) {
68
+ const v3Path = path2.join(this.registryThemesPath, "v3");
69
+ if (!fs2.existsSync(v3Path)) {
45
70
  return themes;
46
71
  }
47
- const files = fs.readdirSync(v3Path);
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 = path.join(this.registryThemesPath, versionFolder, `${themeName}.css`);
70
- if (!fs.existsSync(themeFilePath)) {
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 fs.readFile(themeFilePath, "utf-8");
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
- var TAILWIND_CONFIG = `import type { Config } from "tailwindcss"
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 fs2 from "fs";
154
- import path2 from "path";
178
+ import fs3 from "fs";
179
+ import path3 from "path";
155
180
  function getPackageManager(cwd) {
156
181
  const dir = cwd || process.cwd();
157
- if (fs2.existsSync(path2.join(dir, "bun.lockb"))) return "bun";
158
- if (fs2.existsSync(path2.join(dir, "pnpm-lock.yaml"))) return "pnpm";
159
- if (fs2.existsSync(path2.join(dir, "yarn.lock"))) return "yarn";
160
- if (fs2.existsSync(path2.join(dir, "package-lock.json"))) return "npm";
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 path3 from "path";
172
- import fs3 from "fs-extra";
173
- import { fileURLToPath as fileURLToPath2 } from "url";
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 = fileURLToPath2(import.meta.url);
176
- const __dirname5 = path3.dirname(__filename2);
200
+ const __filename2 = fileURLToPath3(import.meta.url);
201
+ const __dirname5 = path4.dirname(__filename2);
177
202
  const pathsToCheck = [
178
- path3.resolve(__dirname5, "..", "package.json"),
179
- path3.resolve(__dirname5, "..", "..", "..", "package.json")
203
+ path4.resolve(__dirname5, "..", "package.json"),
204
+ path4.resolve(__dirname5, "..", "..", "..", "package.json")
180
205
  ];
181
206
  for (const pkgPath of pathsToCheck) {
182
- if (fs3.existsSync(pkgPath)) {
183
- return fs3.readJSONSync(pkgPath);
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 = path4.dirname(fileURLToPath3(import.meta.url));
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 = path4.resolve(this.options.cwd);
227
- const packageJsonPath = path4.join(cwd, "package.json");
228
- if (!fs4.existsSync(packageJsonPath)) {
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 fs4.readJson(packageJsonPath);
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 = path4.resolve(this.options.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 pkg = await fs4.readJson(path4.join(cwd, "package.json"));
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 = fs4.existsSync(path4.join(cwd, "src"));
248
- const srcPath = hasSrc ? path4.join(cwd, "src") : cwd;
249
- const appPath = path4.join(srcPath, "app");
250
- const pagesPath = path4.join(srcPath, "pages");
251
- const hasAppDir = fs4.existsSync(appPath);
252
- const hasPagesDir = fs4.existsSync(pagesPath);
253
- const libDir = path4.join(srcPath, "lib");
254
- const componentsDir = path4.join(srcPath, "components", "ui");
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 (fs4.existsSync(path4.join(appPath, "globals.css"))) globalsPath = path4.join(appPath, "globals.css");
258
- else if (fs4.existsSync(path4.join(appPath, "global.css"))) globalsPath = path4.join(appPath, "global.css");
259
- else globalsPath = path4.join(appPath, "globals.css");
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 = path4.join(srcPath, "styles");
262
- if (fs4.existsSync(path4.join(stylesPath, "globals.css"))) globalsPath = path4.join(stylesPath, "globals.css");
263
- else if (fs4.existsSync(path4.join(stylesPath, "global.css"))) globalsPath = path4.join(stylesPath, "global.css");
264
- else globalsPath = path4.join(stylesPath, "globals.css");
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 = path4.join(srcPath, "globals.css");
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 fs4.ensureDir(cfg.libDir);
315
- await fs4.ensureDir(cfg.componentsDir);
316
- const utilsPath = path4.join(cfg.libDir, "utils.ts");
317
- const registryUtilsPath = path4.resolve(__dirname3, "..", "src", "registry", "lib", "utils.ts");
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 (fs4.existsSync(registryUtilsPath)) {
320
- utilsContent = await fs4.readFile(registryUtilsPath, "utf-8");
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 fs4.writeFile(utilsPath, utilsContent);
332
- spinner.succeed(`Created ${path4.relative(cfg.cwd, utilsPath)}`);
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 = path4.dirname(cfg.globalsPath);
335
- await fs4.ensureDir(stylesDir);
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 fs4.writeFile(cfg.globalsPath, cssContent);
339
- spinner.succeed(`Updated ${path4.relative(cfg.cwd, cfg.globalsPath)} with ${cfg.selectedTheme} theme (${cfg.isTailwind4 ? "Tailwind 4" : "Tailwind 3"})`);
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 = path4.join(cfg.cwd, "tailwind.config.ts");
348
- await fs4.writeFile(tailwindConfigPath, TAILWIND_CONFIG);
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: path4.relative(cfg.cwd, cfg.componentsDir),
357
- utils: path4.relative(cfg.cwd, utilsPath)
395
+ components: path5.relative(cfg.cwd, cfg.componentsDir),
396
+ utils: path5.relative(cfg.cwd, utilsPath)
358
397
  }
359
398
  };
360
- await fs4.writeJSON(path4.join(cfg.cwd, "srcroot.config.json"), configObj, { spaces: 2 });
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 = path4.join(cfg.cwd, "package.json");
382
- const pkg = await fs4.readJson(packageJsonPath);
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 path6 from "path";
456
+ import path7 from "path";
418
457
 
419
458
  // src/cli/services/component-adder.ts
420
- import fs5 from "fs-extra";
421
- import path5 from "path";
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 fileURLToPath4 } from "url";
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 = path5.dirname(fileURLToPath4(import.meta.url));
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 = fs5.existsSync(path5.join(this.cwd, "src"));
1017
- const srcPath = hasSrc ? path5.join(this.cwd, "src") : this.cwd;
1018
- const componentsDir = path5.join(srcPath, "components", "ui");
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 fs5.ensureDir(componentsDir);
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 = path5.basename(comp.file);
1028
- const targetPath = path5.join(componentsDir, fileName);
1029
- if (fs5.existsSync(targetPath)) {
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 = path5.basename(comp.file);
1061
- const targetPath = path5.join(componentsDir, fileName);
1062
- if (fs5.existsSync(targetPath) && !this.options.overwrite && !overwriteAll) {
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
- const registryPath = path5.resolve(__dirname4, "..", "..", "registry", comp.file);
1082
- if (!fs5.existsSync(registryPath)) {
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
- const content = await fs5.readFile(registryPath, "utf-8");
1087
- await fs5.writeFile(targetPath, content);
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 ${components.length} components`);
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 = path6.resolve(options.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.64",
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
+ }