@fragments-sdk/cli 0.10.1 → 0.12.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 (223) hide show
  1. package/dist/ai-client-I6MDWNYA.js +21 -0
  2. package/dist/bin.js +292 -367
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
  5. package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
  6. package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
  7. package/dist/chunk-GVDSFQ4E.js.map +1 -0
  8. package/dist/chunk-JJ2VRTBU.js +626 -0
  9. package/dist/chunk-JJ2VRTBU.js.map +1 -0
  10. package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
  11. package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
  12. package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
  13. package/dist/chunk-OQKMEFOS.js.map +1 -0
  14. package/dist/chunk-SXTKFDCR.js +104 -0
  15. package/dist/chunk-SXTKFDCR.js.map +1 -0
  16. package/dist/chunk-T5OMVL7E.js +443 -0
  17. package/dist/chunk-T5OMVL7E.js.map +1 -0
  18. package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
  19. package/dist/chunk-TPWGL2XS.js.map +1 -0
  20. package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
  21. package/dist/chunk-WFS63PCW.js.map +1 -0
  22. package/dist/core/index.js +9 -1
  23. package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
  24. package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
  25. package/dist/index.js +7 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/init-ZSX3NRCZ.js +636 -0
  28. package/dist/init-ZSX3NRCZ.js.map +1 -0
  29. package/dist/mcp-bin.js +2 -2
  30. package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
  31. package/dist/scan-generate-SYU4PYZD.js +1115 -0
  32. package/dist/scan-generate-SYU4PYZD.js.map +1 -0
  33. package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
  34. package/dist/snapshot-XOISO2IS.js +139 -0
  35. package/dist/snapshot-XOISO2IS.js.map +1 -0
  36. package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
  37. package/dist/static-viewer-5GXH2MGE.js.map +1 -0
  38. package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
  39. package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
  40. package/dist/{viewer-DNMNC5VS.js → viewer-7ZEAFBVN.js} +80 -58
  41. package/dist/viewer-7ZEAFBVN.js.map +1 -0
  42. package/package.json +6 -14
  43. package/src/ai-client.ts +156 -0
  44. package/src/bin.ts +74 -2
  45. package/src/build.ts +95 -33
  46. package/src/commands/__tests__/drift-sync.test.ts +252 -0
  47. package/src/commands/__tests__/scan-generate.test.ts +497 -45
  48. package/src/commands/enhance.ts +11 -35
  49. package/src/commands/init.ts +296 -193
  50. package/src/commands/scan-generate.ts +740 -139
  51. package/src/commands/scan.ts +37 -32
  52. package/src/commands/setup.ts +143 -52
  53. package/src/commands/snapshot.ts +197 -0
  54. package/src/commands/sync.ts +357 -0
  55. package/src/commands/validate.ts +43 -1
  56. package/src/core/component-extractor.test.ts +282 -0
  57. package/src/core/component-extractor.ts +1030 -0
  58. package/src/core/discovery.ts +93 -7
  59. package/src/service/enhance/props-extractor.ts +235 -13
  60. package/src/validators.ts +236 -0
  61. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  62. package/src/viewer/server.ts +37 -22
  63. package/src/viewer/vite-plugin.ts +25 -9
  64. package/dist/chunk-5G3VZH43.js.map +0 -1
  65. package/dist/chunk-OQO55NKV.js.map +0 -1
  66. package/dist/chunk-WXSR2II7.js.map +0 -1
  67. package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
  68. package/dist/init-NDQXUWDU.js +0 -796
  69. package/dist/init-NDQXUWDU.js.map +0 -1
  70. package/dist/scan-generate-SJAN5MVI.js +0 -691
  71. package/dist/scan-generate-SJAN5MVI.js.map +0 -1
  72. package/dist/viewer-DNMNC5VS.js.map +0 -1
  73. package/src/ai.ts +0 -266
  74. package/src/commands/init-framework.ts +0 -414
  75. package/src/mcp/bin.ts +0 -36
  76. package/src/migrate/bin.ts +0 -114
  77. package/src/theme/index.ts +0 -77
  78. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  79. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  80. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  81. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  82. package/src/viewer/assets/fragments-logo.ts +0 -4
  83. package/src/viewer/assets/fragments_logo.png +0 -0
  84. package/src/viewer/bin.ts +0 -86
  85. package/src/viewer/cli/health.ts +0 -256
  86. package/src/viewer/cli/index.ts +0 -33
  87. package/src/viewer/cli/scan.ts +0 -124
  88. package/src/viewer/cli/utils.ts +0 -174
  89. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  90. package/src/viewer/components/ActionCapture.tsx +0 -172
  91. package/src/viewer/components/ActionsPanel.tsx +0 -332
  92. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  93. package/src/viewer/components/App.tsx +0 -582
  94. package/src/viewer/components/BottomPanel.tsx +0 -288
  95. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  96. package/src/viewer/components/CodePanel.tsx +0 -118
  97. package/src/viewer/components/CommandPalette.tsx +0 -392
  98. package/src/viewer/components/ComponentDocView.tsx +0 -164
  99. package/src/viewer/components/ComponentGraph.tsx +0 -380
  100. package/src/viewer/components/ComponentHeader.tsx +0 -88
  101. package/src/viewer/components/ContractPanel.tsx +0 -241
  102. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  103. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  104. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  105. package/src/viewer/components/FragmentEditor.tsx +0 -525
  106. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  107. package/src/viewer/components/HeaderSearch.tsx +0 -24
  108. package/src/viewer/components/HealthDashboard.tsx +0 -441
  109. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  110. package/src/viewer/components/Icons.tsx +0 -479
  111. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  112. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  113. package/src/viewer/components/IsolatedRender.tsx +0 -113
  114. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  115. package/src/viewer/components/LandingPage.tsx +0 -421
  116. package/src/viewer/components/Layout.tsx +0 -27
  117. package/src/viewer/components/LeftSidebar.tsx +0 -472
  118. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  119. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  120. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  121. package/src/viewer/components/PanelShell.tsx +0 -161
  122. package/src/viewer/components/PerformancePanel.tsx +0 -304
  123. package/src/viewer/components/PreviewArea.tsx +0 -472
  124. package/src/viewer/components/PreviewAside.tsx +0 -168
  125. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  126. package/src/viewer/components/PreviewPane.tsx +0 -149
  127. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  128. package/src/viewer/components/PropsEditor.tsx +0 -506
  129. package/src/viewer/components/PropsTable.tsx +0 -111
  130. package/src/viewer/components/RelationsSection.tsx +0 -88
  131. package/src/viewer/components/ResizablePanel.tsx +0 -271
  132. package/src/viewer/components/RightSidebar.tsx +0 -102
  133. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  134. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  135. package/src/viewer/components/Sidebar.tsx +0 -169
  136. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  137. package/src/viewer/components/ThemeProvider.tsx +0 -42
  138. package/src/viewer/components/Toast.tsx +0 -3
  139. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  140. package/src/viewer/components/TopToolbar.tsx +0 -159
  141. package/src/viewer/components/UsageSection.tsx +0 -95
  142. package/src/viewer/components/VariantMatrix.tsx +0 -388
  143. package/src/viewer/components/VariantRenderer.tsx +0 -131
  144. package/src/viewer/components/VariantTabs.tsx +0 -40
  145. package/src/viewer/components/ViewerHeader.tsx +0 -69
  146. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  147. package/src/viewer/components/ViewportSelector.tsx +0 -172
  148. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  149. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  150. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  151. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  152. package/src/viewer/components/viewer-utils.ts +0 -16
  153. package/src/viewer/composition-renderer.ts +0 -381
  154. package/src/viewer/constants/index.ts +0 -1
  155. package/src/viewer/constants/ui.ts +0 -166
  156. package/src/viewer/entry.tsx +0 -335
  157. package/src/viewer/hooks/index.ts +0 -2
  158. package/src/viewer/hooks/useA11yCache.ts +0 -383
  159. package/src/viewer/hooks/useA11yService.ts +0 -364
  160. package/src/viewer/hooks/useActions.ts +0 -138
  161. package/src/viewer/hooks/useAppState.ts +0 -147
  162. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  163. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  164. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  165. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  166. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  167. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  168. package/src/viewer/hooks/useUrlState.ts +0 -318
  169. package/src/viewer/hooks/useViewSettings.ts +0 -111
  170. package/src/viewer/index.html +0 -28
  171. package/src/viewer/intelligence/healthReport.ts +0 -505
  172. package/src/viewer/intelligence/styleDrift.ts +0 -340
  173. package/src/viewer/intelligence/usageScanner.ts +0 -309
  174. package/src/viewer/jsx-parser.ts +0 -486
  175. package/src/viewer/preview-frame-entry.tsx +0 -25
  176. package/src/viewer/preview-frame.html +0 -125
  177. package/src/viewer/public/favicon.ico +0 -0
  178. package/src/viewer/render-template.html +0 -68
  179. package/src/viewer/styles/globals.css +0 -278
  180. package/src/viewer/types/a11y.ts +0 -197
  181. package/src/viewer/utils/a11y-fixes.ts +0 -509
  182. package/src/viewer/utils/actionExport.ts +0 -372
  183. package/src/viewer/utils/colorSchemes.ts +0 -201
  184. package/src/viewer/utils/detectRelationships.ts +0 -256
  185. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  186. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  187. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  188. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  189. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  190. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  191. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  192. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  193. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  194. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  195. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  196. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  197. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  198. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -137
  199. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  200. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  201. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  202. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  203. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  204. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  205. package/src/viewer/vendor/shared/src/index.ts +0 -34
  206. package/src/viewer/vendor/shared/src/types.ts +0 -53
  207. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  208. package/src/viewer/webmcp/analytics.ts +0 -165
  209. package/src/viewer/webmcp/index.ts +0 -3
  210. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  211. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  212. package/src/viewer/webmcp/scan-utils.ts +0 -135
  213. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  214. package/src/viewer/webmcp/viewer-state.ts +0 -45
  215. /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
  216. /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
  217. /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
  218. /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
  219. /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
  220. /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
  221. /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
  222. /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
  223. /package/dist/{tokens-CE46OTMD.js.map → tokens-T6SIVUT5.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
- }