92 lines
2.7 KiB
TypeScript
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>
|
|
);
|
|
}
|