@fragments-sdk/cli 0.6.0 → 0.7.1
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 +529 -285
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-F7ITZPDJ.js → chunk-32VIEOQY.js} +18 -18
- package/dist/chunk-32VIEOQY.js.map +1 -0
- package/dist/{chunk-SSLQXHNX.js → chunk-5ITIP3ES.js} +27 -27
- package/dist/chunk-5ITIP3ES.js.map +1 -0
- package/dist/{chunk-RVRTRESS.js → chunk-DQHWLAUV.js} +29 -29
- package/dist/chunk-DQHWLAUV.js.map +1 -0
- package/dist/{chunk-Q7GOHVOK.js → chunk-GCZMFLDI.js} +67 -32
- package/dist/chunk-GCZMFLDI.js.map +1 -0
- package/dist/{chunk-6JBGU74P.js → chunk-GHYYFAQN.js} +23 -23
- package/dist/chunk-GHYYFAQN.js.map +1 -0
- package/dist/{chunk-NWQ4CJOQ.js → chunk-GKX2HPZ6.js} +40 -40
- package/dist/chunk-GKX2HPZ6.js.map +1 -0
- package/dist/{chunk-D35RGPAG.js → chunk-U6VTHBNI.js} +499 -83
- package/dist/chunk-U6VTHBNI.js.map +1 -0
- package/dist/{core-SKRPJQZG.js → core-SFHPYR5H.js} +24 -26
- package/dist/{generate-7AF7WRVK.js → generate-54GJAWUY.js} +5 -5
- package/dist/generate-54GJAWUY.js.map +1 -0
- package/dist/index.d.ts +23 -27
- package/dist/index.js +10 -10
- package/dist/{init-WKGDPYI4.js → init-EIM5WNMP.js} +5 -5
- package/dist/{init-WKGDPYI4.js.map → init-EIM5WNMP.js.map} +1 -1
- package/dist/mcp-bin.js +73 -73
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-KQBKUS64.js +12 -0
- package/dist/{service-F3E4JJM7.js → service-ED2LNCTU.js} +6 -6
- package/dist/{static-viewer-4LQZ5AGA.js → static-viewer-Q4F4QP5M.js} +4 -4
- package/dist/{test-CJDNJTPZ.js → test-6VN2DA3S.js} +19 -19
- package/dist/test-6VN2DA3S.js.map +1 -0
- package/dist/{tokens-JAJABYXP.js → tokens-P2B7ZAM3.js} +5 -5
- package/dist/{viewer-R3Q6WAMJ.js → viewer-GM7IQPPB.js} +199 -199
- package/dist/viewer-GM7IQPPB.js.map +1 -0
- package/package.json +2 -2
- package/src/ai.ts +5 -5
- package/src/analyze.ts +11 -11
- package/src/bin.ts +24 -1
- package/src/build.ts +64 -21
- package/src/commands/a11y.ts +6 -6
- package/src/commands/add.ts +11 -11
- package/src/commands/audit.ts +4 -4
- package/src/commands/baseline.ts +3 -3
- package/src/commands/build.ts +8 -8
- package/src/commands/compare.ts +20 -20
- package/src/commands/context.ts +16 -16
- package/src/commands/enhance.ts +36 -36
- package/src/commands/generate.ts +1 -1
- package/src/commands/graph.ts +274 -0
- package/src/commands/init.ts +1 -1
- package/src/commands/link/figma.ts +82 -82
- package/src/commands/link/index.ts +3 -3
- package/src/commands/link/storybook.ts +9 -9
- package/src/commands/list.ts +2 -2
- package/src/commands/reset.ts +15 -15
- package/src/commands/scan.ts +27 -27
- package/src/commands/storygen.ts +24 -24
- package/src/commands/validate.ts +2 -2
- package/src/commands/verify.ts +8 -8
- package/src/core/auto-props.ts +4 -4
- package/src/core/composition.test.ts +36 -36
- package/src/core/composition.ts +83 -20
- package/src/core/config.ts +6 -6
- package/src/core/{defineSegment.ts → defineFragment.ts} +16 -22
- package/src/core/discovery.ts +6 -6
- package/src/core/figma.ts +2 -2
- package/src/core/graph-extractor.test.ts +542 -0
- package/src/core/graph-extractor.ts +601 -0
- package/src/core/importAnalyzer.ts +6 -1
- package/src/core/index.ts +22 -23
- package/src/core/loader.ts +22 -22
- package/src/core/node.ts +5 -5
- package/src/core/parser.ts +31 -31
- package/src/core/previewLoader.ts +1 -1
- package/src/core/schema.ts +16 -16
- package/src/core/storyAdapter.test.ts +87 -87
- package/src/core/storyAdapter.ts +16 -16
- package/src/core/types.ts +21 -26
- package/src/diff.ts +22 -22
- package/src/index.ts +2 -2
- package/src/mcp/server.ts +80 -80
- package/src/migrate/__tests__/utils/utils.test.ts +3 -3
- package/src/migrate/bin.ts +4 -4
- package/src/migrate/converter.ts +16 -16
- package/src/migrate/index.ts +3 -3
- package/src/migrate/migrate.ts +3 -3
- package/src/migrate/parser.ts +8 -8
- package/src/migrate/report.ts +2 -2
- package/src/migrate/types.ts +4 -4
- package/src/screenshot.ts +22 -22
- package/src/service/__tests__/props-extractor.test.ts +15 -15
- package/src/service/analytics.ts +39 -39
- package/src/service/enhance/codebase-scanner.ts +1 -1
- package/src/service/enhance/index.ts +1 -1
- package/src/service/enhance/props-extractor.ts +2 -2
- package/src/service/enhance/types.ts +2 -2
- package/src/service/index.ts +2 -2
- package/src/service/metrics-store.ts +1 -1
- package/src/service/patch-generator.ts +1 -1
- package/src/setup.ts +52 -52
- package/src/shared/dev-server-client.ts +7 -7
- package/src/shared/fragment-loader.ts +59 -0
- package/src/shared/index.ts +1 -1
- package/src/shared/types.ts +4 -4
- package/src/static-viewer.ts +35 -35
- package/src/test/discovery.ts +6 -6
- package/src/test/index.ts +5 -5
- package/src/test/reporters/console.ts +1 -1
- package/src/test/reporters/junit.ts +1 -1
- package/src/test/runner.ts +7 -7
- package/src/test/types.ts +3 -3
- package/src/test/watch.ts +9 -9
- package/src/validators.ts +26 -26
- package/src/viewer/__tests__/render-utils.test.ts +28 -28
- package/src/viewer/__tests__/viewer-integration.test.ts +4 -4
- package/src/viewer/cli/health.ts +26 -26
- package/src/viewer/components/App.tsx +201 -103
- package/src/viewer/components/BottomPanel.tsx +17 -17
- package/src/viewer/components/CodePanel.tsx +3 -3
- package/src/viewer/components/CommandPalette.tsx +11 -11
- package/src/viewer/components/ComponentGraph.tsx +28 -28
- package/src/viewer/components/ComponentHeader.tsx +2 -2
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/FigmaEmbed.tsx +9 -9
- package/src/viewer/components/HealthDashboard.tsx +17 -17
- package/src/viewer/components/Icons.tsx +53 -1
- package/src/viewer/components/InteractionsPanel.tsx +2 -2
- package/src/viewer/components/IsolatedPreviewFrame.tsx +6 -6
- package/src/viewer/components/IsolatedRender.tsx +10 -10
- package/src/viewer/components/Layout.tsx +7 -3
- package/src/viewer/components/LeftSidebar.tsx +92 -114
- package/src/viewer/components/MultiViewportPreview.tsx +14 -14
- package/src/viewer/components/PreviewArea.tsx +11 -11
- package/src/viewer/components/PreviewFrameHost.tsx +77 -48
- package/src/viewer/components/PreviewToolbar.tsx +57 -10
- package/src/viewer/components/RightSidebar.tsx +9 -9
- package/src/viewer/components/Sidebar.tsx +17 -17
- package/src/viewer/components/StoryRenderer.tsx +2 -2
- package/src/viewer/components/TokenStylePanel.tsx +1 -1
- package/src/viewer/components/UsageSection.tsx +2 -2
- package/src/viewer/components/VariantMatrix.tsx +11 -11
- package/src/viewer/components/VariantRenderer.tsx +3 -3
- package/src/viewer/components/VariantTabs.tsx +2 -2
- package/src/viewer/components/ViewportSelector.tsx +56 -45
- package/src/viewer/components/_future/CreatePage.tsx +6 -6
- package/src/viewer/composition-renderer.ts +11 -11
- package/src/viewer/constants/ui.ts +4 -4
- package/src/viewer/entry.tsx +40 -40
- package/src/viewer/hooks/useFigmaIntegration.ts +1 -1
- package/src/viewer/hooks/usePreviewBridge.ts +5 -5
- package/src/viewer/hooks/useUrlState.ts +6 -6
- package/src/viewer/index.ts +2 -2
- package/src/viewer/intelligence/healthReport.ts +17 -17
- package/src/viewer/intelligence/styleDrift.ts +1 -1
- package/src/viewer/intelligence/usageScanner.ts +1 -1
- package/src/viewer/preview-frame.html +22 -13
- package/src/viewer/render-template.html +1 -1
- package/src/viewer/render-utils.ts +21 -21
- package/src/viewer/server.ts +18 -18
- package/src/viewer/styles/globals.css +42 -81
- package/src/viewer/utils/detectRelationships.ts +22 -22
- package/src/viewer/vite-plugin.ts +213 -213
- package/dist/chunk-6JBGU74P.js.map +0 -1
- package/dist/chunk-D35RGPAG.js.map +0 -1
- package/dist/chunk-F7ITZPDJ.js.map +0 -1
- package/dist/chunk-NWQ4CJOQ.js.map +0 -1
- package/dist/chunk-Q7GOHVOK.js.map +0 -1
- package/dist/chunk-RVRTRESS.js.map +0 -1
- package/dist/chunk-SSLQXHNX.js.map +0 -1
- package/dist/generate-7AF7WRVK.js.map +0 -1
- package/dist/scan-K6JNMCGM.js +0 -12
- package/dist/test-CJDNJTPZ.js.map +0 -1
- package/dist/viewer-R3Q6WAMJ.js.map +0 -1
- package/src/shared/segment-loader.ts +0 -59
- /package/dist/{core-SKRPJQZG.js.map → core-SFHPYR5H.js.map} +0 -0
- /package/dist/{scan-K6JNMCGM.js.map → scan-KQBKUS64.js.map} +0 -0
- /package/dist/{service-F3E4JJM7.js.map → service-ED2LNCTU.js.map} +0 -0
- /package/dist/{static-viewer-4LQZ5AGA.js.map → static-viewer-Q4F4QP5M.js.map} +0 -0
- /package/dist/{tokens-JAJABYXP.js.map → tokens-P2B7ZAM3.js.map} +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FragmentDefinition } from '../../core/index.js';
|
|
3
3
|
import { BRAND } from '../../core/index.js';
|
|
4
4
|
import { useTheme } from './ThemeProvider.js';
|
|
5
|
-
import {
|
|
6
|
-
import { Sidebar, Input, Button, Badge, Text } from '@fragments/ui';
|
|
5
|
+
import { Sidebar, useSidebar, Badge, Text, ThemeToggle } from '@fragments/ui';
|
|
7
6
|
|
|
8
7
|
// Fuzzy matching utility
|
|
9
8
|
interface FuzzyMatch {
|
|
@@ -46,19 +45,19 @@ function fuzzyMatch(text: string, pattern: string): FuzzyMatch | null {
|
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
interface SearchResult {
|
|
49
|
-
item: { path: string;
|
|
48
|
+
item: { path: string; fragment: FragmentDefinition };
|
|
50
49
|
score: number;
|
|
51
50
|
nameIndices: number[];
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
function
|
|
55
|
-
item: { path: string;
|
|
53
|
+
function searchFragment(
|
|
54
|
+
item: { path: string; fragment: FragmentDefinition },
|
|
56
55
|
query: string
|
|
57
56
|
): SearchResult | null {
|
|
58
|
-
const {
|
|
59
|
-
// Skip invalid
|
|
60
|
-
if (!
|
|
61
|
-
const { name, category, tags } =
|
|
57
|
+
const { fragment } = item;
|
|
58
|
+
// Skip invalid fragments
|
|
59
|
+
if (!fragment?.meta) return null;
|
|
60
|
+
const { name, category, tags } = fragment.meta;
|
|
62
61
|
|
|
63
62
|
const nameMatch = fuzzyMatch(name, query);
|
|
64
63
|
if (nameMatch) {
|
|
@@ -120,34 +119,34 @@ function HighlightedText({ text, indices }: { text: string; indices: number[] })
|
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
interface LeftSidebarProps {
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
123
|
+
activeFragment: string | null;
|
|
124
|
+
searchQuery: string;
|
|
125
125
|
onSelect: (path: string) => void;
|
|
126
126
|
showHealth?: boolean;
|
|
127
127
|
onHealthClick?: () => void;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
export function LeftSidebar({
|
|
131
|
-
const [search, setSearch] = useState('');
|
|
130
|
+
export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect, showHealth, onHealthClick }: LeftSidebarProps) {
|
|
132
131
|
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
133
|
-
const {
|
|
134
|
-
const
|
|
132
|
+
const { setTheme, resolvedTheme } = useTheme();
|
|
133
|
+
const { isMobile, setOpen } = useSidebar();
|
|
135
134
|
const navRef = useRef<HTMLDivElement>(null);
|
|
136
135
|
|
|
137
|
-
const debouncedSearch = useDebounce(
|
|
136
|
+
const debouncedSearch = useDebounce(searchQuery, 150);
|
|
138
137
|
|
|
139
138
|
const searchResults = useMemo(() => {
|
|
140
139
|
if (!debouncedSearch) return null;
|
|
141
140
|
|
|
142
141
|
const results: SearchResult[] = [];
|
|
143
|
-
for (const item of
|
|
144
|
-
const result =
|
|
142
|
+
for (const item of fragments) {
|
|
143
|
+
const result = searchFragment(item, debouncedSearch);
|
|
145
144
|
if (result) results.push(result);
|
|
146
145
|
}
|
|
147
146
|
|
|
148
147
|
results.sort((a, b) => b.score - a.score);
|
|
149
148
|
return results;
|
|
150
|
-
}, [
|
|
149
|
+
}, [fragments, debouncedSearch]);
|
|
151
150
|
|
|
152
151
|
const highlightMap = useMemo(() => {
|
|
153
152
|
const map = new Map<string, number[]>();
|
|
@@ -162,94 +161,96 @@ export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onH
|
|
|
162
161
|
const grouped = useMemo(() => {
|
|
163
162
|
const source = searchResults
|
|
164
163
|
? searchResults.map(r => r.item)
|
|
165
|
-
:
|
|
164
|
+
: fragments;
|
|
166
165
|
|
|
167
|
-
const groups: Record<string, typeof
|
|
166
|
+
const groups: Record<string, typeof fragments> = {};
|
|
168
167
|
for (const item of source) {
|
|
169
|
-
// Skip invalid
|
|
170
|
-
if (!item.
|
|
171
|
-
const category = item.
|
|
168
|
+
// Skip invalid fragments
|
|
169
|
+
if (!item.fragment?.meta) continue;
|
|
170
|
+
const category = item.fragment.meta.category || 'uncategorized';
|
|
172
171
|
if (!groups[category]) groups[category] = [];
|
|
173
172
|
groups[category].push(item);
|
|
174
173
|
}
|
|
175
174
|
return groups;
|
|
176
|
-
}, [
|
|
177
|
-
|
|
178
|
-
const toggleTheme = () => {
|
|
179
|
-
// Simple toggle between light and dark
|
|
180
|
-
setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
|
181
|
-
};
|
|
175
|
+
}, [fragments, searchResults]);
|
|
182
176
|
|
|
183
177
|
const flatItems = useMemo(() => {
|
|
184
|
-
const items: Array<{ path: string;
|
|
178
|
+
const items: Array<{ path: string; fragment: FragmentDefinition }> = [];
|
|
185
179
|
const sortedEntries = Object.entries(grouped).sort(([a], [b]) =>
|
|
186
180
|
a.toLowerCase().localeCompare(b.toLowerCase())
|
|
187
181
|
);
|
|
188
182
|
for (const [, categoryItems] of sortedEntries) {
|
|
189
183
|
const sorted = [...categoryItems]
|
|
190
|
-
.filter(item => item.
|
|
184
|
+
.filter(item => item.fragment?.meta?.name)
|
|
191
185
|
.sort((a, b) =>
|
|
192
|
-
a.
|
|
186
|
+
a.fragment.meta.name.toLowerCase().localeCompare(b.fragment.meta.name.toLowerCase())
|
|
193
187
|
);
|
|
194
188
|
items.push(...sorted);
|
|
195
189
|
}
|
|
196
190
|
return items;
|
|
197
191
|
}, [grouped]);
|
|
198
192
|
|
|
193
|
+
const keyboardItems = useMemo(() => {
|
|
194
|
+
const componentItems = flatItems.map((item) => ({ type: 'component' as const, path: item.path }));
|
|
195
|
+
if (!onHealthClick) return componentItems;
|
|
196
|
+
return [{ type: 'dashboard' as const }, ...componentItems];
|
|
197
|
+
}, [flatItems, onHealthClick]);
|
|
198
|
+
|
|
199
199
|
useEffect(() => {
|
|
200
|
-
if (focusedIndex >= 0 && focusedIndex <
|
|
200
|
+
if (focusedIndex >= 0 && focusedIndex < keyboardItems.length && navRef.current) {
|
|
201
201
|
// Query all nav item buttons rendered by Sidebar.Item inside the nav
|
|
202
202
|
const buttons = navRef.current.querySelectorAll<HTMLButtonElement>('li > button[type="button"]');
|
|
203
203
|
if (buttons[focusedIndex]) {
|
|
204
204
|
buttons[focusedIndex].focus();
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
|
-
}, [focusedIndex,
|
|
207
|
+
}, [focusedIndex, keyboardItems.length]);
|
|
208
208
|
|
|
209
209
|
useEffect(() => {
|
|
210
210
|
setFocusedIndex(-1);
|
|
211
|
-
}, [
|
|
211
|
+
}, [searchQuery]);
|
|
212
212
|
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
return;
|
|
213
|
+
const handleSelect = (path: string) => {
|
|
214
|
+
onSelect(path);
|
|
215
|
+
if (isMobile) {
|
|
216
|
+
setOpen(false);
|
|
218
217
|
}
|
|
218
|
+
};
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
220
|
+
const handleHealthClick = () => {
|
|
221
|
+
onHealthClick?.();
|
|
222
|
+
if (isMobile) {
|
|
223
|
+
setOpen(false);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
|
228
|
+
const target = e.target as HTMLElement;
|
|
229
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
|
225
230
|
return;
|
|
226
231
|
}
|
|
227
232
|
|
|
228
233
|
if (e.key === 'Escape') {
|
|
229
|
-
|
|
230
|
-
setSearch('');
|
|
231
|
-
searchInputRef.current?.blur();
|
|
232
|
-
if (flatItems.length > 0) setFocusedIndex(0);
|
|
233
|
-
} else {
|
|
234
|
-
setSearch('');
|
|
235
|
-
setFocusedIndex(-1);
|
|
236
|
-
}
|
|
234
|
+
setFocusedIndex(-1);
|
|
237
235
|
return;
|
|
238
236
|
}
|
|
239
237
|
|
|
240
|
-
if (document.activeElement === searchInputRef.current) return;
|
|
241
|
-
|
|
242
238
|
if (e.key === 'ArrowDown') {
|
|
243
239
|
e.preventDefault();
|
|
244
|
-
setFocusedIndex(prev => (prev + 1) >=
|
|
240
|
+
setFocusedIndex(prev => (prev + 1) >= keyboardItems.length ? 0 : prev + 1);
|
|
245
241
|
} else if (e.key === 'ArrowUp') {
|
|
246
242
|
e.preventDefault();
|
|
247
|
-
setFocusedIndex(prev => (prev - 1) < 0 ?
|
|
248
|
-
} else if (e.key === 'Enter' && focusedIndex >= 0 && focusedIndex <
|
|
243
|
+
setFocusedIndex(prev => (prev - 1) < 0 ? keyboardItems.length - 1 : prev - 1);
|
|
244
|
+
} else if (e.key === 'Enter' && focusedIndex >= 0 && focusedIndex < keyboardItems.length) {
|
|
249
245
|
e.preventDefault();
|
|
250
|
-
|
|
246
|
+
const currentItem = keyboardItems[focusedIndex];
|
|
247
|
+
if (currentItem.type === 'dashboard') {
|
|
248
|
+
handleHealthClick();
|
|
249
|
+
} else {
|
|
250
|
+
handleSelect(currentItem.path);
|
|
251
|
+
}
|
|
251
252
|
}
|
|
252
|
-
}, [
|
|
253
|
+
}, [keyboardItems, focusedIndex, handleHealthClick, handleSelect]);
|
|
253
254
|
|
|
254
255
|
useEffect(() => {
|
|
255
256
|
document.addEventListener('keydown', handleKeyDown);
|
|
@@ -261,65 +262,42 @@ export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onH
|
|
|
261
262
|
);
|
|
262
263
|
|
|
263
264
|
return (
|
|
264
|
-
|
|
265
|
+
<>
|
|
265
266
|
{/* Header */}
|
|
266
267
|
<Sidebar.Header>
|
|
267
268
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
|
268
269
|
<Text size="sm" weight="medium">{BRAND.name}</Text>
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
) : (
|
|
279
|
-
<SunIcon />
|
|
280
|
-
)}
|
|
281
|
-
</Button>
|
|
270
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
271
|
+
<ThemeToggle
|
|
272
|
+
size="sm"
|
|
273
|
+
value={resolvedTheme}
|
|
274
|
+
onValueChange={(value) => setTheme(value)}
|
|
275
|
+
aria-label={`Theme: ${resolvedTheme}`}
|
|
276
|
+
/>
|
|
277
|
+
<Sidebar.CollapseToggle />
|
|
278
|
+
</div>
|
|
282
279
|
</div>
|
|
283
280
|
</Sidebar.Header>
|
|
284
281
|
|
|
285
|
-
{/* Search */}
|
|
286
|
-
<div style={{ padding: '12px' }}>
|
|
287
|
-
<Input
|
|
288
|
-
ref={searchInputRef}
|
|
289
|
-
value={search}
|
|
290
|
-
onChange={(value: string) => setSearch(value)}
|
|
291
|
-
placeholder="Search"
|
|
292
|
-
size="sm"
|
|
293
|
-
/>
|
|
294
|
-
</div>
|
|
295
|
-
|
|
296
|
-
{/* Dashboard Link */}
|
|
297
|
-
{onHealthClick && (
|
|
298
|
-
<div style={{ padding: '0 8px 12px' }}>
|
|
299
|
-
<Button
|
|
300
|
-
variant="ghost"
|
|
301
|
-
size="sm"
|
|
302
|
-
onClick={onHealthClick}
|
|
303
|
-
style={{
|
|
304
|
-
width: '100%',
|
|
305
|
-
justifyContent: 'flex-start',
|
|
306
|
-
...(showHealth ? { backgroundColor: 'var(--bg-hover)', color: 'var(--text-primary)' } : {}),
|
|
307
|
-
}}
|
|
308
|
-
>
|
|
309
|
-
<DashboardIcon />
|
|
310
|
-
<span style={{ marginLeft: '8px' }}>Dashboard</span>
|
|
311
|
-
</Button>
|
|
312
|
-
</div>
|
|
313
|
-
)}
|
|
314
|
-
|
|
315
282
|
{/* Component list */}
|
|
316
|
-
<div ref={navRef}>
|
|
283
|
+
<div ref={navRef} style={{ flex: 1, minHeight: 0, display: 'flex', overflow: 'hidden' }}>
|
|
317
284
|
<Sidebar.Nav aria-label="Components">
|
|
285
|
+
{onHealthClick && (
|
|
286
|
+
<Sidebar.Section>
|
|
287
|
+
<Sidebar.Item
|
|
288
|
+
active={!!showHealth}
|
|
289
|
+
onClick={handleHealthClick}
|
|
290
|
+
>
|
|
291
|
+
Dashboard
|
|
292
|
+
</Sidebar.Item>
|
|
293
|
+
</Sidebar.Section>
|
|
294
|
+
)}
|
|
295
|
+
|
|
318
296
|
{sortedEntries.map(([category, items]) => {
|
|
319
297
|
const sortedItems = [...items]
|
|
320
|
-
.filter(item => item.
|
|
298
|
+
.filter(item => item.fragment?.meta?.name)
|
|
321
299
|
.sort((a, b) =>
|
|
322
|
-
a.
|
|
300
|
+
a.fragment.meta.name.toLowerCase().localeCompare(b.fragment.meta.name.toLowerCase())
|
|
323
301
|
);
|
|
324
302
|
|
|
325
303
|
return (
|
|
@@ -330,11 +308,11 @@ export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onH
|
|
|
330
308
|
return (
|
|
331
309
|
<Sidebar.Item
|
|
332
310
|
key={item.path}
|
|
333
|
-
active={
|
|
334
|
-
onClick={() =>
|
|
311
|
+
active={activeFragment === item.path}
|
|
312
|
+
onClick={() => handleSelect(item.path)}
|
|
335
313
|
>
|
|
336
314
|
<HighlightedText
|
|
337
|
-
text={item.
|
|
315
|
+
text={item.fragment.meta.name}
|
|
338
316
|
indices={nameIndices}
|
|
339
317
|
/>
|
|
340
318
|
</Sidebar.Item>
|
|
@@ -354,8 +332,8 @@ export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onH
|
|
|
354
332
|
|
|
355
333
|
{/* Footer */}
|
|
356
334
|
<Sidebar.Footer>
|
|
357
|
-
<Badge size="sm">{
|
|
335
|
+
<Badge size="sm">{fragments.length} components</Badge>
|
|
358
336
|
</Sidebar.Footer>
|
|
359
|
-
|
|
337
|
+
</>
|
|
360
338
|
);
|
|
361
339
|
}
|
|
@@ -42,8 +42,8 @@ const DEFAULT_VIEWPORTS: ViewportConfig[] = [
|
|
|
42
42
|
interface MultiViewportPreviewProps {
|
|
43
43
|
/** Component name for error boundary */
|
|
44
44
|
componentName: string;
|
|
45
|
-
/**
|
|
46
|
-
|
|
45
|
+
/** Fragment path for iframe rendering */
|
|
46
|
+
fragmentPath: string;
|
|
47
47
|
/** Variant name for iframe rendering */
|
|
48
48
|
variantName: string;
|
|
49
49
|
/** Render function that returns the component (fallback) */
|
|
@@ -60,7 +60,7 @@ interface MultiViewportPreviewProps {
|
|
|
60
60
|
|
|
61
61
|
export function MultiViewportPreview({
|
|
62
62
|
componentName,
|
|
63
|
-
|
|
63
|
+
fragmentPath,
|
|
64
64
|
variantName,
|
|
65
65
|
renderContent,
|
|
66
66
|
previewTheme,
|
|
@@ -203,7 +203,7 @@ export function MultiViewportPreview({
|
|
|
203
203
|
key={`${vp.name}-${vp.width}`}
|
|
204
204
|
viewport={vp}
|
|
205
205
|
componentName={componentName}
|
|
206
|
-
|
|
206
|
+
fragmentPath={fragmentPath}
|
|
207
207
|
variantName={variantName}
|
|
208
208
|
renderContent={renderContent}
|
|
209
209
|
previewTheme={previewTheme}
|
|
@@ -220,7 +220,7 @@ export function MultiViewportPreview({
|
|
|
220
220
|
interface ViewportPanelProps {
|
|
221
221
|
viewport: ViewportConfig;
|
|
222
222
|
componentName: string;
|
|
223
|
-
|
|
223
|
+
fragmentPath: string;
|
|
224
224
|
variantName: string;
|
|
225
225
|
renderContent: () => ReactNode;
|
|
226
226
|
previewTheme: "light" | "dark";
|
|
@@ -231,7 +231,7 @@ interface ViewportPanelProps {
|
|
|
231
231
|
function ViewportPanel({
|
|
232
232
|
viewport,
|
|
233
233
|
componentName,
|
|
234
|
-
|
|
234
|
+
fragmentPath,
|
|
235
235
|
variantName,
|
|
236
236
|
renderContent,
|
|
237
237
|
previewTheme,
|
|
@@ -247,7 +247,7 @@ function ViewportPanel({
|
|
|
247
247
|
previewTheme={previewTheme}
|
|
248
248
|
background={background}
|
|
249
249
|
componentName={componentName}
|
|
250
|
-
|
|
250
|
+
fragmentPath={fragmentPath}
|
|
251
251
|
variantName={variantName}
|
|
252
252
|
renderContent={renderContent}
|
|
253
253
|
useIframeIsolation={useIframeIsolation}
|
|
@@ -264,7 +264,7 @@ function ViewportPanel({
|
|
|
264
264
|
previewTheme={previewTheme}
|
|
265
265
|
background={background}
|
|
266
266
|
componentName={componentName}
|
|
267
|
-
|
|
267
|
+
fragmentPath={fragmentPath}
|
|
268
268
|
variantName={variantName}
|
|
269
269
|
renderContent={renderContent}
|
|
270
270
|
useIframeIsolation={useIframeIsolation}
|
|
@@ -280,7 +280,7 @@ interface DeviceMockupProps {
|
|
|
280
280
|
previewTheme: "light" | "dark";
|
|
281
281
|
background: BackgroundOption;
|
|
282
282
|
componentName: string;
|
|
283
|
-
|
|
283
|
+
fragmentPath: string;
|
|
284
284
|
variantName: string;
|
|
285
285
|
renderContent: () => ReactNode;
|
|
286
286
|
useIframeIsolation: boolean;
|
|
@@ -294,7 +294,7 @@ function DeviceMockup({
|
|
|
294
294
|
previewTheme,
|
|
295
295
|
background,
|
|
296
296
|
componentName,
|
|
297
|
-
|
|
297
|
+
fragmentPath,
|
|
298
298
|
variantName,
|
|
299
299
|
renderContent,
|
|
300
300
|
useIframeIsolation,
|
|
@@ -427,7 +427,7 @@ function DeviceMockup({
|
|
|
427
427
|
>
|
|
428
428
|
{useIframeIsolation ? (
|
|
429
429
|
<IsolatedPreviewFrame
|
|
430
|
-
|
|
430
|
+
fragmentPath={fragmentPath}
|
|
431
431
|
variantName={variantName}
|
|
432
432
|
theme={previewTheme}
|
|
433
433
|
width="100%"
|
|
@@ -481,7 +481,7 @@ interface DesktopMockupProps {
|
|
|
481
481
|
previewTheme: "light" | "dark";
|
|
482
482
|
background: BackgroundOption;
|
|
483
483
|
componentName: string;
|
|
484
|
-
|
|
484
|
+
fragmentPath: string;
|
|
485
485
|
variantName: string;
|
|
486
486
|
renderContent: () => ReactNode;
|
|
487
487
|
useIframeIsolation: boolean;
|
|
@@ -494,7 +494,7 @@ function DesktopMockup({
|
|
|
494
494
|
previewTheme,
|
|
495
495
|
background,
|
|
496
496
|
componentName,
|
|
497
|
-
|
|
497
|
+
fragmentPath,
|
|
498
498
|
variantName,
|
|
499
499
|
renderContent,
|
|
500
500
|
useIframeIsolation,
|
|
@@ -574,7 +574,7 @@ function DesktopMockup({
|
|
|
574
574
|
>
|
|
575
575
|
{useIframeIsolation ? (
|
|
576
576
|
<IsolatedPreviewFrame
|
|
577
|
-
|
|
577
|
+
fragmentPath={fragmentPath}
|
|
578
578
|
variantName={variantName}
|
|
579
579
|
theme={previewTheme}
|
|
580
580
|
width="100%"
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { memo, type ReactNode } from 'react';
|
|
10
|
-
import type {
|
|
10
|
+
import type { FragmentVariant } from '../../core/index.js';
|
|
11
11
|
import { ErrorBoundary } from './ErrorBoundary.js';
|
|
12
12
|
import { FigmaEmbed } from './FigmaEmbed.js';
|
|
13
13
|
import { VariantMatrix } from './VariantMatrix.js';
|
|
@@ -20,9 +20,9 @@ import { getViewportWidth, type ViewportPreset, type ViewportSize } from './View
|
|
|
20
20
|
interface PreviewAreaProps {
|
|
21
21
|
// Component data
|
|
22
22
|
componentName: string;
|
|
23
|
-
|
|
24
|
-
variant:
|
|
25
|
-
variants:
|
|
23
|
+
fragmentPath: string;
|
|
24
|
+
variant: FragmentVariant | undefined;
|
|
25
|
+
variants: FragmentVariant[] | undefined;
|
|
26
26
|
|
|
27
27
|
// View settings
|
|
28
28
|
zoom: ZoomLevel;
|
|
@@ -185,7 +185,7 @@ const PreviewContent = memo(function PreviewContent({ zoom, previewTheme, backgr
|
|
|
185
185
|
|
|
186
186
|
export function PreviewArea({
|
|
187
187
|
componentName,
|
|
188
|
-
|
|
188
|
+
fragmentPath,
|
|
189
189
|
variant,
|
|
190
190
|
variants,
|
|
191
191
|
zoom,
|
|
@@ -210,7 +210,7 @@ export function PreviewArea({
|
|
|
210
210
|
<VariantMatrix
|
|
211
211
|
variants={variants}
|
|
212
212
|
componentName={componentName}
|
|
213
|
-
|
|
213
|
+
fragmentPath={fragmentPath}
|
|
214
214
|
zoom={zoom}
|
|
215
215
|
previewTheme={previewTheme}
|
|
216
216
|
background={background}
|
|
@@ -227,7 +227,7 @@ export function PreviewArea({
|
|
|
227
227
|
return (
|
|
228
228
|
<MultiViewportPreview
|
|
229
229
|
componentName={componentName}
|
|
230
|
-
|
|
230
|
+
fragmentPath={fragmentPath}
|
|
231
231
|
variantName={variant.name}
|
|
232
232
|
renderContent={renderContent}
|
|
233
233
|
previewTheme={previewTheme}
|
|
@@ -257,7 +257,7 @@ export function PreviewArea({
|
|
|
257
257
|
<DeviceMockup type={viewport as 'tablet' | 'mobile'} width={viewportWidth}>
|
|
258
258
|
{useIframeIsolation ? (
|
|
259
259
|
<IsolatedPreviewFrame
|
|
260
|
-
|
|
260
|
+
fragmentPath={fragmentPath}
|
|
261
261
|
variantName={variant.name}
|
|
262
262
|
theme={previewTheme}
|
|
263
263
|
width={viewportWidth}
|
|
@@ -300,7 +300,7 @@ export function PreviewArea({
|
|
|
300
300
|
<DeviceMockup type={viewport as 'tablet' | 'mobile'} width={viewportWidth}>
|
|
301
301
|
{useIframeIsolation ? (
|
|
302
302
|
<IsolatedPreviewFrame
|
|
303
|
-
|
|
303
|
+
fragmentPath={fragmentPath}
|
|
304
304
|
variantName={variant.name}
|
|
305
305
|
theme={previewTheme}
|
|
306
306
|
width={viewportWidth}
|
|
@@ -337,7 +337,7 @@ export function PreviewArea({
|
|
|
337
337
|
>
|
|
338
338
|
{useIframeIsolation ? (
|
|
339
339
|
<IsolatedPreviewFrame
|
|
340
|
-
|
|
340
|
+
fragmentPath={fragmentPath}
|
|
341
341
|
variantName={variant.name}
|
|
342
342
|
theme={previewTheme}
|
|
343
343
|
width="100%"
|
|
@@ -420,7 +420,7 @@ export function PreviewArea({
|
|
|
420
420
|
}}
|
|
421
421
|
>
|
|
422
422
|
<IsolatedPreviewFrame
|
|
423
|
-
|
|
423
|
+
fragmentPath={fragmentPath}
|
|
424
424
|
variantName={variant.name}
|
|
425
425
|
theme={previewTheme}
|
|
426
426
|
width="100%"
|