SPB Git

Repository Public MIT

theme-toggle.tsx 2.0 KB 46 lines tsx

1"use client";2import { Monitor, Moon, Sun } from "lucide-react";3import { useEffect, useState } from "react";45type Mode = "system" | "light" | "dark";6const ORDER: Mode[] = ["system", "light", "dark"];7const LABEL: Record<Mode, string> = { system: "System theme", light: "Light theme", dark: "Dark theme" };89function apply(mode: Mode) {10  const dark = mode === "dark" || (mode === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);11  document.documentElement.dataset.theme = dark ? "dark" : "light";12  document.querySelector('meta[name="theme-color"]')?.setAttribute("content", dark ? "#080b10" : "#f6f7f9");13}1415/** Cycles system → light → dark. The choice is a display preference only, kept in localStorage. */16export function ThemeToggle() {17  const [mode, setMode] = useState<Mode>("system");18  useEffect(() => {19    const saved = localStorage.getItem("skills-theme");20    if (saved === "light" || saved === "dark") setMode(saved);21    const mq = window.matchMedia("(prefers-color-scheme: dark)");22    const onChange = () => { if ((localStorage.getItem("skills-theme") ?? "system") === "system") apply("system"); };23    mq.addEventListener("change", onChange);24    return () => mq.removeEventListener("change", onChange);25  }, []);26  const Icon = mode === "light" ? Sun : mode === "dark" ? Moon : Monitor;27  const next = ORDER[(ORDER.indexOf(mode) + 1) % ORDER.length]!;28  return (29    <button30      type="button"31      onClick={() => {32        setMode(next);33        if (next === "system") localStorage.removeItem("skills-theme"); else localStorage.setItem("skills-theme", next);34        apply(next);35      }}36      className="grid h-9 w-9 place-items-center rounded-lg border border-line text-muted transition-colors hover:border-line-strong hover:text-text"37      aria-label={`${LABEL[mode]}. Switch to ${LABEL[next].toLowerCase()}`}38      title={`${LABEL[mode]} — click for ${LABEL[next].toLowerCase()}`}39      data-testid="theme-toggle"40      data-mode={mode}41    >42      <Icon size={15} aria-hidden />43    </button>44  );45}46
Files main
Menu

Theme