Files
Jyotisha/frontend/src/components/theme-preference-menu.tsx
T
Jesse_Chen a671986a1d
Independent Staging Quality Gate / validate (push) Failing after 10m21s
Independent Staging Quality Gate / publish (push) Has been skipped
fix(frontend): complete settings MVP structure
2026-08-30 16:32:58 +08:00

92 lines
2.7 KiB
TypeScript

"use client";
import { Menu } from "@base-ui/react/menu";
import { Check, Monitor, Moon, Sun } from "lucide-react";
import { useSyncExternalStore } from "react";
import {
applyThemePreference,
readThemePreference,
subscribeThemePreference,
type ThemePreference,
} from "@/lib/theme-preference";
const options: ReadonlyArray<{ value: ThemePreference; label: string }> = [
{ value: "light", label: "浅色" },
{ value: "dark", label: "深色" },
{ value: "system", label: "跟随系统" },
];
function OptionIcon({ value }: { readonly value: ThemePreference }) {
if (value === "light") return <Sun aria-hidden="true" />;
if (value === "dark") return <Moon aria-hidden="true" />;
return <Monitor aria-hidden="true" />;
}
function useThemePreference() {
return useSyncExternalStore(
subscribeThemePreference,
readThemePreference,
() => "system" as ThemePreference,
);
}
export function ThemePreferencePanel() {
const preference = useThemePreference();
return (
<section className="theme-preference-panel" aria-labelledby="theme-preference-title">
<div className="section-heading">
<b id="theme-preference-title">外观主题</b>
<small>选择 Jyotisha 的显示方式</small>
</div>
<div className="theme-preference-options" role="group" aria-label="外观主题">
{options.map((option) => (
<button
className="theme-preference-option"
key={option.value}
type="button"
aria-pressed={preference === option.value}
onClick={() => applyThemePreference(option.value)}
>
<OptionIcon value={option.value} />
<span>{option.label}</span>
{preference === option.value ? <Check aria-hidden="true" /> : null}
</button>
))}
</div>
</section>
);
}
export function ThemePreferenceMenu() {
const preference = useThemePreference();
return (
<Menu.Group className="theme-menu">
<Menu.GroupLabel className="theme-menu-label">外观</Menu.GroupLabel>
<Menu.RadioGroup
value={preference}
onValueChange={(next) => {
applyThemePreference(next as ThemePreference);
}}
>
{options.map((option) => (
<Menu.RadioItem
className="account-menu-item theme-menu-item"
closeOnClick={false}
key={option.value}
value={option.value}
>
<OptionIcon value={option.value} />
<span>{option.label}</span>
<Menu.RadioItemIndicator className="theme-menu-check">
<Check aria-hidden="true" />
</Menu.RadioItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Group>
);
}