@fragments-sdk/cli 0.2.2
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/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main App component for the Fragments viewer.
|
|
3
|
+
* Refactored for better performance and maintainability.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
|
7
|
+
import type { SegmentDefinition } from "../../core/index.js";
|
|
8
|
+
import clsx from "clsx";
|
|
9
|
+
|
|
10
|
+
// Layout & Navigation
|
|
11
|
+
import { Layout } from "./Layout.js";
|
|
12
|
+
import { LeftSidebar } from "./LeftSidebar.js";
|
|
13
|
+
import { VariantTabs } from "./VariantTabs.js";
|
|
14
|
+
import { CommandPalette } from "./CommandPalette.js";
|
|
15
|
+
import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp.js";
|
|
16
|
+
import { Toast, type ToastMessage } from "./Toast.js";
|
|
17
|
+
|
|
18
|
+
// Toolbar
|
|
19
|
+
import { PreviewToolbar, getBackgroundStyle } from "./PreviewToolbar.js";
|
|
20
|
+
import { ViewportSelector } from "./ViewportSelector.js";
|
|
21
|
+
|
|
22
|
+
// Preview & Rendering
|
|
23
|
+
import { PreviewArea } from "./PreviewArea.js";
|
|
24
|
+
import { BottomPanel } from "./BottomPanel.js";
|
|
25
|
+
import { IsolatedRender } from "./IsolatedRender.js";
|
|
26
|
+
import { StoryRenderer, LoaderIndicator } from "./StoryRenderer.js";
|
|
27
|
+
import { HealthDashboard } from "./HealthDashboard.js";
|
|
28
|
+
import { useAllFigmaUrls } from "./FigmaEmbed.js";
|
|
29
|
+
import { ActionCapture } from "./ActionCapture.js";
|
|
30
|
+
|
|
31
|
+
// Icons
|
|
32
|
+
import { EmptyIcon, ExternalLinkIcon, CameraIcon, FigmaIcon, CompareIcon, CheckIcon, LinkIcon, GridIcon, DevicesIcon } from "./Icons.js";
|
|
33
|
+
|
|
34
|
+
// Hooks
|
|
35
|
+
import { useAppState } from "../hooks/useAppState.js";
|
|
36
|
+
import { useViewSettings } from "../hooks/useViewSettings.js";
|
|
37
|
+
import { useFigmaIntegration } from "../hooks/useFigmaIntegration.js";
|
|
38
|
+
import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts.js";
|
|
39
|
+
import { useActions } from "../hooks/useActions.js";
|
|
40
|
+
import { useUrlState, findSegmentByName, findVariantIndex } from "../hooks/useUrlState.js";
|
|
41
|
+
import { usePanelDock } from "./ResizablePanel.js";
|
|
42
|
+
import { useTheme } from "./ThemeProvider.js";
|
|
43
|
+
|
|
44
|
+
// Utilities
|
|
45
|
+
import { ScreenshotButton } from "./ScreenshotButton.js";
|
|
46
|
+
|
|
47
|
+
interface AppProps {
|
|
48
|
+
segments: Array<{ path: string; segment: SegmentDefinition }>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function App({ segments }: AppProps) {
|
|
52
|
+
// URL state management
|
|
53
|
+
const { state: urlState, setComponent: setUrlComponent, setVariant: setUrlVariant, setViewSettings: setUrlViewSettings, copyUrl } = useUrlState();
|
|
54
|
+
|
|
55
|
+
// UI state (modals, panels, view modes)
|
|
56
|
+
const { state: uiState, actions: uiActions } = useAppState();
|
|
57
|
+
|
|
58
|
+
// View settings (zoom, background, viewport, theme)
|
|
59
|
+
const viewSettings = useViewSettings({
|
|
60
|
+
initialState: {
|
|
61
|
+
zoom: urlState.zoom as any,
|
|
62
|
+
background: urlState.background as any,
|
|
63
|
+
viewport: urlState.viewport as any,
|
|
64
|
+
customSize: { width: urlState.customWidth, height: urlState.customHeight },
|
|
65
|
+
},
|
|
66
|
+
onZoomChange: (zoom) => setUrlViewSettings({ zoom }),
|
|
67
|
+
onBackgroundChange: (bg) => setUrlViewSettings({ background: bg }),
|
|
68
|
+
onViewportChange: (vp, size) => setUrlViewSettings({
|
|
69
|
+
viewport: vp,
|
|
70
|
+
customWidth: size?.width,
|
|
71
|
+
customHeight: size?.height,
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Panel dock position
|
|
76
|
+
const panelDock = usePanelDock();
|
|
77
|
+
|
|
78
|
+
// Get resolved theme from ThemeProvider for iframe preview
|
|
79
|
+
const { resolvedTheme } = useTheme();
|
|
80
|
+
|
|
81
|
+
// Toast notifications
|
|
82
|
+
const [toasts, setToasts] = useState<ToastMessage[]>([]);
|
|
83
|
+
const addToast = useCallback((type: ToastMessage['type'], message: string, duration?: number) => {
|
|
84
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
85
|
+
setToasts(prev => [...prev, { id, type, message, duration }]);
|
|
86
|
+
}, []);
|
|
87
|
+
const dismissToast = useCallback((id: string) => {
|
|
88
|
+
setToasts(prev => prev.filter(t => t.id !== id));
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
// Navigation state
|
|
92
|
+
const [activeSegmentPath, setActiveSegmentPath] = useState<string | null>(() => {
|
|
93
|
+
if (urlState.component) {
|
|
94
|
+
const found = findSegmentByName(segments, urlState.component);
|
|
95
|
+
return found?.path ?? segments[0]?.path ?? null;
|
|
96
|
+
}
|
|
97
|
+
return segments[0]?.path ?? null;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const [activeVariantIndex, setActiveVariantIndex] = useState<number>(() => {
|
|
101
|
+
const segment = segments.find(s => s.path === activeSegmentPath);
|
|
102
|
+
if (urlState.variant && segment?.segment.variants) {
|
|
103
|
+
return findVariantIndex(segment.segment.variants, urlState.variant);
|
|
104
|
+
}
|
|
105
|
+
return 0;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Derived values
|
|
109
|
+
const activeSegment = useMemo(
|
|
110
|
+
() => segments.find((s) => s.path === activeSegmentPath),
|
|
111
|
+
[segments, activeSegmentPath]
|
|
112
|
+
);
|
|
113
|
+
const activeVariant = activeSegment?.segment.variants?.[activeVariantIndex];
|
|
114
|
+
const figmaUrl = activeVariant?.figma || activeSegment?.segment.meta.figma;
|
|
115
|
+
|
|
116
|
+
// Figma integration
|
|
117
|
+
const figmaIntegration = useFigmaIntegration({
|
|
118
|
+
figmaUrl,
|
|
119
|
+
showComparison: uiState.showComparison,
|
|
120
|
+
dependencies: [activeSegmentPath, activeVariantIndex],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Actions logging
|
|
124
|
+
const { logs: actionLogs, logAction, clearLogs: clearActionLogs } = useActions();
|
|
125
|
+
const useActionsRef = useRef({ logAction });
|
|
126
|
+
useActionsRef.current = { logAction };
|
|
127
|
+
|
|
128
|
+
// Figma URLs for preloading
|
|
129
|
+
const allFigmaUrls = useAllFigmaUrls(activeSegment?.segment);
|
|
130
|
+
|
|
131
|
+
// Reset action logs on variant change
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
clearActionLogs();
|
|
134
|
+
}, [activeSegmentPath, activeVariantIndex, clearActionLogs]);
|
|
135
|
+
|
|
136
|
+
// Extract rendered styles after component renders
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (uiState.showComparison && activeVariant) {
|
|
139
|
+
const timer = setTimeout(figmaIntegration.extractRenderedStyles, 100);
|
|
140
|
+
return () => clearTimeout(timer);
|
|
141
|
+
}
|
|
142
|
+
}, [uiState.showComparison, activeVariant, figmaIntegration.extractRenderedStyles, uiState.previewKey]);
|
|
143
|
+
|
|
144
|
+
// Sync URL state on browser navigation
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (urlState.component) {
|
|
147
|
+
const found = findSegmentByName(segments, urlState.component);
|
|
148
|
+
if (found && found.path !== activeSegmentPath) {
|
|
149
|
+
setActiveSegmentPath(found.path);
|
|
150
|
+
const variantIndex = findVariantIndex(found.segment.variants, urlState.variant);
|
|
151
|
+
setActiveVariantIndex(variantIndex);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}, [urlState.component, urlState.variant, segments, activeSegmentPath]);
|
|
155
|
+
|
|
156
|
+
// HMR toast notifications
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const hot = (import.meta as any).hot;
|
|
159
|
+
if (!hot) return;
|
|
160
|
+
|
|
161
|
+
const handleUpdate = (data: any) => {
|
|
162
|
+
if (data?.updates?.length > 0) {
|
|
163
|
+
const paths = data.updates.map((u: any) => u.path.split('/').pop()).join(', ');
|
|
164
|
+
addToast('info', `Updated: ${paths}`, 2000);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
hot.on('vite:beforeUpdate', handleUpdate);
|
|
169
|
+
return () => hot.off?.('vite:beforeUpdate', handleUpdate);
|
|
170
|
+
}, [addToast]);
|
|
171
|
+
|
|
172
|
+
// Navigation handlers
|
|
173
|
+
const handleSelectSegment = useCallback((path: string) => {
|
|
174
|
+
const segment = segments.find((s) => s.path === path);
|
|
175
|
+
const componentName = segment?.segment.meta.name || path;
|
|
176
|
+
const firstVariant = segment?.segment.variants?.[0]?.name;
|
|
177
|
+
|
|
178
|
+
setActiveSegmentPath(path);
|
|
179
|
+
setActiveVariantIndex(0);
|
|
180
|
+
uiActions.setHealthDashboard(false);
|
|
181
|
+
setUrlComponent(componentName, firstVariant);
|
|
182
|
+
}, [segments, setUrlComponent, uiActions]);
|
|
183
|
+
|
|
184
|
+
const handleSelectVariant = useCallback((index: number) => {
|
|
185
|
+
const variantName = activeSegment?.segment.variants?.[index]?.name;
|
|
186
|
+
setActiveVariantIndex(index);
|
|
187
|
+
setUrlVariant(variantName || null);
|
|
188
|
+
}, [activeSegment, setUrlVariant]);
|
|
189
|
+
|
|
190
|
+
// Copy link handler
|
|
191
|
+
const handleCopyLink = useCallback(async () => {
|
|
192
|
+
const success = await copyUrl();
|
|
193
|
+
if (success) {
|
|
194
|
+
uiActions.setLinkCopied(true);
|
|
195
|
+
addToast('success', 'Link copied to clipboard', 2000);
|
|
196
|
+
setTimeout(() => uiActions.setLinkCopied(false), 2000);
|
|
197
|
+
}
|
|
198
|
+
}, [copyUrl, addToast, uiActions]);
|
|
199
|
+
|
|
200
|
+
// Sorted segment paths for keyboard navigation
|
|
201
|
+
const sortedSegmentPaths = useMemo(() => {
|
|
202
|
+
return [...segments]
|
|
203
|
+
.filter(s => s.segment?.meta?.name)
|
|
204
|
+
.sort((a, b) => a.segment.meta.name.localeCompare(b.segment.meta.name))
|
|
205
|
+
.map(s => s.path);
|
|
206
|
+
}, [segments]);
|
|
207
|
+
|
|
208
|
+
const currentSegmentIndex = sortedSegmentPaths.indexOf(activeSegmentPath || '');
|
|
209
|
+
const variantCount = activeSegment?.segment.variants?.length || 0;
|
|
210
|
+
|
|
211
|
+
// Keyboard shortcuts
|
|
212
|
+
useKeyboardShortcuts(
|
|
213
|
+
{
|
|
214
|
+
nextComponent: () => {
|
|
215
|
+
const nextIndex = currentSegmentIndex < sortedSegmentPaths.length - 1 ? currentSegmentIndex + 1 : 0;
|
|
216
|
+
handleSelectSegment(sortedSegmentPaths[nextIndex]);
|
|
217
|
+
},
|
|
218
|
+
prevComponent: () => {
|
|
219
|
+
const prevIndex = currentSegmentIndex > 0 ? currentSegmentIndex - 1 : sortedSegmentPaths.length - 1;
|
|
220
|
+
handleSelectSegment(sortedSegmentPaths[prevIndex]);
|
|
221
|
+
},
|
|
222
|
+
nextVariant: () => handleSelectVariant(activeVariantIndex < variantCount - 1 ? activeVariantIndex + 1 : 0),
|
|
223
|
+
prevVariant: () => handleSelectVariant(activeVariantIndex > 0 ? activeVariantIndex - 1 : variantCount - 1),
|
|
224
|
+
goToVariant: (index) => index < variantCount && handleSelectVariant(index),
|
|
225
|
+
toggleTheme: viewSettings.toggleTheme,
|
|
226
|
+
togglePanel: uiActions.togglePanel,
|
|
227
|
+
copyLink: handleCopyLink,
|
|
228
|
+
showHelp: uiActions.toggleShortcutsHelp,
|
|
229
|
+
openSearch: () => uiActions.setCommandPalette(true),
|
|
230
|
+
escape: uiActions.closeAllModals,
|
|
231
|
+
},
|
|
232
|
+
{ enabled: !uiState.showShortcutsHelp, variantCount }
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Render variant with action logging via DOM event capture
|
|
236
|
+
const renderVariantWithProps = useCallback(() => {
|
|
237
|
+
if (!activeVariant) return null;
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<ActionCapture onAction={useActionsRef.current.logAction}>
|
|
241
|
+
<StoryRenderer variant={activeVariant}>
|
|
242
|
+
{(content, isLoading, error) => {
|
|
243
|
+
if (isLoading) return <div className="flex items-center justify-center p-8"><LoaderIndicator /></div>;
|
|
244
|
+
if (error) return <EmptyVariantMessage reason={`Error: ${error.message}`} variantName={activeVariant.name} hint="Check the console for the full error stack trace." />;
|
|
245
|
+
if (content === null || content === undefined) return <EmptyVariantMessage reason="render() returned null or undefined" variantName={activeVariant.name} hint="The variant's render function didn't return any JSX." />;
|
|
246
|
+
return content;
|
|
247
|
+
}}
|
|
248
|
+
</StoryRenderer>
|
|
249
|
+
</ActionCapture>
|
|
250
|
+
);
|
|
251
|
+
}, [activeVariant]);
|
|
252
|
+
|
|
253
|
+
// Check if isolated mode
|
|
254
|
+
const isIsolated = useMemo(() => {
|
|
255
|
+
const params = new URLSearchParams(window.location.search);
|
|
256
|
+
return params.get("isolated") === "true";
|
|
257
|
+
}, []);
|
|
258
|
+
|
|
259
|
+
if (isIsolated) {
|
|
260
|
+
return <IsolatedRender segments={segments} />;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<>
|
|
265
|
+
<Toast messages={toasts} onDismiss={dismissToast} />
|
|
266
|
+
<KeyboardShortcutsHelp isOpen={uiState.showShortcutsHelp} onClose={() => uiActions.setShortcutsHelp(false)} />
|
|
267
|
+
<CommandPalette
|
|
268
|
+
isOpen={uiState.showCommandPalette}
|
|
269
|
+
onClose={() => uiActions.setCommandPalette(false)}
|
|
270
|
+
segments={segments}
|
|
271
|
+
onSelectComponent={handleSelectSegment}
|
|
272
|
+
onSelectVariant={(path, variantIndex) => {
|
|
273
|
+
handleSelectSegment(path);
|
|
274
|
+
setTimeout(() => handleSelectVariant(variantIndex), 0);
|
|
275
|
+
}}
|
|
276
|
+
/>
|
|
277
|
+
|
|
278
|
+
<Layout
|
|
279
|
+
leftSidebar={
|
|
280
|
+
<LeftSidebar
|
|
281
|
+
segments={segments}
|
|
282
|
+
activeSegment={uiState.showHealthDashboard ? null : activeSegmentPath}
|
|
283
|
+
onSelect={handleSelectSegment}
|
|
284
|
+
showHealth={uiState.showHealthDashboard}
|
|
285
|
+
onHealthClick={() => {
|
|
286
|
+
uiActions.setHealthDashboard(true);
|
|
287
|
+
setActiveSegmentPath(null);
|
|
288
|
+
}}
|
|
289
|
+
/>
|
|
290
|
+
}
|
|
291
|
+
>
|
|
292
|
+
{uiState.showHealthDashboard ? (
|
|
293
|
+
<div className="h-full overflow-auto bg-[--bg-primary]">
|
|
294
|
+
<div className="max-w-4xl mx-auto py-8 px-6">
|
|
295
|
+
<HealthDashboard
|
|
296
|
+
segments={segments}
|
|
297
|
+
onNavigate={(componentName) => {
|
|
298
|
+
const target = segments.find(s => s.segment.meta.name === componentName);
|
|
299
|
+
if (target) {
|
|
300
|
+
uiActions.setHealthDashboard(false);
|
|
301
|
+
handleSelectSegment(target.path);
|
|
302
|
+
}
|
|
303
|
+
}}
|
|
304
|
+
/>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
) : activeSegment ? (
|
|
308
|
+
<div className={clsx("flex h-full", panelDock === "bottom" ? "flex-col" : "flex-row")}>
|
|
309
|
+
{/* Main Content Area */}
|
|
310
|
+
<div className="flex-1 flex flex-col min-w-0 min-h-0">
|
|
311
|
+
{/* Top Toolbar */}
|
|
312
|
+
<TopToolbar
|
|
313
|
+
segment={activeSegment}
|
|
314
|
+
variant={activeVariant}
|
|
315
|
+
viewSettings={viewSettings}
|
|
316
|
+
uiState={uiState}
|
|
317
|
+
uiActions={uiActions}
|
|
318
|
+
figmaUrl={figmaUrl}
|
|
319
|
+
linkCopied={uiState.linkCopied}
|
|
320
|
+
onCopyLink={handleCopyLink}
|
|
321
|
+
/>
|
|
322
|
+
|
|
323
|
+
{/* Variant Tabs */}
|
|
324
|
+
{activeSegment.segment.variants && activeSegment.segment.variants.length > 0 && (
|
|
325
|
+
<VariantTabsBar
|
|
326
|
+
variants={activeSegment.segment.variants}
|
|
327
|
+
activeIndex={activeVariantIndex}
|
|
328
|
+
onSelect={handleSelectVariant}
|
|
329
|
+
showMatrixView={uiState.showMatrixView}
|
|
330
|
+
showMultiViewport={uiState.showMultiViewport}
|
|
331
|
+
onToggleMatrix={() => uiActions.setMatrixView(!uiState.showMatrixView)}
|
|
332
|
+
onToggleMultiViewport={() => uiActions.setMultiViewport(!uiState.showMultiViewport)}
|
|
333
|
+
/>
|
|
334
|
+
)}
|
|
335
|
+
|
|
336
|
+
{/* Preview Area */}
|
|
337
|
+
<div
|
|
338
|
+
className="flex-1 overflow-auto relative"
|
|
339
|
+
style={uiState.showMatrixView ? undefined : getBackgroundStyle(viewSettings.background)}
|
|
340
|
+
>
|
|
341
|
+
{activeVariant ? (
|
|
342
|
+
<PreviewArea
|
|
343
|
+
componentName={activeSegment.segment.meta.name}
|
|
344
|
+
segmentPath={activeSegment.path}
|
|
345
|
+
variant={activeVariant}
|
|
346
|
+
variants={activeSegment.segment.variants}
|
|
347
|
+
zoom={viewSettings.zoom}
|
|
348
|
+
background={viewSettings.background}
|
|
349
|
+
viewport={viewSettings.viewport}
|
|
350
|
+
customSize={viewSettings.customSize}
|
|
351
|
+
previewTheme={resolvedTheme}
|
|
352
|
+
showMatrixView={uiState.showMatrixView}
|
|
353
|
+
showMultiViewport={uiState.showMultiViewport}
|
|
354
|
+
showComparison={uiState.showComparison}
|
|
355
|
+
figmaUrl={figmaUrl}
|
|
356
|
+
allFigmaUrls={allFigmaUrls}
|
|
357
|
+
onSelectVariant={(index) => {
|
|
358
|
+
uiActions.setMatrixView(false);
|
|
359
|
+
handleSelectVariant(index);
|
|
360
|
+
}}
|
|
361
|
+
onRetry={uiActions.incrementPreviewKey}
|
|
362
|
+
renderContent={renderVariantWithProps}
|
|
363
|
+
previewKey={`${activeSegmentPath}-${activeVariantIndex}-${uiState.previewKey}`}
|
|
364
|
+
/>
|
|
365
|
+
) : (
|
|
366
|
+
<NoVariantsMessage segment={activeSegment?.segment} />
|
|
367
|
+
)}
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
{/* Bottom Panel */}
|
|
372
|
+
{activeVariant && (
|
|
373
|
+
<BottomPanel
|
|
374
|
+
segment={activeSegment.segment}
|
|
375
|
+
variant={activeVariant}
|
|
376
|
+
segments={segments}
|
|
377
|
+
activePanel={uiState.activePanel}
|
|
378
|
+
onPanelChange={uiActions.setActivePanel}
|
|
379
|
+
figmaUrl={figmaUrl}
|
|
380
|
+
figmaStyles={figmaIntegration.figmaStyles.status === 'success' ? figmaIntegration.figmaStyles.styles || null : null}
|
|
381
|
+
renderedStyles={figmaIntegration.renderedStyles}
|
|
382
|
+
figmaLoading={figmaIntegration.isLoading}
|
|
383
|
+
figmaError={figmaIntegration.errorMessage}
|
|
384
|
+
onFetchFigma={figmaIntegration.fetchFigmaStyles}
|
|
385
|
+
onRefreshRendered={figmaIntegration.extractRenderedStyles}
|
|
386
|
+
actionLogs={actionLogs}
|
|
387
|
+
onClearActionLogs={clearActionLogs}
|
|
388
|
+
onNavigateToComponent={(name) => {
|
|
389
|
+
const target = segments.find(s => s.segment.meta.name === name);
|
|
390
|
+
if (target) handleSelectSegment(target.path);
|
|
391
|
+
}}
|
|
392
|
+
previewKey={uiState.previewKey}
|
|
393
|
+
segmentKey={`${activeSegmentPath}-${activeVariantIndex}`}
|
|
394
|
+
/>
|
|
395
|
+
)}
|
|
396
|
+
</div>
|
|
397
|
+
) : (
|
|
398
|
+
<div className="flex flex-col items-center justify-center h-full text-secondary">
|
|
399
|
+
<EmptyIcon className="w-12 h-12 mb-4 text-[--text-muted]" />
|
|
400
|
+
<p className="text-base font-medium text-primary">No component selected</p>
|
|
401
|
+
<p className="text-sm mt-1 text-tertiary">Select a component from the sidebar</p>
|
|
402
|
+
</div>
|
|
403
|
+
)}
|
|
404
|
+
</Layout>
|
|
405
|
+
</>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Top Toolbar Component
|
|
410
|
+
interface TopToolbarProps {
|
|
411
|
+
segment: { path: string; segment: SegmentDefinition };
|
|
412
|
+
variant: any;
|
|
413
|
+
viewSettings: ReturnType<typeof useViewSettings>;
|
|
414
|
+
uiState: ReturnType<typeof useAppState>['state'];
|
|
415
|
+
uiActions: ReturnType<typeof useAppState>['actions'];
|
|
416
|
+
figmaUrl?: string;
|
|
417
|
+
linkCopied: boolean;
|
|
418
|
+
onCopyLink: () => void;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function TopToolbar({ segment, variant, viewSettings, uiState, uiActions, figmaUrl, linkCopied, onCopyLink }: TopToolbarProps) {
|
|
422
|
+
return (
|
|
423
|
+
<div className="flex items-center justify-between px-4 py-2 border-b border-[--border] bg-[--bg-secondary] flex-shrink-0">
|
|
424
|
+
<div className="flex items-center gap-3">
|
|
425
|
+
<h1 className="text-sm font-medium text-primary">{segment.segment.meta.name}</h1>
|
|
426
|
+
<span className="text-xs text-tertiary">{segment.segment.meta.category}</span>
|
|
427
|
+
</div>
|
|
428
|
+
<div className="flex items-center gap-2">
|
|
429
|
+
<PreviewToolbar
|
|
430
|
+
zoom={viewSettings.zoom}
|
|
431
|
+
background={viewSettings.background}
|
|
432
|
+
onZoomChange={viewSettings.setZoom}
|
|
433
|
+
onBackgroundChange={viewSettings.setBackground}
|
|
434
|
+
/>
|
|
435
|
+
<div className="w-px h-4 bg-[--border]" />
|
|
436
|
+
<ViewportSelector
|
|
437
|
+
viewport={viewSettings.viewport}
|
|
438
|
+
customSize={viewSettings.customSize}
|
|
439
|
+
onViewportChange={viewSettings.setViewport}
|
|
440
|
+
onCustomSizeChange={viewSettings.setCustomSize}
|
|
441
|
+
/>
|
|
442
|
+
<div className="w-px h-4 bg-[--border]" />
|
|
443
|
+
|
|
444
|
+
{figmaUrl && (
|
|
445
|
+
<>
|
|
446
|
+
<button
|
|
447
|
+
onClick={uiActions.toggleComparison}
|
|
448
|
+
className={clsx(
|
|
449
|
+
"p-1.5 rounded transition-colors",
|
|
450
|
+
uiState.showComparison
|
|
451
|
+
? "text-[--color-accent] bg-[--bg-hover]"
|
|
452
|
+
: "text-tertiary hover:text-primary hover:bg-[--bg-hover]"
|
|
453
|
+
)}
|
|
454
|
+
title={uiState.showComparison ? "Hide Figma comparison" : "Compare with Figma design"}
|
|
455
|
+
>
|
|
456
|
+
<CompareIcon className="w-4 h-4" />
|
|
457
|
+
</button>
|
|
458
|
+
<button
|
|
459
|
+
onClick={() => window.open(figmaUrl, '_blank', 'noopener,noreferrer')}
|
|
460
|
+
className="p-1.5 text-tertiary hover:text-primary hover:bg-[--bg-hover] rounded transition-colors"
|
|
461
|
+
title="View in Figma"
|
|
462
|
+
>
|
|
463
|
+
<FigmaIcon className="w-4 h-4" />
|
|
464
|
+
</button>
|
|
465
|
+
<div className="w-px h-4 bg-[--border]" />
|
|
466
|
+
</>
|
|
467
|
+
)}
|
|
468
|
+
|
|
469
|
+
{variant && (
|
|
470
|
+
<>
|
|
471
|
+
<button
|
|
472
|
+
onClick={() => {
|
|
473
|
+
const url = new URL(window.location.href);
|
|
474
|
+
// Clear the hash to avoid malformed URLs
|
|
475
|
+
url.hash = '';
|
|
476
|
+
url.searchParams.set('isolated', 'true');
|
|
477
|
+
url.searchParams.set('component', segment.segment.meta.name);
|
|
478
|
+
url.searchParams.set('variant', variant.name);
|
|
479
|
+
if (viewSettings.zoom !== 100) url.searchParams.set('zoom', String(viewSettings.zoom));
|
|
480
|
+
if (viewSettings.background !== 'transparent') url.searchParams.set('bg', viewSettings.background);
|
|
481
|
+
window.open(url.toString(), '_blank', 'noopener,noreferrer');
|
|
482
|
+
}}
|
|
483
|
+
className="p-1.5 text-tertiary hover:text-primary hover:bg-[--bg-hover] rounded transition-colors"
|
|
484
|
+
title="Open in new window"
|
|
485
|
+
>
|
|
486
|
+
<ExternalLinkIcon className="w-4 h-4" />
|
|
487
|
+
</button>
|
|
488
|
+
<ScreenshotButton componentName={segment.segment.meta.name} variantName={variant.name} />
|
|
489
|
+
<button
|
|
490
|
+
onClick={onCopyLink}
|
|
491
|
+
className={clsx(
|
|
492
|
+
"p-1.5 rounded transition-colors",
|
|
493
|
+
linkCopied
|
|
494
|
+
? "text-green-600 bg-green-100 dark:bg-green-900/30"
|
|
495
|
+
: "text-tertiary hover:text-primary hover:bg-[--bg-hover]"
|
|
496
|
+
)}
|
|
497
|
+
title="Copy link to share"
|
|
498
|
+
>
|
|
499
|
+
{linkCopied ? <CheckIcon className="w-4 h-4" /> : <LinkIcon className="w-4 h-4" />}
|
|
500
|
+
</button>
|
|
501
|
+
</>
|
|
502
|
+
)}
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Variant Tabs Bar Component
|
|
509
|
+
interface VariantTabsBarProps {
|
|
510
|
+
variants: any[];
|
|
511
|
+
activeIndex: number;
|
|
512
|
+
onSelect: (index: number) => void;
|
|
513
|
+
showMatrixView: boolean;
|
|
514
|
+
showMultiViewport: boolean;
|
|
515
|
+
onToggleMatrix: () => void;
|
|
516
|
+
onToggleMultiViewport: () => void;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView, showMultiViewport, onToggleMatrix, onToggleMultiViewport }: VariantTabsBarProps) {
|
|
520
|
+
return (
|
|
521
|
+
<div className="px-4 py-2 border-b border-[--border] bg-[--bg-primary] flex-shrink-0 flex items-center justify-between">
|
|
522
|
+
{!showMatrixView ? (
|
|
523
|
+
<VariantTabs variants={variants} activeIndex={activeIndex} onSelect={onSelect} />
|
|
524
|
+
) : (
|
|
525
|
+
<div className="text-sm text-secondary">Showing all {variants.length} variants</div>
|
|
526
|
+
)}
|
|
527
|
+
<div className="flex items-center gap-2 ml-4">
|
|
528
|
+
{variants.length > 1 && (
|
|
529
|
+
<button
|
|
530
|
+
onClick={onToggleMatrix}
|
|
531
|
+
className={clsx(
|
|
532
|
+
"flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium transition-colors",
|
|
533
|
+
showMatrixView
|
|
534
|
+
? "bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300"
|
|
535
|
+
: "text-tertiary hover:text-secondary hover:bg-[--bg-hover]"
|
|
536
|
+
)}
|
|
537
|
+
title={showMatrixView ? "Show single variant" : "Show all variants in grid"}
|
|
538
|
+
>
|
|
539
|
+
<GridIcon className="w-4 h-4" />
|
|
540
|
+
{showMatrixView ? "Exit Matrix" : "Matrix"}
|
|
541
|
+
</button>
|
|
542
|
+
)}
|
|
543
|
+
<button
|
|
544
|
+
onClick={onToggleMultiViewport}
|
|
545
|
+
className={clsx(
|
|
546
|
+
"flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium transition-colors",
|
|
547
|
+
showMultiViewport
|
|
548
|
+
? "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300"
|
|
549
|
+
: "text-tertiary hover:text-secondary hover:bg-[--bg-hover]"
|
|
550
|
+
)}
|
|
551
|
+
title={showMultiViewport ? "Exit multi-viewport" : "Show at multiple screen sizes"}
|
|
552
|
+
>
|
|
553
|
+
<DevicesIcon className="w-4 h-4" />
|
|
554
|
+
{showMultiViewport ? "Exit Responsive" : "Responsive"}
|
|
555
|
+
</button>
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// No variants message
|
|
562
|
+
interface NoVariantsMessageProps {
|
|
563
|
+
segment?: SegmentDefinition;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function NoVariantsMessage({ segment }: NoVariantsMessageProps) {
|
|
567
|
+
const skippedVariants = (segment?._generated as any)?.skippedVariants;
|
|
568
|
+
|
|
569
|
+
if (!skippedVariants || skippedVariants.length === 0) {
|
|
570
|
+
return <div className="flex items-center justify-center h-full text-secondary text-sm">No variants defined</div>;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return (
|
|
574
|
+
<div className="flex items-center justify-center h-full p-6">
|
|
575
|
+
<div className="p-6 bg-sky-50 dark:bg-sky-950 border border-sky-300 dark:border-sky-700 rounded-lg max-w-lg">
|
|
576
|
+
<div className="flex items-start gap-3">
|
|
577
|
+
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-sky-200 dark:bg-sky-800 flex items-center justify-center">
|
|
578
|
+
<span className="text-sky-700 dark:text-sky-200 text-lg">ℹ</span>
|
|
579
|
+
</div>
|
|
580
|
+
<div className="flex-1 min-w-0">
|
|
581
|
+
<h3 className="text-sm font-semibold text-sky-900 dark:text-sky-100">
|
|
582
|
+
{skippedVariants.length} variant{skippedVariants.length === 1 ? '' : 's'} skipped
|
|
583
|
+
</h3>
|
|
584
|
+
<p className="mt-1 text-xs text-sky-800 dark:text-sky-200">
|
|
585
|
+
These variants couldn't be rendered because they use syntax the parser doesn't support yet:
|
|
586
|
+
</p>
|
|
587
|
+
<ul className="mt-2 space-y-1">
|
|
588
|
+
{skippedVariants.map((sv: any, i: number) => (
|
|
589
|
+
<li key={i} className="text-xs text-sky-800 dark:text-sky-200">
|
|
590
|
+
<span className="font-semibold">{sv.name}:</span>{' '}
|
|
591
|
+
<span className="text-sky-700 dark:text-sky-300">{sv.reason}</span>
|
|
592
|
+
</li>
|
|
593
|
+
))}
|
|
594
|
+
</ul>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
</div>
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Empty variant message
|
|
603
|
+
interface EmptyVariantMessageProps {
|
|
604
|
+
reason: string;
|
|
605
|
+
variantName: string;
|
|
606
|
+
hint?: string;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function EmptyVariantMessage({ reason, variantName, hint }: EmptyVariantMessageProps) {
|
|
610
|
+
return (
|
|
611
|
+
<div className="p-6 bg-amber-50 dark:bg-amber-950 border border-amber-300 dark:border-amber-700 rounded-lg max-w-md">
|
|
612
|
+
<div className="flex items-start gap-3">
|
|
613
|
+
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-amber-200 dark:bg-amber-800 flex items-center justify-center">
|
|
614
|
+
<span className="text-amber-700 dark:text-amber-200 text-lg">⚠</span>
|
|
615
|
+
</div>
|
|
616
|
+
<div className="flex-1 min-w-0">
|
|
617
|
+
<h3 className="text-sm font-semibold text-amber-900 dark:text-amber-100">
|
|
618
|
+
Variant "{variantName}" rendered empty
|
|
619
|
+
</h3>
|
|
620
|
+
<p className="mt-1 text-xs text-amber-800 dark:text-amber-200">{reason}</p>
|
|
621
|
+
{hint && (
|
|
622
|
+
<p className="mt-2 text-xs text-amber-700 dark:text-amber-300">
|
|
623
|
+
<strong>Tip:</strong> {hint}
|
|
624
|
+
</p>
|
|
625
|
+
)}
|
|
626
|
+
<div className="mt-3 text-xs text-amber-700 dark:text-amber-300">
|
|
627
|
+
<strong>Common causes:</strong>
|
|
628
|
+
<ul className="mt-1 ml-4 list-disc space-y-0.5">
|
|
629
|
+
<li>Component requires props that weren't provided</li>
|
|
630
|
+
<li>Component renders conditionally and conditions aren't met</li>
|
|
631
|
+
<li>Story args reference variables that don't exist in this context</li>
|
|
632
|
+
</ul>
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
);
|
|
638
|
+
}
|