Repository Public
1"use client";23import { useEffect, useState } from "react";45const KEY = "clip:theme";6/** Runs before first paint (inline in <head>) so the saved theme never flashes. Light is the default. */7export const THEME_SCRIPT = `try{var t=localStorage.getItem("${KEY}");if(t==="dark")document.documentElement.dataset.theme="dark"}catch(e){}`;89export function ThemeToggle({ labels = { light: "Light theme", dark: "Dark theme" }, className = "" }: { labels?: { light: string; dark: string }; className?: string }) {10 const [dark, setDark] = useState(false);11 useEffect(() => { setDark(document.documentElement.dataset.theme === "dark"); }, []);12 const toggle = () => {13 const next = !dark;14 setDark(next);15 if (next) document.documentElement.dataset.theme = "dark"; else delete document.documentElement.dataset.theme;16 try { localStorage.setItem(KEY, next ? "dark" : "light"); } catch { /* private mode */ }17 document.querySelector('meta[name="theme-color"]')?.setAttribute("content", next ? "#09090b" : "#f5f4f1");18 };19 return (20 <button type="button" onClick={toggle} aria-label={dark ? labels.light : labels.dark} title={dark ? labels.light : labels.dark} aria-pressed={dark}21 className={`grid h-11 w-11 place-items-center rounded-full text-[var(--color-muted)] transition-colors hover:bg-[var(--color-raised)] hover:text-[var(--color-text)] ${className}`}>22 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">23 {dark ? <><circle cx="12" cy="12" r="4.2" /><path d="M12 2.5v2.2M12 19.3v2.2M2.5 12h2.2M19.3 12h2.2M5.3 5.3l1.6 1.6M17.1 17.1l1.6 1.6M5.3 18.7l1.6-1.6M17.1 6.9l1.6-1.6" /></> : <path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z" />}24 </svg>25 </button>26 );27}28