From a36ea9430b7fb079c6b90383ff5c634b34e46937 Mon Sep 17 00:00:00 2001 From: AprilNEA Date: Wed, 26 Mar 2025 14:17:43 +0000 Subject: [PATCH 1/3] refactor: move color type from config into cssvars --- src/styles/globals.css | 145 +++++++++++++++++++---------------------- tailwind.config.ts | 54 +++++++-------- 2 files changed, 93 insertions(+), 106 deletions(-) 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: { From 5c449a7b489f7c295d3d4becb91c47dadfd1b756 Mon Sep 17 00:00:00 2001 From: AprilNEA Date: Wed, 26 Mar 2025 16:34:49 +0000 Subject: [PATCH 2/3] feat: finish dynamic color system in both client and server side --- src/app/layout.tsx | 5 ++- src/components/providers.tsx | 13 +++--- src/providers/color/index.tsx | 73 ++++++++++++++++++++++++++++++++++ src/providers/color/script.tsx | 49 +++++++++++++++++++++++ src/providers/color/types.ts | 46 +++++++++++++++++++++ 5 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 src/providers/color/index.tsx create mode 100644 src/providers/color/script.tsx create mode 100644 src/providers/color/types.ts 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 ( - + + + - - {children} - - + + + + {children} + + + ); } diff --git a/src/providers/color/index.tsx b/src/providers/color/index.tsx new file mode 100644 index 0000000..0c69f41 --- /dev/null +++ b/src/providers/color/index.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { + createContext, + type SetStateAction, + useContext, + useEffect, + useState, +} from "react"; +import type { DerivedTheme } from "./types"; + +type ThemeContextType = { + theme: DerivedTheme | null; + setTheme: (theme: DerivedTheme | 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"); + + style.innerHTML = ` + :root { + ${theme.light} + } + .dark { + ${theme.dark} + } + `; + 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 ( +