@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.
- package/dist/bin.js +26 -8
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-ZDA3PLQ6.js → chunk-5G3VZH43.js} +2 -2
- package/dist/{chunk-566BNPQZ.js → chunk-HRFUSSZI.js} +25 -6
- package/dist/chunk-HRFUSSZI.js.map +1 -0
- package/dist/{chunk-CAMXG5HJ.js → chunk-ZM4ZQZWZ.js} +2 -2
- package/dist/{generate-BGKTKO6E.js → generate-FBHSXR3D.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{init-Q53R5Q2T.js → init-UFGK5TCN.js} +77 -6
- package/dist/init-UFGK5TCN.js.map +1 -0
- package/dist/{scan-OQU7M4GH.js → scan-CJF2DOQW.js} +3 -3
- package/dist/{scan-generate-T5QNUG7N.js → scan-generate-SJAN5MVI.js} +2 -2
- package/dist/snapshot-SV2JOFZH.js +139 -0
- package/dist/snapshot-SV2JOFZH.js.map +1 -0
- package/dist/{test-2CSOSS3B.js → test-Z5LVO724.js} +2 -2
- package/dist/{tokens-DXEGYTOJ.js → tokens-CE46OTMD.js} +2 -2
- package/dist/{viewer-DBEPYM3G.js → viewer-DLLJIMCK.js} +69 -47
- package/dist/viewer-DLLJIMCK.js.map +1 -0
- package/package.json +6 -14
- package/src/bin.ts +30 -0
- package/src/commands/init.ts +76 -1
- package/src/commands/snapshot.ts +197 -0
- package/src/core/loader.ts +38 -8
- package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
- package/src/viewer/server.ts +37 -22
- package/src/viewer/vite-plugin.ts +25 -9
- package/dist/chunk-566BNPQZ.js.map +0 -1
- package/dist/init-Q53R5Q2T.js.map +0 -1
- package/dist/viewer-DBEPYM3G.js.map +0 -1
- package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
- package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
- package/src/viewer/__tests__/render-utils.test.ts +0 -232
- package/src/viewer/__tests__/style-utils.test.ts +0 -404
- package/src/viewer/assets/fragments-logo.ts +0 -4
- package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
- package/src/viewer/components/ActionCapture.tsx +0 -172
- package/src/viewer/components/ActionsPanel.tsx +0 -332
- package/src/viewer/components/AllVariantsPreview.tsx +0 -78
- package/src/viewer/components/App.tsx +0 -582
- package/src/viewer/components/BottomPanel.tsx +0 -288
- package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
- package/src/viewer/components/CodePanel.tsx +0 -118
- package/src/viewer/components/CommandPalette.tsx +0 -392
- package/src/viewer/components/ComponentDocView.tsx +0 -164
- package/src/viewer/components/ComponentGraph.tsx +0 -380
- package/src/viewer/components/ComponentHeader.tsx +0 -88
- package/src/viewer/components/ContractPanel.tsx +0 -241
- package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
- package/src/viewer/components/ErrorBoundary.tsx +0 -97
- package/src/viewer/components/FigmaEmbed.tsx +0 -238
- package/src/viewer/components/FragmentEditor.tsx +0 -525
- package/src/viewer/components/FragmentRenderer.tsx +0 -61
- package/src/viewer/components/HeaderSearch.tsx +0 -24
- package/src/viewer/components/HealthDashboard.tsx +0 -441
- package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
- package/src/viewer/components/Icons.tsx +0 -479
- package/src/viewer/components/InteractionsPanel.tsx +0 -757
- package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
- package/src/viewer/components/IsolatedRender.tsx +0 -113
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
- package/src/viewer/components/LandingPage.tsx +0 -421
- package/src/viewer/components/Layout.tsx +0 -27
- package/src/viewer/components/LeftSidebar.tsx +0 -472
- package/src/viewer/components/LoadErrorMessage.tsx +0 -102
- package/src/viewer/components/MultiViewportPreview.tsx +0 -522
- package/src/viewer/components/NoVariantsMessage.tsx +0 -59
- package/src/viewer/components/PanelShell.tsx +0 -161
- package/src/viewer/components/PerformancePanel.tsx +0 -304
- package/src/viewer/components/PreviewArea.tsx +0 -472
- package/src/viewer/components/PreviewAside.tsx +0 -168
- package/src/viewer/components/PreviewFrameHost.tsx +0 -303
- package/src/viewer/components/PreviewPane.tsx +0 -149
- package/src/viewer/components/PreviewToolbar.tsx +0 -80
- package/src/viewer/components/PropsEditor.tsx +0 -506
- package/src/viewer/components/PropsTable.tsx +0 -111
- package/src/viewer/components/RelationsSection.tsx +0 -88
- package/src/viewer/components/ResizablePanel.tsx +0 -271
- package/src/viewer/components/RightSidebar.tsx +0 -102
- package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
- package/src/viewer/components/ScreenshotButton.tsx +0 -90
- package/src/viewer/components/Sidebar.tsx +0 -169
- package/src/viewer/components/SkeletonLoader.tsx +0 -161
- package/src/viewer/components/ThemeProvider.tsx +0 -42
- package/src/viewer/components/Toast.tsx +0 -3
- package/src/viewer/components/TokenStylePanel.tsx +0 -699
- package/src/viewer/components/TopToolbar.tsx +0 -159
- package/src/viewer/components/UsageSection.tsx +0 -95
- package/src/viewer/components/VariantMatrix.tsx +0 -388
- package/src/viewer/components/VariantRenderer.tsx +0 -131
- package/src/viewer/components/VariantTabs.tsx +0 -40
- package/src/viewer/components/ViewerHeader.tsx +0 -69
- package/src/viewer/components/ViewerStateSync.tsx +0 -52
- package/src/viewer/components/ViewportSelector.tsx +0 -172
- package/src/viewer/components/WebMCPDevTools.tsx +0 -503
- package/src/viewer/components/WebMCPIntegration.tsx +0 -47
- package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
- package/src/viewer/components/_future/CreatePage.tsx +0 -836
- package/src/viewer/components/viewer-utils.ts +0 -16
- package/src/viewer/composition-renderer.ts +0 -381
- package/src/viewer/constants/index.ts +0 -1
- package/src/viewer/constants/ui.ts +0 -166
- package/src/viewer/entry.tsx +0 -335
- package/src/viewer/hooks/index.ts +0 -2
- package/src/viewer/hooks/useA11yCache.ts +0 -383
- package/src/viewer/hooks/useA11yService.ts +0 -364
- package/src/viewer/hooks/useActions.ts +0 -138
- package/src/viewer/hooks/useAppState.ts +0 -147
- package/src/viewer/hooks/useCompiledFragments.ts +0 -42
- package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
- package/src/viewer/hooks/useHmrStatus.ts +0 -109
- package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
- package/src/viewer/hooks/usePreviewBridge.ts +0 -347
- package/src/viewer/hooks/useScrollSpy.ts +0 -78
- package/src/viewer/hooks/useUrlState.ts +0 -318
- package/src/viewer/hooks/useViewSettings.ts +0 -111
- package/src/viewer/index.html +0 -28
- package/src/viewer/intelligence/healthReport.ts +0 -505
- package/src/viewer/intelligence/styleDrift.ts +0 -340
- package/src/viewer/intelligence/usageScanner.ts +0 -309
- package/src/viewer/jsx-parser.ts +0 -486
- package/src/viewer/preview-frame-entry.tsx +0 -25
- package/src/viewer/preview-frame.html +0 -125
- package/src/viewer/public/favicon.ico +0 -0
- package/src/viewer/render-template.html +0 -68
- package/src/viewer/styles/globals.css +0 -278
- package/src/viewer/types/a11y.ts +0 -197
- package/src/viewer/utils/a11y-fixes.ts +0 -509
- package/src/viewer/utils/actionExport.ts +0 -372
- package/src/viewer/utils/colorSchemes.ts +0 -201
- package/src/viewer/utils/detectRelationships.ts +0 -256
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
- package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
- package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
- package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
- package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
- package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
- package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
- package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
- package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
- package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
- package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -134
- package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
- package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
- package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
- package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
- package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
- package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
- package/src/viewer/vendor/shared/src/index.ts +0 -34
- package/src/viewer/vendor/shared/src/types.ts +0 -53
- package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
- package/src/viewer/webmcp/analytics.ts +0 -165
- package/src/viewer/webmcp/index.ts +0 -3
- package/src/viewer/webmcp/posthog-bridge.ts +0 -39
- package/src/viewer/webmcp/runtime-tools.ts +0 -152
- package/src/viewer/webmcp/scan-utils.ts +0 -135
- package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
- package/src/viewer/webmcp/viewer-state.ts +0 -45
- /package/dist/{chunk-ZDA3PLQ6.js.map → chunk-5G3VZH43.js.map} +0 -0
- /package/dist/{chunk-CAMXG5HJ.js.map → chunk-ZM4ZQZWZ.js.map} +0 -0
- /package/dist/{generate-BGKTKO6E.js.map → generate-FBHSXR3D.js.map} +0 -0
- /package/dist/{scan-OQU7M4GH.js.map → scan-CJF2DOQW.js.map} +0 -0
- /package/dist/{scan-generate-T5QNUG7N.js.map → scan-generate-SJAN5MVI.js.map} +0 -0
- /package/dist/{test-2CSOSS3B.js.map → test-Z5LVO724.js.map} +0 -0
- /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
|
-
}
|