支持 app.ini 配置与系统服务安装,并优化前端布局与无障碍体验。
引入类 Gitea 的 app.ini、Windows Service/systemd 控制;前端增加侧栏抽屉、回到顶部、标签输入与浮层 a11y。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,19 +1,64 @@
|
||||
import { useEffect, useId, useRef, useState } from 'react';
|
||||
import { EMOJI_LIST } from '../utils/emojis';
|
||||
|
||||
interface Props {
|
||||
onSelect: (emoji: string) => void;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
/** OwO 表情选择面板 */
|
||||
export default function EmojiPicker({ onSelect }: Props) {
|
||||
/** OwO 表情选择面板(方向键浏览,Enter 选中) */
|
||||
export default function EmojiPicker({ onSelect, id }: Props) {
|
||||
const autoId = useId();
|
||||
const listId = id ?? autoId;
|
||||
const [active, setActive] = useState(0);
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
listRef.current?.querySelectorAll<HTMLElement>('[role="option"]')[active]?.focus();
|
||||
}, [active]);
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent) => {
|
||||
const cols = 8;
|
||||
let next = active;
|
||||
if (e.key === 'ArrowRight') next = Math.min(EMOJI_LIST.length - 1, active + 1);
|
||||
else if (e.key === 'ArrowLeft') next = Math.max(0, active - 1);
|
||||
else if (e.key === 'ArrowDown') next = Math.min(EMOJI_LIST.length - 1, active + cols);
|
||||
else if (e.key === 'ArrowUp') next = Math.max(0, active - cols);
|
||||
else if (e.key === 'Home') next = 0;
|
||||
else if (e.key === 'End') next = EMOJI_LIST.length - 1;
|
||||
else if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onSelect(EMOJI_LIST[active]);
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
setActive(next);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="emoji-picker">
|
||||
{EMOJI_LIST.map((e) => (
|
||||
<div
|
||||
id={listId}
|
||||
ref={listRef}
|
||||
className="emoji-picker"
|
||||
role="listbox"
|
||||
aria-label="表情列表"
|
||||
aria-activedescendant={`${listId}-opt-${active}`}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
{EMOJI_LIST.map((e, i) => (
|
||||
<button
|
||||
key={e}
|
||||
id={`${listId}-opt-${i}`}
|
||||
type="button"
|
||||
className="emoji-picker-item"
|
||||
role="option"
|
||||
tabIndex={active === i ? 0 : -1}
|
||||
aria-selected={active === i}
|
||||
className={`emoji-picker-item${active === i ? ' emoji-picker-item--active' : ''}`}
|
||||
aria-label={e}
|
||||
onClick={() => onSelect(e)}
|
||||
onFocus={() => setActive(i)}
|
||||
>
|
||||
{e}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user