@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
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Refactored for better performance and maintainability.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
|
7
|
-
import type
|
|
6
|
+
import { useState, useMemo, useEffect, useCallback, useRef, type RefObject } from "react";
|
|
7
|
+
import { BRAND, type FragmentDefinition } from "../../core/index.js";
|
|
8
8
|
|
|
9
9
|
// Layout & Navigation
|
|
10
10
|
import { Layout } from "./Layout.js";
|
|
@@ -28,10 +28,10 @@ import { useAllFigmaUrls } from "./FigmaEmbed.js";
|
|
|
28
28
|
import { ActionCapture } from "./ActionCapture.js";
|
|
29
29
|
|
|
30
30
|
// Fragments UI
|
|
31
|
-
import { Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert } from "@fragments/ui";
|
|
31
|
+
import { Header, Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert, ScrollArea, Input } from "@fragments/ui";
|
|
32
32
|
|
|
33
33
|
// Icons
|
|
34
|
-
import { EmptyIcon, ExternalLinkIcon,
|
|
34
|
+
import { EmptyIcon, ExternalLinkIcon, FigmaIcon, CompareIcon, CheckIcon, LinkIcon, GridIcon, DevicesIcon } from "./Icons.js";
|
|
35
35
|
|
|
36
36
|
// Hooks
|
|
37
37
|
import { useAppState } from "../hooks/useAppState.js";
|
|
@@ -39,7 +39,7 @@ import { useViewSettings } from "../hooks/useViewSettings.js";
|
|
|
39
39
|
import { useFigmaIntegration } from "../hooks/useFigmaIntegration.js";
|
|
40
40
|
import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts.js";
|
|
41
41
|
import { useActions } from "../hooks/useActions.js";
|
|
42
|
-
import { useUrlState,
|
|
42
|
+
import { useUrlState, findFragmentByName, findVariantIndex } from "../hooks/useUrlState.js";
|
|
43
43
|
import { usePanelDock } from "./ResizablePanel.js";
|
|
44
44
|
import { useTheme } from "./ThemeProvider.js";
|
|
45
45
|
|
|
@@ -47,10 +47,10 @@ import { useTheme } from "./ThemeProvider.js";
|
|
|
47
47
|
import { ScreenshotButton } from "./ScreenshotButton.js";
|
|
48
48
|
|
|
49
49
|
interface AppProps {
|
|
50
|
-
|
|
50
|
+
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export function App({
|
|
53
|
+
export function App({ fragments }: AppProps) {
|
|
54
54
|
// URL state management
|
|
55
55
|
const { state: urlState, setComponent: setUrlComponent, setVariant: setUrlVariant, setViewSettings: setUrlViewSettings, copyUrl } = useUrlState();
|
|
56
56
|
|
|
@@ -81,38 +81,40 @@ export function App({ segments }: AppProps) {
|
|
|
81
81
|
const { resolvedTheme } = useTheme();
|
|
82
82
|
|
|
83
83
|
// Toast notifications (via Fragments UI ToastProvider)
|
|
84
|
-
const {
|
|
84
|
+
const { info, success } = useToast();
|
|
85
85
|
|
|
86
86
|
// Navigation state
|
|
87
|
-
const [
|
|
87
|
+
const [activeFragmentPath, setActiveFragmentPath] = useState<string | null>(() => {
|
|
88
88
|
if (urlState.component) {
|
|
89
|
-
const found =
|
|
90
|
-
return found?.path ??
|
|
89
|
+
const found = findFragmentByName(fragments, urlState.component);
|
|
90
|
+
return found?.path ?? fragments[0]?.path ?? null;
|
|
91
91
|
}
|
|
92
|
-
return
|
|
92
|
+
return fragments[0]?.path ?? null;
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
const [activeVariantIndex, setActiveVariantIndex] = useState<number>(() => {
|
|
96
|
-
const
|
|
97
|
-
if (urlState.variant &&
|
|
98
|
-
return findVariantIndex(
|
|
96
|
+
const fragment = fragments.find(s => s.path === activeFragmentPath);
|
|
97
|
+
if (urlState.variant && fragment?.fragment.variants) {
|
|
98
|
+
return findVariantIndex(fragment.fragment.variants, urlState.variant);
|
|
99
99
|
}
|
|
100
100
|
return 0;
|
|
101
101
|
});
|
|
102
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
103
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
102
104
|
|
|
103
105
|
// Derived values
|
|
104
|
-
const
|
|
105
|
-
() =>
|
|
106
|
-
[
|
|
106
|
+
const activeFragment = useMemo(
|
|
107
|
+
() => fragments.find((s) => s.path === activeFragmentPath),
|
|
108
|
+
[fragments, activeFragmentPath]
|
|
107
109
|
);
|
|
108
|
-
const activeVariant =
|
|
109
|
-
const figmaUrl = activeVariant?.figma ||
|
|
110
|
+
const activeVariant = activeFragment?.fragment.variants?.[activeVariantIndex];
|
|
111
|
+
const figmaUrl = activeVariant?.figma || activeFragment?.fragment.meta.figma;
|
|
110
112
|
|
|
111
113
|
// Figma integration
|
|
112
114
|
const figmaIntegration = useFigmaIntegration({
|
|
113
115
|
figmaUrl,
|
|
114
116
|
showComparison: uiState.showComparison,
|
|
115
|
-
dependencies: [
|
|
117
|
+
dependencies: [activeFragmentPath, activeVariantIndex],
|
|
116
118
|
});
|
|
117
119
|
|
|
118
120
|
// Actions logging
|
|
@@ -121,12 +123,12 @@ export function App({ segments }: AppProps) {
|
|
|
121
123
|
useActionsRef.current = { logAction };
|
|
122
124
|
|
|
123
125
|
// Figma URLs for preloading
|
|
124
|
-
const allFigmaUrls = useAllFigmaUrls(
|
|
126
|
+
const allFigmaUrls = useAllFigmaUrls(activeFragment?.fragment);
|
|
125
127
|
|
|
126
128
|
// Reset action logs on variant change
|
|
127
129
|
useEffect(() => {
|
|
128
130
|
clearActionLogs();
|
|
129
|
-
}, [
|
|
131
|
+
}, [activeFragmentPath, activeVariantIndex, clearActionLogs]);
|
|
130
132
|
|
|
131
133
|
// Extract rendered styles after component renders
|
|
132
134
|
useEffect(() => {
|
|
@@ -139,14 +141,14 @@ export function App({ segments }: AppProps) {
|
|
|
139
141
|
// Sync URL state on browser navigation
|
|
140
142
|
useEffect(() => {
|
|
141
143
|
if (urlState.component) {
|
|
142
|
-
const found =
|
|
143
|
-
if (found && found.path !==
|
|
144
|
-
|
|
145
|
-
const variantIndex = findVariantIndex(found.
|
|
144
|
+
const found = findFragmentByName(fragments, urlState.component);
|
|
145
|
+
if (found && found.path !== activeFragmentPath) {
|
|
146
|
+
setActiveFragmentPath(found.path);
|
|
147
|
+
const variantIndex = findVariantIndex(found.fragment.variants, urlState.variant);
|
|
146
148
|
setActiveVariantIndex(variantIndex);
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
|
-
}, [urlState.component, urlState.variant,
|
|
151
|
+
}, [urlState.component, urlState.variant, fragments, activeFragmentPath]);
|
|
150
152
|
|
|
151
153
|
// HMR toast notifications
|
|
152
154
|
useEffect(() => {
|
|
@@ -165,22 +167,22 @@ export function App({ segments }: AppProps) {
|
|
|
165
167
|
}, [info]);
|
|
166
168
|
|
|
167
169
|
// Navigation handlers
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
const componentName =
|
|
171
|
-
const firstVariant =
|
|
170
|
+
const handleSelectFragment = useCallback((path: string) => {
|
|
171
|
+
const fragment = fragments.find((s) => s.path === path);
|
|
172
|
+
const componentName = fragment?.fragment.meta.name || path;
|
|
173
|
+
const firstVariant = fragment?.fragment.variants?.[0]?.name;
|
|
172
174
|
|
|
173
|
-
|
|
175
|
+
setActiveFragmentPath(path);
|
|
174
176
|
setActiveVariantIndex(0);
|
|
175
177
|
uiActions.setHealthDashboard(false);
|
|
176
178
|
setUrlComponent(componentName, firstVariant);
|
|
177
|
-
}, [
|
|
179
|
+
}, [fragments, setUrlComponent, uiActions]);
|
|
178
180
|
|
|
179
181
|
const handleSelectVariant = useCallback((index: number) => {
|
|
180
|
-
const variantName =
|
|
182
|
+
const variantName = activeFragment?.fragment.variants?.[index]?.name;
|
|
181
183
|
setActiveVariantIndex(index);
|
|
182
184
|
setUrlVariant(variantName || null);
|
|
183
|
-
}, [
|
|
185
|
+
}, [activeFragment, setUrlVariant]);
|
|
184
186
|
|
|
185
187
|
// Copy link handler
|
|
186
188
|
const handleCopyLink = useCallback(async () => {
|
|
@@ -192,27 +194,32 @@ export function App({ segments }: AppProps) {
|
|
|
192
194
|
}
|
|
193
195
|
}, [copyUrl, success, uiActions]);
|
|
194
196
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
const focusSearchInput = useCallback(() => {
|
|
198
|
+
searchInputRef.current?.focus();
|
|
199
|
+
searchInputRef.current?.select();
|
|
200
|
+
}, []);
|
|
201
|
+
|
|
202
|
+
// Sorted fragment paths for keyboard navigation
|
|
203
|
+
const sortedFragmentPaths = useMemo(() => {
|
|
204
|
+
return [...fragments]
|
|
205
|
+
.filter(s => s.fragment?.meta?.name)
|
|
206
|
+
.sort((a, b) => a.fragment.meta.name.localeCompare(b.fragment.meta.name))
|
|
200
207
|
.map(s => s.path);
|
|
201
|
-
}, [
|
|
208
|
+
}, [fragments]);
|
|
202
209
|
|
|
203
|
-
const
|
|
204
|
-
const variantCount =
|
|
210
|
+
const currentFragmentIndex = sortedFragmentPaths.indexOf(activeFragmentPath || '');
|
|
211
|
+
const variantCount = activeFragment?.fragment.variants?.length || 0;
|
|
205
212
|
|
|
206
213
|
// Keyboard shortcuts
|
|
207
214
|
useKeyboardShortcuts(
|
|
208
215
|
{
|
|
209
216
|
nextComponent: () => {
|
|
210
|
-
const nextIndex =
|
|
211
|
-
|
|
217
|
+
const nextIndex = currentFragmentIndex < sortedFragmentPaths.length - 1 ? currentFragmentIndex + 1 : 0;
|
|
218
|
+
handleSelectFragment(sortedFragmentPaths[nextIndex]);
|
|
212
219
|
},
|
|
213
220
|
prevComponent: () => {
|
|
214
|
-
const prevIndex =
|
|
215
|
-
|
|
221
|
+
const prevIndex = currentFragmentIndex > 0 ? currentFragmentIndex - 1 : sortedFragmentPaths.length - 1;
|
|
222
|
+
handleSelectFragment(sortedFragmentPaths[prevIndex]);
|
|
216
223
|
},
|
|
217
224
|
nextVariant: () => handleSelectVariant(activeVariantIndex < variantCount - 1 ? activeVariantIndex + 1 : 0),
|
|
218
225
|
prevVariant: () => handleSelectVariant(activeVariantIndex > 0 ? activeVariantIndex - 1 : variantCount - 1),
|
|
@@ -221,8 +228,18 @@ export function App({ segments }: AppProps) {
|
|
|
221
228
|
togglePanel: uiActions.togglePanel,
|
|
222
229
|
copyLink: handleCopyLink,
|
|
223
230
|
showHelp: uiActions.toggleShortcutsHelp,
|
|
224
|
-
openSearch:
|
|
225
|
-
escape:
|
|
231
|
+
openSearch: focusSearchInput,
|
|
232
|
+
escape: () => {
|
|
233
|
+
if (document.activeElement === searchInputRef.current) {
|
|
234
|
+
if (searchQuery) {
|
|
235
|
+
setSearchQuery('');
|
|
236
|
+
} else {
|
|
237
|
+
searchInputRef.current.blur();
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
uiActions.closeAllModals();
|
|
242
|
+
},
|
|
226
243
|
},
|
|
227
244
|
{ enabled: !uiState.showShortcutsHelp, variantCount }
|
|
228
245
|
);
|
|
@@ -252,7 +269,7 @@ export function App({ segments }: AppProps) {
|
|
|
252
269
|
}, []);
|
|
253
270
|
|
|
254
271
|
if (isIsolated) {
|
|
255
|
-
return <IsolatedRender
|
|
272
|
+
return <IsolatedRender fragments={fragments} />;
|
|
256
273
|
}
|
|
257
274
|
|
|
258
275
|
return (
|
|
@@ -261,24 +278,49 @@ export function App({ segments }: AppProps) {
|
|
|
261
278
|
<CommandPalette
|
|
262
279
|
isOpen={uiState.showCommandPalette}
|
|
263
280
|
onClose={() => uiActions.setCommandPalette(false)}
|
|
264
|
-
|
|
265
|
-
onSelectComponent={
|
|
281
|
+
fragments={fragments}
|
|
282
|
+
onSelectComponent={handleSelectFragment}
|
|
266
283
|
onSelectVariant={(path, variantIndex) => {
|
|
267
|
-
|
|
284
|
+
handleSelectFragment(path);
|
|
268
285
|
setTimeout(() => handleSelectVariant(variantIndex), 0);
|
|
269
286
|
}}
|
|
270
287
|
/>
|
|
271
288
|
|
|
272
289
|
<Layout
|
|
290
|
+
header={
|
|
291
|
+
activeFragment && !uiState.showHealthDashboard ? (
|
|
292
|
+
<TopToolbar
|
|
293
|
+
fragment={activeFragment}
|
|
294
|
+
variant={activeVariant}
|
|
295
|
+
viewSettings={viewSettings}
|
|
296
|
+
uiState={uiState}
|
|
297
|
+
uiActions={uiActions}
|
|
298
|
+
figmaUrl={figmaUrl}
|
|
299
|
+
linkCopied={uiState.linkCopied}
|
|
300
|
+
onCopyLink={handleCopyLink}
|
|
301
|
+
searchQuery={searchQuery}
|
|
302
|
+
onSearchChange={setSearchQuery}
|
|
303
|
+
searchInputRef={searchInputRef}
|
|
304
|
+
/>
|
|
305
|
+
) : (
|
|
306
|
+
<ViewerHeader
|
|
307
|
+
showHealth={uiState.showHealthDashboard}
|
|
308
|
+
searchQuery={searchQuery}
|
|
309
|
+
onSearchChange={setSearchQuery}
|
|
310
|
+
searchInputRef={searchInputRef}
|
|
311
|
+
/>
|
|
312
|
+
)
|
|
313
|
+
}
|
|
273
314
|
leftSidebar={
|
|
274
315
|
<LeftSidebar
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
316
|
+
fragments={fragments}
|
|
317
|
+
activeFragment={uiState.showHealthDashboard ? null : activeFragmentPath}
|
|
318
|
+
searchQuery={searchQuery}
|
|
319
|
+
onSelect={handleSelectFragment}
|
|
278
320
|
showHealth={uiState.showHealthDashboard}
|
|
279
321
|
onHealthClick={() => {
|
|
280
322
|
uiActions.setHealthDashboard(true);
|
|
281
|
-
|
|
323
|
+
setActiveFragmentPath(null);
|
|
282
324
|
}}
|
|
283
325
|
/>
|
|
284
326
|
}
|
|
@@ -287,37 +329,25 @@ export function App({ segments }: AppProps) {
|
|
|
287
329
|
<div style={{ height: '100%', overflow: 'auto', backgroundColor: 'var(--bg-primary)' }}>
|
|
288
330
|
<Box padding="lg" style={{ maxWidth: '896px', margin: '0 auto' }}>
|
|
289
331
|
<HealthDashboard
|
|
290
|
-
|
|
332
|
+
fragments={fragments}
|
|
291
333
|
onNavigate={(componentName) => {
|
|
292
|
-
const target =
|
|
334
|
+
const target = fragments.find(s => s.fragment.meta.name === componentName);
|
|
293
335
|
if (target) {
|
|
294
336
|
uiActions.setHealthDashboard(false);
|
|
295
|
-
|
|
337
|
+
handleSelectFragment(target.path);
|
|
296
338
|
}
|
|
297
339
|
}}
|
|
298
340
|
/>
|
|
299
341
|
</Box>
|
|
300
342
|
</div>
|
|
301
|
-
) :
|
|
343
|
+
) : activeFragment ? (
|
|
302
344
|
<div style={{ display: 'flex', height: '100%', flexDirection: panelDock === "bottom" ? 'column' : 'row' }}>
|
|
303
345
|
{/* Main Content Area */}
|
|
304
346
|
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, minHeight: 0 }}>
|
|
305
|
-
{/* Top Toolbar */}
|
|
306
|
-
<TopToolbar
|
|
307
|
-
segment={activeSegment}
|
|
308
|
-
variant={activeVariant}
|
|
309
|
-
viewSettings={viewSettings}
|
|
310
|
-
uiState={uiState}
|
|
311
|
-
uiActions={uiActions}
|
|
312
|
-
figmaUrl={figmaUrl}
|
|
313
|
-
linkCopied={uiState.linkCopied}
|
|
314
|
-
onCopyLink={handleCopyLink}
|
|
315
|
-
/>
|
|
316
|
-
|
|
317
347
|
{/* Variant Tabs */}
|
|
318
|
-
{
|
|
348
|
+
{activeFragment.fragment.variants && activeFragment.fragment.variants.length > 0 && (
|
|
319
349
|
<VariantTabsBar
|
|
320
|
-
variants={
|
|
350
|
+
variants={activeFragment.fragment.variants}
|
|
321
351
|
activeIndex={activeVariantIndex}
|
|
322
352
|
onSelect={handleSelectVariant}
|
|
323
353
|
showMatrixView={uiState.showMatrixView}
|
|
@@ -338,10 +368,10 @@ export function App({ segments }: AppProps) {
|
|
|
338
368
|
>
|
|
339
369
|
{activeVariant ? (
|
|
340
370
|
<PreviewArea
|
|
341
|
-
componentName={
|
|
342
|
-
|
|
371
|
+
componentName={activeFragment.fragment.meta.name}
|
|
372
|
+
fragmentPath={activeFragment.path}
|
|
343
373
|
variant={activeVariant}
|
|
344
|
-
variants={
|
|
374
|
+
variants={activeFragment.fragment.variants}
|
|
345
375
|
zoom={viewSettings.zoom}
|
|
346
376
|
background={viewSettings.background}
|
|
347
377
|
viewport={viewSettings.viewport}
|
|
@@ -358,10 +388,10 @@ export function App({ segments }: AppProps) {
|
|
|
358
388
|
}}
|
|
359
389
|
onRetry={uiActions.incrementPreviewKey}
|
|
360
390
|
renderContent={renderVariantWithProps}
|
|
361
|
-
previewKey={`${
|
|
391
|
+
previewKey={`${activeFragmentPath}-${activeVariantIndex}-${uiState.previewKey}`}
|
|
362
392
|
/>
|
|
363
393
|
) : (
|
|
364
|
-
<NoVariantsMessage
|
|
394
|
+
<NoVariantsMessage fragment={activeFragment?.fragment} />
|
|
365
395
|
)}
|
|
366
396
|
</div>
|
|
367
397
|
</div>
|
|
@@ -369,9 +399,9 @@ export function App({ segments }: AppProps) {
|
|
|
369
399
|
{/* Bottom Panel */}
|
|
370
400
|
{activeVariant && (
|
|
371
401
|
<BottomPanel
|
|
372
|
-
|
|
402
|
+
fragment={activeFragment.fragment}
|
|
373
403
|
variant={activeVariant}
|
|
374
|
-
|
|
404
|
+
fragments={fragments}
|
|
375
405
|
activePanel={uiState.activePanel}
|
|
376
406
|
onPanelChange={uiActions.setActivePanel}
|
|
377
407
|
figmaUrl={figmaUrl}
|
|
@@ -384,11 +414,11 @@ export function App({ segments }: AppProps) {
|
|
|
384
414
|
actionLogs={actionLogs}
|
|
385
415
|
onClearActionLogs={clearActionLogs}
|
|
386
416
|
onNavigateToComponent={(name) => {
|
|
387
|
-
const target =
|
|
388
|
-
if (target)
|
|
417
|
+
const target = fragments.find(s => s.fragment.meta.name === name);
|
|
418
|
+
if (target) handleSelectFragment(target.path);
|
|
389
419
|
}}
|
|
390
420
|
previewKey={uiState.previewKey}
|
|
391
|
-
|
|
421
|
+
fragmentKey={`${activeFragmentPath}-${activeVariantIndex}`}
|
|
392
422
|
/>
|
|
393
423
|
)}
|
|
394
424
|
</div>
|
|
@@ -408,7 +438,7 @@ export function App({ segments }: AppProps) {
|
|
|
408
438
|
|
|
409
439
|
// Top Toolbar Component
|
|
410
440
|
interface TopToolbarProps {
|
|
411
|
-
|
|
441
|
+
fragment: { path: string; fragment: FragmentDefinition };
|
|
412
442
|
variant: any;
|
|
413
443
|
viewSettings: ReturnType<typeof useViewSettings>;
|
|
414
444
|
uiState: ReturnType<typeof useAppState>['state'];
|
|
@@ -416,16 +446,82 @@ interface TopToolbarProps {
|
|
|
416
446
|
figmaUrl?: string;
|
|
417
447
|
linkCopied: boolean;
|
|
418
448
|
onCopyLink: () => void;
|
|
449
|
+
searchQuery: string;
|
|
450
|
+
onSearchChange: (value: string) => void;
|
|
451
|
+
searchInputRef: RefObject<HTMLInputElement>;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
interface ViewerHeaderProps {
|
|
455
|
+
showHealth: boolean;
|
|
456
|
+
searchQuery: string;
|
|
457
|
+
onSearchChange: (value: string) => void;
|
|
458
|
+
searchInputRef: RefObject<HTMLInputElement>;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
interface HeaderSearchProps {
|
|
462
|
+
value: string;
|
|
463
|
+
onChange: (value: string) => void;
|
|
464
|
+
inputRef: RefObject<HTMLInputElement>;
|
|
419
465
|
}
|
|
420
466
|
|
|
421
|
-
function
|
|
467
|
+
function HeaderSearch({ value, onChange, inputRef }: HeaderSearchProps) {
|
|
422
468
|
return (
|
|
423
|
-
<
|
|
424
|
-
<
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
469
|
+
<Header.Search expandable>
|
|
470
|
+
<Input
|
|
471
|
+
ref={inputRef}
|
|
472
|
+
value={value}
|
|
473
|
+
onChange={onChange}
|
|
474
|
+
placeholder="Search components"
|
|
475
|
+
aria-label="Search components"
|
|
476
|
+
size="sm"
|
|
477
|
+
shortcut="⌘K"
|
|
478
|
+
style={{ width: '220px' }}
|
|
479
|
+
/>
|
|
480
|
+
</Header.Search>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function ViewerHeader({ showHealth, searchQuery, onSearchChange, searchInputRef }: ViewerHeaderProps) {
|
|
485
|
+
return (
|
|
486
|
+
<Header aria-label="Fragments viewer header">
|
|
487
|
+
<Header.Trigger />
|
|
488
|
+
<Header.Brand>
|
|
489
|
+
<Stack direction="row" gap="sm" align="center">
|
|
490
|
+
<Text weight="medium" size="sm">{BRAND.name}</Text>
|
|
491
|
+
<Text size="xs" color="tertiary">{showHealth ? 'health dashboard' : 'preview'}</Text>
|
|
492
|
+
</Stack>
|
|
493
|
+
</Header.Brand>
|
|
494
|
+
<HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
|
|
495
|
+
<Header.Spacer />
|
|
496
|
+
</Header>
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function TopToolbar({
|
|
501
|
+
fragment,
|
|
502
|
+
variant,
|
|
503
|
+
viewSettings,
|
|
504
|
+
uiState,
|
|
505
|
+
uiActions,
|
|
506
|
+
figmaUrl,
|
|
507
|
+
linkCopied,
|
|
508
|
+
onCopyLink,
|
|
509
|
+
searchQuery,
|
|
510
|
+
onSearchChange,
|
|
511
|
+
searchInputRef,
|
|
512
|
+
}: TopToolbarProps) {
|
|
513
|
+
return (
|
|
514
|
+
<Header aria-label="Component preview toolbar">
|
|
515
|
+
<Header.Trigger />
|
|
516
|
+
<Header.Brand>
|
|
517
|
+
<Stack direction="row" align="center" gap="sm">
|
|
518
|
+
<Text weight="medium" size="sm">{fragment.fragment.meta.name}</Text>
|
|
519
|
+
<Text size="xs" color="tertiary">{fragment.fragment.meta.category}</Text>
|
|
520
|
+
</Stack>
|
|
521
|
+
</Header.Brand>
|
|
522
|
+
<HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
|
|
523
|
+
<Header.Spacer />
|
|
524
|
+
<Header.Actions>
|
|
429
525
|
<PreviewToolbar
|
|
430
526
|
zoom={viewSettings.zoom}
|
|
431
527
|
background={viewSettings.background}
|
|
@@ -474,7 +570,7 @@ function TopToolbar({ segment, variant, viewSettings, uiState, uiActions, figmaU
|
|
|
474
570
|
const url = new URL(window.location.href);
|
|
475
571
|
url.hash = '';
|
|
476
572
|
url.searchParams.set('isolated', 'true');
|
|
477
|
-
url.searchParams.set('component',
|
|
573
|
+
url.searchParams.set('component', fragment.fragment.meta.name);
|
|
478
574
|
url.searchParams.set('variant', variant.name);
|
|
479
575
|
if (viewSettings.zoom !== 100) url.searchParams.set('zoom', String(viewSettings.zoom));
|
|
480
576
|
if (viewSettings.background !== 'transparent') url.searchParams.set('bg', viewSettings.background);
|
|
@@ -486,7 +582,7 @@ function TopToolbar({ segment, variant, viewSettings, uiState, uiActions, figmaU
|
|
|
486
582
|
<ExternalLinkIcon style={{ width: '16px', height: '16px' }} />
|
|
487
583
|
</Button>
|
|
488
584
|
</Tooltip>
|
|
489
|
-
<ScreenshotButton componentName={
|
|
585
|
+
<ScreenshotButton componentName={fragment.fragment.meta.name} variantName={variant.name} />
|
|
490
586
|
<Tooltip content="Copy link to share">
|
|
491
587
|
<Button
|
|
492
588
|
onClick={onCopyLink}
|
|
@@ -499,8 +595,8 @@ function TopToolbar({ segment, variant, viewSettings, uiState, uiActions, figmaU
|
|
|
499
595
|
</Tooltip>
|
|
500
596
|
</>
|
|
501
597
|
)}
|
|
502
|
-
</
|
|
503
|
-
</
|
|
598
|
+
</Header.Actions>
|
|
599
|
+
</Header>
|
|
504
600
|
);
|
|
505
601
|
}
|
|
506
602
|
|
|
@@ -519,11 +615,13 @@ function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView, showM
|
|
|
519
615
|
return (
|
|
520
616
|
<Stack direction="row" align="center" justify="between" style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', backgroundColor: 'var(--bg-primary)', flexShrink: 0 }}>
|
|
521
617
|
{!showMatrixView ? (
|
|
522
|
-
<
|
|
618
|
+
<ScrollArea orientation="horizontal" showFades style={{ flex: 1, minWidth: 0 }}>
|
|
619
|
+
<VariantTabs variants={variants} activeIndex={activeIndex} onSelect={onSelect} />
|
|
620
|
+
</ScrollArea>
|
|
523
621
|
) : (
|
|
524
622
|
<Text size="sm" color="secondary">Showing all {variants.length} variants</Text>
|
|
525
623
|
)}
|
|
526
|
-
<Stack direction="row" align="center" gap="sm" style={{ marginLeft: '16px' }}>
|
|
624
|
+
<Stack direction="row" align="center" gap="sm" style={{ marginLeft: '16px', flexShrink: 0 }}>
|
|
527
625
|
{variants.length > 1 && (
|
|
528
626
|
<Button
|
|
529
627
|
onClick={onToggleMatrix}
|
|
@@ -553,11 +651,11 @@ function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView, showM
|
|
|
553
651
|
|
|
554
652
|
// No variants message
|
|
555
653
|
interface NoVariantsMessageProps {
|
|
556
|
-
|
|
654
|
+
fragment?: FragmentDefinition;
|
|
557
655
|
}
|
|
558
656
|
|
|
559
|
-
function NoVariantsMessage({
|
|
560
|
-
const skippedVariants = (
|
|
657
|
+
function NoVariantsMessage({ fragment }: NoVariantsMessageProps) {
|
|
658
|
+
const skippedVariants = (fragment?._generated as any)?.skippedVariants;
|
|
561
659
|
|
|
562
660
|
if (!skippedVariants || skippedVariants.length === 0) {
|
|
563
661
|
return (
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { memo, useCallback } from 'react';
|
|
7
|
-
import type {
|
|
7
|
+
import type { FragmentDefinition, FragmentVariant } from '../../core/index.js';
|
|
8
8
|
import { Tabs, Badge } from '@fragments/ui';
|
|
9
9
|
import { ResizablePanel } from './ResizablePanel.js';
|
|
10
10
|
import { CodePanel } from './CodePanel.js';
|
|
@@ -19,9 +19,9 @@ import type { ActionLog } from '../hooks/useActions.js';
|
|
|
19
19
|
|
|
20
20
|
interface BottomPanelProps {
|
|
21
21
|
// Component data
|
|
22
|
-
|
|
23
|
-
variant:
|
|
24
|
-
|
|
22
|
+
fragment: FragmentDefinition;
|
|
23
|
+
variant: FragmentVariant;
|
|
24
|
+
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
25
25
|
|
|
26
26
|
// Panel state
|
|
27
27
|
activePanel: ActivePanel;
|
|
@@ -45,13 +45,13 @@ interface BottomPanelProps {
|
|
|
45
45
|
|
|
46
46
|
// Keys
|
|
47
47
|
previewKey: number;
|
|
48
|
-
|
|
48
|
+
fragmentKey: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export const BottomPanel = memo(function BottomPanel({
|
|
52
|
-
|
|
52
|
+
fragment,
|
|
53
53
|
variant,
|
|
54
|
-
|
|
54
|
+
fragments,
|
|
55
55
|
activePanel,
|
|
56
56
|
onPanelChange,
|
|
57
57
|
figmaUrl,
|
|
@@ -65,7 +65,7 @@ export const BottomPanel = memo(function BottomPanel({
|
|
|
65
65
|
onClearActionLogs,
|
|
66
66
|
onNavigateToComponent,
|
|
67
67
|
previewKey,
|
|
68
|
-
|
|
68
|
+
fragmentKey,
|
|
69
69
|
}: BottomPanelProps) {
|
|
70
70
|
const handleStylesClick = useCallback(() => {
|
|
71
71
|
onPanelChange('styles');
|
|
@@ -121,8 +121,8 @@ export const BottomPanel = memo(function BottomPanel({
|
|
|
121
121
|
<div style={{ padding: '16px' }}>
|
|
122
122
|
<CodePanel
|
|
123
123
|
variant={variant}
|
|
124
|
-
componentName={
|
|
125
|
-
propDefs={
|
|
124
|
+
componentName={fragment.meta.name}
|
|
125
|
+
propDefs={fragment.props}
|
|
126
126
|
compact
|
|
127
127
|
/>
|
|
128
128
|
</div>
|
|
@@ -142,17 +142,17 @@ export const BottomPanel = memo(function BottomPanel({
|
|
|
142
142
|
|
|
143
143
|
{activePanel === 'accessibility' && (
|
|
144
144
|
<AccessibilityPanel
|
|
145
|
-
cacheKey={
|
|
145
|
+
cacheKey={fragmentKey}
|
|
146
146
|
previewKey={previewKey}
|
|
147
147
|
autoScan={true}
|
|
148
|
-
componentName={
|
|
148
|
+
componentName={fragment.meta.name}
|
|
149
149
|
variantName={variant.name}
|
|
150
150
|
/>
|
|
151
151
|
)}
|
|
152
152
|
|
|
153
153
|
{activePanel === 'interactions' && (
|
|
154
154
|
<InteractionsPanel
|
|
155
|
-
key={
|
|
155
|
+
key={fragmentKey}
|
|
156
156
|
variant={variant}
|
|
157
157
|
previewKey={previewKey}
|
|
158
158
|
/>
|
|
@@ -167,16 +167,16 @@ export const BottomPanel = memo(function BottomPanel({
|
|
|
167
167
|
|
|
168
168
|
{activePanel === 'graph' && (
|
|
169
169
|
<ComponentGraph
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
fragment={fragment}
|
|
171
|
+
allFragments={fragments}
|
|
172
172
|
onNavigate={onNavigateToComponent}
|
|
173
173
|
/>
|
|
174
174
|
)}
|
|
175
175
|
|
|
176
176
|
{activePanel === 'contract' && (
|
|
177
177
|
<ContractPanel
|
|
178
|
-
contract={
|
|
179
|
-
componentName={
|
|
178
|
+
contract={fragment.contract}
|
|
179
|
+
componentName={fragment.meta.name}
|
|
180
180
|
/>
|
|
181
181
|
)}
|
|
182
182
|
</ResizablePanel>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { useMemo, isValidElement, type ReactNode } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FragmentVariant, PropDefinition } from '../../core/index.js';
|
|
3
3
|
import { CodeBlock } from '@fragments/ui';
|
|
4
4
|
|
|
5
5
|
interface CodePanelProps {
|
|
6
|
-
variant:
|
|
6
|
+
variant: FragmentVariant;
|
|
7
7
|
componentName: string;
|
|
8
8
|
compact?: boolean;
|
|
9
9
|
propDefs?: Record<string, PropDefinition>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
// Extract props from rendered element by calling render() and introspecting
|
|
13
|
-
function extractPropsFromRender(variant:
|
|
13
|
+
function extractPropsFromRender(variant: FragmentVariant, componentName: string): Record<string, unknown> | null {
|
|
14
14
|
try {
|
|
15
15
|
const rendered = variant.render();
|
|
16
16
|
if (!isValidElement(rendered)) return null;
|