diff --git a/messages/zh.json b/messages/zh.json
index a1ce44a..8329f42 100644
--- a/messages/zh.json
+++ b/messages/zh.json
@@ -173,6 +173,34 @@
"Notification": {
"title": "通知设置",
"description": "管理您的通知设置"
+ },
+ "Theme": {
+ "title": "主题设置",
+ "description": "自定义浅色和深色模式的主题颜色",
+ "light": "浅色模式",
+ "dark": "深色模式",
+ "system": "系统",
+ "Colors": {
+ "background": "背景色",
+ "foreground": "前景色",
+ "muted": "柔和色",
+ "muted-foreground": "柔和前景色",
+ "popover": "弹出层背景色",
+ "popover-foreground": "弹出层前景色",
+ "card": "卡片背景色",
+ "card-foreground": "卡片前景色",
+ "border": "边框色",
+ "input": "输入框色",
+ "primary": "主色",
+ "primary-foreground": "主色前景色",
+ "secondary": "次色",
+ "secondary-foreground": "次色前景色",
+ "accent": "强调色",
+ "accent-foreground": "强调色前景色",
+ "destructive": "危险色",
+ "destructive-foreground": "危险色前景色",
+ "ring": "环形色"
+ }
}
}
}
diff --git a/package.json b/package.json
index 40dda25..d735ead 100644
--- a/package.json
+++ b/package.json
@@ -41,13 +41,14 @@
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.5",
"@radix-ui/react-label": "^2.1.1",
- "@radix-ui/react-popover": "^1.1.5",
+ "@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-portal": "^1.1.3",
"@radix-ui/react-progress": "^1.1.2",
"@radix-ui/react-select": "^2.1.5",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.1.3",
+ "@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.8",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 04b64c3..17e1f92 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -51,7 +51,7 @@ importers:
specifier: ^2.1.1
version: 2.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-popover':
- specifier: ^1.1.5
+ specifier: ^1.1.6
version: 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-portal':
specifier: ^1.1.3
@@ -71,6 +71,9 @@ importers:
'@radix-ui/react-switch':
specifier: ^1.1.3
version: 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-tabs':
+ specifier: ^1.1.3
+ version: 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-toggle':
specifier: ^1.1.1
version: 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -1507,6 +1510,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-tabs@1.1.3':
+ resolution: {integrity: sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-toggle-group@1.1.2':
resolution: {integrity: sha512-JBm6s6aVG/nwuY5eadhU2zDi/IwYS0sDM5ZWb4nymv/hn3hZdkw+gENn0LP4iY1yCd7+bgJaCwueMYJIU3vk4A==}
peerDependencies:
@@ -4931,6 +4947,22 @@ snapshots:
'@types/react': 19.0.10
'@types/react-dom': 19.0.4(@types/react@19.0.10)
+ '@radix-ui/react-tabs@1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-context': 1.1.1(@types/react@19.0.10)(react@19.0.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@19.0.10)(react@19.0.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@19.0.10)(react@19.0.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-roving-focus': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.10)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.10
+ '@types/react-dom': 19.0.4(@types/react@19.0.10)
+
'@radix-ui/react-toggle-group@1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/primitive': 1.1.1
diff --git a/src/app/dash/setting/site/theme/page.tsx b/src/app/dash/setting/site/theme/page.tsx
new file mode 100644
index 0000000..f005973
--- /dev/null
+++ b/src/app/dash/setting/site/theme/page.tsx
@@ -0,0 +1,94 @@
+"use client";
+
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { ColorPicker } from "@/components/derive-ui/color-picker";
+import { useTranslations } from "next-intl";
+import type { ThemeColors } from "@/providers/color/types";
+import { defaultTheme, useTheme } from "@/providers/color";
+
+export default function ThemePage() {
+ const t = useTranslations("Private.Setting.Site.Theme");
+ const { theme, setTheme } = useTheme();
+
+ const updateColor = (
+ mode: "light" | "dark",
+ key: keyof ThemeColors,
+ value: string,
+ ) => {
+ if (mode === "light") {
+ setTheme({
+ name: "custom",
+ ...theme,
+ light: { ...theme?.light, [key]: value },
+ dark: theme?.dark ?? {},
+ });
+ } else {
+ setTheme({
+ name: "custom",
+ ...theme,
+ light: theme?.light ?? {},
+ dark: { ...theme?.dark, [key]: value },
+ });
+ }
+ };
+
+ const ColorPickerRow = ({
+ colorKey,
+ value,
+ onChange,
+ }: {
+ mode: "light" | "dark";
+ colorKey: keyof ThemeColors;
+ value: string;
+ onChange: (value: string) => void;
+ }) => (
+
+
+ {t(`Variables.${colorKey}`)}
+ {colorKey}
+
+
+
+ );
+
+ return (
+
+
+ {t("light")}
+ {t("dark")}
+
+
+
+ {Object.entries(defaultTheme.light).map(([key, value]) => (
+
+ updateColor("light", key as keyof ThemeColors, newValue)
+ }
+ />
+ ))}
+
+
+
+ {Object.entries(defaultTheme.dark).map(([key, value]) => (
+
+ updateColor("dark", key as keyof ThemeColors, newValue)
+ }
+ />
+ ))}
+
+
+ );
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 582b4b5..5fa0ec4 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -15,6 +15,7 @@ import { getLocale, getMessages } from "next-intl/server";
import { NextIntlClientProvider } from "next-intl";
import { pickPublic } from "@/i18n/pick";
import type { Messages } from "global";
+import { ThemeScript } from "@/providers/color/script";
export const metadata: Metadata = {
metadataBase: new URL(siteConfig.url),
@@ -70,7 +71,9 @@ export default async function RootLayout({
return (
-
+
+
+ );
+}
+
+export function ColorPicker({
+ background,
+ setBackground,
+ className,
+}: {
+ background: string;
+ setBackground: (background: string) => void;
+ className?: string;
+}) {
+ const solids = [
+ "#E2E2E2",
+ "#ff75c3",
+ "#ffa647",
+ "#ffe83f",
+ "#9fff5b",
+ "#70e2ff",
+ "#cd93ff",
+ "#09203f",
+ ];
+
+ const gradients = [
+ "linear-gradient(to top left,#accbee,#e7f0fd)",
+ "linear-gradient(to top left,#d5d4d0,#d5d4d0,#eeeeec)",
+ "linear-gradient(to top left,#000000,#434343)",
+ "linear-gradient(to top left,#09203f,#537895)",
+ "linear-gradient(to top left,#AC32E4,#7918F2,#4801FF)",
+ "linear-gradient(to top left,#f953c6,#b91d73)",
+ "linear-gradient(to top left,#ee0979,#ff6a00)",
+ "linear-gradient(to top left,#F00000,#DC281E)",
+ "linear-gradient(to top left,#00c6ff,#0072ff)",
+ "linear-gradient(to top left,#4facfe,#00f2fe)",
+ "linear-gradient(to top left,#0ba360,#3cba92)",
+ "linear-gradient(to top left,#FDFC47,#24FE41)",
+ "linear-gradient(to top left,#8a2be2,#0000cd,#228b22,#ccff00)",
+ "linear-gradient(to top left,#40E0D0,#FF8C00,#FF0080)",
+ "linear-gradient(to top left,#fcc5e4,#fda34b,#ff7882,#c8699e,#7046aa,#0c1db8,#020f75)",
+ "linear-gradient(to top left,#ff75c3,#ffa647,#ffe83f,#9fff5b,#70e2ff,#cd93ff)",
+ ];
+
+ const images = [
+ "url(https://images.unsplash.com/photo-1691200099282-16fd34790ade?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2532&q=90)",
+ "url(https://images.unsplash.com/photo-1691226099773-b13a89a1d167?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2532&q=90",
+ "url(https://images.unsplash.com/photo-1688822863426-8c5f9b257090?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2532&q=90)",
+ "url(https://images.unsplash.com/photo-1691225850735-6e4e51834cad?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2532&q=90)",
+ ];
+
+ const defaultTab = useMemo(() => {
+ if (background.includes("url")) return "image";
+ if (background.includes("gradient")) return "gradient";
+ return "solid";
+ }, [background]);
+
+ return (
+
+
+
+
+
+
+
+
+ Solid
+
+
+ Gradient
+
+
+ Image
+
+
+
+
+ {solids.map((s) => (
+ setBackground(s)}
+ />
+ ))}
+
+
+
+
+ {gradients.map((s) => (
+
setBackground(s)}
+ />
+ ))}
+
+
+
+ 💡 Get more at{" "}
+
+ Gradient Page
+
+
+
+
+
+
+ {images.map((s) => (
+
setBackground(s)}
+ />
+ ))}
+
+
+
+ 🎁 Get abstract{" "}
+
+ wallpapers
+
+
+
+
+
Change your password here.
+
+
+
setBackground(e.currentTarget.value)}
+ />
+
+
+ );
+}
+
+const GradientButton = ({
+ background,
+ children,
+}: {
+ background: string;
+ children: React.ReactNode;
+}) => {
+ return (
+
+ );
+};
diff --git a/src/components/providers.tsx b/src/components/providers.tsx
index bdc2084..38c2e4a 100644
--- a/src/components/providers.tsx
+++ b/src/components/providers.tsx
@@ -4,16 +4,19 @@ import {
ThemeProvider as NextThemesProvider,
type ThemeProviderProps,
} from "next-themes";
+import { ThemeProvider as ColorThemeProvider } from "@/providers/color";
import { NuqsAdapter } from "nuqs/adapters/next/app";
import { TooltipProvider } from "@/components/ui/tooltip";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return (
-
-
- {children}
-
-
+
+
+
+ {children}
+
+
+
);
}
diff --git a/src/components/setting/items.tsx b/src/components/setting/items.tsx
index 0cc0463..d5abd93 100644
--- a/src/components/setting/items.tsx
+++ b/src/components/setting/items.tsx
@@ -1,4 +1,4 @@
-import { Calendar, Home, Inbox, Search, Settings } from "lucide-react";
+import { Bell, Cable, Home, Palette } from "lucide-react";
// Menu items.
export const userItems = [
@@ -36,10 +36,14 @@ export const siteItems = [
},
{
id: "api",
- icon: Home,
+ icon: Cable,
},
{
id: "notification",
- icon: Home,
+ icon: Bell,
+ },
+ {
+ id: "theme",
+ icon: Palette,
},
];
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..0f4caeb
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,55 @@
+"use client"
+
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/src/providers/color/index.tsx b/src/providers/color/index.tsx
new file mode 100644
index 0000000..a005661
--- /dev/null
+++ b/src/providers/color/index.tsx
@@ -0,0 +1,159 @@
+"use client";
+
+import { createContext, useContext, useEffect, useState } from "react";
+import type { Theme } from "./types";
+
+export const defaultTheme: Theme = {
+ name: "default",
+ light: {
+ background: "hsl(0 0% 100%)",
+ foreground: "hsl(240 10% 3.9%)",
+ muted: "hsl(240 4.8% 95.9%)",
+ "muted-foreground": "hsl(240 3.8% 46.1%)",
+ popover: "hsl(0 0% 100%)",
+ "popover-foreground": "hsl(240 10% 3.9%)",
+ card: "hsl(0 0% 100%)",
+ "card-foreground": "hsl(240 10% 3.9%)",
+ border: "hsl(240 5.9% 90%)",
+ input: "hsl(240 5.9% 90%)",
+ primary: "hsl(240 5.9% 10%)",
+ "primary-foreground": "hsl(0 0% 98%)",
+ secondary: "hsl(240 4.8% 95.9%)",
+ "secondary-foreground": "hsl(240 5.9% 10%)",
+ accent: "hsl(240 4.8% 95.9%)",
+ "accent-foreground": "hsl(240 5.9% 10%)",
+ destructive: "hsl(0 84.2% 60.2%)",
+ "destructive-foreground": "hsl(0 0% 98%)",
+ ring: "hsl(240 5% 64.9%)",
+ radius: "0.5rem",
+ "chart-1": "hsl(220 70% 50%)",
+ "chart-2": "hsl(340 75% 55%)",
+ "chart-3": "hsl(30 80% 55%)",
+ "chart-4": "hsl(280 65% 60%)",
+ "chart-5": "hsl(160 60% 45%)",
+ "chart-6": "hsl(180 50% 50%)",
+ "chart-7": "hsl(216 50% 50%)",
+ "chart-8": "hsl(252 50% 50%)",
+ "chart-9": "hsl(288 50% 50%)",
+ "chart-10": "hsl(324 50% 50%)",
+ timing: "cubic-bezier(0.4, 0, 0.2, 1)",
+ "sidebar-background": "hsl(0 0% 98%)",
+ "sidebar-foreground": "hsl(240 5.3% 26.1%)",
+ "sidebar-primary": "hsl(240 5.9% 10%)",
+ "sidebar-primary-foreground": "hsl(0 0% 98%)",
+ "sidebar-accent": "hsl(240 4.8% 95.9%)",
+ "sidebar-accent-foreground": "hsl(240 5.9% 10%)",
+ "sidebar-border": "hsl(220 13% 91%)",
+ "sidebar-ring": "hsl(217.2 91.2% 59.8%)",
+ },
+ dark: {
+ background: "hsl(240 10% 3.9%)",
+ foreground: "hsl(0 0% 98%)",
+ muted: "hsl(240 3.7% 15.9%)",
+ "muted-foreground": "hsl(240 5% 64.9%)",
+ popover: "hsl(240 10% 3.9%)",
+ "popover-foreground": "hsl(0 0% 98%)",
+ card: "hsl(240 10% 3.9%)",
+ "card-foreground": "hsl(0 0% 98%)",
+ border: "hsl(240 3.7% 15.9%)",
+ input: "hsl(240 3.7% 15.9%)",
+ primary: "hsl(0 0% 98%)",
+ "primary-foreground": "hsl(240 5.9% 10%)",
+ secondary: "hsl(240 3.7% 15.9%)",
+ "secondary-foreground": "hsl(0 0% 98%)",
+ accent: "hsl(240 3.7% 15.9%)",
+ "accent-foreground": "hsl(0 0% 98%)",
+ destructive: "hsl(0 62.8% 30.6%)",
+ "destructive-foreground": "hsl(0 85.7% 97.3%)",
+ ring: "hsl(240 3.7% 15.9%)",
+ radius: "0.5rem",
+ "chart-1": "hsl(220 70% 50%)",
+ "chart-2": "hsl(340 75% 55%)",
+ "chart-3": "hsl(30 80% 55%)",
+ "chart-4": "hsl(280 65% 60%)",
+ "chart-5": "hsl(160 60% 45%)",
+ "chart-6": "hsl(180 50% 50%)",
+ "chart-7": "hsl(216 50% 50%)",
+ "chart-8": "hsl(252 50% 50%)",
+ "chart-9": "hsl(288 50% 50%)",
+ "chart-10": "hsl(324 50% 50%)",
+ timing: "cubic-bezier(0.4, 0, 0.2, 1)",
+ "sidebar-background": "hsl(240 5.9% 10%)",
+ "sidebar-foreground": "hsl(240 4.8% 95.9%)",
+ "sidebar-primary": "hsl(224.3 76.3% 48%)",
+ "sidebar-primary-foreground": "hsl(0 0% 100%)",
+ "sidebar-accent": "hsl(240 3.7% 15.9%)",
+ "sidebar-accent-foreground": "hsl(240 4.8% 95.9%)",
+ "sidebar-border": "hsl(240 3.7% 15.9%)",
+ "sidebar-ring": "hsl(217.2 91.2% 59.8%)",
+ },
+};
+
+type ThemeContextType = {
+ theme: Theme | null;
+ setTheme: (theme: Theme | null) => void;
+};
+
+const ThemeContext = createContext(undefined);
+
+export function useTheme() {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error("useTheme must be used within a ThemeProvider");
+ }
+ return context;
+}
+
+export function ThemeProvider({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const [theme, setTheme] = useState(null);
+
+ useEffect(() => {
+ if (!theme) return;
+
+ const remove = () => {
+ const oldStyle = document.getElementById("theme-vars");
+ if (oldStyle) {
+ oldStyle.remove();
+ }
+ };
+
+ const style = document.createElement("style");
+ style.setAttribute("id", "theme-vars");
+
+ const lightCssVars = Object.entries(theme.light)
+ .map(([key, value]) => `--${key}: ${value};`)
+ .join("\n");
+ const darkCssVars = Object.entries(theme.dark)
+ .map(([key, value]) => `--${key}: ${value};`)
+ .join("\n");
+ style.innerHTML = `
+ :root {
+ ${lightCssVars}
+ }
+ .dark {
+ ${darkCssVars}
+ }
+ `;
+ remove();
+ document.head.appendChild(style);
+
+ return () => {
+ style.remove();
+ };
+ }, [theme]);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/providers/color/script.tsx b/src/providers/color/script.tsx
new file mode 100644
index 0000000..9722fcb
--- /dev/null
+++ b/src/providers/color/script.tsx
@@ -0,0 +1,49 @@
+import type { DerivedTheme, Theme } from "./types";
+
+const testTheme: Theme = {
+ name: "test",
+ light: {
+ primary: "red",
+ },
+ dark: {
+ primary: "blue",
+ },
+};
+const getTheme = async (): Promise => {
+ const theme = testTheme;
+ const lightCssVars = Object.entries(theme.light)
+ .map(([key, value]) => {
+ const cssVar = key.replace(/([A-Z])/g, "-$1").toLowerCase();
+ return `--${cssVar}: ${value};`;
+ })
+ .join("\n");
+ const darkCssVars = Object.entries(theme.dark)
+ .map(([key, value]) => {
+ const cssVar = key.replace(/([A-Z])/g, "-$1").toLowerCase();
+ return `--${cssVar}: ${value};`;
+ })
+ .join("\n");
+ return {
+ name: theme.name,
+ light: lightCssVars,
+ dark: darkCssVars,
+ };
+};
+
+export async function ThemeScript() {
+ const theme = await getTheme();
+ const styleContent = `
+ :root {
+ ${theme.light}
+ }
+ .dark {
+ ${theme.dark}
+ }
+ `;
+ return (
+
+ );
+}
diff --git a/src/providers/color/types.ts b/src/providers/color/types.ts
new file mode 100644
index 0000000..1e68634
--- /dev/null
+++ b/src/providers/color/types.ts
@@ -0,0 +1,59 @@
+export interface ThemeColors {
+ background: string;
+ foreground: string;
+ card: string;
+ "card-foreground": string;
+ popover: string;
+ "popover-foreground": string;
+ primary: string;
+ "primary-foreground": string;
+ secondary: string;
+ "secondary-foreground": string;
+ muted: string;
+ "muted-foreground": string;
+ accent: string;
+ "accent-foreground": string;
+ destructive: string;
+ "destructive-foreground": string;
+
+ border: string;
+ input: string;
+ ring: string;
+
+ "chart-1": string;
+ "chart-2": string;
+ "chart-3": string;
+ "chart-4": string;
+ "chart-5": string;
+ "chart-6": string;
+ "chart-7": string;
+ "chart-8": string;
+ "chart-9": string;
+ "chart-10": string;
+
+ "sidebar-background": string;
+ "sidebar-foreground": string;
+ "sidebar-primary": string;
+ "sidebar-primary-foreground": string;
+ "sidebar-accent": string;
+ "sidebar-accent-foreground": string;
+ "sidebar-border": string;
+ "sidebar-ring": string;
+}
+
+export interface ThemeVariables extends ThemeColors {
+ radius: string;
+ timing: string;
+}
+
+export interface Theme {
+ name: string;
+ light: Partial;
+ dark: Partial;
+}
+
+export interface DerivedTheme {
+ name: string;
+ light: string;
+ dark: string;
+}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 8a36031..f4af160 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -4,112 +4,98 @@
@layer base {
:root {
- --background: 0 0% 100%;
- --foreground: 240 10% 3.9%;
+ --background: hsl(0 0% 100%);
+ --foreground: hsl(240 10% 3.9%);
- --muted: 240 4.8% 95.9%;
- --muted-foreground: 240 3.8% 46.1%;
+ --muted: hsl(240 4.8% 95.9%);
+ --muted-foreground: hsl(240 3.8% 46.1%);
- --popover: 0 0% 100%;
- --popover-foreground: 240 10% 3.9%;
+ --popover: hsl(0 0% 100%);
+ --popover-foreground: hsl(240 10% 3.9%);
- --card: 0 0% 100%;
- --card-foreground: 240 10% 3.9%;
+ --card: hsl(0 0% 100%);
+ --card-foreground: hsl(240 10% 3.9%);
- --border: 240 5.9% 90%;
- --input: 240 5.9% 90%;
+ --border: hsl(240 5.9% 90%);
+ --input: hsl(240 5.9% 90%);
- --primary: 240 5.9% 10%;
- --primary-foreground: 0 0% 98%;
+ --primary: hsl(240 5.9% 10%);
+ --primary-foreground: hsl(0 0% 98%);
- --secondary: 240 4.8% 95.9%;
- --secondary-foreground: 240 5.9% 10%;
+ --secondary: hsl(240 4.8% 95.9%);
+ --secondary-foreground: hsl(240 5.9% 10%);
- --accent: 240 4.8% 95.9%;
- --accent-foreground: 240 5.9% 10%;
+ --accent: hsl(240 4.8% 95.9%);
+ --accent-foreground: hsl(240 5.9% 10%);
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
+ --destructive: hsl(0 84.2% 60.2%);
+ --destructive-foreground: hsl(0 0% 98%);
- --ring: 240 5% 64.9%;
+ --ring: hsl(240 5% 64.9%);
--radius: 0.5rem;
- --chart-1: 220 70% 50%;
- --chart-2: 340 75% 55%;
- --chart-3: 30 80% 55%;
- --chart-4: 280 65% 60%;
- --chart-5: 160 60% 45%;
- --chart-6: 180 50% 50%;
- --chart-7: 216 50% 50%;
- --chart-8: 252 50% 50%;
- --chart-9: 288 50% 50%;
- --chart-10: 324 50% 50%;
+ --chart-1: hsl(220 70% 50%);
+ --chart-2: hsl(340 75% 55%);
+ --chart-3: hsl(30 80% 55%);
+ --chart-4: hsl(280 65% 60%);
+ --chart-5: hsl(160 60% 45%);
+ --chart-6: hsl(180 50% 50%);
+ --chart-7: hsl(216 50% 50%);
+ --chart-8: hsl(252 50% 50%);
+ --chart-9: hsl(288 50% 50%);
+ --chart-10: hsl(324 50% 50%);
--timing: cubic-bezier(0.4, 0, 0.2, 1);
- --sidebar-background: 0 0% 98%;
-
- --sidebar-foreground: 240 5.3% 26.1%;
-
- --sidebar-primary: 240 5.9% 10%;
-
- --sidebar-primary-foreground: 0 0% 98%;
-
- --sidebar-accent: 240 4.8% 95.9%;
-
- --sidebar-accent-foreground: 240 5.9% 10%;
-
- --sidebar-border: 220 13% 91%;
-
- --sidebar-ring: 217.2 91.2% 59.8%;
+ --sidebar-background: hsl(0 0% 98%);
+ --sidebar-foreground: hsl(240 5.3% 26.1%);
+ --sidebar-primary: hsl(240 5.9% 10%);
+ --sidebar-primary-foreground: hsl(0 0% 98%);
+ --sidebar-accent: hsl(240 4.8% 95.9%);
+ --sidebar-accent-foreground: hsl(240 5.9% 10%);
+ --sidebar-border: hsl(220 13% 91%);
+ --sidebar-ring: hsl(217.2 91.2% 59.8%);
}
.dark {
- --background: 240 10% 3.9%;
- --foreground: 0 0% 98%;
-
- --muted: 240 3.7% 15.9%;
- --muted-foreground: 240 5% 64.9%;
-
- --popover: 240 10% 3.9%;
- --popover-foreground: 0 0% 98%;
-
- --card: 240 10% 3.9%;
- --card-foreground: 0 0% 98%;
-
- --border: 240 3.7% 15.9%;
- --input: 240 3.7% 15.9%;
-
- --primary: 0 0% 98%;
- --primary-foreground: 240 5.9% 10%;
-
- --secondary: 240 3.7% 15.9%;
- --secondary-foreground: 0 0% 98%;
-
- --accent: 240 3.7% 15.9%;
- --accent-foreground: 0 0% 98%;
+ --background: hsl(240 10% 3.9%);
+ --foreground: hsl(0 0% 98%);
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 0 85.7% 97.3%;
+ --muted: hsl(240 3.7% 15.9%);
+ --muted-foreground: hsl(240 5% 64.9%);
- --ring: 240 3.7% 15.9%;
+ --popover: hsl(240 10% 3.9%);
+ --popover-foreground: hsl(0 0% 98%);
- --sidebar-background: 240 5.9% 10%;
+ --card: hsl(240 10% 3.9%);
+ --card-foreground: hsl(0 0% 98%);
- --sidebar-foreground: 240 4.8% 95.9%;
+ --border: hsl(240 3.7% 15.9%);
+ --input: hsl(240 3.7% 15.9%);
- --sidebar-primary: 224.3 76.3% 48%;
+ --primary: hsl(0 0% 98%);
+ --primary-foreground: hsl(240 5.9% 10%);
- --sidebar-primary-foreground: 0 0% 100%;
+ --secondary: hsl(240 3.7% 15.9%);
+ --secondary-foreground: hsl(0 0% 98%);
- --sidebar-accent: 240 3.7% 15.9%;
+ --accent: hsl(240 3.7% 15.9%);
+ --accent-foreground: hsl(0 0% 98%);
- --sidebar-accent-foreground: 240 4.8% 95.9%;
+ --destructive: hsl(0 62.8% 30.6%);
+ --destructive-foreground: hsl(0 85.7% 97.3%);
- --sidebar-border: 240 3.7% 15.9%;
+ --ring: hsl(240 3.7% 15.9%);
- --sidebar-ring: 217.2 91.2% 59.8%;
+ --sidebar-background: hsl(240 5.9% 10%);
+ --sidebar-foreground: hsl(240 4.8% 95.9%);
+ --sidebar-primary: hsl(224.3 76.3% 48%);
+ --sidebar-primary-foreground: hsl(0 0% 100%);
+ --sidebar-accent: hsl(240 3.7% 15.9%);
+ --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
+ --sidebar-border: hsl(240 3.7% 15.9%);
+ --sidebar-ring: hsl(217.2 91.2% 59.8%);
}
}
@@ -130,7 +116,8 @@
@layer base {
* {
- @apply border-border outline-ring/50;
+ /* @apply border-border outline-ring/50; */
+ outline-color: rgb(var(--ring) / 0.5);
}
body {
@apply bg-background text-foreground;
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 040e50f..25cf87b 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -14,48 +14,48 @@ export default {
},
extend: {
colors: {
- border: 'hsl(var(--border))',
- input: 'hsl(var(--input))',
- ring: 'hsl(var(--ring))',
- background: 'hsl(var(--background))',
- foreground: 'hsl(var(--foreground))',
+ border: 'var(--border)',
+ input: 'var(--input)',
+ ring: 'var(--ring)',
+ background: 'var(--background)',
+ foreground: 'var(--foreground)',
primary: {
- DEFAULT: 'hsl(var(--primary))',
- foreground: 'hsl(var(--primary-foreground))'
+ DEFAULT: 'var(--primary)',
+ foreground: 'var(--primary-foreground)'
},
secondary: {
- DEFAULT: 'hsl(var(--secondary))',
- foreground: 'hsl(var(--secondary-foreground))'
+ DEFAULT: 'var(--secondary)',
+ foreground: 'var(--secondary-foreground)'
},
destructive: {
- DEFAULT: 'hsl(var(--destructive))',
- foreground: 'hsl(var(--destructive-foreground))'
+ DEFAULT: 'var(--destructive)',
+ foreground: 'var(--destructive-foreground)'
},
muted: {
- DEFAULT: 'hsl(var(--muted))',
- foreground: 'hsl(var(--muted-foreground))'
+ DEFAULT: 'var(--muted)',
+ foreground: 'var(--muted-foreground)'
},
accent: {
- DEFAULT: 'hsl(var(--accent))',
- foreground: 'hsl(var(--accent-foreground))'
+ DEFAULT: 'var(--accent)',
+ foreground: 'var(--accent-foreground)'
},
popover: {
- DEFAULT: 'hsl(var(--popover))',
- foreground: 'hsl(var(--popover-foreground))'
+ DEFAULT: 'var(--popover)',
+ foreground: 'var(--popover-foreground)'
},
card: {
- DEFAULT: 'hsl(var(--card))',
- foreground: 'hsl(var(--card-foreground))'
+ DEFAULT: 'var(--card)',
+ foreground: 'var(--card-foreground)'
},
sidebar: {
- DEFAULT: 'hsl(var(--sidebar-background))',
- foreground: 'hsl(var(--sidebar-foreground))',
- primary: 'hsl(var(--sidebar-primary))',
- 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
- accent: 'hsl(var(--sidebar-accent))',
- 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
- border: 'hsl(var(--sidebar-border))',
- ring: 'hsl(var(--sidebar-ring))'
+ DEFAULT: 'var(--sidebar-background)',
+ foreground: 'var(--sidebar-foreground)',
+ primary: 'var(--sidebar-primary)',
+ 'primary-foreground': 'var(--sidebar-primary-foreground)',
+ accent: 'var(--sidebar-accent)',
+ 'accent-foreground': 'var(--sidebar-accent-foreground)',
+ border: 'var(--sidebar-border)',
+ ring: 'var(--sidebar-ring)'
}
},
borderRadius: {