diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx new file mode 100644 index 0000000..da6009a --- /dev/null +++ b/src/components/Dropdown.tsx @@ -0,0 +1,73 @@ +// 自绘下拉(替代原生 select:原生展开层是系统样式,与应用风格脱节)。 +// 外观对齐 .input;展开层沿用应用菜单语言(面板底、6px 圆角、悬停 --hover、 +// 选中 --accent-weak);点别处/Esc 关闭走统一的 useDismiss;支持上下键选行。 +import { useEffect, useRef, useState } from 'react' +import { useDismiss } from '../hooks/useDismiss' + +export interface DropdownOption { value: string; label: string } + +export function Dropdown({ value, options, onChange, disabled, ariaLabel }: { + value: string + options: DropdownOption[] + onChange: (value: string) => void + disabled?: boolean + ariaLabel?: string +}) { + const [open, setOpen] = useState(false) + const ref = useRef(null) + const menuRef = useRef(null) + useDismiss(ref, open, () => setOpen(false)) + + const current = options.find((o) => o.value === value) ?? options[0] + + // 打开时把焦点放到选中项,便于直接上下键选行 + useEffect(() => { + if (!open) return + const idx = Math.max(0, options.findIndex((o) => o.value === value)) + menuRef.current?.querySelectorAll('button')[idx]?.focus() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open ]) + + const pick = (v: string) => { setOpen(false); onChange(v) } + + const onMenuKey = (e: React.KeyboardEvent) => { + if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return + e.preventDefault() + const buttons = [...(menuRef.current?.querySelectorAll('button') ?? [])] + const idx = buttons.indexOf(document.activeElement as HTMLButtonElement) + const next = e.key === 'ArrowDown' + ? Math.min(buttons.length - 1, idx + 1) + : Math.max(0, idx - 1) + buttons[next]?.focus() + } + + return ( +
+ + {open && ( +
+ {options.map((o) => ( + + ))} +
+ )} +
+ ) +} diff --git a/src/components/Stage5Modals.tsx b/src/components/Stage5Modals.tsx index 7a52709..cf5e96a 100644 --- a/src/components/Stage5Modals.tsx +++ b/src/components/Stage5Modals.tsx @@ -1,5 +1,6 @@ import { useEffect } from 'react' import { DOC_THEMES } from '../themes' +import { Dropdown } from './Dropdown' /** 外观与设置(阶段 5):文档主题、当前文档临时主题、自定义 CSS、自动保存间隔。 */ export function AppearanceModal({ docTheme, override, customCss, autosave, autosaveSeconds, hasTab, onDocTheme, onOverride, onCustomCss, onAutosave, onClose }: { @@ -38,18 +39,18 @@ export function AppearanceModal({ docTheme, override, customCss, autosave, autos ))} -