Files
jiang13-forum/frontend/src/components/EmojiPicker.tsx
freefire 9487c8ab02 支持 app.ini 配置与系统服务安装,并优化前端布局与无障碍体验。
引入类 Gitea 的 app.ini、Windows Service/systemd 控制;前端增加侧栏抽屉、回到顶部、标签输入与浮层 a11y。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 14:09:58 +08:00

69 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useId, useRef, useState } from 'react';
import { EMOJI_LIST } from '../utils/emojis';
interface Props {
onSelect: (emoji: string) => void;
id?: string;
}
/** 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
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"
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>
))}
</div>
);
}