@fragments-sdk/cli 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/dist/bin.js +26 -8
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ZDA3PLQ6.js → chunk-5G3VZH43.js} +2 -2
  4. package/dist/{chunk-566BNPQZ.js → chunk-HRFUSSZI.js} +25 -6
  5. package/dist/chunk-HRFUSSZI.js.map +1 -0
  6. package/dist/{chunk-CAMXG5HJ.js → chunk-ZM4ZQZWZ.js} +2 -2
  7. package/dist/{generate-BGKTKO6E.js → generate-FBHSXR3D.js} +2 -2
  8. package/dist/index.js +2 -2
  9. package/dist/{init-Q53R5Q2T.js → init-UFGK5TCN.js} +77 -6
  10. package/dist/init-UFGK5TCN.js.map +1 -0
  11. package/dist/{scan-OQU7M4GH.js → scan-CJF2DOQW.js} +3 -3
  12. package/dist/{scan-generate-T5QNUG7N.js → scan-generate-SJAN5MVI.js} +2 -2
  13. package/dist/snapshot-SV2JOFZH.js +139 -0
  14. package/dist/snapshot-SV2JOFZH.js.map +1 -0
  15. package/dist/{test-2CSOSS3B.js → test-Z5LVO724.js} +2 -2
  16. package/dist/{tokens-DXEGYTOJ.js → tokens-CE46OTMD.js} +2 -2
  17. package/dist/{viewer-DBEPYM3G.js → viewer-DLLJIMCK.js} +69 -47
  18. package/dist/viewer-DLLJIMCK.js.map +1 -0
  19. package/package.json +6 -14
  20. package/src/bin.ts +30 -0
  21. package/src/commands/init.ts +76 -1
  22. package/src/commands/snapshot.ts +197 -0
  23. package/src/core/loader.ts +38 -8
  24. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  25. package/src/viewer/server.ts +37 -22
  26. package/src/viewer/vite-plugin.ts +25 -9
  27. package/dist/chunk-566BNPQZ.js.map +0 -1
  28. package/dist/init-Q53R5Q2T.js.map +0 -1
  29. package/dist/viewer-DBEPYM3G.js.map +0 -1
  30. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  31. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  32. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  33. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  34. package/src/viewer/assets/fragments-logo.ts +0 -4
  35. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  36. package/src/viewer/components/ActionCapture.tsx +0 -172
  37. package/src/viewer/components/ActionsPanel.tsx +0 -332
  38. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  39. package/src/viewer/components/App.tsx +0 -582
  40. package/src/viewer/components/BottomPanel.tsx +0 -288
  41. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  42. package/src/viewer/components/CodePanel.tsx +0 -118
  43. package/src/viewer/components/CommandPalette.tsx +0 -392
  44. package/src/viewer/components/ComponentDocView.tsx +0 -164
  45. package/src/viewer/components/ComponentGraph.tsx +0 -380
  46. package/src/viewer/components/ComponentHeader.tsx +0 -88
  47. package/src/viewer/components/ContractPanel.tsx +0 -241
  48. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  49. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  50. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  51. package/src/viewer/components/FragmentEditor.tsx +0 -525
  52. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  53. package/src/viewer/components/HeaderSearch.tsx +0 -24
  54. package/src/viewer/components/HealthDashboard.tsx +0 -441
  55. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  56. package/src/viewer/components/Icons.tsx +0 -479
  57. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  58. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  59. package/src/viewer/components/IsolatedRender.tsx +0 -113
  60. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  61. package/src/viewer/components/LandingPage.tsx +0 -421
  62. package/src/viewer/components/Layout.tsx +0 -27
  63. package/src/viewer/components/LeftSidebar.tsx +0 -472
  64. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  65. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  66. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  67. package/src/viewer/components/PanelShell.tsx +0 -161
  68. package/src/viewer/components/PerformancePanel.tsx +0 -304
  69. package/src/viewer/components/PreviewArea.tsx +0 -472
  70. package/src/viewer/components/PreviewAside.tsx +0 -168
  71. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  72. package/src/viewer/components/PreviewPane.tsx +0 -149
  73. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  74. package/src/viewer/components/PropsEditor.tsx +0 -506
  75. package/src/viewer/components/PropsTable.tsx +0 -111
  76. package/src/viewer/components/RelationsSection.tsx +0 -88
  77. package/src/viewer/components/ResizablePanel.tsx +0 -271
  78. package/src/viewer/components/RightSidebar.tsx +0 -102
  79. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  80. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  81. package/src/viewer/components/Sidebar.tsx +0 -169
  82. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  83. package/src/viewer/components/ThemeProvider.tsx +0 -42
  84. package/src/viewer/components/Toast.tsx +0 -3
  85. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  86. package/src/viewer/components/TopToolbar.tsx +0 -159
  87. package/src/viewer/components/UsageSection.tsx +0 -95
  88. package/src/viewer/components/VariantMatrix.tsx +0 -388
  89. package/src/viewer/components/VariantRenderer.tsx +0 -131
  90. package/src/viewer/components/VariantTabs.tsx +0 -40
  91. package/src/viewer/components/ViewerHeader.tsx +0 -69
  92. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  93. package/src/viewer/components/ViewportSelector.tsx +0 -172
  94. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  95. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  96. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  97. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  98. package/src/viewer/components/viewer-utils.ts +0 -16
  99. package/src/viewer/composition-renderer.ts +0 -381
  100. package/src/viewer/constants/index.ts +0 -1
  101. package/src/viewer/constants/ui.ts +0 -166
  102. package/src/viewer/entry.tsx +0 -335
  103. package/src/viewer/hooks/index.ts +0 -2
  104. package/src/viewer/hooks/useA11yCache.ts +0 -383
  105. package/src/viewer/hooks/useA11yService.ts +0 -364
  106. package/src/viewer/hooks/useActions.ts +0 -138
  107. package/src/viewer/hooks/useAppState.ts +0 -147
  108. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  109. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  110. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  111. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  112. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  113. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  114. package/src/viewer/hooks/useUrlState.ts +0 -318
  115. package/src/viewer/hooks/useViewSettings.ts +0 -111
  116. package/src/viewer/index.html +0 -28
  117. package/src/viewer/intelligence/healthReport.ts +0 -505
  118. package/src/viewer/intelligence/styleDrift.ts +0 -340
  119. package/src/viewer/intelligence/usageScanner.ts +0 -309
  120. package/src/viewer/jsx-parser.ts +0 -486
  121. package/src/viewer/preview-frame-entry.tsx +0 -25
  122. package/src/viewer/preview-frame.html +0 -125
  123. package/src/viewer/public/favicon.ico +0 -0
  124. package/src/viewer/render-template.html +0 -68
  125. package/src/viewer/styles/globals.css +0 -278
  126. package/src/viewer/types/a11y.ts +0 -197
  127. package/src/viewer/utils/a11y-fixes.ts +0 -509
  128. package/src/viewer/utils/actionExport.ts +0 -372
  129. package/src/viewer/utils/colorSchemes.ts +0 -201
  130. package/src/viewer/utils/detectRelationships.ts +0 -256
  131. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  132. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  133. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  134. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  135. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  136. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  137. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  138. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  139. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  140. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  141. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  142. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  143. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  144. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -134
  145. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  146. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  147. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  148. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  149. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  150. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  151. package/src/viewer/vendor/shared/src/index.ts +0 -34
  152. package/src/viewer/vendor/shared/src/types.ts +0 -53
  153. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  154. package/src/viewer/webmcp/analytics.ts +0 -165
  155. package/src/viewer/webmcp/index.ts +0 -3
  156. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  157. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  158. package/src/viewer/webmcp/scan-utils.ts +0 -135
  159. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  160. package/src/viewer/webmcp/viewer-state.ts +0 -45
  161. /package/dist/{chunk-ZDA3PLQ6.js.map → chunk-5G3VZH43.js.map} +0 -0
  162. /package/dist/{chunk-CAMXG5HJ.js.map → chunk-ZM4ZQZWZ.js.map} +0 -0
  163. /package/dist/{generate-BGKTKO6E.js.map → generate-FBHSXR3D.js.map} +0 -0
  164. /package/dist/{scan-OQU7M4GH.js.map → scan-CJF2DOQW.js.map} +0 -0
  165. /package/dist/{scan-generate-T5QNUG7N.js.map → scan-generate-SJAN5MVI.js.map} +0 -0
  166. /package/dist/{test-2CSOSS3B.js.map → test-Z5LVO724.js.map} +0 -0
  167. /package/dist/{tokens-DXEGYTOJ.js.map → tokens-CE46OTMD.js.map} +0 -0
@@ -1,159 +0,0 @@
1
- import type { RefObject } from "react";
2
- import type { FragmentDefinition } from "../../core/index.js";
3
- import type { useViewSettings } from "../hooks/useViewSettings.js";
4
- import type { useAppState } from "../hooks/useAppState.js";
5
- import {
6
- Header,
7
- Stack,
8
- Text,
9
- Separator,
10
- Tooltip,
11
- Button,
12
- ThemeToggle,
13
- FragmentsLogo,
14
- } from "@fragments-sdk/ui";
15
- import { DeviceMobile, GridFour, SidebarSimple } from "@phosphor-icons/react";
16
- import { GitHubIcon, FigmaIcon, CompareIcon } from "./Icons.js";
17
- import { PreviewToolbar } from "./PreviewToolbar.js";
18
- import { HeaderSearch } from "./HeaderSearch.js";
19
- import { useTheme } from "./ThemeProvider.js";
20
- import { WebMCPStatusIndicator } from "./WebMCPStatusIndicator.js";
21
-
22
- /** Normalize category to Title Case for display */
23
- function titleCase(str: string): string {
24
- return str.replace(/\b\w/g, (c) => c.toUpperCase());
25
- }
26
-
27
- interface TopToolbarProps {
28
- fragment: { path: string; fragment: FragmentDefinition };
29
- viewSettings: ReturnType<typeof useViewSettings>;
30
- uiState: ReturnType<typeof useAppState>["state"];
31
- uiActions: ReturnType<typeof useAppState>["actions"];
32
- figmaUrl?: string;
33
- searchQuery: string;
34
- onSearchChange: (value: string) => void;
35
- searchInputRef: RefObject<HTMLInputElement>;
36
- }
37
-
38
- export function TopToolbar({
39
- fragment,
40
- viewSettings,
41
- uiState,
42
- uiActions,
43
- figmaUrl,
44
- searchQuery,
45
- onSearchChange,
46
- searchInputRef,
47
- }: TopToolbarProps) {
48
- const { setTheme, resolvedTheme } = useTheme();
49
- return (
50
- <Header aria-label="Component preview toolbar">
51
- <Header.Trigger />
52
- <Header.Brand>
53
- <Stack direction="row" align="center" gap="sm">
54
- <FragmentsLogo size={20} />
55
- <Text weight="medium" size="sm">
56
- {fragment.fragment.meta.name}
57
- </Text>
58
- <Text size="xs" color="tertiary">
59
- {titleCase(fragment.fragment.meta.category || '')}
60
- </Text>
61
- </Stack>
62
- </Header.Brand>
63
- <HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
64
- <Header.Spacer />
65
- <Header.Actions>
66
- <PreviewToolbar zoom={viewSettings.zoom} onZoomChange={viewSettings.setZoom} />
67
- <Separator orientation="vertical" style={{ height: "16px" }} />
68
- <Tooltip content={uiState.showMatrixView ? "Disable matrix view" : "Enable matrix view"}>
69
- <Button
70
- variant={uiState.showMatrixView ? "secondary" : "ghost"}
71
- size="sm"
72
- icon
73
- aria-pressed={uiState.showMatrixView}
74
- aria-label="Toggle matrix view"
75
- onClick={() => uiActions.setMatrixView(!uiState.showMatrixView)}
76
- >
77
- <GridFour size={16} />
78
- </Button>
79
- </Tooltip>
80
- <Tooltip
81
- content={uiState.showMultiViewport ? "Disable responsive view" : "Enable responsive view"}
82
- >
83
- <Button
84
- variant={uiState.showMultiViewport ? "secondary" : "ghost"}
85
- size="sm"
86
- icon
87
- aria-pressed={uiState.showMultiViewport}
88
- aria-label="Toggle responsive view"
89
- onClick={() => uiActions.setMultiViewport(!uiState.showMultiViewport)}
90
- >
91
- <DeviceMobile size={16} />
92
- </Button>
93
- </Tooltip>
94
- <Separator orientation="vertical" style={{ height: "16px" }} />
95
- {figmaUrl && (
96
- <>
97
- <Tooltip
98
- content={
99
- uiState.showComparison ? "Hide Figma comparison" : "Compare with Figma design"
100
- }
101
- >
102
- <Button
103
- variant={uiState.showComparison ? "secondary" : "ghost"}
104
- size="sm"
105
- icon
106
- onClick={uiActions.toggleComparison}
107
- >
108
- <CompareIcon style={{ width: "16px", height: "16px" }} />
109
- </Button>
110
- </Tooltip>
111
- <Tooltip content="View in Figma">
112
- <Button
113
- onClick={() => window.open(figmaUrl, "_blank", "noopener,noreferrer")}
114
- variant="ghost"
115
- size="sm"
116
- icon
117
- >
118
- <FigmaIcon style={{ width: "16px", height: "16px" }} />
119
- </Button>
120
- </Tooltip>
121
- <Separator orientation="vertical" style={{ height: "16px" }} />
122
- </>
123
- )}
124
- <WebMCPStatusIndicator />
125
- <Tooltip content={uiState.showAside ? "Hide side panel" : "Show side panel"}>
126
- <Button
127
- variant={uiState.showAside ? "secondary" : "ghost"}
128
- size="sm"
129
- icon
130
- aria-pressed={uiState.showAside}
131
- aria-label="Toggle side panel"
132
- onClick={uiActions.toggleAside}
133
- >
134
- <SidebarSimple size={16} style={{ transform: "scaleX(-1)" }} />
135
- </Button>
136
- </Tooltip>
137
- <Separator orientation="vertical" style={{ height: "16px" }} />
138
- <ThemeToggle
139
- size="sm"
140
- value={resolvedTheme}
141
- onValueChange={(value) => setTheme(value)}
142
- aria-label={`Theme: ${resolvedTheme}`}
143
- />
144
- <Button
145
- as="a"
146
- variant="ghost"
147
- size="sm"
148
- icon
149
- href="https://github.com/ConanMcN/fragments"
150
- target="_blank"
151
- rel="noopener noreferrer"
152
- aria-label="View on GitHub"
153
- >
154
- <GitHubIcon />
155
- </Button>
156
- </Header.Actions>
157
- </Header>
158
- );
159
- }
@@ -1,95 +0,0 @@
1
- import type { FragmentUsage } from '../../core/index.js';
2
- import { CheckIcon, XIcon, AccessibilityIcon } from './Icons.js';
3
-
4
- interface UsageSectionProps {
5
- usage: FragmentUsage;
6
- }
7
-
8
- export function UsageSection({ usage }: UsageSectionProps) {
9
- const hasWhen = usage.when && usage.when.length > 0;
10
- const hasWhenNot = usage.whenNot && usage.whenNot.length > 0;
11
- const hasGuidelines = usage.guidelines && usage.guidelines.length > 0;
12
- const hasAccessibility = usage.accessibility && usage.accessibility.length > 0;
13
-
14
- if (!hasWhen && !hasWhenNot && !hasGuidelines && !hasAccessibility) {
15
- return null;
16
- }
17
-
18
- return (
19
- <section id="usage" style={{ scrollMarginTop: '96px' }}>
20
- <h2 style={{ fontSize: '16px', fontWeight: 600, color: 'var(--text-primary)', marginBottom: '20px' }}>Usage</h2>
21
-
22
- {/* When to use / When not to use */}
23
- {(hasWhen || hasWhenNot) && (
24
- <div style={{ display: 'grid', gridTemplateColumns: hasWhen && hasWhenNot ? '1fr 1fr' : '1fr', gap: '32px', marginBottom: '32px' }}>
25
- {hasWhen && (
26
- <div style={{ padding: '16px', borderRadius: '12px', background: 'var(--color-success-bg)', border: '1px solid rgba(16, 163, 127, 0.2)' }}>
27
- <h3 style={{ fontSize: '13px', fontWeight: 500, color: 'var(--color-success)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '8px' }}>
28
- <CheckIcon style={{ width: '16px', height: '16px' }} />
29
- When to use
30
- </h3>
31
- <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '8px' }}>
32
- {usage.when!.map((item, index) => (
33
- <li key={index} style={{ fontSize: '13px', color: 'var(--text-primary)', lineHeight: 1.6, display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
34
- <span style={{ color: 'var(--color-success)', marginTop: '6px', fontSize: '12px' }}>&#8226;</span>
35
- <span>{item}</span>
36
- </li>
37
- ))}
38
- </ul>
39
- </div>
40
- )}
41
-
42
- {hasWhenNot && (
43
- <div style={{ padding: '16px', borderRadius: '12px', background: 'var(--color-danger-bg)', border: '1px solid rgba(239, 68, 68, 0.2)' }}>
44
- <h3 style={{ fontSize: '13px', fontWeight: 500, color: 'var(--color-danger)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '8px' }}>
45
- <XIcon style={{ width: '16px', height: '16px' }} />
46
- When not to use
47
- </h3>
48
- <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '8px' }}>
49
- {usage.whenNot!.map((item, index) => (
50
- <li key={index} style={{ fontSize: '13px', color: 'var(--text-primary)', lineHeight: 1.6, display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
51
- <span style={{ color: 'var(--color-danger)', marginTop: '6px', fontSize: '12px' }}>&#8226;</span>
52
- <span>{item}</span>
53
- </li>
54
- ))}
55
- </ul>
56
- </div>
57
- )}
58
- </div>
59
- )}
60
-
61
- {/* Guidelines */}
62
- {hasGuidelines && (
63
- <div style={{ marginBottom: '24px' }}>
64
- <h3 style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', marginBottom: '12px' }}>Guidelines</h3>
65
- <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '8px' }}>
66
- {usage.guidelines!.map((item, index) => (
67
- <li key={index} style={{ fontSize: '13px', color: 'var(--text-secondary)', lineHeight: 1.6, display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
68
- <span style={{ color: 'var(--color-accent)', marginTop: '6px', fontSize: '12px' }}>&#8226;</span>
69
- <span>{item}</span>
70
- </li>
71
- ))}
72
- </ul>
73
- </div>
74
- )}
75
-
76
- {/* Accessibility */}
77
- {hasAccessibility && (
78
- <div style={{ padding: '16px', borderRadius: '12px', border: '1px solid var(--border)', background: 'var(--bg-secondary)' }}>
79
- <h3 style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '8px' }}>
80
- <AccessibilityIcon style={{ width: '16px', height: '16px', color: 'var(--text-secondary)' }} />
81
- Accessibility
82
- </h3>
83
- <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '8px' }}>
84
- {usage.accessibility!.map((item, index) => (
85
- <li key={index} style={{ fontSize: '13px', color: 'var(--text-secondary)', lineHeight: 1.6, display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
86
- <span style={{ color: 'var(--text-tertiary)', marginTop: '6px', fontSize: '12px' }}>&#8226;</span>
87
- <span>{item}</span>
88
- </li>
89
- ))}
90
- </ul>
91
- </div>
92
- )}
93
- </section>
94
- );
95
- }
@@ -1,388 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * Variant Matrix View - Display all variants in a grid
4
- *
5
- * Shows all variants of a component simultaneously, making it easy to:
6
- * - Compare states/variants at a glance
7
- * - Spot visual inconsistencies
8
- * - Review all component states quickly
9
- *
10
- * Uses virtualization to only render visible variants for better performance.
11
- */
12
-
13
- import { useState, useMemo, useRef, useCallback } from "react";
14
- import { useVirtualizer } from "@tanstack/react-virtual";
15
- import { Box, Stack, Text, Button, Badge, EmptyState } from "@fragments-sdk/ui";
16
- import type { FragmentVariant } from "../../core/index.js";
17
- import { ErrorBoundary } from "./ErrorBoundary.js";
18
- import { FragmentRenderer, LoaderIndicator } from "./FragmentRenderer.js";
19
- import { IsolatedPreviewFrame } from "./IsolatedPreviewFrame.js";
20
- import { ChevronDownIcon } from "./Icons.js";
21
-
22
- interface VariantMatrixProps {
23
- /** All variants to display */
24
- variants: FragmentVariant[];
25
- /** Component name for error display */
26
- componentName: string;
27
- /** Fragment path for iframe rendering */
28
- fragmentPath: string;
29
- /** Current zoom level */
30
- zoom: number;
31
- /** Preview theme */
32
- previewTheme: "light" | "dark";
33
- /** Whether to use iframe isolation */
34
- useIframeIsolation?: boolean;
35
- /** Callback when a variant is clicked to focus on it */
36
- onSelectVariant?: (index: number) => void;
37
- }
38
-
39
- type GridSize = "small" | "medium" | "large";
40
-
41
- interface GridConfig {
42
- gridTemplateColumns: string;
43
- minHeight: string;
44
- heightPx: number; // For virtualization
45
- scale: number;
46
- colCount: number; // Default column count for virtualization
47
- }
48
-
49
- const GRID_SIZES: Record<GridSize, GridConfig> = {
50
- small: { gridTemplateColumns: "repeat(4, 1fr)", minHeight: "150px", heightPx: 150, scale: 0.5, colCount: 4 },
51
- medium: { gridTemplateColumns: "repeat(3, 1fr)", minHeight: "200px", heightPx: 200, scale: 0.75, colCount: 3 },
52
- large: { gridTemplateColumns: "repeat(2, 1fr)", minHeight: "300px", heightPx: 300, scale: 1, colCount: 2 },
53
- };
54
-
55
- /** Threshold for enabling virtualization */
56
- const VIRTUALIZATION_THRESHOLD = 12;
57
-
58
- export function VariantMatrix({
59
- variants,
60
- componentName,
61
- fragmentPath,
62
- zoom,
63
- previewTheme,
64
- useIframeIsolation = true,
65
- onSelectVariant,
66
- }: VariantMatrixProps) {
67
- const [gridSize, setGridSize] = useState<GridSize>("medium");
68
- const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
69
- const scrollRef = useRef<HTMLDivElement>(null);
70
-
71
- const gridConfig = GRID_SIZES[gridSize];
72
- const effectiveScale = (zoom / 100) * gridConfig.scale;
73
-
74
- // Determine if we should use virtualization
75
- const useVirtualization = variants.length > VIRTUALIZATION_THRESHOLD;
76
-
77
- // Calculate number of rows for virtualization
78
- const columns = gridConfig.colCount;
79
- const rowCount = Math.ceil(variants.length / columns);
80
-
81
- // Row height includes card height + gap
82
- const rowHeight = gridConfig.heightPx + 16; // 16px gap
83
-
84
- const rowVirtualizer = useVirtualizer({
85
- count: rowCount,
86
- getScrollElement: () => scrollRef.current,
87
- estimateSize: () => rowHeight,
88
- overscan: 2, // Render 2 extra rows above/below for smooth scrolling
89
- });
90
-
91
- if (variants.length === 0) {
92
- return (
93
- <EmptyState style={{ height: '100%' }}>
94
- <EmptyState.Title>No variants to display</EmptyState.Title>
95
- </EmptyState>
96
- );
97
- }
98
-
99
- return (
100
- <Stack style={{ height: '100%' }}>
101
- {/* Toolbar */}
102
- <Box paddingX="md" paddingY="sm" borderBottom background="secondary" style={{ flexShrink: 0 }}>
103
- <Stack direction="row" align="center" justify="between">
104
- <Text size="sm" color="secondary">
105
- {variants.length} variant{variants.length !== 1 ? "s" : ""}
106
- {useVirtualization && <Text as="span" size="xs" color="tertiary"> (virtualized)</Text>}
107
- </Text>
108
- <Stack direction="row" align="center" gap="sm">
109
- <Text size="xs" color="tertiary">Grid size:</Text>
110
- <Stack direction="row" style={{ borderRadius: 6, border: '1px solid var(--border)', overflow: 'hidden' }}>
111
- {(["small", "medium", "large"] as GridSize[]).map((size) => (
112
- <Button
113
- key={size}
114
- variant={gridSize === size ? "secondary" : "ghost"}
115
- size="sm"
116
- onClick={() => setGridSize(size)}
117
- style={{ textTransform: 'capitalize', borderRadius: 0 }}
118
- >
119
- {size}
120
- </Button>
121
- ))}
122
- </Stack>
123
- </Stack>
124
- </Stack>
125
- </Box>
126
-
127
- {/* Grid - Virtualized or Regular */}
128
- {useVirtualization ? (
129
- <Box ref={scrollRef} overflow="auto" padding="md" style={{ flex: 1 }}>
130
- <div
131
- style={{
132
- height: `${rowVirtualizer.getTotalSize()}px`,
133
- width: "100%",
134
- position: "relative",
135
- }}
136
- >
137
- {rowVirtualizer.getVirtualItems().map((virtualRow) => {
138
- const startIndex = virtualRow.index * columns;
139
- const rowVariants = variants.slice(startIndex, startIndex + columns);
140
-
141
- return (
142
- <div
143
- key={virtualRow.key}
144
- style={{
145
- position: "absolute",
146
- top: 0,
147
- left: 0,
148
- width: "100%",
149
- height: `${virtualRow.size}px`,
150
- transform: `translateY(${virtualRow.start}px)`,
151
- }}
152
- >
153
- <div style={{
154
- display: 'grid',
155
- gap: 16,
156
- gridTemplateColumns: gridConfig.gridTemplateColumns,
157
- height: gridConfig.minHeight,
158
- }}>
159
- {rowVariants.map((variant, colIndex) => {
160
- const index = startIndex + colIndex;
161
- return (
162
- <VariantCard
163
- key={variant.name}
164
- variant={variant}
165
- index={index}
166
- componentName={componentName}
167
- fragmentPath={fragmentPath}
168
- scale={effectiveScale}
169
- minHeight={gridConfig.minHeight}
170
- previewTheme={previewTheme}
171
- useIframeIsolation={useIframeIsolation}
172
- isHovered={hoveredIndex === index}
173
- onHover={() => setHoveredIndex(index)}
174
- onLeave={() => setHoveredIndex(null)}
175
- onClick={() => onSelectVariant?.(index)}
176
- />
177
- );
178
- })}
179
- </div>
180
- </div>
181
- );
182
- })}
183
- </div>
184
- </Box>
185
- ) : (
186
- <Box overflow="auto" padding="md" style={{ flex: 1 }}>
187
- <div style={{
188
- display: 'grid',
189
- gap: 16,
190
- gridTemplateColumns: gridConfig.gridTemplateColumns,
191
- }}>
192
- {variants.map((variant, index) => (
193
- <VariantCard
194
- key={variant.name}
195
- variant={variant}
196
- index={index}
197
- componentName={componentName}
198
- fragmentPath={fragmentPath}
199
- scale={effectiveScale}
200
- minHeight={gridConfig.minHeight}
201
- previewTheme={previewTheme}
202
- useIframeIsolation={useIframeIsolation}
203
- isHovered={hoveredIndex === index}
204
- onHover={() => setHoveredIndex(index)}
205
- onLeave={() => setHoveredIndex(null)}
206
- onClick={() => onSelectVariant?.(index)}
207
- />
208
- ))}
209
- </div>
210
- </Box>
211
- )}
212
- </Stack>
213
- );
214
- }
215
-
216
- interface VariantCardProps {
217
- variant: FragmentVariant;
218
- index: number;
219
- componentName: string;
220
- fragmentPath: string;
221
- scale: number;
222
- minHeight: string;
223
- previewTheme: "light" | "dark";
224
- useIframeIsolation: boolean;
225
- isHovered: boolean;
226
- onHover: () => void;
227
- onLeave: () => void;
228
- onClick: () => void;
229
- }
230
-
231
- function VariantCard({
232
- variant,
233
- index,
234
- componentName,
235
- fragmentPath,
236
- scale,
237
- minHeight,
238
- previewTheme,
239
- useIframeIsolation,
240
- isHovered,
241
- onHover,
242
- onLeave,
243
- onClick,
244
- }: VariantCardProps) {
245
- return (
246
- <div
247
- style={{
248
- position: 'relative',
249
- borderRadius: 8,
250
- border: isHovered
251
- ? '2px solid #3b82f6'
252
- : '1px solid var(--border)',
253
- overflow: 'hidden',
254
- transition: 'border-color 0.2s, box-shadow 0.2s',
255
- cursor: 'pointer',
256
- minHeight,
257
- boxShadow: isHovered
258
- ? '0 10px 15px -3px rgba(0,0,0,0.1), 0 0 0 3px rgba(59,130,246,0.2)'
259
- : 'none',
260
- }}
261
- onMouseEnter={onHover}
262
- onMouseLeave={onLeave}
263
- onClick={onClick}
264
- >
265
- {/* Header overlay - keep inline styles (CSS art) */}
266
- <div style={{
267
- position: 'absolute',
268
- top: 0,
269
- left: 0,
270
- right: 0,
271
- zIndex: 10,
272
- padding: '4px 8px',
273
- background: 'linear-gradient(to bottom, rgba(0,0,0,0.6), transparent)',
274
- }}>
275
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
276
- <span style={{
277
- fontSize: 12,
278
- fontWeight: 500,
279
- color: '#ffffff',
280
- overflow: 'hidden',
281
- textOverflow: 'ellipsis',
282
- whiteSpace: 'nowrap',
283
- }}>
284
- {variant.name}
285
- </span>
286
- <span style={{ fontSize: 10, color: 'rgba(255,255,255,0.7)' }}>
287
- #{index + 1}
288
- </span>
289
- </div>
290
- </div>
291
-
292
- {/* Click to view overlay */}
293
- <div
294
- style={{
295
- position: 'absolute',
296
- inset: 0,
297
- zIndex: 10,
298
- display: 'flex',
299
- alignItems: 'center',
300
- justifyContent: 'center',
301
- background: 'rgba(0,0,0,0.4)',
302
- transition: 'opacity 0.2s',
303
- opacity: isHovered ? 1 : 0,
304
- pointerEvents: isHovered ? 'auto' : 'none',
305
- }}
306
- >
307
- <span style={{
308
- padding: '6px 12px',
309
- background: '#2563eb',
310
- color: '#ffffff',
311
- fontSize: 12,
312
- fontWeight: 500,
313
- borderRadius: 9999,
314
- boxShadow: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)',
315
- }}>
316
- Click to focus
317
- </span>
318
- </div>
319
-
320
- {/* Preview content */}
321
- <div
322
- data-theme={previewTheme}
323
- style={{
324
- height: '100%',
325
- width: '100%',
326
- overflow: 'hidden',
327
- display: 'flex',
328
- alignItems: 'center',
329
- justifyContent: 'center',
330
- }}
331
- >
332
- {useIframeIsolation ? (
333
- <IsolatedPreviewFrame
334
- fragmentPath={fragmentPath}
335
- variantName={variant.name}
336
- theme={previewTheme}
337
- width="100%"
338
- height="100%"
339
- minHeight={minHeight}
340
- />
341
- ) : (
342
- <div
343
- style={{
344
- padding: 16,
345
- transform: `scale(${scale})`,
346
- }}
347
- >
348
- <ErrorBoundary
349
- componentName={componentName}
350
- fallback={
351
- <Text size="xs" style={{ color: 'var(--color-danger)' }}>
352
- Error rendering variant
353
- </Text>
354
- }
355
- >
356
- <FragmentRenderer variant={variant}>
357
- {(content, isLoading, error) => {
358
- if (isLoading) {
359
- return (
360
- <Stack align="center" justify="center" style={{ padding: 16 }}>
361
- <LoaderIndicator />
362
- </Stack>
363
- );
364
- }
365
- if (error) {
366
- return (
367
- <Text size="xs" style={{ color: 'var(--color-danger)', padding: 8 }}>
368
- {error.message}
369
- </Text>
370
- );
371
- }
372
- return content;
373
- }}
374
- </FragmentRenderer>
375
- </ErrorBoundary>
376
- </div>
377
- )}
378
- </div>
379
-
380
- {/* Tags/badges */}
381
- {variant.hasPlayFunction && (
382
- <div style={{ position: 'absolute', bottom: 8, right: 8, zIndex: 10 }}>
383
- <Badge variant="info" size="sm">play</Badge>
384
- </div>
385
- )}
386
- </div>
387
- );
388
- }