feat(组件): 添加快捷键帮助面板 UI
- 实现快捷键帮助弹窗界面 - 按分类(导航、编辑、通用)分组显示 - 支持 Mac/Windows 快捷键格式切换 - 预置常用快捷键说明 - 按 Esc 关闭面板
This commit is contained in:
parent
827a033d41
commit
f859ffe4cd
207
src/components/ui/HotkeysHelper.tsx
Normal file
207
src/components/ui/HotkeysHelper.tsx
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState, useMemo } from 'react';
|
||||||
|
import { X, Keyboard } from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import type { RegisteredHotkey, HotkeyCategory } from '@/types/hotkeys';
|
||||||
|
import { formatHotkey } from '@/types/hotkeys';
|
||||||
|
|
||||||
|
interface HotkeysHelperProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
registeredHotkeys?: RegisteredHotkey[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预定义的快捷键列表(包括系统内置的)
|
||||||
|
const BUILTIN_HOTKEYS: RegisteredHotkey[] = [
|
||||||
|
{
|
||||||
|
id: 'new-chat',
|
||||||
|
config: {
|
||||||
|
key: 'n',
|
||||||
|
description: '新建对话',
|
||||||
|
category: '导航',
|
||||||
|
action: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'open-search',
|
||||||
|
config: {
|
||||||
|
key: 'k',
|
||||||
|
modifiers: ['meta'],
|
||||||
|
description: '打开搜索',
|
||||||
|
category: '导航',
|
||||||
|
action: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'help',
|
||||||
|
config: {
|
||||||
|
key: '?',
|
||||||
|
description: '显示快捷键帮助',
|
||||||
|
category: '通用',
|
||||||
|
action: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'close-modal',
|
||||||
|
config: {
|
||||||
|
key: 'Escape',
|
||||||
|
description: '关闭弹窗',
|
||||||
|
category: '通用',
|
||||||
|
action: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快捷键帮助面板
|
||||||
|
*/
|
||||||
|
export function HotkeysHelper({ isOpen, onClose, registeredHotkeys = [] }: HotkeysHelperProps) {
|
||||||
|
// 检测是否为 Mac 系统
|
||||||
|
const [isMac, setIsMac] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMac(navigator.platform.toLowerCase().includes('mac'));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 合并内置和动态快捷键,按分类分组
|
||||||
|
const groupedHotkeys = useMemo(() => {
|
||||||
|
const allHotkeys = [...BUILTIN_HOTKEYS, ...registeredHotkeys];
|
||||||
|
|
||||||
|
// 去重(以 id 为准,优先保留 BUILTIN)
|
||||||
|
const uniqueHotkeys = allHotkeys.reduce((acc, hotkey) => {
|
||||||
|
if (!acc.find((h) => h.id === hotkey.id)) {
|
||||||
|
acc.push(hotkey);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, [] as RegisteredHotkey[]);
|
||||||
|
|
||||||
|
// 按分类分组
|
||||||
|
const groups: Record<HotkeyCategory, RegisteredHotkey[]> = {
|
||||||
|
导航: [],
|
||||||
|
编辑: [],
|
||||||
|
通用: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
uniqueHotkeys.forEach((hotkey) => {
|
||||||
|
const category = hotkey.config.category || '通用';
|
||||||
|
if (groups[category]) {
|
||||||
|
groups[category].push(hotkey);
|
||||||
|
} else {
|
||||||
|
groups['通用'].push(hotkey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 过滤空分类
|
||||||
|
return Object.entries(groups).filter(([, items]) => items.length > 0);
|
||||||
|
}, [registeredHotkeys]);
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* 遮罩层 */}
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[200] animate-fade-in-fast"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 帮助面板 */}
|
||||||
|
<div className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[201] w-full max-w-[480px] px-4">
|
||||||
|
<div className="bg-[var(--color-bg-primary)] rounded shadow-2xl overflow-hidden animate-scale-in-fast">
|
||||||
|
{/* 头部 */}
|
||||||
|
<div className="flex items-center justify-between px-6 py-4 border-b border-[var(--color-border-light)]">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 flex items-center justify-center bg-[var(--color-primary-alpha)] rounded-lg">
|
||||||
|
<Keyboard size={20} className="text-[var(--color-primary)]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-[var(--color-text-primary)]">
|
||||||
|
键盘快捷键
|
||||||
|
</h2>
|
||||||
|
<p className="text-xs text-[var(--color-text-tertiary)]">
|
||||||
|
使用快捷键提高效率
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="w-8 h-8 flex items-center justify-center rounded-lg text-[var(--color-text-tertiary)] hover:bg-[var(--color-bg-hover)] hover:text-[var(--color-text-secondary)] transition-colors"
|
||||||
|
>
|
||||||
|
<X size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 快捷键列表 */}
|
||||||
|
<div className="px-6 py-4 max-h-[60vh] overflow-y-auto">
|
||||||
|
{groupedHotkeys.map(([category, hotkeys], groupIndex) => (
|
||||||
|
<div
|
||||||
|
key={category}
|
||||||
|
className={cn(groupIndex > 0 && 'mt-6')}
|
||||||
|
>
|
||||||
|
{/* 分类标题 */}
|
||||||
|
<h3 className="text-xs font-semibold text-[var(--color-text-tertiary)] uppercase tracking-wider mb-3">
|
||||||
|
{category}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* 快捷键项 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
{hotkeys.map((hotkey) => (
|
||||||
|
<div
|
||||||
|
key={hotkey.id}
|
||||||
|
className="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-[var(--color-bg-secondary)] transition-colors"
|
||||||
|
>
|
||||||
|
<span className="text-sm text-[var(--color-text-primary)]">
|
||||||
|
{hotkey.config.description}
|
||||||
|
</span>
|
||||||
|
<HotkeyBadge config={hotkey.config} isMac={isMac} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 底部提示 */}
|
||||||
|
<div className="px-6 py-3 bg-[var(--color-bg-secondary)] border-t border-[var(--color-border-light)]">
|
||||||
|
<div className="flex items-center justify-between text-xs text-[var(--color-text-tertiary)]">
|
||||||
|
<span>按 <kbd className="inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded text-[10px] mx-1">Esc</kbd> 关闭</span>
|
||||||
|
<span>{isMac ? 'macOS' : 'Windows/Linux'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快捷键徽章组件
|
||||||
|
*/
|
||||||
|
function HotkeyBadge({
|
||||||
|
config,
|
||||||
|
isMac,
|
||||||
|
}: {
|
||||||
|
config: RegisteredHotkey['config'];
|
||||||
|
isMac: boolean;
|
||||||
|
}) {
|
||||||
|
const keys = formatHotkey(config, isMac).split(isMac ? ' ' : '+');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{keys.map((key, index) => (
|
||||||
|
<kbd
|
||||||
|
key={index}
|
||||||
|
className={cn(
|
||||||
|
'inline-flex items-center justify-center min-w-[24px] h-6 px-2',
|
||||||
|
'bg-[var(--color-bg-tertiary)] border border-[var(--color-border)]',
|
||||||
|
'rounded text-xs font-medium text-[var(--color-text-secondary)]',
|
||||||
|
'shadow-sm'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
</kbd>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user