87 lines
3 KiB
TypeScript
87 lines
3 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
import {
|
|
SegmentedControlRoot,
|
|
SegmentedControlItem,
|
|
SegmentedControlItemHiddenInput,
|
|
SegmentedControlIndicator,
|
|
} from "@seed-design/react";
|
|
|
|
/** "system" follows the OS via prefers-color-scheme; "light"/"dark" pin it. */
|
|
export type MercuryTheme = "system" | "light" | "dark";
|
|
|
|
const STORAGE_KEY = "mercury.theme";
|
|
|
|
/** Mirrors the inline script in app/layout.tsx (kept in sync manually — the
|
|
* script must stay dependency-free/pre-hydration, so it can't import this). */
|
|
function toColorMode(theme: MercuryTheme): "system" | "light-only" | "dark-only" {
|
|
if (theme === "light") return "light-only";
|
|
if (theme === "dark") return "dark-only";
|
|
return "system";
|
|
}
|
|
|
|
export function getStoredTheme(): MercuryTheme {
|
|
try {
|
|
const v = localStorage.getItem(STORAGE_KEY);
|
|
if (v === "light" || v === "dark" || v === "system") return v;
|
|
} catch {
|
|
// localStorage unavailable (private mode, SSR) — fall through to default.
|
|
}
|
|
return "system";
|
|
}
|
|
|
|
function persistTheme(theme: MercuryTheme): void {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, theme);
|
|
} catch {
|
|
// Best-effort; the toggle still works for the current page load.
|
|
}
|
|
}
|
|
|
|
function applyTheme(theme: MercuryTheme): void {
|
|
if (typeof document === "undefined") return;
|
|
document.documentElement.setAttribute("data-seed-color-mode", toColorMode(theme));
|
|
}
|
|
|
|
export interface ThemeToggleProps {
|
|
labels?: { system: string; light: string; dark: string };
|
|
}
|
|
|
|
const DEFAULT_LABELS = { system: "Систем", light: "Гэрэл", dark: "Харанхуй" };
|
|
|
|
/** 3-way Систем / Гэрэл / Харанхуй display-mode control. Updates
|
|
* localStorage and the live `data-seed-color-mode` attribute on `<html>`
|
|
* immediately, so every `--seed-color-*` token flips without a reload. */
|
|
export function ThemeToggle({ labels = DEFAULT_LABELS }: ThemeToggleProps) {
|
|
// Lazy initializer runs on the client at mount (same pattern as
|
|
// HideAmountsToggle/isHidden) so it picks up the real preference
|
|
// immediately — layout.tsx's inline script has already applied it to the
|
|
// DOM before this ever mounts, so both agree from the first paint.
|
|
const [theme, setTheme] = React.useState<MercuryTheme>(() => getStoredTheme());
|
|
|
|
function handleChange(next: string) {
|
|
const nextTheme = next as MercuryTheme;
|
|
setTheme(nextTheme);
|
|
persistTheme(nextTheme);
|
|
applyTheme(nextTheme);
|
|
}
|
|
|
|
return (
|
|
<SegmentedControlRoot value={theme} onValueChange={handleChange} aria-label="Дэлгэцийн горим">
|
|
<SegmentedControlIndicator />
|
|
<SegmentedControlItem value="system">
|
|
<SegmentedControlItemHiddenInput />
|
|
{labels.system}
|
|
</SegmentedControlItem>
|
|
<SegmentedControlItem value="light">
|
|
<SegmentedControlItemHiddenInput />
|
|
{labels.light}
|
|
</SegmentedControlItem>
|
|
<SegmentedControlItem value="dark">
|
|
<SegmentedControlItemHiddenInput />
|
|
{labels.dark}
|
|
</SegmentedControlItem>
|
|
</SegmentedControlRoot>
|
|
);
|
|
}
|