| import { useEffect, useState } from "react"; |
| import { Moon, Sun } from "lucide-react"; |
| import { cn } from "@/lib/utils"; |
|
|
| type Theme = "dark" | "light"; |
| const STORAGE_KEY = "adfd:theme"; |
|
|
| |
| export function useTheme() { |
| const [theme, setTheme] = useState<Theme>(() => { |
| if (typeof document === "undefined") return "dark"; |
| const stored = window.localStorage.getItem(STORAGE_KEY); |
| if (stored === "light" || stored === "dark") return stored; |
| |
| return window.matchMedia?.("(prefers-color-scheme: light)").matches |
| ? "light" |
| : "dark"; |
| }); |
|
|
| useEffect(() => { |
| const root = document.documentElement; |
| if (theme === "light") { |
| root.classList.add("theme-light"); |
| root.classList.remove("dark"); |
| } else { |
| root.classList.add("dark"); |
| root.classList.remove("theme-light"); |
| } |
| window.localStorage.setItem(STORAGE_KEY, theme); |
| }, [theme]); |
|
|
| return { theme, setTheme }; |
| } |
|
|
| interface Props { |
| className?: string; |
| size?: "sm" | "md"; |
| showLabel?: boolean; |
| } |
|
|
| |
| export default function ThemeToggle({ |
| className, |
| size = "md", |
| showLabel = true, |
| }: Props) { |
| const { theme, setTheme } = useTheme(); |
| const isLight = theme === "light"; |
|
|
| const dim = size === "sm" ? "h-7" : "h-8"; |
| const padX = size === "sm" ? "px-1" : "px-1.5"; |
| const knob = |
| size === "sm" ? "h-5 w-5" : "h-6 w-6"; |
|
|
| return ( |
| <div className={cn("inline-flex items-center gap-2", className)}> |
| {showLabel && ( |
| <span className="font-mono text-[10px] uppercase tracking-wider text-ink-mute"> |
| theme |
| </span> |
| )} |
| <button |
| type="button" |
| role="switch" |
| aria-checked={isLight} |
| aria-label={isLight ? "Switch to dark mode" : "Switch to light mode"} |
| onClick={() => setTheme(isLight ? "dark" : "light")} |
| className={cn( |
| "relative inline-flex items-center rounded-full border border-line bg-surface-alt transition-colors", |
| dim, |
| padX, |
| "w-[58px]", |
| isLight && "border-warn/40 bg-warn/15" |
| )} |
| > |
| {/* Sun icon (left) */} |
| <Sun |
| className={cn( |
| "absolute left-1.5 h-3 w-3 transition-opacity", |
| isLight ? "opacity-100 text-warn" : "opacity-50 text-ink-dim" |
| )} |
| /> |
| {/* Moon icon (right) */} |
| <Moon |
| className={cn( |
| "absolute right-1.5 h-3 w-3 transition-opacity", |
| isLight ? "opacity-50 text-ink-dim" : "opacity-100 text-cyber" |
| )} |
| /> |
| {/* Knob */} |
| <span |
| className={cn( |
| "z-10 inline-block rounded-full shadow-ridge transition-all duration-200", |
| knob, |
| isLight |
| ? "translate-x-[28px] bg-bg" |
| : "translate-x-0 bg-bg" |
| )} |
| /> |
| </button> |
| </div> |
| ); |
| } |
|
|