chore: directory migration - gateway→server, web→client/electron
This commit is contained in:
83
client/electron/src/components/ThemePicker.tsx
Normal file
83
client/electron/src/components/ThemePicker.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import { useThemeStore, type ThemePref } from '../stores/themeStore';
|
||||
import { SunIcon, MoonIcon, MonitorIcon } from './icons';
|
||||
|
||||
/**
|
||||
* 主题切换。
|
||||
*
|
||||
* 两种形态共用一份状态:
|
||||
* - `compact`(侧栏 / 底部导航):单按钮,点一下翻转
|
||||
* - 默认(「我的」页):三选一,因为 `system` 只有在能明确选中时才有意义
|
||||
*
|
||||
* 单按钮不足以表达三态,但侧栏放不下三个选项;而只给单按钮的话
|
||||
* 用户一旦点过就永久脱离了「跟随系统」—— 那是个回不去的单向门。
|
||||
* 所以两个入口都提供,compact 用于快速切换,完整形态用于设定偏好。
|
||||
*/
|
||||
|
||||
const OPTIONS: { value: ThemePref; label: string; hint: string; Icon: (p: { className?: string }) => JSX.Element }[] = [
|
||||
{ value: 'light', label: '浅色', hint: '始终使用浅色', Icon: SunIcon },
|
||||
{ value: 'dark', label: '深色', hint: '始终使用深色', Icon: MoonIcon },
|
||||
{ value: 'system', label: '跟随系统', hint: '随系统的深浅色设置切换', Icon: MonitorIcon }
|
||||
];
|
||||
|
||||
/** 侧栏用的单按钮:点一下在浅/深之间翻转。 */
|
||||
export function ThemeToggleButton({ className = '' }: { className?: string }) {
|
||||
const resolved = useThemeStore(s => s.resolved);
|
||||
const pref = useThemeStore(s => s.pref);
|
||||
const toggle = useThemeStore(s => s.toggle);
|
||||
|
||||
const dark = resolved === 'dark';
|
||||
return (
|
||||
<button
|
||||
onClick={toggle}
|
||||
title={
|
||||
pref === 'system'
|
||||
? `跟随系统(当前${dark ? '深色' : '浅色'})—— 点击固定为${dark ? '浅色' : '深色'}`
|
||||
: `当前${dark ? '深色' : '浅色'} —— 点击切换`
|
||||
}
|
||||
aria-label="切换主题"
|
||||
className={className}
|
||||
>
|
||||
{dark ? <MoonIcon className="w-3.5 h-3.5" /> : <SunIcon className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/** 「我的」页用的三选一。 */
|
||||
export default function ThemePicker() {
|
||||
const pref = useThemeStore(s => s.pref);
|
||||
const resolved = useThemeStore(s => s.resolved);
|
||||
const setPref = useThemeStore(s => s.setPref);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<h3 className="text-sm font-medium text-gray-900">外观</h3>
|
||||
{pref === 'system' && (
|
||||
<span className="text-xs text-gray-500">
|
||||
当前跟随系统:{resolved === 'dark' ? '深色' : '浅色'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{OPTIONS.map(o => {
|
||||
const active = pref === o.value;
|
||||
return (
|
||||
<button
|
||||
key={o.value}
|
||||
onClick={() => setPref(o.value)}
|
||||
title={o.hint}
|
||||
className={`px-3 py-2.5 rounded border text-xs flex flex-col items-center gap-1.5 transition-colors ${
|
||||
active
|
||||
? 'border-blue-500 bg-blue-50 text-blue-700'
|
||||
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<o.Icon className="w-4 h-4" />
|
||||
{o.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user