@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,303 +0,0 @@
1
- /**
2
- * PreviewFrameHost - Iframe-side component that renders components in isolation
3
- *
4
- * This component runs inside the preview iframe and:
5
- * 1. Listens for render requests from the parent window
6
- * 2. Loads and renders the requested fragment variant
7
- * 3. Applies theme styling
8
- * 4. Reports render status back to parent
9
- */
10
-
11
- import { useState, useEffect, useRef } from 'react';
12
- import {
13
- usePreviewVariantRuntime,
14
- type FragmentVariant,
15
- } from '../../core/index.js';
16
- import { useFrameBridge } from '../hooks/usePreviewBridge.js';
17
-
18
- // Types for fragment data
19
- interface PreviewFragmentDefinition {
20
- meta: {
21
- name: string;
22
- description?: string;
23
- category?: string;
24
- };
25
- variants?: FragmentVariant[];
26
- }
27
-
28
- interface FragmentItem {
29
- path: string;
30
- fragment: PreviewFragmentDefinition;
31
- }
32
-
33
- // Cached fragments
34
- let cachedFragments: FragmentItem[] | null = null;
35
- let fragmentsPromise: Promise<FragmentItem[]> | null = null;
36
-
37
- /**
38
- * Load fragments from the virtual module
39
- */
40
- async function loadFragments(): Promise<FragmentItem[]> {
41
- if (cachedFragments) {
42
- return cachedFragments;
43
- }
44
-
45
- if (fragmentsPromise) {
46
- return fragmentsPromise;
47
- }
48
-
49
- fragmentsPromise = (async () => {
50
- try {
51
- // @ts-expect-error Virtual module
52
- const module = await import('virtual:fragments');
53
- if (module.fragmentsPromise) {
54
- cachedFragments = await module.fragmentsPromise;
55
- } else {
56
- cachedFragments = module.fragments || [];
57
- }
58
- return cachedFragments!;
59
- } catch (error) {
60
- console.error('[PreviewFrameHost] Failed to load fragments:', error);
61
- throw error;
62
- }
63
- })();
64
-
65
- return fragmentsPromise;
66
- }
67
-
68
- /**
69
- * Find a fragment by its path
70
- */
71
- function findFragmentByPath(fragments: FragmentItem[], path: string): FragmentItem | undefined {
72
- return fragments.find(s => s.path === path);
73
- }
74
-
75
- /**
76
- * Find a variant by name within a fragment
77
- */
78
- function findVariant(fragment: PreviewFragmentDefinition, variantName: string): FragmentVariant | undefined {
79
- return fragment.variants?.find(v => v.name === variantName);
80
- }
81
-
82
- type PreviewMode = 'centered' | 'full-bleed';
83
-
84
- function resolvePreviewMode(fragment: PreviewFragmentDefinition): PreviewMode {
85
- const name = fragment.meta.name.toLowerCase();
86
- const category = (fragment.meta.category || '').toLowerCase();
87
-
88
- if (name.includes('appshell') || name.includes('sidebar') || name.includes('header') || name.includes('layout')) {
89
- return 'full-bleed';
90
- }
91
-
92
- return 'centered';
93
- }
94
-
95
- /**
96
- * Error boundary for catching render errors
97
- */
98
- function ErrorDisplay({ message, stack }: { message: string; stack?: string }) {
99
- return (
100
- <div style={{ padding: '16px', color: '#dc2626', background: 'rgba(254, 242, 242, 0.95)', borderRadius: '8px', margin: '16px' }}>
101
- <div style={{ fontWeight: 500, marginBottom: 8 }}>Render Error</div>
102
- <div>{message}</div>
103
- {stack && (
104
- <pre style={{ marginTop: 8, fontSize: 11, opacity: 0.8 }}>
105
- {stack}
106
- </pre>
107
- )}
108
- </div>
109
- );
110
- }
111
-
112
- /**
113
- * Loading indicator
114
- */
115
- function LoadingIndicator() {
116
- return (
117
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px', gap: '8px', color: '#6b7280' }}>
118
- <div style={{ width: '16px', height: '16px', border: '2px solid #e5e7eb', borderTopColor: '#3b82f6', borderRadius: '50%', animation: 'spin 0.8s linear infinite' }} />
119
- <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
120
- <span>Loading component...</span>
121
- </div>
122
- );
123
- }
124
-
125
- /**
126
- * Variant renderer that handles async loaders
127
- */
128
- function VariantRenderer({
129
- variant,
130
- props,
131
- mode,
132
- onRendered,
133
- onError,
134
- }: {
135
- variant: FragmentVariant;
136
- props?: Record<string, unknown>;
137
- mode: PreviewMode;
138
- onRendered: (width: number, height: number) => void;
139
- onError: (message: string, stack?: string) => void;
140
- }) {
141
- const { content, isLoading, error } = usePreviewVariantRuntime({
142
- variant,
143
- loadedData: props,
144
- });
145
- const containerRef = useRef<HTMLDivElement>(null);
146
- const hasReported = useRef(false);
147
- const lastReportedError = useRef<string | null>(null);
148
-
149
- useEffect(() => {
150
- hasReported.current = false;
151
- }, [variant, props, mode]);
152
-
153
- useEffect(() => {
154
- if (!error) {
155
- lastReportedError.current = null;
156
- return;
157
- }
158
-
159
- const signature = `${error.message}:${error.stack ?? ''}`;
160
- if (lastReportedError.current === signature) {
161
- return;
162
- }
163
-
164
- lastReportedError.current = signature;
165
- onError(error.message, error.stack);
166
- }, [error, onError]);
167
-
168
- // Report rendered size after content renders
169
- useEffect(() => {
170
- if (!content || hasReported.current || isLoading || error) return;
171
-
172
- // Wait for next frame to ensure DOM has updated
173
- requestAnimationFrame(() => {
174
- if (containerRef.current && !hasReported.current) {
175
- const rect = containerRef.current.getBoundingClientRect();
176
- hasReported.current = true;
177
- onRendered(rect.width, rect.height);
178
- }
179
- });
180
- }, [content, error, isLoading, onRendered]);
181
-
182
- if (isLoading) {
183
- return <LoadingIndicator />;
184
- }
185
-
186
- if (error) {
187
- return <ErrorDisplay message={error.message} stack={error.stack} />;
188
- }
189
-
190
- return (
191
- <div
192
- ref={containerRef}
193
- style={{
194
- display: 'block',
195
- width: '100%',
196
- minHeight: '100%',
197
- transition: 'opacity 150ms',
198
- opacity: content ? 1 : 0,
199
- }}
200
- >
201
- {content}
202
- </div>
203
- );
204
- }
205
-
206
- /**
207
- * Main PreviewFrameHost component
208
- */
209
- export function PreviewFrameHost() {
210
- const { renderRequest, theme, notifyReady, notifyRendered, notifyError } = useFrameBridge();
211
- const [fragments, setFragments] = useState<FragmentItem[] | null>(null);
212
- const [loadError, setLoadError] = useState<string | null>(null);
213
- const [currentVariant, setCurrentVariant] = useState<FragmentVariant | null>(null);
214
- const [currentProps, setCurrentProps] = useState<Record<string, unknown> | undefined>(undefined);
215
- const [previewMode, setPreviewMode] = useState<PreviewMode>('centered');
216
-
217
- // Apply theme to document
218
- useEffect(() => {
219
- if (theme === 'dark') {
220
- document.documentElement.classList.add('dark');
221
- } else {
222
- document.documentElement.classList.remove('dark');
223
- }
224
- }, [theme]);
225
-
226
- useEffect(() => {
227
- document.body.setAttribute('data-preview-mode', previewMode);
228
- }, [previewMode]);
229
-
230
- // Load fragments on mount
231
- useEffect(() => {
232
- loadFragments()
233
- .then(segs => {
234
- setFragments(segs);
235
- notifyReady();
236
- })
237
- .catch(err => {
238
- const message = err instanceof Error ? err.message : 'Failed to load fragments';
239
- setLoadError(message);
240
- notifyError(message);
241
- });
242
- }, [notifyReady, notifyError]);
243
-
244
- // Handle render requests
245
- useEffect(() => {
246
- if (!renderRequest || !fragments) return;
247
-
248
- const { fragmentPath, variantName, props } = renderRequest;
249
-
250
- // Find fragment
251
- const fragmentItem = findFragmentByPath(fragments, fragmentPath);
252
- if (!fragmentItem) {
253
- notifyError(`Fragment not found: ${fragmentPath}`);
254
- setCurrentVariant(null);
255
- return;
256
- }
257
-
258
- // Find variant
259
- const variant = findVariant(fragmentItem.fragment, variantName);
260
- if (!variant) {
261
- notifyError(`Variant not found: ${variantName} in ${fragmentPath}`);
262
- setCurrentVariant(null);
263
- return;
264
- }
265
-
266
- setPreviewMode(resolvePreviewMode(fragmentItem.fragment));
267
- setCurrentVariant(variant);
268
- setCurrentProps(props);
269
- }, [renderRequest, fragments, notifyError]);
270
-
271
- // Show loading state
272
- if (!fragments && !loadError) {
273
- return <LoadingIndicator />;
274
- }
275
-
276
- // Show load error
277
- if (loadError) {
278
- return <ErrorDisplay message={loadError} />;
279
- }
280
-
281
- // Show waiting state
282
- if (!currentVariant) {
283
- return (
284
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px', gap: '8px', color: '#6b7280' }}>
285
- <span>Waiting for render request...</span>
286
- </div>
287
- );
288
- }
289
-
290
- // Render the variant
291
- return (
292
- <VariantRenderer
293
- key={`${renderRequest?.fragmentPath}-${renderRequest?.variantName}`}
294
- variant={currentVariant}
295
- props={currentProps}
296
- mode={previewMode}
297
- onRendered={notifyRendered}
298
- onError={notifyError}
299
- />
300
- );
301
- }
302
-
303
- export default PreviewFrameHost;
@@ -1,149 +0,0 @@
1
- import { useRef, useEffect, useState, type ReactNode } from 'react';
2
- import { createRoot, type Root } from 'react-dom/client';
3
-
4
- interface PreviewPaneProps {
5
- children: ReactNode;
6
- className?: string;
7
- style?: React.CSSProperties;
8
- /**
9
- * If true, copy component stylesheets into the shadow root.
10
- * This allows Tailwind and other CSS to work inside the preview.
11
- */
12
- includeComponentStyles?: boolean;
13
- }
14
-
15
- /**
16
- * Collect CSS that should be injected into the shadow root.
17
- * This includes component stylesheets and CSS custom properties.
18
- */
19
- function collectComponentStyles(): string[] {
20
- const styles: string[] = [];
21
-
22
- // Collect inline styles (often used by Vite for CSS-in-JS)
23
- document.querySelectorAll('style[data-vite-dev-id]').forEach((style) => {
24
- if (style.textContent) {
25
- styles.push(style.textContent);
26
- }
27
- });
28
-
29
- // Collect external stylesheets from the same origin
30
- document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
31
- const href = link.getAttribute('href');
32
- // Skip viewer-specific stylesheets
33
- if (href && !href.includes('viewer') && !href.includes('docs')) {
34
- // For linked stylesheets, we can't easily get the content
35
- // Instead, we'll create a link element in the shadow root
36
- }
37
- });
38
-
39
- return styles;
40
- }
41
-
42
-
43
- /**
44
- * PreviewPane renders children in a Shadow DOM for CSS isolation.
45
- * This prevents viewer styles from leaking into the component preview.
46
- */
47
- export function PreviewPane({ children, className, style, includeComponentStyles = true }: PreviewPaneProps) {
48
- const containerRef = useRef<HTMLDivElement>(null);
49
- const shadowRootRef = useRef<ShadowRoot | null>(null);
50
- const reactRootRef = useRef<Root | null>(null);
51
- const [mounted, setMounted] = useState(false);
52
-
53
- // Create shadow root on mount
54
- useEffect(() => {
55
- if (!containerRef.current) return;
56
-
57
- // Create shadow root if it doesn't exist
58
- if (!shadowRootRef.current) {
59
- shadowRootRef.current = containerRef.current.attachShadow({ mode: 'open' });
60
-
61
- // Create a container div inside shadow root
62
- const innerContainer = document.createElement('div');
63
- innerContainer.id = 'preview-root';
64
- innerContainer.setAttribute('data-preview-container', 'true');
65
- innerContainer.style.cssText = 'width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; padding: 24px;';
66
- shadowRootRef.current.appendChild(innerContainer);
67
-
68
- // Add base reset styles
69
- // Component library CSS variables are loaded via Vite's pipeline
70
- // when the library imports its own styles (e.g., import './styles/globals.scss')
71
- const baseStyle = document.createElement('style');
72
- baseStyle.textContent = `
73
- /* Reset and base styles */
74
- *, *::before, *::after {
75
- box-sizing: border-box;
76
- }
77
- :host {
78
- display: block;
79
- -webkit-font-smoothing: antialiased;
80
- }
81
- #preview-root {
82
- background-color: transparent;
83
- }
84
- `;
85
- shadowRootRef.current.appendChild(baseStyle);
86
-
87
- // Include component styles if enabled
88
- if (includeComponentStyles) {
89
- const componentStyles = collectComponentStyles();
90
- componentStyles.forEach((css) => {
91
- const styleEl = document.createElement('style');
92
- styleEl.textContent = css;
93
- shadowRootRef.current!.appendChild(styleEl);
94
- });
95
-
96
- // Also copy any linked stylesheets that might be from the component library
97
- document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
98
- const href = link.getAttribute('href');
99
- // Include component library stylesheets (exclude viewer-specific ones)
100
- if (href && !href.includes('/viewer/') && !href.includes('/docs/')) {
101
- const linkClone = document.createElement('link');
102
- linkClone.rel = 'stylesheet';
103
- linkClone.href = href;
104
- shadowRootRef.current!.appendChild(linkClone);
105
- }
106
- });
107
- }
108
-
109
- // Create React root
110
- reactRootRef.current = createRoot(innerContainer);
111
- setMounted(true);
112
- }
113
-
114
- return () => {
115
- // Cleanup React root on unmount
116
- if (reactRootRef.current) {
117
- reactRootRef.current.unmount();
118
- reactRootRef.current = null;
119
- }
120
- };
121
- }, [includeComponentStyles]);
122
-
123
- // Render children into shadow root
124
- useEffect(() => {
125
- if (mounted && reactRootRef.current) {
126
- reactRootRef.current.render(children);
127
- }
128
- }, [children, mounted]);
129
-
130
- return (
131
- <div
132
- ref={containerRef}
133
- style={{ minHeight: '120px', ...style }}
134
- data-preview-wrapper="true"
135
- />
136
- );
137
- }
138
-
139
- /**
140
- * SimplePreviewPane - A simpler preview without Shadow DOM isolation.
141
- * Use this when full isolation isn't needed.
142
- */
143
- export function SimplePreviewPane({ children, style }: PreviewPaneProps) {
144
- return (
145
- <div style={style}>
146
- {children}
147
- </div>
148
- );
149
- }
@@ -1,80 +0,0 @@
1
- import { useEffect, useCallback } from 'react';
2
- import { Button, Menu, Stack } from '@fragments-sdk/ui';
3
- import { ZOOM_LEVELS, type ZoomLevel } from '../constants/ui.js';
4
- import { ZoomIcon, ChevronDownIcon } from './Icons.js';
5
-
6
- // Re-export types for consumers
7
- export type { ZoomLevel };
8
-
9
- interface PreviewToolbarProps {
10
- zoom: ZoomLevel;
11
- onZoomChange: (zoom: ZoomLevel) => void;
12
- }
13
-
14
- export function PreviewToolbar({
15
- zoom,
16
- onZoomChange,
17
- }: PreviewToolbarProps) {
18
- // Keyboard shortcuts for zoom
19
- const handleKeyDown = useCallback((e: KeyboardEvent) => {
20
- // Don't handle if in input/textarea
21
- const target = e.target as HTMLElement;
22
- if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
23
- return;
24
- }
25
-
26
- if (e.key === '=' || e.key === '+') {
27
- e.preventDefault();
28
- const currentIndex = ZOOM_LEVELS.indexOf(zoom);
29
- if (currentIndex < ZOOM_LEVELS.length - 1) {
30
- onZoomChange(ZOOM_LEVELS[currentIndex + 1]);
31
- }
32
- } else if (e.key === '-') {
33
- e.preventDefault();
34
- const currentIndex = ZOOM_LEVELS.indexOf(zoom);
35
- if (currentIndex > 0) {
36
- onZoomChange(ZOOM_LEVELS[currentIndex - 1]);
37
- }
38
- } else if (e.key === '0') {
39
- e.preventDefault();
40
- onZoomChange(100);
41
- }
42
- }, [zoom, onZoomChange]);
43
-
44
- useEffect(() => {
45
- document.addEventListener('keydown', handleKeyDown);
46
- return () => document.removeEventListener('keydown', handleKeyDown);
47
- }, [handleKeyDown]);
48
-
49
- return (
50
- <Stack direction="row" gap="sm" align="center">
51
- <Menu>
52
- <Menu.Trigger asChild>
53
- <Button variant="ghost" size="sm" title="Zoom level (+/-/0)">
54
- <Stack direction="row" gap="xs" align="center">
55
- <span style={{ display: 'inline-flex', width: '14px', height: '14px' }}>
56
- <ZoomIcon />
57
- </span>
58
- <span>{zoom}%</span>
59
- <span style={{ display: 'inline-flex', width: '12px', height: '12px' }}>
60
- <ChevronDownIcon />
61
- </span>
62
- </Stack>
63
- </Button>
64
- </Menu.Trigger>
65
- <Menu.Content side="bottom" align="start">
66
- <Menu.RadioGroup
67
- value={String(zoom)}
68
- onValueChange={(value: string) => onZoomChange(Number(value) as ZoomLevel)}
69
- >
70
- {ZOOM_LEVELS.map((level) => (
71
- <Menu.RadioItem key={level} value={String(level)}>
72
- {level}%
73
- </Menu.RadioItem>
74
- ))}
75
- </Menu.RadioGroup>
76
- </Menu.Content>
77
- </Menu>
78
- </Stack>
79
- );
80
- }