@fragments-sdk/cli 0.10.1 → 0.11.1

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