@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 +73 -10
- package/package.json +1 -1
- 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
|
@@ -101,15 +101,13 @@ var ThemeService = class {
|
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
// src/cli/utils/templates.ts
|
|
104
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
+
}
|