@srcroot/ui 0.0.65 → 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
@@ -101,15 +101,13 @@ var ThemeService = class {
101
101
  };
102
102
 
103
103
  // src/cli/utils/templates.ts
104
- 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"
105
107
 
106
108
  const config: Config = {
107
109
  darkMode: ["class"],
108
- content: [
109
- "./src/**/*.{js,ts,jsx,tsx,mdx}",
110
- "./app/**/*.{js,ts,jsx,tsx,mdx}",
111
- "./components/**/*.{js,ts,jsx,tsx,mdx}",
112
- ],
110
+ content: ${JSON.stringify(contentPaths, null, 6).replace(/\n/g, "\n ").replace(/\[$/, " [").replace(/\]$/, " ]")},
113
111
  theme: {
114
112
  extend: {
115
113
  colors: {
@@ -173,6 +171,8 @@ const config: Config = {
173
171
 
174
172
  export default config
175
173
  `;
174
+ }
175
+ var TAILWIND_CONFIG = getTailwindConfig("nextjs");
176
176
 
177
177
  // src/cli/utils/get-package-manager.ts
178
178
  import fs3 from "fs";
@@ -261,10 +261,23 @@ var ProjectInitializer = class {
261
261
  process.exit(1);
262
262
  }
263
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
+ }
264
276
  async detectConfiguration() {
265
277
  const cwd = path5.resolve(this.options.cwd);
266
278
  const packageManager = getPackageManager(cwd);
267
279
  const installCmd = packageManager === "npm" ? "install" : "add";
280
+ const framework = this.detectFramework(cwd);
268
281
  const pkg = await fs5.readJson(path5.join(cwd, "package.json"));
269
282
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
270
283
  const tailwindVersion = allDeps["tailwindcss"] || "";
@@ -303,7 +316,8 @@ var ProjectInitializer = class {
303
316
  hasPagesDir,
304
317
  libDir,
305
318
  componentsDir,
306
- globalsPath
319
+ globalsPath,
320
+ framework
307
321
  };
308
322
  }
309
323
  async promptUser() {
@@ -370,7 +384,7 @@ export function cn(...inputs: ClassValue[]) {
370
384
  if (!cfg.isTailwind4) {
371
385
  spinner.start("Setting up Tailwind config...");
372
386
  const tailwindConfigPath = path5.join(cfg.cwd, "tailwind.config.ts");
373
- await fs5.writeFile(tailwindConfigPath, TAILWIND_CONFIG);
387
+ await fs5.writeFile(tailwindConfigPath, getTailwindConfig(cfg.framework));
374
388
  spinner.succeed(`Created tailwind.config.ts`);
375
389
  }
376
390
  const packageInfo = getPackageInfo();
@@ -924,14 +938,49 @@ var REGISTRY = {
924
938
 
925
939
  // src/cli/services/component-adder.ts
926
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
+ };
927
949
  var ComponentAdder = class {
928
950
  cwd;
929
951
  options;
952
+ framework = "unknown";
930
953
  constructor(cwd, options) {
931
954
  this.cwd = cwd;
932
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();
933
977
  }
934
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
+ }
935
984
  components = await this.resolveComponents(components);
936
985
  const { valid, invalid } = this.validateComponents(components);
937
986
  if (invalid.length > 0) {
@@ -1104,12 +1153,26 @@ Please manually install: ${packages.join(" ")}`);
1104
1153
  }
1105
1154
  spinner.start("Adding components...");
1106
1155
  }
1107
- const registryPath = path6.resolve(getRegistryPath(), comp.file);
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
+ }
1108
1168
  if (!fs6.existsSync(registryPath)) {
1109
1169
  spinner.warn(`Registry file not found for ${name}: ${registryPath}`);
1110
1170
  continue;
1111
1171
  }
1112
- const content = await fs6.readFile(registryPath, "utf-8");
1172
+ content = await fs6.readFile(registryPath, "utf-8");
1173
+ if (this.framework === "vite") {
1174
+ content = this.transformForVite(name, content);
1175
+ }
1113
1176
  await fs6.writeFile(targetPath, content);
1114
1177
  addedCount++;
1115
1178
  if (components.length > 10) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srcroot/ui",
3
- "version": "0.0.65",
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",
@@ -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
+ }