diff --git a/src/hooks/useHotkeys.ts b/src/hooks/useHotkeys.ts new file mode 100644 index 0000000..91e7fd3 --- /dev/null +++ b/src/hooks/useHotkeys.ts @@ -0,0 +1,208 @@ +'use client'; + +import { useEffect, DependencyList, useMemo } from 'react'; +import { useHotkeysContext } from '@/providers/HotkeysProvider'; +import type { HotkeyConfig, ModifierKey, HotkeyCategory } from '@/types/hotkeys'; + +/** + * useHotkeys Hook 配置选项 + */ +export interface UseHotkeysOptions { + /** 主键 */ + key: string; + /** 修饰键 */ + modifiers?: ModifierKey[]; + /** 快捷键描述 */ + description: string; + /** 分类 */ + category?: HotkeyCategory; + /** 触发回调 */ + action: () => void; + /** 是否启用 */ + enabled?: boolean | (() => boolean); + /** 是否阻止默认行为 */ + preventDefault?: boolean; + /** 是否允许在输入框中触发 */ + allowInInput?: boolean; +} + +/** + * 注册快捷键的 Hook + * + * @param id 快捷键唯一标识符 + * @param options 快捷键配置 + * @param deps 依赖数组(当依赖变化时重新注册) + * + * @example + * ```tsx + * // 单键快捷键(非输入框时触发) + * useHotkeys('new-chat', { + * key: 'n', + * description: '新建对话', + * category: '导航', + * action: () => setShowNewChatModal(true), + * }); + * + * // 组合键快捷键 + * useHotkeys('open-search', { + * key: 'k', + * modifiers: ['ctrl', 'meta'], // Ctrl+K 或 Cmd+K + * description: '打开搜索', + * category: '导航', + * action: () => setShowSearchModal(true), + * allowInInput: true, // 在输入框中也生效 + * }); + * ``` + */ +export function useHotkeys( + id: string, + options: UseHotkeysOptions, + deps: DependencyList = [] +): void { + const { register, unregister } = useHotkeysContext(); + + // 将 options 转换为 HotkeyConfig + const config: HotkeyConfig = useMemo( + () => ({ + key: options.key, + modifiers: options.modifiers, + description: options.description, + category: options.category, + action: options.action, + enabled: options.enabled, + preventDefault: options.preventDefault, + allowInInput: options.allowInInput, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + options.key, + options.description, + options.category, + options.enabled, + options.preventDefault, + options.allowInInput, + // modifiers 需要特殊处理(数组比较) + // eslint-disable-next-line react-hooks/exhaustive-deps + JSON.stringify(options.modifiers), + // action 放在 deps 中由用户控制 + ...deps, + ] + ); + + useEffect(() => { + // 注册快捷键 + register(id, { + ...config, + action: options.action, // 使用最新的 action + }); + + // 清理:注销快捷键 + return () => { + unregister(id); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id, config, register, unregister]); +} + +/** + * 批量注册快捷键的 Hook + * + * @param configs 快捷键配置数组 + * @param deps 依赖数组 + * + * @example + * ```tsx + * useHotkeysGroup([ + * { + * id: 'new-chat', + * key: 'n', + * description: '新建对话', + * action: () => setShowNewChatModal(true), + * }, + * { + * id: 'open-search', + * key: 'k', + * modifiers: ['ctrl', 'meta'], + * description: '打开搜索', + * action: () => setShowSearchModal(true), + * }, + * ]); + * ``` + */ +export function useHotkeysGroup( + configs: (UseHotkeysOptions & { id: string })[], + deps: DependencyList = [] +): void { + const { register, unregister } = useHotkeysContext(); + + useEffect(() => { + // 批量注册 + configs.forEach(({ id, ...options }) => { + register(id, { + key: options.key, + modifiers: options.modifiers, + description: options.description, + category: options.category, + action: options.action, + enabled: options.enabled, + preventDefault: options.preventDefault, + allowInInput: options.allowInInput, + }); + }); + + // 清理:批量注销 + return () => { + configs.forEach(({ id }) => { + unregister(id); + }); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [register, unregister, JSON.stringify(configs.map((c) => c.id)), ...deps]); +} + +/** + * 获取快捷键帮助面板控制的 Hook + * + * @example + * ```tsx + * const { isOpen, toggle, open, close } = useHotkeysHelper(); + * + * return ( + * + * ); + * ``` + */ +export function useHotkeysHelper() { + const { isHelperOpen, toggleHelper, openHelper, closeHelper } = + useHotkeysContext(); + + return { + isOpen: isHelperOpen, + toggle: toggleHelper, + open: openHelper, + close: closeHelper, + }; +} + +/** + * 获取所有已注册快捷键的 Hook + * + * @example + * ```tsx + * const hotkeys = useRegisteredHotkeys(); + * + * return ( + * + * ); + * ``` + */ +export function useRegisteredHotkeys() { + const { getAll } = useHotkeysContext(); + return getAll(); +}