@fragments-sdk/cli 0.7.12 → 0.7.13
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/package.json +1 -1
- package/src/viewer/components/App.tsx +46 -103
- package/src/viewer/components/IsolatedRender.tsx +3 -7
- package/src/viewer/components/MultiViewportPreview.tsx +0 -17
- package/src/viewer/components/PreviewArea.tsx +5 -15
- package/src/viewer/components/PreviewToolbar.tsx +3 -103
- package/src/viewer/components/VariantMatrix.tsx +0 -11
- package/src/viewer/constants/ui.ts +0 -34
- package/src/viewer/hooks/useUrlState.ts +2 -14
- package/src/viewer/hooks/useViewSettings.ts +4 -17
package/package.json
CHANGED
|
@@ -15,7 +15,6 @@ import { useToast } from "./Toast.js";
|
|
|
15
15
|
|
|
16
16
|
// Toolbar
|
|
17
17
|
import { PreviewToolbar } from "./PreviewToolbar.js";
|
|
18
|
-
import { getBackgroundStyle } from "../constants/ui.js";
|
|
19
18
|
|
|
20
19
|
// Preview & Rendering
|
|
21
20
|
import { PreviewArea } from "./PreviewArea.js";
|
|
@@ -65,16 +64,14 @@ export function App({ fragments }: AppProps) {
|
|
|
65
64
|
// UI state (modals, panels, view modes)
|
|
66
65
|
const { state: uiState, actions: uiActions } = useAppState();
|
|
67
66
|
|
|
68
|
-
// View settings (zoom,
|
|
67
|
+
// View settings (zoom, viewport, theme)
|
|
69
68
|
const viewSettings = useViewSettings({
|
|
70
69
|
initialState: {
|
|
71
70
|
zoom: urlState.zoom as any,
|
|
72
|
-
background: urlState.background as any,
|
|
73
71
|
viewport: urlState.viewport as any,
|
|
74
72
|
customSize: { width: urlState.customWidth, height: urlState.customHeight },
|
|
75
73
|
},
|
|
76
74
|
onZoomChange: (zoom) => setUrlViewSettings({ zoom }),
|
|
77
|
-
onBackgroundChange: (bg) => setUrlViewSettings({ background: bg }),
|
|
78
75
|
onViewportChange: (vp, size) => setUrlViewSettings({
|
|
79
76
|
viewport: vp,
|
|
80
77
|
customWidth: size?.width,
|
|
@@ -386,6 +383,7 @@ export function App({ fragments }: AppProps) {
|
|
|
386
383
|
activeFragment && !uiState.showHealthDashboard ? (
|
|
387
384
|
<TopToolbar
|
|
388
385
|
fragment={activeFragment}
|
|
386
|
+
viewSettings={viewSettings}
|
|
389
387
|
uiState={uiState}
|
|
390
388
|
uiActions={uiActions}
|
|
391
389
|
figmaUrl={figmaUrl}
|
|
@@ -450,19 +448,6 @@ export function App({ fragments }: AppProps) {
|
|
|
450
448
|
<div id="preview-layout" style={{ display: 'flex', height: '100%', flexDirection: panelDock === "bottom" ? 'column' : 'row' }}>
|
|
451
449
|
{/* Main Content Area */}
|
|
452
450
|
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, minHeight: 0 }}>
|
|
453
|
-
<PreviewControlsBar
|
|
454
|
-
zoom={viewSettings.zoom}
|
|
455
|
-
background={viewSettings.background}
|
|
456
|
-
onZoomChange={viewSettings.setZoom}
|
|
457
|
-
onBackgroundChange={viewSettings.setBackground}
|
|
458
|
-
showMatrixView={uiState.showMatrixView}
|
|
459
|
-
showMultiViewport={uiState.showMultiViewport}
|
|
460
|
-
panelOpen={uiState.panelOpen}
|
|
461
|
-
onToggleMatrix={() => uiActions.setMatrixView(!uiState.showMatrixView)}
|
|
462
|
-
onToggleMultiViewport={() => uiActions.setMultiViewport(!uiState.showMultiViewport)}
|
|
463
|
-
onTogglePanel={uiActions.togglePanel}
|
|
464
|
-
/>
|
|
465
|
-
|
|
466
451
|
{/* Preview Area */}
|
|
467
452
|
<div
|
|
468
453
|
id="preview-canvas"
|
|
@@ -470,7 +455,6 @@ export function App({ fragments }: AppProps) {
|
|
|
470
455
|
flex: 1,
|
|
471
456
|
overflow: 'auto',
|
|
472
457
|
position: 'relative',
|
|
473
|
-
...(uiState.showMatrixView ? {} : getBackgroundStyle(viewSettings.background)),
|
|
474
458
|
}}
|
|
475
459
|
>
|
|
476
460
|
{variantCount === 0 ? (
|
|
@@ -482,7 +466,6 @@ export function App({ fragments }: AppProps) {
|
|
|
482
466
|
variants={variants}
|
|
483
467
|
focusedVariantIndex={safeVariantIndex}
|
|
484
468
|
zoom={viewSettings.zoom}
|
|
485
|
-
background={viewSettings.background}
|
|
486
469
|
viewport={viewSettings.viewport}
|
|
487
470
|
customSize={viewSettings.customSize}
|
|
488
471
|
previewTheme={resolvedTheme}
|
|
@@ -500,7 +483,6 @@ export function App({ fragments }: AppProps) {
|
|
|
500
483
|
variant={activeVariant}
|
|
501
484
|
variants={variants}
|
|
502
485
|
zoom={viewSettings.zoom}
|
|
503
|
-
background={viewSettings.background}
|
|
504
486
|
viewport={viewSettings.viewport}
|
|
505
487
|
customSize={viewSettings.customSize}
|
|
506
488
|
previewTheme={resolvedTheme}
|
|
@@ -566,6 +548,7 @@ export function App({ fragments }: AppProps) {
|
|
|
566
548
|
// Top Toolbar Component
|
|
567
549
|
interface TopToolbarProps {
|
|
568
550
|
fragment: { path: string; fragment: FragmentDefinition };
|
|
551
|
+
viewSettings: ReturnType<typeof useViewSettings>;
|
|
569
552
|
uiState: ReturnType<typeof useAppState>['state'];
|
|
570
553
|
uiActions: ReturnType<typeof useAppState>['actions'];
|
|
571
554
|
figmaUrl?: string;
|
|
@@ -764,6 +747,7 @@ function PreviewAside({
|
|
|
764
747
|
|
|
765
748
|
function TopToolbar({
|
|
766
749
|
fragment,
|
|
750
|
+
viewSettings,
|
|
767
751
|
uiState,
|
|
768
752
|
uiActions,
|
|
769
753
|
figmaUrl,
|
|
@@ -785,6 +769,48 @@ function TopToolbar({
|
|
|
785
769
|
<HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
|
|
786
770
|
<Header.Spacer />
|
|
787
771
|
<Header.Actions>
|
|
772
|
+
<PreviewToolbar
|
|
773
|
+
zoom={viewSettings.zoom}
|
|
774
|
+
onZoomChange={viewSettings.setZoom}
|
|
775
|
+
/>
|
|
776
|
+
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
777
|
+
<Tooltip content={uiState.showMatrixView ? "Disable matrix view" : "Enable matrix view"}>
|
|
778
|
+
<Button
|
|
779
|
+
variant="ghost"
|
|
780
|
+
size="sm"
|
|
781
|
+
aria-pressed={uiState.showMatrixView}
|
|
782
|
+
aria-label="Toggle matrix view"
|
|
783
|
+
onClick={() => uiActions.setMatrixView(!uiState.showMatrixView)}
|
|
784
|
+
style={uiState.showMatrixView ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}}
|
|
785
|
+
>
|
|
786
|
+
<GridFour size={16} />
|
|
787
|
+
</Button>
|
|
788
|
+
</Tooltip>
|
|
789
|
+
<Tooltip content={uiState.showMultiViewport ? "Disable responsive view" : "Enable responsive view"}>
|
|
790
|
+
<Button
|
|
791
|
+
variant="ghost"
|
|
792
|
+
size="sm"
|
|
793
|
+
aria-pressed={uiState.showMultiViewport}
|
|
794
|
+
aria-label="Toggle responsive view"
|
|
795
|
+
onClick={() => uiActions.setMultiViewport(!uiState.showMultiViewport)}
|
|
796
|
+
style={uiState.showMultiViewport ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}}
|
|
797
|
+
>
|
|
798
|
+
<DeviceMobile size={16} />
|
|
799
|
+
</Button>
|
|
800
|
+
</Tooltip>
|
|
801
|
+
<Tooltip content={uiState.panelOpen ? "Hide addons panel" : "Show addons panel"}>
|
|
802
|
+
<Button
|
|
803
|
+
variant="ghost"
|
|
804
|
+
size="sm"
|
|
805
|
+
aria-pressed={uiState.panelOpen}
|
|
806
|
+
aria-label="Toggle addons panel"
|
|
807
|
+
onClick={uiActions.togglePanel}
|
|
808
|
+
style={uiState.panelOpen ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}}
|
|
809
|
+
>
|
|
810
|
+
<Rows size={16} />
|
|
811
|
+
</Button>
|
|
812
|
+
</Tooltip>
|
|
813
|
+
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
788
814
|
{figmaUrl && (
|
|
789
815
|
<>
|
|
790
816
|
<Tooltip content={uiState.showComparison ? "Hide Figma comparison" : "Compare with Figma design"}>
|
|
@@ -851,93 +877,12 @@ function getVariantSectionId(componentName: string, variantName: string): string
|
|
|
851
877
|
return `preview-${normalizeAnchorSegment(componentName)}-${normalizeAnchorSegment(variantName)}`;
|
|
852
878
|
}
|
|
853
879
|
|
|
854
|
-
interface PreviewControlsBarProps {
|
|
855
|
-
zoom: ReturnType<typeof useViewSettings>["zoom"];
|
|
856
|
-
background: ReturnType<typeof useViewSettings>["background"];
|
|
857
|
-
onZoomChange: ReturnType<typeof useViewSettings>["setZoom"];
|
|
858
|
-
onBackgroundChange: ReturnType<typeof useViewSettings>["setBackground"];
|
|
859
|
-
showMatrixView: boolean;
|
|
860
|
-
showMultiViewport: boolean;
|
|
861
|
-
panelOpen: boolean;
|
|
862
|
-
onToggleMatrix: () => void;
|
|
863
|
-
onToggleMultiViewport: () => void;
|
|
864
|
-
onTogglePanel: () => void;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function PreviewControlsBar({
|
|
868
|
-
zoom,
|
|
869
|
-
background,
|
|
870
|
-
onZoomChange,
|
|
871
|
-
onBackgroundChange,
|
|
872
|
-
showMatrixView,
|
|
873
|
-
showMultiViewport,
|
|
874
|
-
panelOpen,
|
|
875
|
-
onToggleMatrix,
|
|
876
|
-
onToggleMultiViewport,
|
|
877
|
-
onTogglePanel,
|
|
878
|
-
}: PreviewControlsBarProps) {
|
|
879
|
-
const toggleButtonStyle = (active: boolean): CSSProperties => (
|
|
880
|
-
active ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}
|
|
881
|
-
);
|
|
882
|
-
|
|
883
|
-
return (
|
|
884
|
-
<div style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', backgroundColor: 'var(--bg-primary)', flexShrink: 0 }}>
|
|
885
|
-
<Stack direction="row" gap="sm" align="center" justify="end">
|
|
886
|
-
<PreviewToolbar
|
|
887
|
-
zoom={zoom}
|
|
888
|
-
background={background}
|
|
889
|
-
onZoomChange={onZoomChange}
|
|
890
|
-
onBackgroundChange={onBackgroundChange}
|
|
891
|
-
/>
|
|
892
|
-
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
893
|
-
<Tooltip content={showMatrixView ? "Disable matrix view" : "Enable matrix view"}>
|
|
894
|
-
<Button
|
|
895
|
-
variant="ghost"
|
|
896
|
-
size="sm"
|
|
897
|
-
aria-pressed={showMatrixView}
|
|
898
|
-
aria-label="Toggle matrix view"
|
|
899
|
-
onClick={onToggleMatrix}
|
|
900
|
-
style={toggleButtonStyle(showMatrixView)}
|
|
901
|
-
>
|
|
902
|
-
<GridFour size={16} />
|
|
903
|
-
</Button>
|
|
904
|
-
</Tooltip>
|
|
905
|
-
<Tooltip content={showMultiViewport ? "Disable responsive view" : "Enable responsive view"}>
|
|
906
|
-
<Button
|
|
907
|
-
variant="ghost"
|
|
908
|
-
size="sm"
|
|
909
|
-
aria-pressed={showMultiViewport}
|
|
910
|
-
aria-label="Toggle responsive view"
|
|
911
|
-
onClick={onToggleMultiViewport}
|
|
912
|
-
style={toggleButtonStyle(showMultiViewport)}
|
|
913
|
-
>
|
|
914
|
-
<DeviceMobile size={16} />
|
|
915
|
-
</Button>
|
|
916
|
-
</Tooltip>
|
|
917
|
-
<Tooltip content={panelOpen ? "Hide addons panel" : "Show addons panel"}>
|
|
918
|
-
<Button
|
|
919
|
-
variant="ghost"
|
|
920
|
-
size="sm"
|
|
921
|
-
aria-pressed={panelOpen}
|
|
922
|
-
aria-label="Toggle addons panel"
|
|
923
|
-
onClick={onTogglePanel}
|
|
924
|
-
style={toggleButtonStyle(panelOpen)}
|
|
925
|
-
>
|
|
926
|
-
<Rows size={16} />
|
|
927
|
-
</Button>
|
|
928
|
-
</Tooltip>
|
|
929
|
-
</Stack>
|
|
930
|
-
</div>
|
|
931
|
-
);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
880
|
interface AllVariantsPreviewProps {
|
|
935
881
|
componentName: string;
|
|
936
882
|
fragmentPath: string;
|
|
937
883
|
variants: FragmentVariant[];
|
|
938
884
|
focusedVariantIndex: number;
|
|
939
885
|
zoom: ReturnType<typeof useViewSettings>["zoom"];
|
|
940
|
-
background: ReturnType<typeof useViewSettings>["background"];
|
|
941
886
|
viewport: ReturnType<typeof useViewSettings>["viewport"];
|
|
942
887
|
customSize: ReturnType<typeof useViewSettings>["customSize"];
|
|
943
888
|
previewTheme: ReturnType<typeof useTheme>["resolvedTheme"];
|
|
@@ -955,7 +900,6 @@ function AllVariantsPreview({
|
|
|
955
900
|
variants,
|
|
956
901
|
focusedVariantIndex,
|
|
957
902
|
zoom,
|
|
958
|
-
background,
|
|
959
903
|
viewport,
|
|
960
904
|
customSize,
|
|
961
905
|
previewTheme,
|
|
@@ -994,7 +938,6 @@ function AllVariantsPreview({
|
|
|
994
938
|
variant={variant}
|
|
995
939
|
variants={variants}
|
|
996
940
|
zoom={zoom}
|
|
997
|
-
background={background}
|
|
998
941
|
viewport={viewport}
|
|
999
942
|
customSize={customSize}
|
|
1000
943
|
previewTheme={previewTheme}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useMemo, useEffect, useState } from "react";
|
|
2
2
|
import type { FragmentDefinition } from "../../core/index.js";
|
|
3
3
|
import { VariantRenderer } from "./VariantRenderer.js";
|
|
4
|
-
import {
|
|
4
|
+
import { type ZoomLevel } from "../constants/ui.js";
|
|
5
5
|
|
|
6
6
|
interface IsolatedRenderProps {
|
|
7
7
|
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
@@ -10,7 +10,7 @@ interface IsolatedRenderProps {
|
|
|
10
10
|
/**
|
|
11
11
|
* Isolated render component for screenshot capture and standalone viewing.
|
|
12
12
|
* Renders a single variant with minimal UI for visual testing.
|
|
13
|
-
* URL params: ?isolated=true&component=Name&variant=VariantName&zoom=100&
|
|
13
|
+
* URL params: ?isolated=true&component=Name&variant=VariantName&zoom=100&theme=light
|
|
14
14
|
*/
|
|
15
15
|
export function IsolatedRender({ fragments }: IsolatedRenderProps) {
|
|
16
16
|
const [ready, setReady] = useState(false);
|
|
@@ -19,15 +19,11 @@ export function IsolatedRender({ fragments }: IsolatedRenderProps) {
|
|
|
19
19
|
const params = useMemo(() => {
|
|
20
20
|
const searchParams = new URLSearchParams(window.location.search);
|
|
21
21
|
const zoomParam = parseInt(searchParams.get("zoom") || "100", 10);
|
|
22
|
-
const bgParam = searchParams.get("bg") || "white";
|
|
23
22
|
return {
|
|
24
23
|
component: searchParams.get("component"),
|
|
25
24
|
variant: searchParams.get("variant"),
|
|
26
25
|
theme: searchParams.get("theme") || "light",
|
|
27
26
|
zoom: [50, 75, 100, 150, 200].includes(zoomParam) ? zoomParam as ZoomLevel : 100 as ZoomLevel,
|
|
28
|
-
background: ["white", "black", "checkerboard", "transparent"].includes(bgParam)
|
|
29
|
-
? bgParam as BackgroundOption
|
|
30
|
-
: "white" as BackgroundOption,
|
|
31
27
|
};
|
|
32
28
|
}, []);
|
|
33
29
|
|
|
@@ -101,7 +97,7 @@ export function IsolatedRender({ fragments }: IsolatedRenderProps) {
|
|
|
101
97
|
display: 'flex',
|
|
102
98
|
alignItems: 'center',
|
|
103
99
|
justifyContent: 'center',
|
|
104
|
-
|
|
100
|
+
backgroundColor: 'var(--bg-primary)',
|
|
105
101
|
}}
|
|
106
102
|
>
|
|
107
103
|
<div
|
|
@@ -10,7 +10,6 @@ import { useState, type ReactNode } from "react";
|
|
|
10
10
|
import { ErrorBoundary } from "./ErrorBoundary.js";
|
|
11
11
|
import { IsolatedPreviewFrame } from "./IsolatedPreviewFrame.js";
|
|
12
12
|
import { ChevronDownIcon } from "./Icons.js";
|
|
13
|
-
import { getBackgroundStyle, type BackgroundOption } from "../constants/ui.js";
|
|
14
13
|
|
|
15
14
|
interface ViewportConfig {
|
|
16
15
|
name: string;
|
|
@@ -50,8 +49,6 @@ interface MultiViewportPreviewProps {
|
|
|
50
49
|
renderContent: () => ReactNode;
|
|
51
50
|
/** Preview theme */
|
|
52
51
|
previewTheme: "light" | "dark";
|
|
53
|
-
/** Background option for preview */
|
|
54
|
-
background: BackgroundOption;
|
|
55
52
|
/** Base zoom level (used for scaling if needed) */
|
|
56
53
|
zoom: number;
|
|
57
54
|
/** Whether to use iframe isolation */
|
|
@@ -64,7 +61,6 @@ export function MultiViewportPreview({
|
|
|
64
61
|
variantName,
|
|
65
62
|
renderContent,
|
|
66
63
|
previewTheme,
|
|
67
|
-
background,
|
|
68
64
|
zoom,
|
|
69
65
|
useIframeIsolation = true,
|
|
70
66
|
}: MultiViewportPreviewProps) {
|
|
@@ -207,7 +203,6 @@ export function MultiViewportPreview({
|
|
|
207
203
|
variantName={variantName}
|
|
208
204
|
renderContent={renderContent}
|
|
209
205
|
previewTheme={previewTheme}
|
|
210
|
-
background={background}
|
|
211
206
|
useIframeIsolation={useIframeIsolation}
|
|
212
207
|
/>
|
|
213
208
|
))}
|
|
@@ -224,7 +219,6 @@ interface ViewportPanelProps {
|
|
|
224
219
|
variantName: string;
|
|
225
220
|
renderContent: () => ReactNode;
|
|
226
221
|
previewTheme: "light" | "dark";
|
|
227
|
-
background: BackgroundOption;
|
|
228
222
|
useIframeIsolation: boolean;
|
|
229
223
|
}
|
|
230
224
|
|
|
@@ -235,7 +229,6 @@ function ViewportPanel({
|
|
|
235
229
|
variantName,
|
|
236
230
|
renderContent,
|
|
237
231
|
previewTheme,
|
|
238
|
-
background,
|
|
239
232
|
useIframeIsolation,
|
|
240
233
|
}: ViewportPanelProps) {
|
|
241
234
|
if (viewport.type === "desktop") {
|
|
@@ -245,7 +238,6 @@ function ViewportPanel({
|
|
|
245
238
|
height={viewport.height}
|
|
246
239
|
label={viewport.name}
|
|
247
240
|
previewTheme={previewTheme}
|
|
248
|
-
background={background}
|
|
249
241
|
componentName={componentName}
|
|
250
242
|
fragmentPath={fragmentPath}
|
|
251
243
|
variantName={variantName}
|
|
@@ -262,7 +254,6 @@ function ViewportPanel({
|
|
|
262
254
|
height={viewport.height}
|
|
263
255
|
label={viewport.name}
|
|
264
256
|
previewTheme={previewTheme}
|
|
265
|
-
background={background}
|
|
266
257
|
componentName={componentName}
|
|
267
258
|
fragmentPath={fragmentPath}
|
|
268
259
|
variantName={variantName}
|
|
@@ -278,7 +269,6 @@ interface DeviceMockupProps {
|
|
|
278
269
|
height: number;
|
|
279
270
|
label: string;
|
|
280
271
|
previewTheme: "light" | "dark";
|
|
281
|
-
background: BackgroundOption;
|
|
282
272
|
componentName: string;
|
|
283
273
|
fragmentPath: string;
|
|
284
274
|
variantName: string;
|
|
@@ -292,7 +282,6 @@ function DeviceMockup({
|
|
|
292
282
|
height,
|
|
293
283
|
label,
|
|
294
284
|
previewTheme,
|
|
295
|
-
background,
|
|
296
285
|
componentName,
|
|
297
286
|
fragmentPath,
|
|
298
287
|
variantName,
|
|
@@ -302,7 +291,6 @@ function DeviceMockup({
|
|
|
302
291
|
const isMobile = type === "mobile";
|
|
303
292
|
const frameWidth = width + 24; // Add bezel width
|
|
304
293
|
const screenHeight = height;
|
|
305
|
-
const backgroundStyle = getBackgroundStyle(background);
|
|
306
294
|
|
|
307
295
|
return (
|
|
308
296
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
@@ -479,7 +467,6 @@ interface DesktopMockupProps {
|
|
|
479
467
|
height: number;
|
|
480
468
|
label: string;
|
|
481
469
|
previewTheme: "light" | "dark";
|
|
482
|
-
background: BackgroundOption;
|
|
483
470
|
componentName: string;
|
|
484
471
|
fragmentPath: string;
|
|
485
472
|
variantName: string;
|
|
@@ -492,15 +479,12 @@ function DesktopMockup({
|
|
|
492
479
|
height,
|
|
493
480
|
label,
|
|
494
481
|
previewTheme,
|
|
495
|
-
background,
|
|
496
482
|
componentName,
|
|
497
483
|
fragmentPath,
|
|
498
484
|
variantName,
|
|
499
485
|
renderContent,
|
|
500
486
|
useIframeIsolation,
|
|
501
487
|
}: DesktopMockupProps) {
|
|
502
|
-
const backgroundStyle = getBackgroundStyle(background);
|
|
503
|
-
|
|
504
488
|
return (
|
|
505
489
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
506
490
|
{/* Label */}
|
|
@@ -568,7 +552,6 @@ function DesktopMockup({
|
|
|
568
552
|
overflow: 'hidden',
|
|
569
553
|
width: `${width}px`,
|
|
570
554
|
height: `${height}px`,
|
|
571
|
-
...backgroundStyle,
|
|
572
555
|
}}
|
|
573
556
|
data-theme={previewTheme}
|
|
574
557
|
>
|
|
@@ -13,7 +13,7 @@ import { FigmaEmbed } from './FigmaEmbed.js';
|
|
|
13
13
|
import { VariantMatrix } from './VariantMatrix.js';
|
|
14
14
|
import { MultiViewportPreview } from './MultiViewportPreview.js';
|
|
15
15
|
import { IsolatedPreviewFrame } from './IsolatedPreviewFrame.js';
|
|
16
|
-
import {
|
|
16
|
+
import { getViewportWidth, type ZoomLevel, type ViewportPreset, type ViewportSize } from '../constants/ui.js';
|
|
17
17
|
import type { PreviewTheme } from '../hooks/useViewSettings.js';
|
|
18
18
|
|
|
19
19
|
interface PreviewAreaProps {
|
|
@@ -25,7 +25,6 @@ interface PreviewAreaProps {
|
|
|
25
25
|
|
|
26
26
|
// View settings
|
|
27
27
|
zoom: ZoomLevel;
|
|
28
|
-
background: BackgroundOption;
|
|
29
28
|
viewport: ViewportPreset;
|
|
30
29
|
customSize: ViewportSize;
|
|
31
30
|
previewTheme: PreviewTheme;
|
|
@@ -151,11 +150,10 @@ const DeviceMockup = memo(function DeviceMockup({ type, width, children }: Devic
|
|
|
151
150
|
interface PreviewContentProps {
|
|
152
151
|
zoom: ZoomLevel;
|
|
153
152
|
previewTheme: PreviewTheme;
|
|
154
|
-
background: BackgroundOption;
|
|
155
153
|
children: ReactNode;
|
|
156
154
|
}
|
|
157
155
|
|
|
158
|
-
const PreviewContent = memo(function PreviewContent({ zoom, previewTheme,
|
|
156
|
+
const PreviewContent = memo(function PreviewContent({ zoom, previewTheme, children }: PreviewContentProps) {
|
|
159
157
|
return (
|
|
160
158
|
<div
|
|
161
159
|
data-preview-container="true"
|
|
@@ -164,7 +162,6 @@ const PreviewContent = memo(function PreviewContent({ zoom, previewTheme, backgr
|
|
|
164
162
|
width: '100%',
|
|
165
163
|
height: '100%',
|
|
166
164
|
overflow: 'auto',
|
|
167
|
-
backgroundColor: background === 'transparent' ? 'transparent' : undefined,
|
|
168
165
|
}}
|
|
169
166
|
>
|
|
170
167
|
<div
|
|
@@ -188,7 +185,6 @@ export function PreviewArea({
|
|
|
188
185
|
variant,
|
|
189
186
|
variants,
|
|
190
187
|
zoom,
|
|
191
|
-
background,
|
|
192
188
|
viewport,
|
|
193
189
|
customSize,
|
|
194
190
|
previewTheme,
|
|
@@ -212,7 +208,6 @@ export function PreviewArea({
|
|
|
212
208
|
fragmentPath={fragmentPath}
|
|
213
209
|
zoom={zoom}
|
|
214
210
|
previewTheme={previewTheme}
|
|
215
|
-
background={background}
|
|
216
211
|
useIframeIsolation={useIframeIsolation}
|
|
217
212
|
onSelectVariant={(index) => {
|
|
218
213
|
onSelectVariant(index);
|
|
@@ -230,7 +225,6 @@ export function PreviewArea({
|
|
|
230
225
|
variantName={variant.name}
|
|
231
226
|
renderContent={renderContent}
|
|
232
227
|
previewTheme={previewTheme}
|
|
233
|
-
background={background}
|
|
234
228
|
zoom={zoom}
|
|
235
229
|
useIframeIsolation={useIframeIsolation}
|
|
236
230
|
/>
|
|
@@ -240,7 +234,6 @@ export function PreviewArea({
|
|
|
240
234
|
const viewportWidth = getViewportWidth(viewport, customSize);
|
|
241
235
|
const viewportHeight = viewport === 'custom' ? customSize.height : null;
|
|
242
236
|
const isDevice = viewport === 'tablet' || viewport === 'mobile';
|
|
243
|
-
const backgroundStyle = getBackgroundStyle(background);
|
|
244
237
|
|
|
245
238
|
// Device mockup view (tablet/mobile)
|
|
246
239
|
if (isDevice && viewportWidth && variant) {
|
|
@@ -252,7 +245,7 @@ export function PreviewArea({
|
|
|
252
245
|
<div style={{ display: 'flex', gap: '16px', flex: 1 }}>
|
|
253
246
|
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
254
247
|
<div style={{ fontSize: '12px', fontWeight: 500, color: 'var(--text-tertiary)', marginBottom: '8px', textAlign: 'center' }}>Rendered</div>
|
|
255
|
-
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center'
|
|
248
|
+
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
256
249
|
<DeviceMockup type={viewport as 'tablet' | 'mobile'} width={viewportWidth}>
|
|
257
250
|
{useIframeIsolation ? (
|
|
258
251
|
<IsolatedPreviewFrame
|
|
@@ -264,7 +257,7 @@ export function PreviewArea({
|
|
|
264
257
|
previewKey={previewKey}
|
|
265
258
|
/>
|
|
266
259
|
) : (
|
|
267
|
-
<PreviewContent zoom={zoom} previewTheme={previewTheme}
|
|
260
|
+
<PreviewContent zoom={zoom} previewTheme={previewTheme}>
|
|
268
261
|
<ErrorBoundary key={previewKey} componentName={componentName} onRetry={onRetry}>
|
|
269
262
|
{renderContent()}
|
|
270
263
|
</ErrorBoundary>
|
|
@@ -285,7 +278,6 @@ export function PreviewArea({
|
|
|
285
278
|
borderRadius: '8px',
|
|
286
279
|
border: '1px solid var(--border)',
|
|
287
280
|
overflow: 'hidden',
|
|
288
|
-
...backgroundStyle,
|
|
289
281
|
}}
|
|
290
282
|
/>
|
|
291
283
|
</div>
|
|
@@ -307,7 +299,7 @@ export function PreviewArea({
|
|
|
307
299
|
previewKey={previewKey}
|
|
308
300
|
/>
|
|
309
301
|
) : (
|
|
310
|
-
<PreviewContent zoom={zoom} previewTheme={previewTheme}
|
|
302
|
+
<PreviewContent zoom={zoom} previewTheme={previewTheme}>
|
|
311
303
|
<ErrorBoundary key={previewKey} componentName={componentName} onRetry={onRetry}>
|
|
312
304
|
{renderContent()}
|
|
313
305
|
</ErrorBoundary>
|
|
@@ -331,7 +323,6 @@ export function PreviewArea({
|
|
|
331
323
|
borderRadius: '8px',
|
|
332
324
|
border: '1px solid var(--border)',
|
|
333
325
|
overflow: 'auto',
|
|
334
|
-
...backgroundStyle,
|
|
335
326
|
}}
|
|
336
327
|
>
|
|
337
328
|
{useIframeIsolation ? (
|
|
@@ -383,7 +374,6 @@ export function PreviewArea({
|
|
|
383
374
|
borderRadius: '8px',
|
|
384
375
|
border: '1px solid var(--border)',
|
|
385
376
|
overflow: 'hidden',
|
|
386
|
-
...backgroundStyle,
|
|
387
377
|
}}
|
|
388
378
|
/>
|
|
389
379
|
</div>
|
|
@@ -1,85 +1,19 @@
|
|
|
1
1
|
import { useEffect, useCallback } from 'react';
|
|
2
|
-
import { Button, Menu, Stack
|
|
3
|
-
import {
|
|
4
|
-
ZOOM_LEVELS,
|
|
5
|
-
type ZoomLevel,
|
|
6
|
-
type BackgroundOption,
|
|
7
|
-
} from '../constants/ui.js';
|
|
2
|
+
import { Button, Menu, Stack } from '@fragments-sdk/ui';
|
|
3
|
+
import { ZOOM_LEVELS, type ZoomLevel } from '../constants/ui.js';
|
|
8
4
|
import { ZoomIcon, ChevronDownIcon } from './Icons.js';
|
|
9
5
|
|
|
10
6
|
// Re-export types for consumers
|
|
11
|
-
export type { ZoomLevel
|
|
12
|
-
export { getBackgroundStyle } from '../constants/ui.js';
|
|
13
|
-
|
|
14
|
-
// Background options with display metadata
|
|
15
|
-
const BACKGROUND_OPTIONS_UI: { value: BackgroundOption; label: string }[] = [
|
|
16
|
-
{ value: 'white', label: 'White' },
|
|
17
|
-
{ value: 'black', label: 'Black' },
|
|
18
|
-
{ value: 'checkerboard', label: 'Checkerboard' },
|
|
19
|
-
{ value: 'transparent', label: 'Transparent' },
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
function BackgroundSwatch({ background }: { background: BackgroundOption }) {
|
|
23
|
-
const baseStyle = {
|
|
24
|
-
width: '14px',
|
|
25
|
-
height: '14px',
|
|
26
|
-
borderRadius: '4px',
|
|
27
|
-
border: '1px solid var(--border)',
|
|
28
|
-
flexShrink: 0,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
if (background === 'white') {
|
|
32
|
-
return <span aria-hidden="true" style={{ ...baseStyle, backgroundColor: '#ffffff' }} />;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (background === 'black') {
|
|
36
|
-
return <span aria-hidden="true" style={{ ...baseStyle, backgroundColor: '#171717', borderColor: '#2a2a2a' }} />;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (background === 'checkerboard') {
|
|
40
|
-
return (
|
|
41
|
-
<span
|
|
42
|
-
aria-hidden="true"
|
|
43
|
-
style={{
|
|
44
|
-
...baseStyle,
|
|
45
|
-
backgroundColor: '#ffffff',
|
|
46
|
-
backgroundImage: `
|
|
47
|
-
linear-gradient(45deg, #d4d4d8 25%, transparent 25%),
|
|
48
|
-
linear-gradient(-45deg, #d4d4d8 25%, transparent 25%),
|
|
49
|
-
linear-gradient(45deg, transparent 75%, #d4d4d8 75%),
|
|
50
|
-
linear-gradient(-45deg, transparent 75%, #d4d4d8 75%)
|
|
51
|
-
`,
|
|
52
|
-
backgroundSize: '8px 8px',
|
|
53
|
-
backgroundPosition: '0 0, 0 4px, 4px -4px, -4px 0',
|
|
54
|
-
}}
|
|
55
|
-
/>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<span
|
|
61
|
-
aria-hidden="true"
|
|
62
|
-
style={{
|
|
63
|
-
...baseStyle,
|
|
64
|
-
backgroundColor: 'transparent',
|
|
65
|
-
backgroundImage: 'linear-gradient(135deg, transparent 42%, var(--text-tertiary) 43%, var(--text-tertiary) 57%, transparent 58%)',
|
|
66
|
-
}}
|
|
67
|
-
/>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
7
|
+
export type { ZoomLevel };
|
|
70
8
|
|
|
71
9
|
interface PreviewToolbarProps {
|
|
72
10
|
zoom: ZoomLevel;
|
|
73
|
-
background: BackgroundOption;
|
|
74
11
|
onZoomChange: (zoom: ZoomLevel) => void;
|
|
75
|
-
onBackgroundChange: (bg: BackgroundOption) => void;
|
|
76
12
|
}
|
|
77
13
|
|
|
78
14
|
export function PreviewToolbar({
|
|
79
15
|
zoom,
|
|
80
|
-
background,
|
|
81
16
|
onZoomChange,
|
|
82
|
-
onBackgroundChange,
|
|
83
17
|
}: PreviewToolbarProps) {
|
|
84
18
|
// Keyboard shortcuts for zoom
|
|
85
19
|
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
|
@@ -114,7 +48,6 @@ export function PreviewToolbar({
|
|
|
114
48
|
|
|
115
49
|
return (
|
|
116
50
|
<Stack direction="row" gap="sm" align="center">
|
|
117
|
-
{/* Zoom dropdown */}
|
|
118
51
|
<Menu>
|
|
119
52
|
<Menu.Trigger asChild>
|
|
120
53
|
<Button variant="ghost" size="sm" title="Zoom level (+/-/0)">
|
|
@@ -142,39 +75,6 @@ export function PreviewToolbar({
|
|
|
142
75
|
</Menu.RadioGroup>
|
|
143
76
|
</Menu.Content>
|
|
144
77
|
</Menu>
|
|
145
|
-
|
|
146
|
-
{/* Divider */}
|
|
147
|
-
<Separator orientation="vertical" />
|
|
148
|
-
|
|
149
|
-
{/* Background selector */}
|
|
150
|
-
<Menu>
|
|
151
|
-
<Menu.Trigger asChild>
|
|
152
|
-
<Button variant="ghost" size="sm" title="Background color">
|
|
153
|
-
<Stack direction="row" gap="xs" align="center">
|
|
154
|
-
<BackgroundSwatch background={background} />
|
|
155
|
-
<span>{BACKGROUND_OPTIONS_UI.find(o => o.value === background)?.label}</span>
|
|
156
|
-
<span style={{ display: 'inline-flex', width: '12px', height: '12px' }}>
|
|
157
|
-
<ChevronDownIcon />
|
|
158
|
-
</span>
|
|
159
|
-
</Stack>
|
|
160
|
-
</Button>
|
|
161
|
-
</Menu.Trigger>
|
|
162
|
-
<Menu.Content side="bottom" align="start">
|
|
163
|
-
<Menu.RadioGroup
|
|
164
|
-
value={background}
|
|
165
|
-
onValueChange={(value: string) => onBackgroundChange(value as BackgroundOption)}
|
|
166
|
-
>
|
|
167
|
-
{BACKGROUND_OPTIONS_UI.map((option) => (
|
|
168
|
-
<Menu.RadioItem key={option.value} value={option.value}>
|
|
169
|
-
<Stack direction="row" gap="sm" align="center">
|
|
170
|
-
<BackgroundSwatch background={option.value} />
|
|
171
|
-
{option.label}
|
|
172
|
-
</Stack>
|
|
173
|
-
</Menu.RadioItem>
|
|
174
|
-
))}
|
|
175
|
-
</Menu.RadioGroup>
|
|
176
|
-
</Menu.Content>
|
|
177
|
-
</Menu>
|
|
178
78
|
</Stack>
|
|
179
79
|
);
|
|
180
80
|
}
|
|
@@ -17,7 +17,6 @@ import { ErrorBoundary } from "./ErrorBoundary.js";
|
|
|
17
17
|
import { StoryRenderer, LoaderIndicator } from "./StoryRenderer.js";
|
|
18
18
|
import { IsolatedPreviewFrame } from "./IsolatedPreviewFrame.js";
|
|
19
19
|
import { ChevronDownIcon } from "./Icons.js";
|
|
20
|
-
import { getBackgroundStyle, type BackgroundOption } from "../constants/ui.js";
|
|
21
20
|
|
|
22
21
|
interface VariantMatrixProps {
|
|
23
22
|
/** All variants to display */
|
|
@@ -30,8 +29,6 @@ interface VariantMatrixProps {
|
|
|
30
29
|
zoom: number;
|
|
31
30
|
/** Preview theme */
|
|
32
31
|
previewTheme: "light" | "dark";
|
|
33
|
-
/** Background option */
|
|
34
|
-
background: BackgroundOption;
|
|
35
32
|
/** Whether to use iframe isolation */
|
|
36
33
|
useIframeIsolation?: boolean;
|
|
37
34
|
/** Callback when a variant is clicked to focus on it */
|
|
@@ -63,7 +60,6 @@ export function VariantMatrix({
|
|
|
63
60
|
fragmentPath,
|
|
64
61
|
zoom,
|
|
65
62
|
previewTheme,
|
|
66
|
-
background,
|
|
67
63
|
useIframeIsolation = true,
|
|
68
64
|
onSelectVariant,
|
|
69
65
|
}: VariantMatrixProps) {
|
|
@@ -208,7 +204,6 @@ export function VariantMatrix({
|
|
|
208
204
|
scale={effectiveScale}
|
|
209
205
|
minHeight={gridConfig.minHeight}
|
|
210
206
|
previewTheme={previewTheme}
|
|
211
|
-
background={background}
|
|
212
207
|
useIframeIsolation={useIframeIsolation}
|
|
213
208
|
isHovered={hoveredIndex === index}
|
|
214
209
|
onHover={() => setHoveredIndex(index)}
|
|
@@ -240,7 +235,6 @@ export function VariantMatrix({
|
|
|
240
235
|
scale={effectiveScale}
|
|
241
236
|
minHeight={gridConfig.minHeight}
|
|
242
237
|
previewTheme={previewTheme}
|
|
243
|
-
background={background}
|
|
244
238
|
useIframeIsolation={useIframeIsolation}
|
|
245
239
|
isHovered={hoveredIndex === index}
|
|
246
240
|
onHover={() => setHoveredIndex(index)}
|
|
@@ -263,7 +257,6 @@ interface VariantCardProps {
|
|
|
263
257
|
scale: number;
|
|
264
258
|
minHeight: string;
|
|
265
259
|
previewTheme: "light" | "dark";
|
|
266
|
-
background: BackgroundOption;
|
|
267
260
|
useIframeIsolation: boolean;
|
|
268
261
|
isHovered: boolean;
|
|
269
262
|
onHover: () => void;
|
|
@@ -279,15 +272,12 @@ function VariantCard({
|
|
|
279
272
|
scale,
|
|
280
273
|
minHeight,
|
|
281
274
|
previewTheme,
|
|
282
|
-
background,
|
|
283
275
|
useIframeIsolation,
|
|
284
276
|
isHovered,
|
|
285
277
|
onHover,
|
|
286
278
|
onLeave,
|
|
287
279
|
onClick,
|
|
288
280
|
}: VariantCardProps) {
|
|
289
|
-
const backgroundStyle = getBackgroundStyle(background);
|
|
290
|
-
|
|
291
281
|
return (
|
|
292
282
|
<div
|
|
293
283
|
style={{
|
|
@@ -373,7 +363,6 @@ function VariantCard({
|
|
|
373
363
|
display: 'flex',
|
|
374
364
|
alignItems: 'center',
|
|
375
365
|
justifyContent: 'center',
|
|
376
|
-
...backgroundStyle,
|
|
377
366
|
}}
|
|
378
367
|
>
|
|
379
368
|
{useIframeIsolation ? (
|
|
@@ -102,12 +102,6 @@ export function getRelationshipConfig(relationship: string) {
|
|
|
102
102
|
return RELATIONSHIP_CONFIG[relationship as RelationshipType] || RELATIONSHIP_CONFIG.related;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
/**
|
|
106
|
-
* Preview background options.
|
|
107
|
-
*/
|
|
108
|
-
export const BACKGROUND_OPTIONS = ['white', 'black', 'checkerboard', 'transparent'] as const;
|
|
109
|
-
export type BackgroundOption = (typeof BACKGROUND_OPTIONS)[number];
|
|
110
|
-
|
|
111
105
|
/**
|
|
112
106
|
* Zoom level options.
|
|
113
107
|
*/
|
|
@@ -140,34 +134,6 @@ export function getViewportWidth(viewport: ViewportPreset, customSize: ViewportS
|
|
|
140
134
|
return VIEWPORT_PRESETS[viewport]?.width ?? null;
|
|
141
135
|
}
|
|
142
136
|
|
|
143
|
-
/**
|
|
144
|
-
* Get CSS background style for preview pane.
|
|
145
|
-
*/
|
|
146
|
-
export function getBackgroundStyle(bg: BackgroundOption): React.CSSProperties {
|
|
147
|
-
switch (bg) {
|
|
148
|
-
case 'white':
|
|
149
|
-
return { backgroundColor: '#ffffff' };
|
|
150
|
-
case 'black':
|
|
151
|
-
return { backgroundColor: '#000000' };
|
|
152
|
-
case 'transparent':
|
|
153
|
-
return { backgroundColor: 'transparent' };
|
|
154
|
-
case 'checkerboard':
|
|
155
|
-
return {
|
|
156
|
-
backgroundColor: '#ffffff',
|
|
157
|
-
backgroundImage: `
|
|
158
|
-
linear-gradient(45deg, #e5e5e5 25%, transparent 25%),
|
|
159
|
-
linear-gradient(-45deg, #e5e5e5 25%, transparent 25%),
|
|
160
|
-
linear-gradient(45deg, transparent 75%, #e5e5e5 75%),
|
|
161
|
-
linear-gradient(-45deg, transparent 75%, #e5e5e5 75%)
|
|
162
|
-
`,
|
|
163
|
-
backgroundSize: '16px 16px',
|
|
164
|
-
backgroundPosition: '0 0, 0 8px, 8px -8px, -8px 0px',
|
|
165
|
-
};
|
|
166
|
-
default:
|
|
167
|
-
return { backgroundColor: '#ffffff' };
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
137
|
/**
|
|
172
138
|
* Storage keys for localStorage persistence.
|
|
173
139
|
*/
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* - Component path
|
|
6
6
|
* - Variant index/name
|
|
7
7
|
* - Edited props
|
|
8
|
-
* - View settings (zoom,
|
|
8
|
+
* - View settings (zoom, viewport)
|
|
9
9
|
*
|
|
10
|
-
* URL format: #/ComponentName/VariantName?props=base64&zoom=100
|
|
10
|
+
* URL format: #/ComponentName/VariantName?props=base64&zoom=100
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
@@ -21,7 +21,6 @@ export interface UrlState {
|
|
|
21
21
|
props: Record<string, unknown>;
|
|
22
22
|
/** View settings */
|
|
23
23
|
zoom: number;
|
|
24
|
-
background: string;
|
|
25
24
|
viewport: string;
|
|
26
25
|
customWidth: number | null;
|
|
27
26
|
customHeight: number | null;
|
|
@@ -32,7 +31,6 @@ const DEFAULT_STATE: UrlState = {
|
|
|
32
31
|
variant: null,
|
|
33
32
|
props: {},
|
|
34
33
|
zoom: 100,
|
|
35
|
-
background: "transparent",
|
|
36
34
|
viewport: "responsive",
|
|
37
35
|
customWidth: null,
|
|
38
36
|
customHeight: null,
|
|
@@ -99,11 +97,6 @@ function parseUrl(): UrlState {
|
|
|
99
97
|
}
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
const bg = params.get("bg");
|
|
103
|
-
if (bg && ["white", "black", "checkerboard", "transparent"].includes(bg)) {
|
|
104
|
-
state.background = bg;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
100
|
const viewport = params.get("viewport");
|
|
108
101
|
if (viewport && ["responsive", "desktop", "tablet", "mobile", "custom"].includes(viewport)) {
|
|
109
102
|
state.viewport = viewport;
|
|
@@ -155,10 +148,6 @@ function buildUrl(state: Partial<UrlState>): string {
|
|
|
155
148
|
params.set("zoom", String(state.zoom));
|
|
156
149
|
}
|
|
157
150
|
|
|
158
|
-
if (state.background && state.background !== "transparent") {
|
|
159
|
-
params.set("bg", state.background);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
151
|
if (state.viewport && state.viewport !== "responsive") {
|
|
163
152
|
params.set("viewport", state.viewport);
|
|
164
153
|
}
|
|
@@ -249,7 +238,6 @@ export function useUrlState() {
|
|
|
249
238
|
const setViewSettings = useCallback(
|
|
250
239
|
(settings: {
|
|
251
240
|
zoom?: number;
|
|
252
|
-
background?: string;
|
|
253
241
|
viewport?: string;
|
|
254
242
|
customWidth?: number | null;
|
|
255
243
|
customHeight?: number | null;
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* View settings state management.
|
|
3
|
-
* Handles zoom,
|
|
3
|
+
* Handles zoom, viewport, and theme settings with URL sync.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useReducer, useCallback, useMemo } from 'react';
|
|
7
|
-
import type { ZoomLevel,
|
|
7
|
+
import type { ZoomLevel, ViewportPreset, ViewportSize } from '../constants/ui.js';
|
|
8
8
|
|
|
9
9
|
// Preview theme type (for canvas theming)
|
|
10
10
|
export type PreviewTheme = 'light' | 'dark';
|
|
11
11
|
|
|
12
12
|
interface ViewSettings {
|
|
13
13
|
zoom: ZoomLevel;
|
|
14
|
-
background: BackgroundOption;
|
|
15
14
|
viewport: ViewportPreset;
|
|
16
15
|
customSize: ViewportSize;
|
|
17
16
|
previewTheme: PreviewTheme;
|
|
@@ -19,7 +18,6 @@ interface ViewSettings {
|
|
|
19
18
|
|
|
20
19
|
type ViewSettingsAction =
|
|
21
20
|
| { type: 'SET_ZOOM'; payload: ZoomLevel }
|
|
22
|
-
| { type: 'SET_BACKGROUND'; payload: BackgroundOption }
|
|
23
21
|
| { type: 'SET_VIEWPORT'; payload: ViewportPreset }
|
|
24
22
|
| { type: 'SET_CUSTOM_SIZE'; payload: ViewportSize }
|
|
25
23
|
| { type: 'SET_PREVIEW_THEME'; payload: PreviewTheme }
|
|
@@ -28,7 +26,6 @@ type ViewSettingsAction =
|
|
|
28
26
|
|
|
29
27
|
interface ViewSettingsInit {
|
|
30
28
|
zoom?: ZoomLevel;
|
|
31
|
-
background?: BackgroundOption;
|
|
32
29
|
viewport?: ViewportPreset;
|
|
33
30
|
customSize?: ViewportSize;
|
|
34
31
|
previewTheme?: PreviewTheme;
|
|
@@ -37,7 +34,6 @@ interface ViewSettingsInit {
|
|
|
37
34
|
function createInitialState(init?: ViewSettingsInit): ViewSettings {
|
|
38
35
|
return {
|
|
39
36
|
zoom: init?.zoom ?? 100,
|
|
40
|
-
background: init?.background ?? 'transparent',
|
|
41
37
|
viewport: init?.viewport ?? 'responsive',
|
|
42
38
|
customSize: init?.customSize ?? { width: null, height: null },
|
|
43
39
|
previewTheme: init?.previewTheme ?? 'light',
|
|
@@ -48,8 +44,6 @@ function viewSettingsReducer(state: ViewSettings, action: ViewSettingsAction): V
|
|
|
48
44
|
switch (action.type) {
|
|
49
45
|
case 'SET_ZOOM':
|
|
50
46
|
return { ...state, zoom: action.payload };
|
|
51
|
-
case 'SET_BACKGROUND':
|
|
52
|
-
return { ...state, background: action.payload };
|
|
53
47
|
case 'SET_VIEWPORT':
|
|
54
48
|
return { ...state, viewport: action.payload };
|
|
55
49
|
case 'SET_CUSTOM_SIZE':
|
|
@@ -68,12 +62,11 @@ function viewSettingsReducer(state: ViewSettings, action: ViewSettingsAction): V
|
|
|
68
62
|
interface UseViewSettingsOptions {
|
|
69
63
|
initialState?: ViewSettingsInit;
|
|
70
64
|
onZoomChange?: (zoom: ZoomLevel) => void;
|
|
71
|
-
onBackgroundChange?: (bg: BackgroundOption) => void;
|
|
72
65
|
onViewportChange?: (viewport: ViewportPreset, customSize?: ViewportSize) => void;
|
|
73
66
|
}
|
|
74
67
|
|
|
75
68
|
export function useViewSettings(options: UseViewSettingsOptions = {}) {
|
|
76
|
-
const { initialState: init, onZoomChange,
|
|
69
|
+
const { initialState: init, onZoomChange, onViewportChange } = options;
|
|
77
70
|
|
|
78
71
|
const [state, dispatch] = useReducer(
|
|
79
72
|
viewSettingsReducer,
|
|
@@ -86,11 +79,6 @@ export function useViewSettings(options: UseViewSettingsOptions = {}) {
|
|
|
86
79
|
onZoomChange?.(zoom);
|
|
87
80
|
}, [onZoomChange]);
|
|
88
81
|
|
|
89
|
-
const setBackground = useCallback((bg: BackgroundOption) => {
|
|
90
|
-
dispatch({ type: 'SET_BACKGROUND', payload: bg });
|
|
91
|
-
onBackgroundChange?.(bg);
|
|
92
|
-
}, [onBackgroundChange]);
|
|
93
|
-
|
|
94
82
|
const setViewport = useCallback((viewport: ViewportPreset) => {
|
|
95
83
|
dispatch({ type: 'SET_VIEWPORT', payload: viewport });
|
|
96
84
|
onViewportChange?.(viewport);
|
|
@@ -111,12 +99,11 @@ export function useViewSettings(options: UseViewSettingsOptions = {}) {
|
|
|
111
99
|
|
|
112
100
|
const actions = useMemo(() => ({
|
|
113
101
|
setZoom,
|
|
114
|
-
setBackground,
|
|
115
102
|
setViewport,
|
|
116
103
|
setCustomSize,
|
|
117
104
|
setPreviewTheme,
|
|
118
105
|
toggleTheme,
|
|
119
|
-
}), [setZoom,
|
|
106
|
+
}), [setZoom, setViewport, setCustomSize, setPreviewTheme, toggleTheme]);
|
|
120
107
|
|
|
121
108
|
return { ...state, ...actions };
|
|
122
109
|
}
|