@lokvis/ui-react 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/components/AssetPanel.d.ts.map +1 -1
  2. package/dist/components/AssetPanel.js +14 -14
  3. package/dist/components/AssetPanel.js.map +1 -1
  4. package/dist/components/Canvas.d.ts +4 -4
  5. package/dist/components/Canvas.d.ts.map +1 -1
  6. package/dist/components/Canvas.js +8 -8
  7. package/dist/components/Canvas.js.map +1 -1
  8. package/dist/components/CommandPalette.d.ts +7 -7
  9. package/dist/components/CommandPalette.d.ts.map +1 -1
  10. package/dist/components/CommandPalette.js +10 -10
  11. package/dist/components/CommandPalette.js.map +1 -1
  12. package/dist/components/CompareSlider.d.ts +6 -6
  13. package/dist/components/CompareSlider.d.ts.map +1 -1
  14. package/dist/components/CompareSlider.js +7 -7
  15. package/dist/components/CompareSlider.js.map +1 -1
  16. package/dist/components/DownloadPanel.d.ts +3 -3
  17. package/dist/components/DownloadPanel.d.ts.map +1 -1
  18. package/dist/components/DownloadPanel.js +9 -9
  19. package/dist/components/DownloadPanel.js.map +1 -1
  20. package/dist/components/ErrorBanner.d.ts +2 -2
  21. package/dist/components/ErrorBanner.d.ts.map +1 -1
  22. package/dist/components/ErrorBanner.js +3 -3
  23. package/dist/components/ErrorBanner.js.map +1 -1
  24. package/dist/components/ExifPanel.d.ts +1 -1
  25. package/dist/components/ExifPanel.d.ts.map +1 -1
  26. package/dist/components/ExifPanel.js +2 -2
  27. package/dist/components/ExifPanel.js.map +1 -1
  28. package/dist/components/GlobalDropzone.d.ts +5 -5
  29. package/dist/components/GlobalDropzone.d.ts.map +1 -1
  30. package/dist/components/GlobalDropzone.js +6 -6
  31. package/dist/components/GlobalDropzone.js.map +1 -1
  32. package/dist/components/HistoryPanel.d.ts +2 -2
  33. package/dist/components/HistoryPanel.d.ts.map +1 -1
  34. package/dist/components/HistoryPanel.js +23 -23
  35. package/dist/components/HistoryPanel.js.map +1 -1
  36. package/dist/components/Inspector.d.ts.map +1 -1
  37. package/dist/components/Inspector.js +6 -6
  38. package/dist/components/Inspector.js.map +1 -1
  39. package/dist/components/ParamForm.d.ts.map +1 -1
  40. package/dist/components/ParamForm.js +7 -7
  41. package/dist/components/ParamForm.js.map +1 -1
  42. package/dist/components/PipelineBar.d.ts.map +1 -1
  43. package/dist/components/PipelineBar.js +13 -13
  44. package/dist/components/PipelineBar.js.map +1 -1
  45. package/dist/components/ProgressBar.d.ts +4 -4
  46. package/dist/components/ProgressBar.d.ts.map +1 -1
  47. package/dist/components/ProgressBar.js +8 -8
  48. package/dist/components/ProgressBar.js.map +1 -1
  49. package/dist/components/StatusBar.d.ts +3 -3
  50. package/dist/components/StatusBar.d.ts.map +1 -1
  51. package/dist/components/StatusBar.js +11 -11
  52. package/dist/components/StatusBar.js.map +1 -1
  53. package/dist/components/ThemeToggle.d.ts +4 -4
  54. package/dist/components/ThemeToggle.d.ts.map +1 -1
  55. package/dist/components/ThemeToggle.js +7 -7
  56. package/dist/components/ThemeToggle.js.map +1 -1
  57. package/dist/components/Toolbar.d.ts.map +1 -1
  58. package/dist/components/Toolbar.js +2 -2
  59. package/dist/components/Toolbar.js.map +1 -1
  60. package/dist/components/WorkflowEditor.d.ts +6 -6
  61. package/dist/components/WorkflowEditor.d.ts.map +1 -1
  62. package/dist/components/WorkflowEditor.js +25 -25
  63. package/dist/components/WorkflowEditor.js.map +1 -1
  64. package/dist/components/WorkflowTemplates.d.ts +2 -2
  65. package/dist/components/WorkflowTemplates.d.ts.map +1 -1
  66. package/dist/components/WorkflowTemplates.js +3 -3
  67. package/dist/components/WorkflowTemplates.js.map +1 -1
  68. package/dist/components/Workspace.d.ts +22 -22
  69. package/dist/components/Workspace.d.ts.map +1 -1
  70. package/dist/components/Workspace.js +30 -30
  71. package/dist/components/Workspace.js.map +1 -1
  72. package/dist/index.d.ts +1 -0
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +3 -0
  75. package/dist/index.js.map +1 -1
  76. package/package.json +6 -6
  77. package/src/components/AssetPanel.tsx +263 -263
  78. package/src/components/Canvas.tsx +174 -174
  79. package/src/components/CommandPalette.tsx +242 -242
  80. package/src/components/CompareSlider.tsx +163 -163
  81. package/src/components/DownloadPanel.tsx +203 -203
  82. package/src/components/ErrorBanner.tsx +64 -64
  83. package/src/components/ExifPanel.tsx +154 -154
  84. package/src/components/GlobalDropzone.tsx +170 -170
  85. package/src/components/HistoryPanel.tsx +219 -219
  86. package/src/components/Inspector.tsx +136 -136
  87. package/src/components/ParamForm.tsx +136 -136
  88. package/src/components/PipelineBar.tsx +98 -98
  89. package/src/components/ProgressBar.tsx +78 -78
  90. package/src/components/StatusBar.tsx +148 -148
  91. package/src/components/ThemeToggle.tsx +98 -98
  92. package/src/components/Toolbar.tsx +65 -65
  93. package/src/components/WorkflowEditor.tsx +321 -321
  94. package/src/components/WorkflowTemplates.tsx +77 -77
  95. package/src/components/Workspace.tsx +223 -223
  96. package/src/css-modules.d.ts +3 -0
  97. package/src/index.ts +4 -0
@@ -2,10 +2,10 @@
2
2
  * CommandPalette - ⌘K 工具选择器(W9.2)
3
3
  *
4
4
  * Command Palette 风格的快捷工具入口:
5
- * - ⌘K / Ctrl+K 打开
6
- * - ESC 关闭
7
- * - 输入关键词过滤能力列表
8
- * - ↑↓ 选择,Enter 添加到工作流,or 鼠标点击
5
+ * - ⌘K / Ctrl+K 打开
6
+ * - ESC 关闭
7
+ * - 输入关键词过滤能力列表
8
+ * - ↑↓ 选择,Enter 添加到工作流,or 鼠标点击
9
9
  *
10
10
  * 数据源:`store.capabilities`,与 Inspector 右侧能力列表同源。
11
11
  * 选中后调用 `store.addNode(cap.name)` 添加到工作流,并关闭面板。
@@ -19,238 +19,238 @@ import type { Capability } from '@lokvis/schema';
19
19
  import { useWorkspaceStore } from '../store/index.js';
20
20
 
21
21
  export interface CommandPaletteProps {
22
- /** 是否打开(受控) */
23
- open: boolean;
24
- /** 关闭回调 */
25
- onClose: () => void;
26
- className?: string;
22
+ /** 是否打开(受控) */
23
+ open: boolean;
24
+ /** 关闭回调 */
25
+ onClose: () => void;
26
+ className?: string;
27
27
  }
28
28
 
29
29
  export function CommandPalette({ open, onClose, className = '' }: CommandPaletteProps) {
30
- const capabilities = useWorkspaceStore((s) => s.capabilities);
31
- const addNode = useWorkspaceStore((s) => s.addNode);
32
- const undo = useWorkspaceStore((s) => s.undo);
33
- const redo = useWorkspaceStore((s) => s.redo);
34
- const clearWorkflow = useWorkspaceStore((s) => s.clearWorkflow);
35
- const running = useWorkspaceStore((s) => s.running);
30
+ const capabilities = useWorkspaceStore((s) => s.capabilities);
31
+ const addNode = useWorkspaceStore((s) => s.addNode);
32
+ const undo = useWorkspaceStore((s) => s.undo);
33
+ const redo = useWorkspaceStore((s) => s.redo);
34
+ const clearWorkflow = useWorkspaceStore((s) => s.clearWorkflow);
35
+ const running = useWorkspaceStore((s) => s.running);
36
36
 
37
- const [query, setQuery] = React.useState('');
38
- const [activeIndex, setActiveIndex] = React.useState(0);
39
- const inputRef = React.useRef<HTMLInputElement>(null);
40
- const listRef = React.useRef<HTMLUListElement>(null);
37
+ const [query, setQuery] = React.useState('');
38
+ const [activeIndex, setActiveIndex] = React.useState(0);
39
+ const inputRef = React.useRef<HTMLInputElement>(null);
40
+ const listRef = React.useRef<HTMLUListElement>(null);
41
41
 
42
- // 命令项:capability 添加 + 工作流操作
43
- type Command =
44
- | { kind: 'capability'; cap: Capability }
45
- | { kind: 'action'; id: string; label: string; description: string; run: () => void; disabled?: boolean };
42
+ // 命令项:capability 添加 + 工作流操作
43
+ type Command =
44
+ | { kind: 'capability'; cap: Capability }
45
+ | { kind: 'action'; id: string; label: string; description: string; run: () => void; disabled?: boolean };
46
46
 
47
- const commands: Command[] = React.useMemo(() => {
48
- const caps = capabilities.map((cap) => ({ kind: 'capability' as const, cap }));
49
- const actions: Command[] = [
50
- {
51
- kind: 'action',
52
- id: 'undo',
53
- label: 'Undo',
54
- description: '回退到上一步历史',
55
- run: () => void undo(),
56
- disabled: false,
57
- },
58
- {
59
- kind: 'action',
60
- id: 'redo',
61
- label: 'Redo',
62
- description: '重做一步',
63
- run: () => void redo(),
64
- disabled: false,
65
- },
66
- {
67
- kind: 'action',
68
- id: 'clear',
69
- label: 'Clear Workflow',
70
- description: '清空当前工作流节点',
71
- run: () => clearWorkflow(),
72
- disabled: running,
73
- },
74
- ];
75
- return [...caps, ...actions];
76
- }, [capabilities, undo, redo, clearWorkflow, running]);
47
+ const commands: Command[] = React.useMemo(() => {
48
+ const caps = capabilities.map((cap) => ({ kind: 'capability' as const, cap }));
49
+ const actions: Command[] = [
50
+ {
51
+ kind: 'action',
52
+ id: 'undo',
53
+ label: 'Undo',
54
+ description: '回退到上一步历史',
55
+ run: () => void undo(),
56
+ disabled: false,
57
+ },
58
+ {
59
+ kind: 'action',
60
+ id: 'redo',
61
+ label: 'Redo',
62
+ description: '重做一步',
63
+ run: () => void redo(),
64
+ disabled: false,
65
+ },
66
+ {
67
+ kind: 'action',
68
+ id: 'clear',
69
+ label: 'Clear Workflow',
70
+ description: '清空当前工作流节点',
71
+ run: () => clearWorkflow(),
72
+ disabled: running,
73
+ },
74
+ ];
75
+ return [...caps, ...actions];
76
+ }, [capabilities, undo, redo, clearWorkflow, running]);
77
77
 
78
- const filtered = React.useMemo(() => {
79
- const q = query.trim().toLowerCase();
80
- if (!q) return commands;
81
- return commands.filter((cmd) => {
82
- if (cmd.kind === 'capability') {
83
- return (
84
- cmd.cap.name.toLowerCase().includes(q) ||
85
- cmd.cap.description.toLowerCase().includes(q)
86
- );
87
- }
88
- return cmd.label.toLowerCase().includes(q) || cmd.description.toLowerCase().includes(q);
89
- });
90
- }, [commands, query]);
78
+ const filtered = React.useMemo(() => {
79
+ const q = query.trim().toLowerCase();
80
+ if (!q) return commands;
81
+ return commands.filter((cmd) => {
82
+ if (cmd.kind === 'capability') {
83
+ return (
84
+ cmd.cap.name.toLowerCase().includes(q) ||
85
+ cmd.cap.description.toLowerCase().includes(q)
86
+ );
87
+ }
88
+ return cmd.label.toLowerCase().includes(q) || cmd.description.toLowerCase().includes(q);
89
+ });
90
+ }, [commands, query]);
91
91
 
92
- // 打开时重置 query + activeIndex + 聚焦输入
93
- React.useEffect(() => {
94
- if (!open) return;
95
- setQuery('');
96
- setActiveIndex(0);
97
- // Dialog 聚焦首个可聚焦元素(input)后,input ref 才有值
98
- // 用 timer 句柄 + cleanup 避免卸载后 setState 警告
99
- const t = setTimeout(() => inputRef.current?.focus(), 0);
100
- return () => clearTimeout(t);
101
- }, [open]);
92
+ // 打开时重置 query + activeIndex + 聚焦输入
93
+ React.useEffect(() => {
94
+ if (!open) return;
95
+ setQuery('');
96
+ setActiveIndex(0);
97
+ // Dialog 聚焦首个可聚焦元素(input)后,input ref 才有值
98
+ // 用 timer 句柄 + cleanup 避免卸载后 setState 警告
99
+ const t = setTimeout(() => inputRef.current?.focus(), 0);
100
+ return () => clearTimeout(t);
101
+ }, [open]);
102
102
 
103
- // filtered 变化时校正 activeIndex 越界
104
- React.useEffect(() => {
105
- if (activeIndex >= filtered.length) {
106
- setActiveIndex(filtered.length === 0 ? 0 : filtered.length - 1);
107
- }
108
- }, [filtered.length, activeIndex]);
103
+ // filtered 变化时校正 activeIndex 越界
104
+ React.useEffect(() => {
105
+ if (activeIndex >= filtered.length) {
106
+ setActiveIndex(filtered.length === 0 ? 0 : filtered.length - 1);
107
+ }
108
+ }, [filtered.length, activeIndex]);
109
109
 
110
- // 滚动 active 项进入视区
111
- React.useEffect(() => {
112
- if (!open || !listRef.current) return;
113
- const active = listRef.current.querySelector<HTMLElement>(`[data-index="${activeIndex}"]`);
114
- active?.scrollIntoView({ block: 'nearest' });
115
- }, [activeIndex, open]);
110
+ // 滚动 active 项进入视区
111
+ React.useEffect(() => {
112
+ if (!open || !listRef.current) return;
113
+ const active = listRef.current.querySelector<HTMLElement>(`[data-index="${activeIndex}"]`);
114
+ active?.scrollIntoView({ block: 'nearest' });
115
+ }, [activeIndex, open]);
116
116
 
117
- function handleSelect(cmd: Command) {
118
- if (cmd.kind === 'capability') {
119
- addNode(cmd.cap.name);
120
- } else {
121
- if (cmd.disabled) return;
122
- cmd.run();
123
- }
124
- onClose();
125
- }
117
+ function handleSelect(cmd: Command) {
118
+ if (cmd.kind === 'capability') {
119
+ addNode(cmd.cap.name);
120
+ } else {
121
+ if (cmd.disabled) return;
122
+ cmd.run();
123
+ }
124
+ onClose();
125
+ }
126
126
 
127
- function handleKeyDown(e: React.KeyboardEvent) {
128
- if (e.key === 'ArrowDown') {
129
- e.preventDefault();
130
- setActiveIndex((i) => (i + 1) % Math.max(1, filtered.length));
131
- } else if (e.key === 'ArrowUp') {
132
- e.preventDefault();
133
- setActiveIndex((i) => (i - 1 + filtered.length) % Math.max(1, filtered.length));
134
- } else if (e.key === 'Enter') {
135
- e.preventDefault();
136
- const cmd = filtered[activeIndex];
137
- if (cmd) handleSelect(cmd);
138
- }
139
- }
127
+ function handleKeyDown(e: React.KeyboardEvent) {
128
+ if (e.key === 'ArrowDown') {
129
+ e.preventDefault();
130
+ setActiveIndex((i) => (i + 1) % Math.max(1, filtered.length));
131
+ } else if (e.key === 'ArrowUp') {
132
+ e.preventDefault();
133
+ setActiveIndex((i) => (i - 1 + filtered.length) % Math.max(1, filtered.length));
134
+ } else if (e.key === 'Enter') {
135
+ e.preventDefault();
136
+ const cmd = filtered[activeIndex];
137
+ if (cmd) handleSelect(cmd);
138
+ }
139
+ }
140
140
 
141
- return (
142
- <Dialog open={open} onClose={onClose} size="md" closeOnOverlay>
143
- <div className={`-mx-5 -my-5 ${className}`}>
144
- {/* 搜索输入 */}
145
- <div className="flex items-center gap-2 border-b border-zinc-200 px-4 py-3 dark:border-zinc-800">
146
- <Icon size={16} className="text-zinc-400">
147
- <circle cx="11" cy="11" r="8" />
148
- <path d="m21 21-4.3-4.3" />
149
- </Icon>
150
- <input
151
- ref={inputRef}
152
- type="text"
153
- value={query}
154
- onChange={(e) => setQuery(e.target.value)}
155
- onKeyDown={handleKeyDown}
156
- placeholder="Search commands or capabilities..."
157
- aria-label="Search commands"
158
- className="flex-1 bg-transparent text-sm text-zinc-900 placeholder:text-zinc-400 focus:outline-none dark:text-zinc-100"
159
- autoComplete="off"
160
- spellCheck={false}
161
- />
162
- <kbd className="rounded border border-zinc-200 px-1.5 py-0.5 text-[9px] font-medium text-zinc-400 dark:border-zinc-700 dark:text-zinc-500">
163
- ESC
164
- </kbd>
165
- </div>
141
+ return (
142
+ <Dialog open={open} onClose={onClose} size="md" closeOnOverlay>
143
+ <div className={`-mx-5 -my-5 ${className}`}>
144
+ {/* 搜索输入 */}
145
+ <div className="flex items-center gap-2 border-b border-[var(--lokvis-border)] px-4 py-3">
146
+ <Icon size={16} className="text-[var(--lokvis-fg-subtle)]">
147
+ <circle cx="11" cy="11" r="8" />
148
+ <path d="m21 21-4.3-4.3" />
149
+ </Icon>
150
+ <input
151
+ ref={inputRef}
152
+ type="text"
153
+ value={query}
154
+ onChange={(e) => setQuery(e.target.value)}
155
+ onKeyDown={handleKeyDown}
156
+ placeholder="Search commands or capabilities..."
157
+ aria-label="Search commands"
158
+ className="flex-1 bg-transparent text-sm text-[var(--lokvis-fg)] placeholder:text-[var(--lokvis-fg-subtle)] focus:outline-none"
159
+ autoComplete="off"
160
+ spellCheck={false}
161
+ />
162
+ <kbd className="rounded border border-[var(--lokvis-border)] px-1.5 py-0.5 text-[9px] font-medium text-[var(--lokvis-fg-subtle)]">
163
+ ESC
164
+ </kbd>
165
+ </div>
166
166
 
167
- {/* 命令列表 */}
168
- <ul
169
- ref={listRef}
170
- className="max-h-80 overflow-y-auto py-1"
171
- role="listbox"
172
- aria-label="Available commands"
173
- >
174
- {filtered.length === 0 ? (
175
- <li className="px-4 py-6 text-center text-xs text-zinc-400">
176
- No matching commands
177
- </li>
178
- ) : (
179
- filtered.map((cmd, i) => {
180
- const isActive = i === activeIndex;
181
- const isCap = cmd.kind === 'capability';
182
- const primary = isCap ? cmd.cap.name : cmd.label;
183
- const secondary = isCap ? cmd.cap.description : cmd.description;
184
- const disabled = !isCap && cmd.disabled;
185
- return (
186
- <li
187
- key={isCap ? `cap:${cmd.cap.name}` : `act:${cmd.id}`}
188
- data-index={i}
189
- role="option"
190
- aria-selected={isActive}
191
- aria-disabled={disabled}
192
- >
193
- <button
194
- type="button"
195
- onMouseEnter={() => setActiveIndex(i)}
196
- onClick={() => !disabled && handleSelect(cmd)}
197
- disabled={disabled}
198
- className={`flex w-full items-center gap-3 px-4 py-2 text-left transition-colors ${
199
- isActive && !disabled
200
- ? 'bg-indigo-50 dark:bg-indigo-950/30'
201
- : ''
202
- } ${disabled ? 'cursor-not-allowed opacity-40' : 'hover:bg-zinc-50 dark:hover:bg-zinc-800/50'}`}
203
- >
204
- <span
205
- className={`flex h-6 w-6 shrink-0 items-center justify-center rounded ${
206
- isCap
207
- ? 'bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400'
208
- : 'bg-indigo-100 text-indigo-600 dark:bg-indigo-950/50 dark:text-indigo-400'
209
- }`}
210
- >
211
- <Icon size={12} strokeWidth={2}>
212
- {isCap ? (
213
- <path d="M12 4.5v15m7.5-7.5h-15" />
214
- ) : (
215
- <path d="M13 2L3 14h7l-1 8 10-12h-7l1-8z" />
216
- )}
217
- </Icon>
218
- </span>
219
- <div className="min-w-0 flex-1">
220
- <div className="flex items-center justify-between gap-2">
221
- <span className="truncate text-xs font-medium text-zinc-900 dark:text-zinc-100">
222
- {primary}
223
- </span>
224
- {isCap && (
225
- <span className="shrink-0 rounded px-1 py-0.5 text-[9px] font-medium uppercase bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
226
- {cmd.cap.performance}
227
- </span>
228
- )}
229
- </div>
230
- <p className="truncate text-[10px] text-zinc-500 dark:text-zinc-400">
231
- {secondary}
232
- </p>
233
- </div>
234
- </button>
235
- </li>
236
- );
237
- })
238
- )}
239
- </ul>
167
+ {/* 命令列表 */}
168
+ <ul
169
+ ref={listRef}
170
+ className="max-h-80 overflow-y-auto py-1"
171
+ role="listbox"
172
+ aria-label="Available commands"
173
+ >
174
+ {filtered.length === 0 ? (
175
+ <li className="px-4 py-6 text-center text-xs text-[var(--lokvis-fg-subtle)]">
176
+ No matching commands
177
+ </li>
178
+ ) : (
179
+ filtered.map((cmd, i) => {
180
+ const isActive = i === activeIndex;
181
+ const isCap = cmd.kind === 'capability';
182
+ const primary = isCap ? cmd.cap.name : cmd.label;
183
+ const secondary = isCap ? cmd.cap.description : cmd.description;
184
+ const disabled = !isCap && cmd.disabled;
185
+ return (
186
+ <li
187
+ key={isCap ? `cap:${cmd.cap.name}` : `act:${cmd.id}`}
188
+ data-index={i}
189
+ role="option"
190
+ aria-selected={isActive}
191
+ aria-disabled={disabled}
192
+ >
193
+ <button
194
+ type="button"
195
+ onMouseEnter={() => setActiveIndex(i)}
196
+ onClick={() => !disabled && handleSelect(cmd)}
197
+ disabled={disabled}
198
+ className={`flex w-full items-center gap-3 px-4 py-2 text-left transition-colors ${
199
+ isActive && !disabled
200
+ ? 'bg-[var(--lokvis-primary)]/10'
201
+ : ''
202
+ } ${disabled ? 'cursor-not-allowed opacity-40' : 'hover:bg-[var(--lokvis-surface)]'}`}
203
+ >
204
+ <span
205
+ className={`flex h-6 w-6 shrink-0 items-center justify-center rounded ${
206
+ isCap
207
+ ? 'bg-[var(--lokvis-surface-muted)] text-[var(--lokvis-fg-muted)]'
208
+ : 'bg-[var(--lokvis-primary)]/15 text-[var(--lokvis-primary)]'
209
+ }`}
210
+ >
211
+ <Icon size={12} strokeWidth={2}>
212
+ {isCap ? (
213
+ <path d="M12 4.5v15m7.5-7.5h-15" />
214
+ ) : (
215
+ <path d="M13 2L3 14h7l-1 8 10-12h-7l1-8z" />
216
+ )}
217
+ </Icon>
218
+ </span>
219
+ <div className="min-w-0 flex-1">
220
+ <div className="flex items-center justify-between gap-2">
221
+ <span className="truncate text-xs font-medium text-[var(--lokvis-fg)]">
222
+ {primary}
223
+ </span>
224
+ {isCap && (
225
+ <span className="shrink-0 rounded px-1 py-0.5 text-[9px] font-medium uppercase bg-[var(--lokvis-surface-muted)] text-[var(--lokvis-fg-muted)]">
226
+ {cmd.cap.performance}
227
+ </span>
228
+ )}
229
+ </div>
230
+ <p className="truncate text-[10px] text-[var(--lokvis-fg-muted)]">
231
+ {secondary}
232
+ </p>
233
+ </div>
234
+ </button>
235
+ </li>
236
+ );
237
+ })
238
+ )}
239
+ </ul>
240
240
 
241
- {/* 底部提示 */}
242
- <div className="flex items-center justify-between border-t border-zinc-200 px-4 py-2 text-[10px] text-zinc-400 dark:border-zinc-800">
243
- <span className="flex items-center gap-1">
244
- <kbd className="rounded border border-zinc-200 px-1 dark:border-zinc-700">↑↓</kbd>
245
- <span>navigate</span>
246
- <kbd className="ml-2 rounded border border-zinc-200 px-1 dark:border-zinc-700">↵</kbd>
247
- <span>select</span>
248
- </span>
249
- <span>{filtered.length} command{filtered.length !== 1 ? 's' : ''}</span>
250
- </div>
251
- </div>
252
- </Dialog>
253
- );
241
+ {/* 底部提示 */}
242
+ <div className="flex items-center justify-between border-t border-[var(--lokvis-border)] px-4 py-2 text-[10px] text-[var(--lokvis-fg-subtle)]">
243
+ <span className="flex items-center gap-1">
244
+ <kbd className="rounded border border-[var(--lokvis-border)] px-1">↑↓</kbd>
245
+ <span>navigate</span>
246
+ <kbd className="ml-2 rounded border border-[var(--lokvis-border)] px-1">↵</kbd>
247
+ <span>select</span>
248
+ </span>
249
+ <span>{filtered.length} command{filtered.length !== 1 ? 's' : ''}</span>
250
+ </div>
251
+ </div>
252
+ </Dialog>
253
+ );
254
254
  }
255
255
 
256
256
  /**
@@ -266,35 +266,35 @@ export function CommandPalette({ open, onClose, className = '' }: CommandPalette
266
266
  * // 或禁用快捷键(仍可受控使用 open/setOpen):
267
267
  * const [open, setOpen] = useCommandPalette({ enabled: false });
268
268
  * return (
269
- * <>
270
- * <CommandPalette open={open} onClose={() => setOpen(false)} />
271
- * </>
269
+ * <>
270
+ * <CommandPalette open={open} onClose={() => setOpen(false)} />
271
+ * </>
272
272
  * );
273
273
  * ```
274
274
  */
275
275
  export interface UseCommandPaletteOptions {
276
- /** 是否注册 ⌘K 全局快捷键(默认 true)。设为 false 时仍可受控使用 open/setOpen */
277
- enabled?: boolean;
276
+ /** 是否注册 ⌘K 全局快捷键(默认 true)。设为 false 时仍可受控使用 open/setOpen */
277
+ enabled?: boolean;
278
278
  }
279
279
 
280
280
  export function useCommandPalette(
281
- options: UseCommandPaletteOptions = {}
281
+ options: UseCommandPaletteOptions = {}
282
282
  ): [boolean, React.Dispatch<React.SetStateAction<boolean>>] {
283
- const { enabled = true } = options;
284
- const [open, setOpen] = React.useState(false);
283
+ const { enabled = true } = options;
284
+ const [open, setOpen] = React.useState(false);
285
285
 
286
- React.useEffect(() => {
287
- if (!enabled) return;
288
- const onKey = (e: KeyboardEvent) => {
289
- // ⌘K (mac) / Ctrl+K (win/linux),忽略纯 K(避免拦截输入)
290
- if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
291
- e.preventDefault();
292
- setOpen((v) => !v);
293
- }
294
- };
295
- document.addEventListener('keydown', onKey);
296
- return () => document.removeEventListener('keydown', onKey);
297
- }, [enabled]);
286
+ React.useEffect(() => {
287
+ if (!enabled) return;
288
+ const onKey = (e: KeyboardEvent) => {
289
+ // ⌘K (mac) / Ctrl+K (win/linux),忽略纯 K(避免拦截输入)
290
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
291
+ e.preventDefault();
292
+ setOpen((v) => !v);
293
+ }
294
+ };
295
+ document.addEventListener('keydown', onKey);
296
+ return () => document.removeEventListener('keydown', onKey);
297
+ }, [enabled]);
298
298
 
299
- return [open, setOpen];
299
+ return [open, setOpen];
300
300
  }