@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.
- package/dist/ai-client-I6MDWNYA.js +21 -0
- package/dist/bin.js +292 -367
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
- package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
- package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
- package/dist/chunk-GVDSFQ4E.js.map +1 -0
- package/dist/chunk-JJ2VRTBU.js +626 -0
- package/dist/chunk-JJ2VRTBU.js.map +1 -0
- package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
- package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
- package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
- package/dist/chunk-OQKMEFOS.js.map +1 -0
- package/dist/chunk-SXTKFDCR.js +104 -0
- package/dist/chunk-SXTKFDCR.js.map +1 -0
- package/dist/chunk-T5OMVL7E.js +443 -0
- package/dist/chunk-T5OMVL7E.js.map +1 -0
- package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
- package/dist/chunk-TPWGL2XS.js.map +1 -0
- package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
- package/dist/chunk-WFS63PCW.js.map +1 -0
- package/dist/core/index.js +9 -1
- package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
- package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/init-ZSX3NRCZ.js +636 -0
- package/dist/init-ZSX3NRCZ.js.map +1 -0
- package/dist/mcp-bin.js +2 -2
- package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
- package/dist/scan-generate-SYU4PYZD.js +1115 -0
- package/dist/scan-generate-SYU4PYZD.js.map +1 -0
- package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
- package/dist/snapshot-XOISO2IS.js +139 -0
- package/dist/snapshot-XOISO2IS.js.map +1 -0
- package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
- package/dist/static-viewer-5GXH2MGE.js.map +1 -0
- package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
- package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
- package/dist/{viewer-DNMNC5VS.js → viewer-7ZEAFBVN.js} +80 -58
- package/dist/viewer-7ZEAFBVN.js.map +1 -0
- package/package.json +6 -14
- package/src/ai-client.ts +156 -0
- package/src/bin.ts +74 -2
- package/src/build.ts +95 -33
- package/src/commands/__tests__/drift-sync.test.ts +252 -0
- package/src/commands/__tests__/scan-generate.test.ts +497 -45
- package/src/commands/enhance.ts +11 -35
- package/src/commands/init.ts +296 -193
- package/src/commands/scan-generate.ts +740 -139
- package/src/commands/scan.ts +37 -32
- package/src/commands/setup.ts +143 -52
- package/src/commands/snapshot.ts +197 -0
- package/src/commands/sync.ts +357 -0
- package/src/commands/validate.ts +43 -1
- package/src/core/component-extractor.test.ts +282 -0
- package/src/core/component-extractor.ts +1030 -0
- package/src/core/discovery.ts +93 -7
- package/src/service/enhance/props-extractor.ts +235 -13
- package/src/validators.ts +236 -0
- 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-5G3VZH43.js.map +0 -1
- package/dist/chunk-OQO55NKV.js.map +0 -1
- package/dist/chunk-WXSR2II7.js.map +0 -1
- package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
- package/dist/init-NDQXUWDU.js +0 -796
- package/dist/init-NDQXUWDU.js.map +0 -1
- package/dist/scan-generate-SJAN5MVI.js +0 -691
- package/dist/scan-generate-SJAN5MVI.js.map +0 -1
- package/dist/viewer-DNMNC5VS.js.map +0 -1
- package/src/ai.ts +0 -266
- package/src/commands/init-framework.ts +0 -414
- package/src/mcp/bin.ts +0 -36
- package/src/migrate/bin.ts +0 -114
- package/src/theme/index.ts +0 -77
- 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/assets/fragments_logo.png +0 -0
- package/src/viewer/bin.ts +0 -86
- package/src/viewer/cli/health.ts +0 -256
- package/src/viewer/cli/index.ts +0 -33
- package/src/viewer/cli/scan.ts +0 -124
- package/src/viewer/cli/utils.ts +0 -174
- 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 -137
- 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/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
- /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
- /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
- /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
- /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
- /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
- /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
- /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
- /package/dist/{tokens-CE46OTMD.js.map → tokens-T6SIVUT5.js.map} +0 -0
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IsolatedPreviewFrame - Parent-side iframe wrapper for CSS isolation
|
|
3
|
-
*
|
|
4
|
-
* Renders component previews inside an iframe for complete CSS isolation.
|
|
5
|
-
* This prevents CSS conflicts between:
|
|
6
|
-
* - The viewer shell and the user's component library
|
|
7
|
-
* - Global styles from user components
|
|
8
|
-
* - Theme variables with same names
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { memo, useRef, useEffect, useState, useCallback } from 'react';
|
|
12
|
-
import { usePreviewBridge, type ParentMessage } from '../hooks/usePreviewBridge.js';
|
|
13
|
-
|
|
14
|
-
/** Maximum number of retry attempts */
|
|
15
|
-
const MAX_RETRIES = 3;
|
|
16
|
-
|
|
17
|
-
export interface IsolatedPreviewFrameProps {
|
|
18
|
-
/** Fragment path (file path) to render */
|
|
19
|
-
fragmentPath: string;
|
|
20
|
-
/** Variant name to render */
|
|
21
|
-
variantName: string;
|
|
22
|
-
/** Props to pass to the variant render function */
|
|
23
|
-
props?: Record<string, unknown>;
|
|
24
|
-
/** Theme for the preview */
|
|
25
|
-
theme: 'light' | 'dark';
|
|
26
|
-
/** Width of the preview (CSS value) */
|
|
27
|
-
width?: number | string;
|
|
28
|
-
/** Height of the preview (CSS value) */
|
|
29
|
-
height?: number | string;
|
|
30
|
-
/** Minimum height of the preview */
|
|
31
|
-
minHeight?: number | string;
|
|
32
|
-
/** Background style for the preview container */
|
|
33
|
-
background?: React.CSSProperties;
|
|
34
|
-
/** Additional class name for the container */
|
|
35
|
-
className?: string;
|
|
36
|
-
/** Additional styles for the container */
|
|
37
|
-
style?: React.CSSProperties;
|
|
38
|
-
/** Called when content size is reported */
|
|
39
|
-
onContentSize?: (size: { width: number; height: number }) => void;
|
|
40
|
-
/** Called when an error occurs */
|
|
41
|
-
onError?: (error: string) => void;
|
|
42
|
-
/** Unique key to force re-render on variant changes */
|
|
43
|
-
previewKey?: string;
|
|
44
|
-
/** Show size indicator on hover */
|
|
45
|
-
showSizeIndicator?: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* IsolatedPreviewFrame renders a component preview inside an iframe
|
|
50
|
-
* for complete CSS isolation from the viewer shell.
|
|
51
|
-
*/
|
|
52
|
-
export const IsolatedPreviewFrame = memo(function IsolatedPreviewFrame({
|
|
53
|
-
fragmentPath,
|
|
54
|
-
variantName,
|
|
55
|
-
props,
|
|
56
|
-
theme,
|
|
57
|
-
width = '100%',
|
|
58
|
-
height = 'auto',
|
|
59
|
-
minHeight = 120,
|
|
60
|
-
background,
|
|
61
|
-
className = '',
|
|
62
|
-
style,
|
|
63
|
-
onContentSize,
|
|
64
|
-
onError,
|
|
65
|
-
previewKey,
|
|
66
|
-
showSizeIndicator = false,
|
|
67
|
-
}: IsolatedPreviewFrameProps) {
|
|
68
|
-
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
69
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
70
|
-
const [frameError, setFrameError] = useState<string | null>(null);
|
|
71
|
-
const [retryCount, setRetryCount] = useState(0);
|
|
72
|
-
const [iframeKey, setIframeKey] = useState(0);
|
|
73
|
-
const [hasRenderedOnce, setHasRenderedOnce] = useState(false);
|
|
74
|
-
const { isReady, isRendering, lastError, contentSize, render, setTheme, clearError } = usePreviewBridge(iframeRef);
|
|
75
|
-
const lastRenderRef = useRef<string>('');
|
|
76
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
77
|
-
|
|
78
|
-
// Build the preview URL
|
|
79
|
-
const previewUrl = '/fragments/preview/';
|
|
80
|
-
|
|
81
|
-
// Handle iframe load
|
|
82
|
-
const handleLoad = useCallback(() => {
|
|
83
|
-
setIsLoading(false);
|
|
84
|
-
setFrameError(null);
|
|
85
|
-
}, []);
|
|
86
|
-
|
|
87
|
-
// Handle iframe error
|
|
88
|
-
const handleError = useCallback(() => {
|
|
89
|
-
setIsLoading(false);
|
|
90
|
-
setFrameError('Failed to load preview frame');
|
|
91
|
-
onError?.('Failed to load preview frame');
|
|
92
|
-
}, [onError]);
|
|
93
|
-
|
|
94
|
-
// Handle retry
|
|
95
|
-
const handleRetry = useCallback(() => {
|
|
96
|
-
if (retryCount >= MAX_RETRIES) return;
|
|
97
|
-
|
|
98
|
-
setFrameError(null);
|
|
99
|
-
clearError();
|
|
100
|
-
setRetryCount(c => c + 1);
|
|
101
|
-
setIsLoading(true);
|
|
102
|
-
setHasRenderedOnce(false);
|
|
103
|
-
lastRenderRef.current = ''; // Force re-render
|
|
104
|
-
setIframeKey(k => k + 1); // Force iframe reload
|
|
105
|
-
}, [retryCount, clearError]);
|
|
106
|
-
|
|
107
|
-
// Send render request when ready or when render params change
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
if (!isReady) return;
|
|
110
|
-
|
|
111
|
-
// Create a render key to detect changes
|
|
112
|
-
const renderKey = `${fragmentPath}:${variantName}:${JSON.stringify(props)}:${previewKey || ''}`;
|
|
113
|
-
if (renderKey === lastRenderRef.current) return;
|
|
114
|
-
lastRenderRef.current = renderKey;
|
|
115
|
-
|
|
116
|
-
render(fragmentPath, variantName, props);
|
|
117
|
-
}, [isReady, fragmentPath, variantName, props, previewKey, render]);
|
|
118
|
-
|
|
119
|
-
// Sync theme when it changes
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
if (!isReady) return;
|
|
122
|
-
setTheme(theme);
|
|
123
|
-
}, [isReady, theme, setTheme]);
|
|
124
|
-
|
|
125
|
-
// Report content size changes
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
if (contentSize) {
|
|
128
|
-
setHasRenderedOnce(true);
|
|
129
|
-
onContentSize?.(contentSize);
|
|
130
|
-
}
|
|
131
|
-
}, [contentSize, onContentSize]);
|
|
132
|
-
|
|
133
|
-
// Report errors
|
|
134
|
-
useEffect(() => {
|
|
135
|
-
if (lastError) {
|
|
136
|
-
setFrameError(lastError);
|
|
137
|
-
onError?.(lastError);
|
|
138
|
-
}
|
|
139
|
-
}, [lastError, onError]);
|
|
140
|
-
|
|
141
|
-
// Calculate iframe dimensions
|
|
142
|
-
const frameWidth = typeof width === 'number' ? `${width}px` : width;
|
|
143
|
-
const frameHeight = typeof height === 'number' ? `${height}px` : height;
|
|
144
|
-
const frameMinHeight = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
|
|
145
|
-
|
|
146
|
-
// Keep first-load skeleton visible until the first successful render signal.
|
|
147
|
-
// After first render, preserve content visibility and show spinner overlays
|
|
148
|
-
// for later renders to avoid white/blank flashes.
|
|
149
|
-
const showSkeleton = !hasRenderedOnce && !frameError;
|
|
150
|
-
const showSpinner = hasRenderedOnce && !frameError && (isLoading || isRendering);
|
|
151
|
-
const showContent = !frameError && (hasRenderedOnce || (isReady && !isRendering));
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<div
|
|
155
|
-
style={{
|
|
156
|
-
position: 'relative',
|
|
157
|
-
width: frameWidth,
|
|
158
|
-
height: frameHeight,
|
|
159
|
-
minHeight: frameMinHeight,
|
|
160
|
-
...background,
|
|
161
|
-
...style,
|
|
162
|
-
}}
|
|
163
|
-
onMouseEnter={() => setIsHovered(true)}
|
|
164
|
-
onMouseLeave={() => setIsHovered(false)}
|
|
165
|
-
>
|
|
166
|
-
{/* Skeleton loading overlay (initial load) */}
|
|
167
|
-
{showSkeleton && (
|
|
168
|
-
<div
|
|
169
|
-
style={{
|
|
170
|
-
position: 'absolute',
|
|
171
|
-
inset: 0,
|
|
172
|
-
zIndex: 10,
|
|
173
|
-
background: 'var(--bg-primary, rgba(255, 255, 255, 0.95))',
|
|
174
|
-
}}
|
|
175
|
-
>
|
|
176
|
-
<PreviewSkeleton />
|
|
177
|
-
</div>
|
|
178
|
-
)}
|
|
179
|
-
|
|
180
|
-
{/* Spinner overlay (subsequent renders) */}
|
|
181
|
-
{showSpinner && (
|
|
182
|
-
<div
|
|
183
|
-
style={{
|
|
184
|
-
position: 'absolute',
|
|
185
|
-
inset: 0,
|
|
186
|
-
zIndex: 10,
|
|
187
|
-
display: 'flex',
|
|
188
|
-
alignItems: 'center',
|
|
189
|
-
justifyContent: 'center',
|
|
190
|
-
background: 'color-mix(in srgb, var(--bg-primary, white) 80%, transparent)',
|
|
191
|
-
}}
|
|
192
|
-
>
|
|
193
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--text-tertiary, #6b7280)', fontSize: 14 }}>
|
|
194
|
-
<LoadingSpinner />
|
|
195
|
-
<span>Rendering...</span>
|
|
196
|
-
</div>
|
|
197
|
-
</div>
|
|
198
|
-
)}
|
|
199
|
-
|
|
200
|
-
{/* Error overlay */}
|
|
201
|
-
{frameError && !isLoading && (
|
|
202
|
-
<div
|
|
203
|
-
style={{
|
|
204
|
-
position: 'absolute',
|
|
205
|
-
inset: 0,
|
|
206
|
-
zIndex: 10,
|
|
207
|
-
display: 'flex',
|
|
208
|
-
alignItems: 'center',
|
|
209
|
-
justifyContent: 'center',
|
|
210
|
-
padding: '16px',
|
|
211
|
-
background: 'rgba(254, 242, 242, 0.95)',
|
|
212
|
-
}}
|
|
213
|
-
>
|
|
214
|
-
<div
|
|
215
|
-
style={{
|
|
216
|
-
background: 'white',
|
|
217
|
-
border: '1px solid #fecaca',
|
|
218
|
-
borderRadius: 8,
|
|
219
|
-
padding: 16,
|
|
220
|
-
maxWidth: 400,
|
|
221
|
-
}}
|
|
222
|
-
>
|
|
223
|
-
<div style={{ color: '#dc2626', fontWeight: 500, marginBottom: 8 }}>
|
|
224
|
-
Preview Error
|
|
225
|
-
</div>
|
|
226
|
-
<div style={{ color: '#991b1b', fontSize: 13, marginBottom: retryCount < MAX_RETRIES ? 12 : 0 }}>
|
|
227
|
-
{frameError}
|
|
228
|
-
</div>
|
|
229
|
-
{retryCount < MAX_RETRIES && (
|
|
230
|
-
<button
|
|
231
|
-
onClick={handleRetry}
|
|
232
|
-
style={{
|
|
233
|
-
padding: '6px 12px',
|
|
234
|
-
fontSize: 13,
|
|
235
|
-
fontWeight: 500,
|
|
236
|
-
color: 'white',
|
|
237
|
-
background: '#dc2626',
|
|
238
|
-
border: 'none',
|
|
239
|
-
borderRadius: 6,
|
|
240
|
-
cursor: 'pointer',
|
|
241
|
-
}}
|
|
242
|
-
>
|
|
243
|
-
Retry ({MAX_RETRIES - retryCount} remaining)
|
|
244
|
-
</button>
|
|
245
|
-
)}
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
)}
|
|
249
|
-
|
|
250
|
-
{/* The iframe */}
|
|
251
|
-
<iframe
|
|
252
|
-
key={iframeKey}
|
|
253
|
-
ref={iframeRef}
|
|
254
|
-
src={previewUrl}
|
|
255
|
-
title={`Preview: ${variantName}`}
|
|
256
|
-
onLoad={handleLoad}
|
|
257
|
-
onError={handleError}
|
|
258
|
-
style={{
|
|
259
|
-
width: '100%',
|
|
260
|
-
height: '100%',
|
|
261
|
-
minHeight: frameMinHeight,
|
|
262
|
-
border: 'none',
|
|
263
|
-
display: 'block',
|
|
264
|
-
background: 'transparent',
|
|
265
|
-
transition: 'opacity 120ms ease',
|
|
266
|
-
opacity: showContent ? 1 : 0,
|
|
267
|
-
}}
|
|
268
|
-
// Security attributes
|
|
269
|
-
sandbox="allow-scripts allow-same-origin"
|
|
270
|
-
/>
|
|
271
|
-
|
|
272
|
-
{/* Size indicator */}
|
|
273
|
-
{showSizeIndicator && contentSize && (
|
|
274
|
-
<div
|
|
275
|
-
style={{
|
|
276
|
-
position: 'absolute',
|
|
277
|
-
bottom: '4px',
|
|
278
|
-
right: '4px',
|
|
279
|
-
padding: '2px 6px',
|
|
280
|
-
borderRadius: '4px',
|
|
281
|
-
fontFamily: 'monospace',
|
|
282
|
-
opacity: isHovered ? 1 : 0,
|
|
283
|
-
transition: 'opacity 150ms',
|
|
284
|
-
background: 'rgba(0, 0, 0, 0.5)',
|
|
285
|
-
color: 'white',
|
|
286
|
-
fontSize: 10,
|
|
287
|
-
}}
|
|
288
|
-
>
|
|
289
|
-
{contentSize.width} × {contentSize.height}px
|
|
290
|
-
</div>
|
|
291
|
-
)}
|
|
292
|
-
</div>
|
|
293
|
-
);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Skeleton loading placeholder
|
|
298
|
-
*/
|
|
299
|
-
function PreviewSkeleton() {
|
|
300
|
-
return (
|
|
301
|
-
<div style={{ padding: '16px' }}>
|
|
302
|
-
<style>{`@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }`}</style>
|
|
303
|
-
<div style={{ height: '16px', background: 'var(--bg-secondary, #e5e7eb)', borderRadius: '4px', width: '33%', marginBottom: '12px', animation: 'pulse 2s ease-in-out infinite' }} />
|
|
304
|
-
<div style={{ height: '32px', background: 'var(--bg-secondary, #e5e7eb)', borderRadius: '4px', marginBottom: '8px', animation: 'pulse 2s ease-in-out infinite' }} />
|
|
305
|
-
<div style={{ height: '16px', background: 'var(--bg-secondary, #e5e7eb)', borderRadius: '4px', width: '66%', animation: 'pulse 2s ease-in-out infinite' }} />
|
|
306
|
-
</div>
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Simple loading spinner component
|
|
312
|
-
*/
|
|
313
|
-
function LoadingSpinner() {
|
|
314
|
-
return (
|
|
315
|
-
<svg
|
|
316
|
-
style={{
|
|
317
|
-
width: 20,
|
|
318
|
-
height: 20,
|
|
319
|
-
animation: 'spin 0.8s linear infinite',
|
|
320
|
-
}}
|
|
321
|
-
viewBox="0 0 24 24"
|
|
322
|
-
fill="none"
|
|
323
|
-
>
|
|
324
|
-
<style>
|
|
325
|
-
{`@keyframes spin { to { transform: rotate(360deg); } }`}
|
|
326
|
-
</style>
|
|
327
|
-
<circle
|
|
328
|
-
cx="12"
|
|
329
|
-
cy="12"
|
|
330
|
-
r="10"
|
|
331
|
-
stroke="#e5e7eb"
|
|
332
|
-
strokeWidth="2"
|
|
333
|
-
fill="none"
|
|
334
|
-
/>
|
|
335
|
-
<path
|
|
336
|
-
d="M12 2a10 10 0 0 1 10 10"
|
|
337
|
-
stroke="#3b82f6"
|
|
338
|
-
strokeWidth="2"
|
|
339
|
-
strokeLinecap="round"
|
|
340
|
-
fill="none"
|
|
341
|
-
/>
|
|
342
|
-
</svg>
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
export default IsolatedPreviewFrame;
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { useMemo, useEffect, useState } from "react";
|
|
2
|
-
import type { FragmentDefinition } from "../../core/index.js";
|
|
3
|
-
import { VariantRenderer } from "./VariantRenderer.js";
|
|
4
|
-
import { type ZoomLevel } from "../constants/ui.js";
|
|
5
|
-
|
|
6
|
-
interface IsolatedRenderProps {
|
|
7
|
-
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Isolated render component for screenshot capture and standalone viewing.
|
|
12
|
-
* Renders a single variant with minimal UI for visual testing.
|
|
13
|
-
* URL params: ?isolated=true&component=Name&variant=VariantName&zoom=100&theme=light
|
|
14
|
-
*/
|
|
15
|
-
export function IsolatedRender({ fragments }: IsolatedRenderProps) {
|
|
16
|
-
const [ready, setReady] = useState(false);
|
|
17
|
-
|
|
18
|
-
// Parse query parameters
|
|
19
|
-
const params = useMemo(() => {
|
|
20
|
-
const searchParams = new URLSearchParams(window.location.search);
|
|
21
|
-
const zoomParam = parseInt(searchParams.get("zoom") || "100", 10);
|
|
22
|
-
return {
|
|
23
|
-
component: searchParams.get("component"),
|
|
24
|
-
variant: searchParams.get("variant"),
|
|
25
|
-
theme: searchParams.get("theme") || "light",
|
|
26
|
-
zoom: [50, 75, 100, 150, 200].includes(zoomParam) ? zoomParam as ZoomLevel : 100 as ZoomLevel,
|
|
27
|
-
};
|
|
28
|
-
}, []);
|
|
29
|
-
|
|
30
|
-
// Find the matching fragment and variant
|
|
31
|
-
const match = useMemo(() => {
|
|
32
|
-
if (!params.component || !params.variant) {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const fragment = fragments.find(
|
|
37
|
-
(s) => s.fragment.meta.name === params.component
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
if (!fragment) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const variant = fragment.fragment.variants.find(
|
|
45
|
-
(v) => v.name === params.variant
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
if (!variant) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return { fragment: fragment.fragment, variant };
|
|
53
|
-
}, [fragments, params]);
|
|
54
|
-
|
|
55
|
-
// Apply theme
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
document.documentElement.setAttribute("data-theme", params.theme);
|
|
58
|
-
|
|
59
|
-
// Signal ready after a short delay for fonts and styles to settle
|
|
60
|
-
const timer = setTimeout(() => {
|
|
61
|
-
setReady(true);
|
|
62
|
-
}, 50);
|
|
63
|
-
|
|
64
|
-
return () => clearTimeout(timer);
|
|
65
|
-
}, [params.theme]);
|
|
66
|
-
|
|
67
|
-
// Error state - missing component or variant
|
|
68
|
-
if (!params.component || !params.variant) {
|
|
69
|
-
return (
|
|
70
|
-
<div style={{ padding: '16px', color: '#ef4444', fontFamily: 'monospace', fontSize: '14px' }}>
|
|
71
|
-
Error: Missing component or variant parameter
|
|
72
|
-
<pre style={{ marginTop: '8px', fontSize: '12px' }}>
|
|
73
|
-
Required: ?component=ComponentName&variant=VariantName
|
|
74
|
-
</pre>
|
|
75
|
-
</div>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Error state - component/variant not found
|
|
80
|
-
if (!match) {
|
|
81
|
-
return (
|
|
82
|
-
<div style={{ padding: '16px', color: '#ef4444', fontFamily: 'monospace', fontSize: '14px' }}>
|
|
83
|
-
Error: Component "{params.component}" variant "{params.variant}" not
|
|
84
|
-
found
|
|
85
|
-
</div>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Render the variant in isolation
|
|
90
|
-
return (
|
|
91
|
-
<div
|
|
92
|
-
id="isolated-render"
|
|
93
|
-
data-ready={ready}
|
|
94
|
-
style={{
|
|
95
|
-
minHeight: '100vh',
|
|
96
|
-
padding: '32px',
|
|
97
|
-
display: 'flex',
|
|
98
|
-
alignItems: 'center',
|
|
99
|
-
justifyContent: 'center',
|
|
100
|
-
backgroundColor: 'var(--bg-primary)',
|
|
101
|
-
}}
|
|
102
|
-
>
|
|
103
|
-
<div
|
|
104
|
-
style={{
|
|
105
|
-
transform: `scale(${params.zoom / 100})`,
|
|
106
|
-
transformOrigin: 'center center',
|
|
107
|
-
}}
|
|
108
|
-
>
|
|
109
|
-
<VariantRenderer variant={match.variant} />
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Keyboard Shortcuts Help Modal
|
|
3
|
-
*
|
|
4
|
-
* Displays all available keyboard shortcuts in a modal overlay.
|
|
5
|
-
* Uses Fragments UI Dialog compound component.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Dialog, Stack, Text, Badge } from '@fragments-sdk/ui';
|
|
9
|
-
import { SHORTCUTS } from "../hooks/useKeyboardShortcuts.js";
|
|
10
|
-
|
|
11
|
-
interface KeyboardShortcutsHelpProps {
|
|
12
|
-
isOpen: boolean;
|
|
13
|
-
onClose: () => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function KeyboardShortcutsHelp({ isOpen, onClose }: KeyboardShortcutsHelpProps) {
|
|
17
|
-
return (
|
|
18
|
-
<Dialog open={isOpen} onOpenChange={(open: boolean) => { if (!open) onClose(); }}>
|
|
19
|
-
<Dialog.Content size="md">
|
|
20
|
-
<Dialog.Header>
|
|
21
|
-
<Dialog.Title>Keyboard Shortcuts</Dialog.Title>
|
|
22
|
-
<Dialog.Close />
|
|
23
|
-
</Dialog.Header>
|
|
24
|
-
<Dialog.Body>
|
|
25
|
-
<Stack direction="column" gap="sm">
|
|
26
|
-
{SHORTCUTS.map((shortcut, index) => (
|
|
27
|
-
<Stack key={index} direction="row" align="center" justify="between">
|
|
28
|
-
<Text size="sm" color="secondary">{shortcut.description}</Text>
|
|
29
|
-
<Stack direction="row" align="center" gap="xs">
|
|
30
|
-
{shortcut.keys.map((key, keyIndex) => (
|
|
31
|
-
<span key={keyIndex} style={{ display: 'flex', alignItems: 'center' }}>
|
|
32
|
-
{keyIndex > 0 && (
|
|
33
|
-
<Text size="xs" color="tertiary" style={{ margin: '0 4px' }}>or</Text>
|
|
34
|
-
)}
|
|
35
|
-
<Badge variant="default" size="sm">{key}</Badge>
|
|
36
|
-
</span>
|
|
37
|
-
))}
|
|
38
|
-
</Stack>
|
|
39
|
-
</Stack>
|
|
40
|
-
))}
|
|
41
|
-
</Stack>
|
|
42
|
-
</Dialog.Body>
|
|
43
|
-
<Dialog.Footer>
|
|
44
|
-
<Text size="xs" color="tertiary" style={{ textAlign: 'center', width: '100%' }}>
|
|
45
|
-
Press <Badge variant="default" size="sm">?</Badge> to toggle this help
|
|
46
|
-
</Text>
|
|
47
|
-
</Dialog.Footer>
|
|
48
|
-
</Dialog.Content>
|
|
49
|
-
</Dialog>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export default KeyboardShortcutsHelp;
|