init
This commit is contained in:
commit
c19b910c36
80 changed files with 16134 additions and 0 deletions
53
app/components/ThemeToggle.tsx
Normal file
53
app/components/ThemeToggle.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"use client";
|
||||
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
const STORAGE_KEY = "plan-my-meals:theme";
|
||||
const EVENT_NAME = "plan-my-meals:theme-change";
|
||||
|
||||
type ResolvedTheme = "light" | "dark";
|
||||
|
||||
function getSnapshot(): ResolvedTheme {
|
||||
const explicit = document.documentElement.dataset.theme;
|
||||
if (explicit === "light" || explicit === "dark") return explicit;
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
function subscribe(callback: () => void): () => void {
|
||||
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
mq.addEventListener("change", callback);
|
||||
window.addEventListener(EVENT_NAME, callback);
|
||||
return () => {
|
||||
mq.removeEventListener("change", callback);
|
||||
window.removeEventListener(EVENT_NAME, callback);
|
||||
};
|
||||
}
|
||||
|
||||
export function ThemeToggle() {
|
||||
// Server snapshot returns null so hydration never mismatches; button renders a stable shell first.
|
||||
const theme = useSyncExternalStore<ResolvedTheme | null>(subscribe, getSnapshot, () => null);
|
||||
|
||||
function toggleTheme() {
|
||||
const next: ResolvedTheme = (theme ?? getSnapshot()) === "dark" ? "light" : "dark";
|
||||
document.documentElement.dataset.theme = next;
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, next);
|
||||
} catch {
|
||||
// Ignore blocked storage; dataset.theme still updates the active session.
|
||||
}
|
||||
window.dispatchEvent(new Event(EVENT_NAME));
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleTheme}
|
||||
aria-label={theme === "dark" ? "Switch to light paper theme" : "Switch to dark slate theme"}
|
||||
title={theme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
|
||||
className="grid size-10 shrink-0 place-items-center rounded-lg border border-line bg-surface text-ink-muted transition-colors hover:border-line-strong hover:bg-raised hover:text-ink"
|
||||
>
|
||||
{theme === "dark" ? <Sun className="size-4" strokeWidth={1.75} aria-hidden /> : <Moon className="size-4" strokeWidth={1.75} aria-hidden />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue