@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,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
- }