@fragments-sdk/cli 0.10.0 → 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 (167) hide show
  1. package/dist/bin.js +26 -8
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ZDA3PLQ6.js → chunk-5G3VZH43.js} +2 -2
  4. package/dist/{chunk-566BNPQZ.js → chunk-HRFUSSZI.js} +25 -6
  5. package/dist/chunk-HRFUSSZI.js.map +1 -0
  6. package/dist/{chunk-CAMXG5HJ.js → chunk-ZM4ZQZWZ.js} +2 -2
  7. package/dist/{generate-BGKTKO6E.js → generate-FBHSXR3D.js} +2 -2
  8. package/dist/index.js +2 -2
  9. package/dist/{init-Q53R5Q2T.js → init-UFGK5TCN.js} +77 -6
  10. package/dist/init-UFGK5TCN.js.map +1 -0
  11. package/dist/{scan-OQU7M4GH.js → scan-CJF2DOQW.js} +3 -3
  12. package/dist/{scan-generate-T5QNUG7N.js → scan-generate-SJAN5MVI.js} +2 -2
  13. package/dist/snapshot-SV2JOFZH.js +139 -0
  14. package/dist/snapshot-SV2JOFZH.js.map +1 -0
  15. package/dist/{test-2CSOSS3B.js → test-Z5LVO724.js} +2 -2
  16. package/dist/{tokens-DXEGYTOJ.js → tokens-CE46OTMD.js} +2 -2
  17. package/dist/{viewer-DBEPYM3G.js → viewer-DLLJIMCK.js} +69 -47
  18. package/dist/viewer-DLLJIMCK.js.map +1 -0
  19. package/package.json +6 -14
  20. package/src/bin.ts +30 -0
  21. package/src/commands/init.ts +76 -1
  22. package/src/commands/snapshot.ts +197 -0
  23. package/src/core/loader.ts +38 -8
  24. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  25. package/src/viewer/server.ts +37 -22
  26. package/src/viewer/vite-plugin.ts +25 -9
  27. package/dist/chunk-566BNPQZ.js.map +0 -1
  28. package/dist/init-Q53R5Q2T.js.map +0 -1
  29. package/dist/viewer-DBEPYM3G.js.map +0 -1
  30. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  31. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  32. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  33. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  34. package/src/viewer/assets/fragments-logo.ts +0 -4
  35. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  36. package/src/viewer/components/ActionCapture.tsx +0 -172
  37. package/src/viewer/components/ActionsPanel.tsx +0 -332
  38. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  39. package/src/viewer/components/App.tsx +0 -582
  40. package/src/viewer/components/BottomPanel.tsx +0 -288
  41. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  42. package/src/viewer/components/CodePanel.tsx +0 -118
  43. package/src/viewer/components/CommandPalette.tsx +0 -392
  44. package/src/viewer/components/ComponentDocView.tsx +0 -164
  45. package/src/viewer/components/ComponentGraph.tsx +0 -380
  46. package/src/viewer/components/ComponentHeader.tsx +0 -88
  47. package/src/viewer/components/ContractPanel.tsx +0 -241
  48. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  49. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  50. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  51. package/src/viewer/components/FragmentEditor.tsx +0 -525
  52. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  53. package/src/viewer/components/HeaderSearch.tsx +0 -24
  54. package/src/viewer/components/HealthDashboard.tsx +0 -441
  55. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  56. package/src/viewer/components/Icons.tsx +0 -479
  57. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  58. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  59. package/src/viewer/components/IsolatedRender.tsx +0 -113
  60. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  61. package/src/viewer/components/LandingPage.tsx +0 -421
  62. package/src/viewer/components/Layout.tsx +0 -27
  63. package/src/viewer/components/LeftSidebar.tsx +0 -472
  64. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  65. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  66. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  67. package/src/viewer/components/PanelShell.tsx +0 -161
  68. package/src/viewer/components/PerformancePanel.tsx +0 -304
  69. package/src/viewer/components/PreviewArea.tsx +0 -472
  70. package/src/viewer/components/PreviewAside.tsx +0 -168
  71. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  72. package/src/viewer/components/PreviewPane.tsx +0 -149
  73. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  74. package/src/viewer/components/PropsEditor.tsx +0 -506
  75. package/src/viewer/components/PropsTable.tsx +0 -111
  76. package/src/viewer/components/RelationsSection.tsx +0 -88
  77. package/src/viewer/components/ResizablePanel.tsx +0 -271
  78. package/src/viewer/components/RightSidebar.tsx +0 -102
  79. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  80. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  81. package/src/viewer/components/Sidebar.tsx +0 -169
  82. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  83. package/src/viewer/components/ThemeProvider.tsx +0 -42
  84. package/src/viewer/components/Toast.tsx +0 -3
  85. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  86. package/src/viewer/components/TopToolbar.tsx +0 -159
  87. package/src/viewer/components/UsageSection.tsx +0 -95
  88. package/src/viewer/components/VariantMatrix.tsx +0 -388
  89. package/src/viewer/components/VariantRenderer.tsx +0 -131
  90. package/src/viewer/components/VariantTabs.tsx +0 -40
  91. package/src/viewer/components/ViewerHeader.tsx +0 -69
  92. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  93. package/src/viewer/components/ViewportSelector.tsx +0 -172
  94. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  95. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  96. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  97. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  98. package/src/viewer/components/viewer-utils.ts +0 -16
  99. package/src/viewer/composition-renderer.ts +0 -381
  100. package/src/viewer/constants/index.ts +0 -1
  101. package/src/viewer/constants/ui.ts +0 -166
  102. package/src/viewer/entry.tsx +0 -335
  103. package/src/viewer/hooks/index.ts +0 -2
  104. package/src/viewer/hooks/useA11yCache.ts +0 -383
  105. package/src/viewer/hooks/useA11yService.ts +0 -364
  106. package/src/viewer/hooks/useActions.ts +0 -138
  107. package/src/viewer/hooks/useAppState.ts +0 -147
  108. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  109. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  110. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  111. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  112. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  113. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  114. package/src/viewer/hooks/useUrlState.ts +0 -318
  115. package/src/viewer/hooks/useViewSettings.ts +0 -111
  116. package/src/viewer/index.html +0 -28
  117. package/src/viewer/intelligence/healthReport.ts +0 -505
  118. package/src/viewer/intelligence/styleDrift.ts +0 -340
  119. package/src/viewer/intelligence/usageScanner.ts +0 -309
  120. package/src/viewer/jsx-parser.ts +0 -486
  121. package/src/viewer/preview-frame-entry.tsx +0 -25
  122. package/src/viewer/preview-frame.html +0 -125
  123. package/src/viewer/public/favicon.ico +0 -0
  124. package/src/viewer/render-template.html +0 -68
  125. package/src/viewer/styles/globals.css +0 -278
  126. package/src/viewer/types/a11y.ts +0 -197
  127. package/src/viewer/utils/a11y-fixes.ts +0 -509
  128. package/src/viewer/utils/actionExport.ts +0 -372
  129. package/src/viewer/utils/colorSchemes.ts +0 -201
  130. package/src/viewer/utils/detectRelationships.ts +0 -256
  131. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  132. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  133. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  134. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  135. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  136. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  137. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  138. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  139. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  140. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  141. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  142. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  143. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  144. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -134
  145. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  146. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  147. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  148. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  149. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  150. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  151. package/src/viewer/vendor/shared/src/index.ts +0 -34
  152. package/src/viewer/vendor/shared/src/types.ts +0 -53
  153. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  154. package/src/viewer/webmcp/analytics.ts +0 -165
  155. package/src/viewer/webmcp/index.ts +0 -3
  156. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  157. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  158. package/src/viewer/webmcp/scan-utils.ts +0 -135
  159. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  160. package/src/viewer/webmcp/viewer-state.ts +0 -45
  161. /package/dist/{chunk-ZDA3PLQ6.js.map → chunk-5G3VZH43.js.map} +0 -0
  162. /package/dist/{chunk-CAMXG5HJ.js.map → chunk-ZM4ZQZWZ.js.map} +0 -0
  163. /package/dist/{generate-BGKTKO6E.js.map → generate-FBHSXR3D.js.map} +0 -0
  164. /package/dist/{scan-OQU7M4GH.js.map → scan-CJF2DOQW.js.map} +0 -0
  165. /package/dist/{scan-generate-T5QNUG7N.js.map → scan-generate-SJAN5MVI.js.map} +0 -0
  166. /package/dist/{test-2CSOSS3B.js.map → test-Z5LVO724.js.map} +0 -0
  167. /package/dist/{tokens-DXEGYTOJ.js.map → tokens-CE46OTMD.js.map} +0 -0
@@ -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
- }