@fragments-sdk/cli 0.5.2 → 0.7.0
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.
- package/dist/bin.js +996 -79
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
- package/dist/chunk-6JBGU74P.js.map +1 -0
- package/dist/chunk-7OPWMLOE.js +1625 -0
- package/dist/chunk-7OPWMLOE.js.map +1 -0
- package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
- package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
- package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
- package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
- package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
- package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
- package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
- package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
- package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
- package/dist/mcp-bin.js +8 -220
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-WY23TJCP.js +12 -0
- package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
- package/dist/static-viewer-GBR7YNF3.js +12 -0
- package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
- package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
- package/dist/viewer-SUFOISZM.js +1822 -0
- package/dist/viewer-SUFOISZM.js.map +1 -0
- package/package.json +6 -5
- package/src/bin.ts +31 -0
- package/src/build.ts +147 -13
- package/src/cli-commands.ts +18 -0
- package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
- package/src/commands/a11y-report.ts +625 -0
- package/src/commands/a11y.ts +168 -14
- package/src/commands/build.ts +16 -0
- package/src/commands/graph.ts +274 -0
- package/src/core/auto-props.ts +464 -0
- package/src/core/composition.ts +64 -1
- package/src/core/graph-extractor.test.ts +542 -0
- package/src/core/graph-extractor.ts +601 -0
- package/src/core/importAnalyzer.ts +5 -0
- package/src/core/schema.ts +2 -0
- package/src/core/types.ts +3 -1
- package/src/index.ts +4 -0
- package/src/mcp/server.ts +13 -220
- package/src/theme/__tests__/component-contrast.test.ts +338 -0
- package/src/theme/__tests__/contrast-validation.test.ts +326 -0
- package/src/theme/contrast.test.ts +331 -0
- package/src/theme/contrast.ts +246 -0
- package/src/theme/generator.ts +213 -1
- package/src/theme/index.ts +16 -0
- package/src/theme/types.ts +51 -0
- package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
- package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
- package/src/viewer/components/AccessibilityPanel.tsx +493 -433
- package/src/viewer/components/ActionCapture.tsx +1 -1
- package/src/viewer/components/ActionsPanel.tsx +142 -183
- package/src/viewer/components/App.tsx +276 -183
- package/src/viewer/components/BottomPanel.tsx +40 -80
- package/src/viewer/components/CodePanel.tsx +9 -87
- package/src/viewer/components/CommandPalette.tsx +117 -74
- package/src/viewer/components/ComponentGraph.tsx +143 -126
- package/src/viewer/components/ComponentHeader.tsx +46 -43
- package/src/viewer/components/ContractPanel.tsx +124 -117
- package/src/viewer/components/ErrorBoundary.tsx +47 -35
- package/src/viewer/components/FigmaEmbed.tsx +18 -13
- package/src/viewer/components/FragmentEditor.tsx +126 -63
- package/src/viewer/components/HealthDashboard.tsx +146 -171
- package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
- package/src/viewer/components/Icons.tsx +151 -98
- package/src/viewer/components/InteractionsPanel.tsx +317 -264
- package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
- package/src/viewer/components/IsolatedRender.tsx +12 -6
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
- package/src/viewer/components/LandingPage.tsx +285 -305
- package/src/viewer/components/Layout.tsx +12 -10
- package/src/viewer/components/LeftSidebar.tsx +103 -155
- package/src/viewer/components/MultiViewportPreview.tsx +254 -63
- package/src/viewer/components/PreviewArea.tsx +113 -44
- package/src/viewer/components/PreviewFrameHost.tsx +36 -6
- package/src/viewer/components/PreviewPane.tsx +2 -3
- package/src/viewer/components/PreviewToolbar.tsx +109 -105
- package/src/viewer/components/PropsEditor.tsx +154 -74
- package/src/viewer/components/PropsTable.tsx +95 -82
- package/src/viewer/components/RelationsSection.tsx +71 -40
- package/src/viewer/components/ResizablePanel.tsx +158 -55
- package/src/viewer/components/RightSidebar.tsx +46 -56
- package/src/viewer/components/ScreenshotButton.tsx +12 -12
- package/src/viewer/components/SkeletonLoader.tsx +99 -83
- package/src/viewer/components/StoryRenderer.tsx +4 -11
- package/src/viewer/components/Toast.tsx +3 -67
- package/src/viewer/components/TokenStylePanel.tsx +136 -118
- package/src/viewer/components/UsageSection.tsx +26 -26
- package/src/viewer/components/VariantMatrix.tsx +140 -47
- package/src/viewer/components/VariantTabs.tsx +24 -68
- package/src/viewer/components/ViewportSelector.tsx +121 -114
- package/src/viewer/constants/ui.ts +23 -22
- package/src/viewer/entry.tsx +8 -3
- package/src/viewer/index.ts +3 -6
- package/src/viewer/preview-frame.html +43 -18
- package/src/viewer/server.ts +7 -16
- package/src/viewer/styles/globals.css +46 -85
- package/src/viewer/utils/a11y-fixes.ts +53 -30
- package/dist/chunk-ICAIQ57V.js.map +0 -1
- package/dist/chunk-U4GQ2JTD.js +0 -832
- package/dist/chunk-U4GQ2JTD.js.map +0 -1
- package/dist/scan-ESEXV7LF.js +0 -12
- package/dist/static-viewer-O37MJ5B6.js +0 -12
- package/dist/viewer-YDGFDTK5.js +0 -11104
- package/dist/viewer-YDGFDTK5.js.map +0 -1
- package/src/viewer/postcss.config.js +0 -6
- package/src/viewer/tailwind.config.js +0 -37
- /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
- /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
- /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
- /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
- /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
- /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
- /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
- /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
- /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
- /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
- /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { memo, useCallback } from 'react';
|
|
7
7
|
import type { SegmentDefinition, SegmentVariant } from '../../core/index.js';
|
|
8
|
-
import
|
|
8
|
+
import { Tabs, Badge } from '@fragments/ui';
|
|
9
9
|
import { ResizablePanel } from './ResizablePanel.js';
|
|
10
10
|
import { CodePanel } from './CodePanel.js';
|
|
11
11
|
import { TokenStylePanel } from './TokenStylePanel.js';
|
|
@@ -48,35 +48,6 @@ interface BottomPanelProps {
|
|
|
48
48
|
segmentKey: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// Tab button component
|
|
52
|
-
interface TabButtonProps {
|
|
53
|
-
active: boolean;
|
|
54
|
-
onClick: () => void;
|
|
55
|
-
children: React.ReactNode;
|
|
56
|
-
badge?: number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const TabButton = memo(function TabButton({ active, onClick, children, badge }: TabButtonProps) {
|
|
60
|
-
return (
|
|
61
|
-
<button
|
|
62
|
-
onClick={onClick}
|
|
63
|
-
className={clsx(
|
|
64
|
-
"px-3 py-1 text-xs font-medium rounded relative",
|
|
65
|
-
active
|
|
66
|
-
? "text-primary bg-[--bg-hover]"
|
|
67
|
-
: "text-tertiary hover:text-secondary"
|
|
68
|
-
)}
|
|
69
|
-
>
|
|
70
|
-
{children}
|
|
71
|
-
{badge !== undefined && badge > 0 && (
|
|
72
|
-
<span className="absolute -top-1 -right-1 w-4 h-4 text-[10px] bg-blue-500 text-white rounded-full flex items-center justify-center">
|
|
73
|
-
{badge > 99 ? '99+' : badge}
|
|
74
|
-
</span>
|
|
75
|
-
)}
|
|
76
|
-
</button>
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
51
|
export const BottomPanel = memo(function BottomPanel({
|
|
81
52
|
segment,
|
|
82
53
|
variant,
|
|
@@ -102,63 +73,52 @@ export const BottomPanel = memo(function BottomPanel({
|
|
|
102
73
|
setTimeout(onRefreshRendered, 100);
|
|
103
74
|
}, [onPanelChange, onFetchFigma, onRefreshRendered]);
|
|
104
75
|
|
|
76
|
+
// Build tab change handler that also triggers side effects
|
|
77
|
+
const handleTabChange = useCallback((value: string | number) => {
|
|
78
|
+
const panel = String(value);
|
|
79
|
+
if (panel === 'styles') {
|
|
80
|
+
handleStylesClick();
|
|
81
|
+
} else {
|
|
82
|
+
onPanelChange(panel as ActivePanel);
|
|
83
|
+
}
|
|
84
|
+
}, [handleStylesClick, onPanelChange]);
|
|
85
|
+
|
|
105
86
|
return (
|
|
106
87
|
<ResizablePanel
|
|
107
88
|
visible={true}
|
|
108
89
|
header={
|
|
109
|
-
<
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)}
|
|
138
|
-
<TabButton
|
|
139
|
-
active={activePanel === 'actions'}
|
|
140
|
-
onClick={() => onPanelChange('actions')}
|
|
141
|
-
badge={actionLogs.length}
|
|
142
|
-
>
|
|
143
|
-
Actions
|
|
144
|
-
</TabButton>
|
|
145
|
-
<TabButton
|
|
146
|
-
active={activePanel === 'graph'}
|
|
147
|
-
onClick={() => onPanelChange('graph')}
|
|
148
|
-
>
|
|
149
|
-
Graph
|
|
150
|
-
</TabButton>
|
|
151
|
-
<TabButton
|
|
152
|
-
active={activePanel === 'contract'}
|
|
153
|
-
onClick={() => onPanelChange('contract')}
|
|
154
|
-
>
|
|
155
|
-
Contract
|
|
156
|
-
</TabButton>
|
|
157
|
-
</div>
|
|
90
|
+
<Tabs value={activePanel} onValueChange={handleTabChange}>
|
|
91
|
+
<Tabs.List variant="pills">
|
|
92
|
+
<Tabs.Tab value="code">Code</Tabs.Tab>
|
|
93
|
+
{figmaUrl && <Tabs.Tab value="styles">Styles</Tabs.Tab>}
|
|
94
|
+
<Tabs.Tab value="accessibility">Accessibility</Tabs.Tab>
|
|
95
|
+
{variant?.hasPlayFunction && <Tabs.Tab value="interactions">Interactions</Tabs.Tab>}
|
|
96
|
+
<Tabs.Tab value="actions">
|
|
97
|
+
<span style={{ position: 'relative' }}>
|
|
98
|
+
Actions
|
|
99
|
+
{actionLogs.length > 0 && (
|
|
100
|
+
<Badge
|
|
101
|
+
variant="info"
|
|
102
|
+
size="sm"
|
|
103
|
+
style={{
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
top: '-8px',
|
|
106
|
+
right: '-16px',
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
{actionLogs.length > 99 ? '99+' : actionLogs.length}
|
|
110
|
+
</Badge>
|
|
111
|
+
)}
|
|
112
|
+
</span>
|
|
113
|
+
</Tabs.Tab>
|
|
114
|
+
<Tabs.Tab value="graph">Graph</Tabs.Tab>
|
|
115
|
+
<Tabs.Tab value="contract">Contract</Tabs.Tab>
|
|
116
|
+
</Tabs.List>
|
|
117
|
+
</Tabs>
|
|
158
118
|
}
|
|
159
119
|
>
|
|
160
120
|
{activePanel === 'code' && (
|
|
161
|
-
<div
|
|
121
|
+
<div style={{ padding: '16px' }}>
|
|
162
122
|
<CodePanel
|
|
163
123
|
variant={variant}
|
|
164
124
|
componentName={segment.meta.name}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo, isValidElement, type ReactNode } from 'react';
|
|
2
2
|
import type { SegmentVariant, PropDefinition } from '../../core/index.js';
|
|
3
|
-
import {
|
|
4
|
-
import clsx from 'clsx';
|
|
5
|
-
import { CopyIcon, CheckIcon } from './Icons.js';
|
|
3
|
+
import { CodeBlock } from '@fragments/ui';
|
|
6
4
|
|
|
7
5
|
interface CodePanelProps {
|
|
8
6
|
variant: SegmentVariant;
|
|
@@ -213,9 +211,6 @@ function extractRenderBody(renderFn: () => ReactNode): string | null {
|
|
|
213
211
|
}
|
|
214
212
|
|
|
215
213
|
export function CodePanel({ variant, componentName, compact = false, propDefs }: CodePanelProps) {
|
|
216
|
-
const [copied, setCopied] = useState(false);
|
|
217
|
-
const [highlightedHtml, setHighlightedHtml] = useState<string>('');
|
|
218
|
-
|
|
219
214
|
// Generate code - extract from render function source for accuracy
|
|
220
215
|
const generatedCode = useMemo(() => {
|
|
221
216
|
// Priority 1: Use variant.code if available (from compiled JSON/AST)
|
|
@@ -243,90 +238,17 @@ export function CodePanel({ variant, componentName, compact = false, propDefs }:
|
|
|
243
238
|
return generateCombinedCode(componentName, propDefs, effectiveArgs, needsState);
|
|
244
239
|
}, [componentName, variant, propDefs]);
|
|
245
240
|
|
|
246
|
-
// Apply syntax highlighting
|
|
247
|
-
useEffect(() => {
|
|
248
|
-
let cancelled = false;
|
|
249
|
-
|
|
250
|
-
codeToHtml(generatedCode, {
|
|
251
|
-
lang: 'tsx',
|
|
252
|
-
theme: 'one-dark-pro',
|
|
253
|
-
}).then(html => {
|
|
254
|
-
if (!cancelled) {
|
|
255
|
-
setHighlightedHtml(html);
|
|
256
|
-
}
|
|
257
|
-
}).catch(err => {
|
|
258
|
-
console.error('Syntax highlighting failed:', err);
|
|
259
|
-
if (!cancelled) {
|
|
260
|
-
// Fallback to plain text
|
|
261
|
-
setHighlightedHtml(`<pre><code>${escapeHtml(generatedCode)}</code></pre>`);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
return () => { cancelled = true; };
|
|
266
|
-
}, [generatedCode]);
|
|
267
|
-
|
|
268
|
-
const handleCopy = useCallback(async () => {
|
|
269
|
-
try {
|
|
270
|
-
await navigator.clipboard.writeText(generatedCode);
|
|
271
|
-
setCopied(true);
|
|
272
|
-
setTimeout(() => setCopied(false), 2000);
|
|
273
|
-
} catch (err) {
|
|
274
|
-
console.error('Failed to copy:', err);
|
|
275
|
-
}
|
|
276
|
-
}, [generatedCode]);
|
|
277
|
-
|
|
278
241
|
return (
|
|
279
|
-
<
|
|
280
|
-
{
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
'[&_.shiki]:!bg-transparent',
|
|
287
|
-
// Custom selection highlight for better visibility
|
|
288
|
-
'[&_*::selection]:bg-blue-500/40 [&_*::selection]:text-white',
|
|
289
|
-
compact && '[&_code]:text-xs'
|
|
290
|
-
)}
|
|
291
|
-
style={{ maxHeight: 400 }}
|
|
292
|
-
dangerouslySetInnerHTML={{ __html: highlightedHtml || '<div class="p-4 text-gray-500 text-sm">Loading...</div>' }}
|
|
293
|
-
/>
|
|
294
|
-
|
|
295
|
-
{/* Copy button - fixed to top right of editor */}
|
|
296
|
-
<button
|
|
297
|
-
onClick={handleCopy}
|
|
298
|
-
className={clsx(
|
|
299
|
-
'absolute top-2 right-2 flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium rounded-md transition-all duration-200',
|
|
300
|
-
copied
|
|
301
|
-
? 'bg-green-500/20 text-green-400 border border-green-500/30'
|
|
302
|
-
: 'bg-white/10 text-gray-300 border border-white/10 hover:bg-white/15 hover:text-white'
|
|
303
|
-
)}
|
|
304
|
-
>
|
|
305
|
-
{copied ? (
|
|
306
|
-
<>
|
|
307
|
-
<CheckIcon className="w-3.5 h-3.5" />
|
|
308
|
-
<span>Copied!</span>
|
|
309
|
-
</>
|
|
310
|
-
) : (
|
|
311
|
-
<>
|
|
312
|
-
<CopyIcon className="w-3.5 h-3.5" />
|
|
313
|
-
<span>Copy</span>
|
|
314
|
-
</>
|
|
315
|
-
)}
|
|
316
|
-
</button>
|
|
317
|
-
</div>
|
|
242
|
+
<CodeBlock
|
|
243
|
+
code={generatedCode}
|
|
244
|
+
language="tsx"
|
|
245
|
+
showCopy
|
|
246
|
+
maxHeight={400}
|
|
247
|
+
compact={compact}
|
|
248
|
+
/>
|
|
318
249
|
);
|
|
319
250
|
}
|
|
320
251
|
|
|
321
|
-
function escapeHtml(str: string): string {
|
|
322
|
-
return str
|
|
323
|
-
.replace(/&/g, '&')
|
|
324
|
-
.replace(/</g, '<')
|
|
325
|
-
.replace(/>/g, '>')
|
|
326
|
-
.replace(/"/g, '"')
|
|
327
|
-
.replace(/'/g, ''');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
252
|
/**
|
|
331
253
|
* Normalize indentation by removing common leading whitespace from all lines.
|
|
332
254
|
* Handles JSX where first line may be at column 0 but inner content is indented.
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* - Quick navigation to components
|
|
6
6
|
* - Fuzzy search across all components and variants
|
|
7
7
|
* - Keyboard navigation (arrows, enter, escape)
|
|
8
|
+
*
|
|
9
|
+
* Uses Fragments UI Dialog for the overlay shell.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { useState, useEffect, useRef, useMemo, useCallback } from "react";
|
|
11
|
-
import
|
|
13
|
+
import { Dialog, Stack, Text, Badge, Separator, Input } from '@fragments/ui';
|
|
12
14
|
import type { SegmentDefinition } from "../../core/index.js";
|
|
13
15
|
import { SearchIcon, ChevronRightIcon } from "./Icons.js";
|
|
14
16
|
|
|
@@ -168,96 +170,132 @@ export function CommandPalette({
|
|
|
168
170
|
if (!isOpen) return null;
|
|
169
171
|
|
|
170
172
|
return (
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
<div
|
|
174
|
-
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
|
175
|
-
onClick={onClose}
|
|
176
|
-
/>
|
|
177
|
-
|
|
178
|
-
{/* Palette */}
|
|
179
|
-
<div
|
|
180
|
-
className="relative w-full max-w-xl bg-[--bg-primary] rounded-xl shadow-2xl border border-[--border] overflow-hidden"
|
|
181
|
-
onKeyDown={handleKeyDown}
|
|
182
|
-
>
|
|
173
|
+
<Dialog open={isOpen} onOpenChange={(open: boolean) => { if (!open) onClose(); }}>
|
|
174
|
+
<Dialog.Content size="lg">
|
|
183
175
|
{/* Search input */}
|
|
184
|
-
<
|
|
185
|
-
|
|
176
|
+
<Stack direction="row" align="center" style={{
|
|
177
|
+
padding: '12px 16px',
|
|
178
|
+
borderBottom: '2px solid #3b82f6',
|
|
179
|
+
}}>
|
|
180
|
+
<SearchIcon style={{ width: 20, height: 20, color: '#3b82f6', marginRight: 12 }} />
|
|
186
181
|
<input
|
|
187
182
|
ref={inputRef}
|
|
188
183
|
type="text"
|
|
189
184
|
value={query}
|
|
190
185
|
onChange={(e) => setQuery(e.target.value)}
|
|
186
|
+
onKeyDown={handleKeyDown}
|
|
191
187
|
placeholder="Search components and variants..."
|
|
192
|
-
|
|
188
|
+
style={{
|
|
189
|
+
flex: 1,
|
|
190
|
+
background: 'transparent',
|
|
191
|
+
color: 'var(--text-primary)',
|
|
192
|
+
fontSize: '16px',
|
|
193
|
+
border: 'none',
|
|
194
|
+
outline: 'none',
|
|
195
|
+
}}
|
|
193
196
|
/>
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
</kbd>
|
|
197
|
-
</div>
|
|
197
|
+
<Badge size="sm" variant="default">esc</Badge>
|
|
198
|
+
</Stack>
|
|
198
199
|
|
|
199
200
|
{/* Results */}
|
|
200
|
-
<div
|
|
201
|
-
ref={listRef}
|
|
202
|
-
className="max-h-80 overflow-y-auto py-2"
|
|
203
|
-
>
|
|
201
|
+
<div ref={listRef} style={{ maxHeight: '320px', overflowY: 'auto', padding: '8px 0' }}>
|
|
204
202
|
{results.length === 0 ? (
|
|
205
|
-
<div
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
203
|
+
<div style={{ padding: '32px 16px', textAlign: 'center' }}>
|
|
204
|
+
<Text color="tertiary">
|
|
205
|
+
{query ? (
|
|
206
|
+
<>No results for “{query}”</>
|
|
207
|
+
) : (
|
|
208
|
+
<>Start typing to search...</>
|
|
209
|
+
)}
|
|
210
|
+
</Text>
|
|
211
211
|
</div>
|
|
212
212
|
) : (
|
|
213
213
|
results.map((result, index) => (
|
|
214
214
|
<button
|
|
215
215
|
key={`${result.path}-${result.variantName || "component"}`}
|
|
216
216
|
onClick={() => handleSelect(result)}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
217
|
+
style={{
|
|
218
|
+
width: '100%',
|
|
219
|
+
padding: '8px 16px',
|
|
220
|
+
display: 'flex',
|
|
221
|
+
alignItems: 'center',
|
|
222
|
+
gap: '12px',
|
|
223
|
+
textAlign: 'left',
|
|
224
|
+
transition: 'background-color 150ms',
|
|
225
|
+
border: 'none',
|
|
226
|
+
cursor: 'pointer',
|
|
227
|
+
backgroundColor: index === selectedIndex ? 'rgba(59, 130, 246, 0.1)' : 'transparent',
|
|
228
|
+
color: index === selectedIndex ? 'var(--text-primary)' : 'var(--text-secondary)',
|
|
229
|
+
}}
|
|
230
|
+
onMouseEnter={(e) => {
|
|
231
|
+
setSelectedIndex(index);
|
|
232
|
+
if (index !== selectedIndex) {
|
|
233
|
+
e.currentTarget.style.backgroundColor = 'var(--bg-hover)';
|
|
234
|
+
}
|
|
235
|
+
}}
|
|
236
|
+
onMouseLeave={(e) => {
|
|
237
|
+
if (index !== selectedIndex) {
|
|
238
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
239
|
+
}
|
|
240
|
+
}}
|
|
223
241
|
>
|
|
224
242
|
{/* Icon/Badge */}
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
243
|
+
<Badge
|
|
244
|
+
size="sm"
|
|
245
|
+
style={{
|
|
246
|
+
flexShrink: 0,
|
|
247
|
+
width: '32px',
|
|
248
|
+
height: '32px',
|
|
249
|
+
borderRadius: '8px',
|
|
250
|
+
display: 'flex',
|
|
251
|
+
alignItems: 'center',
|
|
252
|
+
justifyContent: 'center',
|
|
253
|
+
backgroundColor: result.type === "component"
|
|
254
|
+
? 'rgba(59, 130, 246, 0.15)'
|
|
255
|
+
: 'rgba(168, 85, 247, 0.15)',
|
|
256
|
+
color: result.type === "component"
|
|
257
|
+
? '#3b82f6'
|
|
258
|
+
: '#a855f7',
|
|
259
|
+
}}
|
|
232
260
|
>
|
|
233
261
|
{result.type === "component" ? "C" : "V"}
|
|
234
|
-
</
|
|
262
|
+
</Badge>
|
|
235
263
|
|
|
236
264
|
{/* Content */}
|
|
237
|
-
<div
|
|
238
|
-
<
|
|
239
|
-
<
|
|
265
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
266
|
+
<Stack direction="row" align="center" gap="sm">
|
|
267
|
+
<Text weight="medium" style={{
|
|
268
|
+
overflow: 'hidden',
|
|
269
|
+
textOverflow: 'ellipsis',
|
|
270
|
+
whiteSpace: 'nowrap',
|
|
271
|
+
}}>
|
|
240
272
|
{result.componentName}
|
|
241
|
-
</
|
|
273
|
+
</Text>
|
|
242
274
|
{result.variantName && (
|
|
243
275
|
<>
|
|
244
|
-
<ChevronRightIcon
|
|
245
|
-
<
|
|
276
|
+
<ChevronRightIcon style={{ width: 12, height: 12, color: 'var(--text-tertiary)', flexShrink: 0 }} />
|
|
277
|
+
<Text color="secondary" style={{
|
|
278
|
+
overflow: 'hidden',
|
|
279
|
+
textOverflow: 'ellipsis',
|
|
280
|
+
whiteSpace: 'nowrap',
|
|
281
|
+
}}>
|
|
246
282
|
{result.variantName}
|
|
247
|
-
</
|
|
283
|
+
</Text>
|
|
248
284
|
</>
|
|
249
285
|
)}
|
|
250
|
-
</
|
|
251
|
-
<
|
|
286
|
+
</Stack>
|
|
287
|
+
<Text size="xs" color="tertiary" style={{
|
|
288
|
+
overflow: 'hidden',
|
|
289
|
+
textOverflow: 'ellipsis',
|
|
290
|
+
whiteSpace: 'nowrap',
|
|
291
|
+
}}>
|
|
252
292
|
{result.category}
|
|
253
|
-
</
|
|
293
|
+
</Text>
|
|
254
294
|
</div>
|
|
255
295
|
|
|
256
296
|
{/* Keyboard hint for selected */}
|
|
257
297
|
{index === selectedIndex && (
|
|
258
|
-
<
|
|
259
|
-
enter
|
|
260
|
-
</kbd>
|
|
298
|
+
<Badge size="sm" variant="default">enter</Badge>
|
|
261
299
|
)}
|
|
262
300
|
</button>
|
|
263
301
|
))
|
|
@@ -265,24 +303,29 @@ export function CommandPalette({
|
|
|
265
303
|
</div>
|
|
266
304
|
|
|
267
305
|
{/* Footer */}
|
|
268
|
-
<div
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
306
|
+
<div style={{
|
|
307
|
+
padding: '8px 16px',
|
|
308
|
+
borderTop: '1px solid var(--border)',
|
|
309
|
+
}}>
|
|
310
|
+
<Stack direction="row" align="center" justify="between">
|
|
311
|
+
<Stack direction="row" align="center" gap="md">
|
|
312
|
+
<Stack direction="row" align="center" gap="xs">
|
|
313
|
+
<Badge size="sm" variant="default">↑</Badge>
|
|
314
|
+
<Badge size="sm" variant="default">↓</Badge>
|
|
315
|
+
<Text size="xs" color="tertiary">to navigate</Text>
|
|
316
|
+
</Stack>
|
|
317
|
+
<Stack direction="row" align="center" gap="xs">
|
|
318
|
+
<Badge size="sm" variant="default">↵</Badge>
|
|
319
|
+
<Text size="xs" color="tertiary">to select</Text>
|
|
320
|
+
</Stack>
|
|
321
|
+
</Stack>
|
|
322
|
+
<Text size="xs" color="tertiary">
|
|
323
|
+
{results.length} result{results.length !== 1 ? "s" : ""}
|
|
324
|
+
</Text>
|
|
325
|
+
</Stack>
|
|
283
326
|
</div>
|
|
284
|
-
</
|
|
285
|
-
</
|
|
327
|
+
</Dialog.Content>
|
|
328
|
+
</Dialog>
|
|
286
329
|
);
|
|
287
330
|
}
|
|
288
331
|
|