@fragments-sdk/cli 0.10.1 → 0.11.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.
Files changed (149) hide show
  1. package/dist/bin.js +20 -2
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{init-NDQXUWDU.js → init-UFGK5TCN.js} +75 -4
  4. package/dist/init-UFGK5TCN.js.map +1 -0
  5. package/dist/snapshot-SV2JOFZH.js +139 -0
  6. package/dist/snapshot-SV2JOFZH.js.map +1 -0
  7. package/dist/{viewer-DNMNC5VS.js → viewer-DLLJIMCK.js} +68 -46
  8. package/dist/viewer-DLLJIMCK.js.map +1 -0
  9. package/package.json +6 -14
  10. package/src/bin.ts +30 -0
  11. package/src/commands/init.ts +76 -1
  12. package/src/commands/snapshot.ts +197 -0
  13. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  14. package/src/viewer/server.ts +37 -22
  15. package/src/viewer/vite-plugin.ts +25 -9
  16. package/dist/init-NDQXUWDU.js.map +0 -1
  17. package/dist/viewer-DNMNC5VS.js.map +0 -1
  18. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  19. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  20. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  21. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  22. package/src/viewer/assets/fragments-logo.ts +0 -4
  23. package/src/viewer/assets/fragments_logo.png +0 -0
  24. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  25. package/src/viewer/components/ActionCapture.tsx +0 -172
  26. package/src/viewer/components/ActionsPanel.tsx +0 -332
  27. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  28. package/src/viewer/components/App.tsx +0 -582
  29. package/src/viewer/components/BottomPanel.tsx +0 -288
  30. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  31. package/src/viewer/components/CodePanel.tsx +0 -118
  32. package/src/viewer/components/CommandPalette.tsx +0 -392
  33. package/src/viewer/components/ComponentDocView.tsx +0 -164
  34. package/src/viewer/components/ComponentGraph.tsx +0 -380
  35. package/src/viewer/components/ComponentHeader.tsx +0 -88
  36. package/src/viewer/components/ContractPanel.tsx +0 -241
  37. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  38. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  39. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  40. package/src/viewer/components/FragmentEditor.tsx +0 -525
  41. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  42. package/src/viewer/components/HeaderSearch.tsx +0 -24
  43. package/src/viewer/components/HealthDashboard.tsx +0 -441
  44. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  45. package/src/viewer/components/Icons.tsx +0 -479
  46. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  47. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  48. package/src/viewer/components/IsolatedRender.tsx +0 -113
  49. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  50. package/src/viewer/components/LandingPage.tsx +0 -421
  51. package/src/viewer/components/Layout.tsx +0 -27
  52. package/src/viewer/components/LeftSidebar.tsx +0 -472
  53. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  54. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  55. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  56. package/src/viewer/components/PanelShell.tsx +0 -161
  57. package/src/viewer/components/PerformancePanel.tsx +0 -304
  58. package/src/viewer/components/PreviewArea.tsx +0 -472
  59. package/src/viewer/components/PreviewAside.tsx +0 -168
  60. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  61. package/src/viewer/components/PreviewPane.tsx +0 -149
  62. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  63. package/src/viewer/components/PropsEditor.tsx +0 -506
  64. package/src/viewer/components/PropsTable.tsx +0 -111
  65. package/src/viewer/components/RelationsSection.tsx +0 -88
  66. package/src/viewer/components/ResizablePanel.tsx +0 -271
  67. package/src/viewer/components/RightSidebar.tsx +0 -102
  68. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  69. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  70. package/src/viewer/components/Sidebar.tsx +0 -169
  71. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  72. package/src/viewer/components/ThemeProvider.tsx +0 -42
  73. package/src/viewer/components/Toast.tsx +0 -3
  74. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  75. package/src/viewer/components/TopToolbar.tsx +0 -159
  76. package/src/viewer/components/UsageSection.tsx +0 -95
  77. package/src/viewer/components/VariantMatrix.tsx +0 -388
  78. package/src/viewer/components/VariantRenderer.tsx +0 -131
  79. package/src/viewer/components/VariantTabs.tsx +0 -40
  80. package/src/viewer/components/ViewerHeader.tsx +0 -69
  81. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  82. package/src/viewer/components/ViewportSelector.tsx +0 -172
  83. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  84. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  85. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  86. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  87. package/src/viewer/components/viewer-utils.ts +0 -16
  88. package/src/viewer/composition-renderer.ts +0 -381
  89. package/src/viewer/constants/index.ts +0 -1
  90. package/src/viewer/constants/ui.ts +0 -166
  91. package/src/viewer/entry.tsx +0 -335
  92. package/src/viewer/hooks/index.ts +0 -2
  93. package/src/viewer/hooks/useA11yCache.ts +0 -383
  94. package/src/viewer/hooks/useA11yService.ts +0 -364
  95. package/src/viewer/hooks/useActions.ts +0 -138
  96. package/src/viewer/hooks/useAppState.ts +0 -147
  97. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  98. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  99. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  100. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  101. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  102. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  103. package/src/viewer/hooks/useUrlState.ts +0 -318
  104. package/src/viewer/hooks/useViewSettings.ts +0 -111
  105. package/src/viewer/index.html +0 -28
  106. package/src/viewer/intelligence/healthReport.ts +0 -505
  107. package/src/viewer/intelligence/styleDrift.ts +0 -340
  108. package/src/viewer/intelligence/usageScanner.ts +0 -309
  109. package/src/viewer/jsx-parser.ts +0 -486
  110. package/src/viewer/preview-frame-entry.tsx +0 -25
  111. package/src/viewer/preview-frame.html +0 -125
  112. package/src/viewer/public/favicon.ico +0 -0
  113. package/src/viewer/render-template.html +0 -68
  114. package/src/viewer/styles/globals.css +0 -278
  115. package/src/viewer/types/a11y.ts +0 -197
  116. package/src/viewer/utils/a11y-fixes.ts +0 -509
  117. package/src/viewer/utils/actionExport.ts +0 -372
  118. package/src/viewer/utils/colorSchemes.ts +0 -201
  119. package/src/viewer/utils/detectRelationships.ts +0 -256
  120. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  121. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  122. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  123. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  124. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  125. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  126. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  127. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  128. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  129. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  130. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  131. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  132. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  133. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -137
  134. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  135. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  136. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  137. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  138. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  139. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  140. package/src/viewer/vendor/shared/src/index.ts +0 -34
  141. package/src/viewer/vendor/shared/src/types.ts +0 -53
  142. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  143. package/src/viewer/webmcp/analytics.ts +0 -165
  144. package/src/viewer/webmcp/index.ts +0 -3
  145. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  146. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  147. package/src/viewer/webmcp/scan-utils.ts +0 -135
  148. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  149. package/src/viewer/webmcp/viewer-state.ts +0 -45
@@ -1,582 +0,0 @@
1
- /**
2
- * Main App component for the Fragments viewer.
3
- * Refactored for better performance and maintainability.
4
- */
5
-
6
- import { useState, useMemo, useEffect, useCallback, useRef, type ReactNode } from "react";
7
- import type { FragmentDefinition, FragmentVariant } from "../../core/index.js";
8
-
9
- // Layout & Navigation
10
- import { Layout } from "./Layout.js";
11
- import { LeftSidebar } from "./LeftSidebar.js";
12
- import { CommandPalette } from "./CommandPalette.js";
13
- import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp.js";
14
- import { useToast } from "./Toast.js";
15
-
16
- // Toolbar & Header
17
- import { TopToolbar } from "./TopToolbar.js";
18
- import { ViewerHeader } from "./ViewerHeader.js";
19
-
20
- // Preview & Rendering
21
- import { PreviewArea } from "./PreviewArea.js";
22
- import { BottomPanel } from "./BottomPanel.js";
23
- import { IsolatedRender } from "./IsolatedRender.js";
24
- import { FragmentRenderer, LoaderIndicator } from "./FragmentRenderer.js";
25
- import { HealthDashboard } from "./HealthDashboard.js";
26
- import { useAllFigmaUrls } from "./FigmaEmbed.js";
27
- import { ActionCapture } from "./ActionCapture.js";
28
-
29
- // Extracted sub-components
30
- import { PreviewAside } from "./PreviewAside.js";
31
- import { AllVariantsPreview } from "./AllVariantsPreview.js";
32
- import { ComponentDocView } from "./ComponentDocView.js";
33
- import { NoVariantsMessage } from "./NoVariantsMessage.js";
34
- import { EmptyVariantMessage } from "./EmptyVariantMessage.js";
35
-
36
- // Fragments UI
37
- import { Stack, Box, EmptyState } from "@fragments-sdk/ui";
38
-
39
- // Icons
40
- import { EmptyIcon } from "./Icons.js";
41
-
42
- // Utilities
43
- import { getVariantSectionId } from "./viewer-utils.js";
44
-
45
- // Hooks
46
- import { useAppState } from "../hooks/useAppState.js";
47
- import { useViewSettings } from "../hooks/useViewSettings.js";
48
- import { useFigmaIntegration } from "../hooks/useFigmaIntegration.js";
49
- import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts.js";
50
- import { useActions } from "../hooks/useActions.js";
51
- import { useUrlState, findFragmentByName, findVariantIndex } from "../hooks/useUrlState.js";
52
- import { useTheme } from "./ThemeProvider.js";
53
- import { ViewerStateSync } from "./ViewerStateSync.js";
54
-
55
- interface AppProps {
56
- fragments: Array<{ path: string; fragment: FragmentDefinition }>;
57
- }
58
-
59
- export function App({ fragments }: AppProps) {
60
- // URL state management
61
- const {
62
- state: urlState,
63
- setComponent: setUrlComponent,
64
- setVariant: setUrlVariant,
65
- setViewSettings: setUrlViewSettings,
66
- copyUrl,
67
- } = useUrlState();
68
-
69
- // UI state (modals, panels, view modes)
70
- const { state: uiState, actions: uiActions } = useAppState();
71
-
72
- // View settings (zoom, viewport, theme)
73
- const viewSettings = useViewSettings({
74
- initialState: {
75
- zoom: urlState.zoom as any,
76
- viewport: urlState.viewport as any,
77
- customSize: { width: urlState.customWidth, height: urlState.customHeight },
78
- },
79
- onZoomChange: (zoom) => setUrlViewSettings({ zoom }),
80
- onViewportChange: (vp, size) =>
81
- setUrlViewSettings({
82
- viewport: vp,
83
- customWidth: size?.width,
84
- customHeight: size?.height,
85
- }),
86
- });
87
-
88
- // Get resolved theme from ThemeProvider for iframe preview
89
- const { resolvedTheme } = useTheme();
90
-
91
- // Toast notifications (via Fragments UI ToastProvider)
92
- const { info, success } = useToast();
93
-
94
- // Navigation state
95
- const [activeFragmentPath, setActiveFragmentPath] = useState<string | null>(() => {
96
- if (urlState.component) {
97
- const found = findFragmentByName(fragments, urlState.component);
98
- return found?.path ?? fragments[0]?.path ?? null;
99
- }
100
- return fragments[0]?.path ?? null;
101
- });
102
- const activeFragmentPathRef = useRef(activeFragmentPath);
103
- activeFragmentPathRef.current = activeFragmentPath;
104
-
105
- const [activeVariantIndex, setActiveVariantIndex] = useState<number>(() => {
106
- const fragment = fragments.find((s) => s.path === activeFragmentPath);
107
- if (urlState.variant && fragment?.fragment.variants) {
108
- return findVariantIndex(fragment.fragment.variants, urlState.variant);
109
- }
110
- return 0;
111
- });
112
- const [searchQuery, setSearchQuery] = useState("");
113
- const searchInputRef = useRef<HTMLInputElement>(null);
114
-
115
- // Derived values
116
- const activeFragment = useMemo(
117
- () => fragments.find((s) => s.path === activeFragmentPath),
118
- [fragments, activeFragmentPath]
119
- );
120
- const variants = activeFragment?.fragment.variants ?? [];
121
- const variantCount = variants.length;
122
- const safeVariantIndex = variantCount > 0 ? Math.min(activeVariantIndex, variantCount - 1) : 0;
123
- const activeVariant = variants[safeVariantIndex];
124
- const figmaUrl = activeVariant?.figma || activeFragment?.fragment.meta.figma;
125
-
126
- // Figma integration
127
- const figmaIntegration = useFigmaIntegration({
128
- figmaUrl,
129
- showComparison: uiState.showComparison,
130
- dependencies: [activeFragmentPath, activeVariantIndex],
131
- });
132
-
133
- // Actions logging
134
- const { logs: actionLogs, logAction, clearLogs: clearActionLogs } = useActions();
135
- const useActionsRef = useRef({ logAction });
136
- useActionsRef.current = { logAction };
137
-
138
- // Figma URLs for preloading
139
- const allFigmaUrls = useAllFigmaUrls(activeFragment?.fragment);
140
-
141
- // Reset action logs on variant change
142
- useEffect(() => {
143
- clearActionLogs();
144
- }, [activeFragmentPath, activeVariantIndex, clearActionLogs]);
145
-
146
- // Extract rendered styles after component renders
147
- useEffect(() => {
148
- if (uiState.showComparison && activeVariant) {
149
- const timer = setTimeout(figmaIntegration.extractRenderedStyles, 100);
150
- return () => clearTimeout(timer);
151
- }
152
- }, [
153
- uiState.showComparison,
154
- activeVariant,
155
- figmaIntegration.extractRenderedStyles,
156
- uiState.previewKey,
157
- ]);
158
-
159
- // Keep focused variant index in range when variant lists change.
160
- useEffect(() => {
161
- if (variantCount === 0) {
162
- setActiveVariantIndex(0);
163
- return;
164
- }
165
- if (activeVariantIndex >= variantCount) {
166
- setActiveVariantIndex(variantCount - 1);
167
- }
168
- }, [activeVariantIndex, variantCount]);
169
-
170
- // Sync URL state on browser navigation
171
- useEffect(() => {
172
- if (urlState.component) {
173
- const found = findFragmentByName(fragments, urlState.component);
174
- if (!found) return;
175
-
176
- const pathChanged = found.path !== activeFragmentPathRef.current;
177
- setActiveFragmentPath(found.path);
178
- uiActions.setHealthDashboard(false);
179
-
180
- // Keep focused variant when entering "All" on the same component.
181
- if (urlState.variant || pathChanged) {
182
- const variantIndex = findVariantIndex(found.fragment.variants, urlState.variant);
183
- setActiveVariantIndex(variantIndex);
184
- }
185
- }
186
- }, [urlState.component, urlState.variant, fragments, uiActions]);
187
-
188
- // HMR toast notifications
189
- useEffect(() => {
190
- const hot = (import.meta as any).hot;
191
- if (!hot) return;
192
-
193
- const handleUpdate = (data: any) => {
194
- if (data?.updates?.length > 0) {
195
- const paths = data.updates.map((u: any) => u.path.split("/").pop()).join(", ");
196
- info("HMR Update", `Updated: ${paths}`);
197
- }
198
- };
199
-
200
- hot.on("vite:beforeUpdate", handleUpdate);
201
- return () => hot.off?.("vite:beforeUpdate", handleUpdate);
202
- }, [info]);
203
-
204
- // Navigation handlers
205
- const handleSelectFragment = useCallback(
206
- (path: string) => {
207
- const fragment = fragments.find((s) => s.path === path);
208
- const componentName = fragment?.fragment.meta.name || path;
209
-
210
- setActiveFragmentPath(path);
211
- setActiveVariantIndex(0);
212
- uiActions.setHealthDashboard(false);
213
- setUrlComponent(componentName, null);
214
- },
215
- [fragments, setUrlComponent, uiActions]
216
- );
217
-
218
- const scrollToVariantSection = useCallback(
219
- (index: number, behavior: ScrollBehavior = "smooth") => {
220
- if (!activeFragment || variantCount === 0) return;
221
- const normalizedIndex = ((index % variantCount) + variantCount) % variantCount;
222
- const targetVariant = variants[normalizedIndex];
223
- if (!targetVariant) return;
224
-
225
- const sectionId = getVariantSectionId(activeFragment.fragment.meta.name, targetVariant.name);
226
- document.getElementById(sectionId)?.scrollIntoView({ behavior, block: "start" });
227
- },
228
- [activeFragment, variantCount, variants]
229
- );
230
-
231
- const focusVariantInAllMode = useCallback(
232
- (index: number, shouldScroll = false) => {
233
- if (variantCount === 0) return;
234
- const normalizedIndex = ((index % variantCount) + variantCount) % variantCount;
235
- setActiveVariantIndex(normalizedIndex);
236
- if (shouldScroll) {
237
- scrollToVariantSection(normalizedIndex);
238
- }
239
- },
240
- [variantCount, scrollToVariantSection]
241
- );
242
-
243
- const handleSelectVariant = useCallback(
244
- (index: number) => {
245
- if (variantCount === 0) return;
246
- const normalizedIndex = ((index % variantCount) + variantCount) % variantCount;
247
- const variantName = variants[normalizedIndex]?.name;
248
- setActiveVariantIndex(normalizedIndex);
249
- setUrlVariant(variantName || null);
250
- },
251
- [variantCount, variants, setUrlVariant]
252
- );
253
-
254
- const handleSelectVariantLink = useCallback(
255
- (index: number) => {
256
- // Always scroll to the variant section in the docs-like view
257
- focusVariantInAllMode(index, true);
258
- },
259
- [focusVariantInAllMode]
260
- );
261
-
262
- // Copy link handler
263
- const handleCopyLink = useCallback(async () => {
264
- const copied = await copyUrl();
265
- if (copied) {
266
- uiActions.setLinkCopied(true);
267
- success("Copied", "Link copied to clipboard");
268
- setTimeout(() => uiActions.setLinkCopied(false), 2000);
269
- }
270
- }, [copyUrl, success, uiActions]);
271
-
272
- // Sorted fragment paths for keyboard navigation
273
- const sortedFragmentPaths = useMemo(() => {
274
- return [...fragments]
275
- .filter((s) => s.fragment?.meta?.name)
276
- .sort((a, b) => a.fragment.meta.name.localeCompare(b.fragment.meta.name))
277
- .map((s) => s.path);
278
- }, [fragments]);
279
-
280
- const currentFragmentIndex = sortedFragmentPaths.indexOf(activeFragmentPath || "");
281
-
282
- // Keyboard shortcuts
283
- useKeyboardShortcuts(
284
- {
285
- nextComponent: () => {
286
- const nextIndex =
287
- currentFragmentIndex < sortedFragmentPaths.length - 1 ? currentFragmentIndex + 1 : 0;
288
- handleSelectFragment(sortedFragmentPaths[nextIndex]);
289
- },
290
- prevComponent: () => {
291
- const prevIndex =
292
- currentFragmentIndex > 0 ? currentFragmentIndex - 1 : sortedFragmentPaths.length - 1;
293
- handleSelectFragment(sortedFragmentPaths[prevIndex]);
294
- },
295
- nextVariant: () => {
296
- if (variantCount === 0) return;
297
- const nextIndex = activeVariantIndex < variantCount - 1 ? activeVariantIndex + 1 : 0;
298
- focusVariantInAllMode(nextIndex, true);
299
- },
300
- prevVariant: () => {
301
- if (variantCount === 0) return;
302
- const prevIndex = activeVariantIndex > 0 ? activeVariantIndex - 1 : variantCount - 1;
303
- focusVariantInAllMode(prevIndex, true);
304
- },
305
- goToVariant: (index) => {
306
- if (index >= variantCount) return;
307
- focusVariantInAllMode(index, true);
308
- },
309
- toggleTheme: viewSettings.toggleTheme,
310
- togglePanel: uiActions.togglePanel,
311
- toggleMatrix: () => uiActions.setMatrixView(!uiState.showMatrixView),
312
- toggleResponsive: () => uiActions.setMultiViewport(!uiState.showMultiViewport),
313
- copyLink: handleCopyLink,
314
- showHelp: uiActions.toggleShortcutsHelp,
315
- openSearch: () => uiActions.setCommandPalette(true),
316
- escape: () => {
317
- if (document.activeElement === searchInputRef.current) {
318
- if (searchQuery) {
319
- setSearchQuery("");
320
- } else {
321
- searchInputRef.current.blur();
322
- }
323
- return;
324
- }
325
- uiActions.closeAllModals();
326
- },
327
- },
328
- { enabled: !uiState.showShortcutsHelp, variantCount }
329
- );
330
-
331
- // Render variant with action logging via DOM event capture
332
- const renderVariantWithProps = useCallback((variant: FragmentVariant | undefined) => {
333
- if (!variant) return null;
334
-
335
- return (
336
- <ActionCapture onAction={useActionsRef.current.logAction}>
337
- <FragmentRenderer variant={variant}>
338
- {(content, isLoading, error) => {
339
- if (isLoading)
340
- return (
341
- <Stack align="center" justify="center" style={{ padding: "32px" }}>
342
- <LoaderIndicator />
343
- </Stack>
344
- );
345
- if (error)
346
- return (
347
- <EmptyVariantMessage
348
- reason={`Error: ${error.message}`}
349
- variantName={variant.name}
350
- hint="Check the console for the full error stack trace."
351
- />
352
- );
353
- if (content === null || content === undefined)
354
- return (
355
- <EmptyVariantMessage
356
- reason="render() returned null or undefined"
357
- variantName={variant.name}
358
- hint="The variant's render function didn't return any JSX."
359
- />
360
- );
361
- return content;
362
- }}
363
- </FragmentRenderer>
364
- </ActionCapture>
365
- );
366
- }, []);
367
-
368
- // Check if isolated mode
369
- const isIsolated = useMemo(() => {
370
- const params = new URLSearchParams(window.location.search);
371
- return params.get("isolated") === "true";
372
- }, []);
373
-
374
- if (isIsolated) {
375
- return <IsolatedRender fragments={fragments} />;
376
- }
377
-
378
- return (
379
- <>
380
- <ViewerStateSync fragments={fragments} activeVariantIndex={safeVariantIndex} />
381
- <KeyboardShortcutsHelp
382
- isOpen={uiState.showShortcutsHelp}
383
- onClose={() => uiActions.setShortcutsHelp(false)}
384
- />
385
- <CommandPalette
386
- isOpen={uiState.showCommandPalette}
387
- onClose={() => uiActions.setCommandPalette(false)}
388
- fragments={fragments}
389
- onSelectComponent={handleSelectFragment}
390
- onSelectVariant={(path, variantIndex) => {
391
- handleSelectFragment(path);
392
- setTimeout(() => handleSelectVariant(variantIndex), 0);
393
- }}
394
- />
395
-
396
- <Layout
397
- header={
398
- activeFragment && !uiState.showHealthDashboard ? (
399
- <TopToolbar
400
- fragment={activeFragment}
401
- viewSettings={viewSettings}
402
- uiState={uiState}
403
- uiActions={uiActions}
404
- figmaUrl={figmaUrl}
405
- searchQuery={searchQuery}
406
- onSearchChange={setSearchQuery}
407
- searchInputRef={searchInputRef}
408
- />
409
- ) : (
410
- <ViewerHeader
411
- showHealth={uiState.showHealthDashboard}
412
- searchQuery={searchQuery}
413
- onSearchChange={setSearchQuery}
414
- searchInputRef={searchInputRef}
415
- />
416
- )
417
- }
418
- leftSidebar={
419
- <LeftSidebar
420
- fragments={fragments}
421
- activeFragment={uiState.showHealthDashboard ? null : activeFragmentPath}
422
- searchQuery={searchQuery}
423
- onSelect={handleSelectFragment}
424
- showHealth={uiState.showHealthDashboard}
425
- onHealthClick={() => {
426
- uiActions.setHealthDashboard(true);
427
- setActiveFragmentPath(null);
428
- }}
429
- />
430
- }
431
- aside={
432
- uiState.showAside && activeFragment && !uiState.showHealthDashboard ? (
433
- <PreviewAside
434
- fragment={activeFragment.fragment}
435
- variants={variants}
436
- focusedVariantIndex={safeVariantIndex}
437
- activePanel={uiState.activePanel}
438
- onSelectVariant={handleSelectVariantLink}
439
- onCopyLink={handleCopyLink}
440
- onShowShortcuts={uiActions.toggleShortcutsHelp}
441
- />
442
- ) : null
443
- }
444
- >
445
- {uiState.showHealthDashboard ? (
446
- <Box height="100%" overflow="auto" background="primary">
447
- <Box padding="lg" style={{ maxWidth: "896px", margin: "0 auto" }}>
448
- <HealthDashboard
449
- fragments={fragments}
450
- onNavigate={(componentName) => {
451
- const target = fragments.find((s) => s.fragment.meta.name === componentName);
452
- if (target) {
453
- uiActions.setHealthDashboard(false);
454
- handleSelectFragment(target.path);
455
- }
456
- }}
457
- />
458
- </Box>
459
- </Box>
460
- ) : activeFragment ? (
461
- <Stack id="preview-layout" style={{ height: "100%" }}>
462
- {/* Main Content Area */}
463
- <Stack style={{ flex: 1, minWidth: 0, minHeight: 0 }}>
464
- {/* Preview Area */}
465
- <Box id="preview-canvas" overflow="auto" style={{ flex: 1, position: "relative" }}>
466
- {variantCount === 0 ? (
467
- <NoVariantsMessage fragment={activeFragment?.fragment} />
468
- ) : uiState.showMatrixView ? (
469
- <PreviewArea
470
- componentName={activeFragment.fragment.meta.name}
471
- fragmentPath={activeFragment.path}
472
- variant={activeVariant}
473
- variants={variants}
474
- zoom={viewSettings.zoom}
475
- viewport={viewSettings.viewport}
476
- customSize={viewSettings.customSize}
477
- previewTheme={resolvedTheme}
478
- showMatrixView={true}
479
- showMultiViewport={false}
480
- showComparison={uiState.showComparison}
481
- figmaUrl={figmaUrl}
482
- allFigmaUrls={allFigmaUrls}
483
- onSelectVariant={(index) => {
484
- uiActions.setMatrixView(false);
485
- handleSelectVariant(index);
486
- }}
487
- onRetry={uiActions.incrementPreviewKey}
488
- renderContent={() => renderVariantWithProps(activeVariant)}
489
- previewKey={`${activeFragmentPath}-${safeVariantIndex}-${uiState.previewKey}`}
490
- />
491
- ) : uiState.showMultiViewport ? (
492
- <PreviewArea
493
- componentName={activeFragment.fragment.meta.name}
494
- fragmentPath={activeFragment.path}
495
- variant={activeVariant}
496
- variants={variants}
497
- zoom={viewSettings.zoom}
498
- viewport={viewSettings.viewport}
499
- customSize={viewSettings.customSize}
500
- previewTheme={resolvedTheme}
501
- showMatrixView={false}
502
- showMultiViewport={true}
503
- showComparison={uiState.showComparison}
504
- figmaUrl={figmaUrl}
505
- allFigmaUrls={allFigmaUrls}
506
- onSelectVariant={(index) => {
507
- handleSelectVariant(index);
508
- }}
509
- onRetry={uiActions.incrementPreviewKey}
510
- renderContent={() => renderVariantWithProps(activeVariant)}
511
- previewKey={`${activeFragmentPath}-${safeVariantIndex}-${uiState.previewKey}`}
512
- />
513
- ) : (
514
- <Box
515
- style={{
516
- padding: "var(--fui-space-6) var(--fui-space-8)",
517
- }}
518
- >
519
- <ComponentDocView
520
- fragment={activeFragment}
521
- fragments={fragments}
522
- renderVariantContent={renderVariantWithProps}
523
- onNavigateToComponent={(name) => {
524
- const target = fragments.find((s) => s.fragment.meta.name === name);
525
- if (target) handleSelectFragment(target.path);
526
- }}
527
- zoom={viewSettings.zoom}
528
- viewport={viewSettings.viewport}
529
- customSize={viewSettings.customSize}
530
- previewTheme={resolvedTheme}
531
- showComparison={uiState.showComparison}
532
- allFigmaUrls={allFigmaUrls}
533
- onRetry={uiActions.incrementPreviewKey}
534
- previewKeyBase={`${activeFragmentPath}-${uiState.previewKey}`}
535
- />
536
- </Box>
537
- )}
538
- </Box>
539
- </Stack>
540
-
541
- {activeVariant && (
542
- <BottomPanel
543
- fragment={activeFragment.fragment}
544
- variant={activeVariant}
545
- fragments={fragments}
546
- open={uiState.panelOpen}
547
- onOpenChange={uiActions.setPanelOpen}
548
- activePanel={uiState.activePanel}
549
- onPanelChange={uiActions.setActivePanel}
550
- figmaUrl={figmaUrl}
551
- figmaStyles={
552
- figmaIntegration.figmaStyles.status === "success"
553
- ? figmaIntegration.figmaStyles.styles || null
554
- : null
555
- }
556
- renderedStyles={figmaIntegration.renderedStyles}
557
- figmaLoading={figmaIntegration.isLoading}
558
- figmaError={figmaIntegration.errorMessage}
559
- onFetchFigma={figmaIntegration.fetchFigmaStyles}
560
- onRefreshRendered={figmaIntegration.extractRenderedStyles}
561
- onNavigateToComponent={(name) => {
562
- const target = fragments.find((s) => s.fragment.meta.name === name);
563
- if (target) handleSelectFragment(target.path);
564
- }}
565
- previewKey={uiState.previewKey}
566
- fragmentKey={`${activeFragmentPath}-${safeVariantIndex}`}
567
- />
568
- )}
569
- </Stack>
570
- ) : (
571
- <EmptyState style={{ height: "100%" }}>
572
- <EmptyState.Icon>
573
- <EmptyIcon style={{ width: "48px", height: "48px" }} />
574
- </EmptyState.Icon>
575
- <EmptyState.Title>No component selected</EmptyState.Title>
576
- <EmptyState.Description>Select a component from the sidebar</EmptyState.Description>
577
- </EmptyState>
578
- )}
579
- </Layout>
580
- </>
581
- );
582
- }