@fragments-sdk/cli 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +83 -33
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-WI6SLMSO.js → chunk-5GT62FCB.js} +2 -2
- package/dist/{chunk-CJEGT3WD.js → chunk-BW3ZATBW.js} +20 -3
- package/dist/chunk-BW3ZATBW.js.map +1 -0
- package/dist/{chunk-2JIKCJX3.js → chunk-D7372LQX.js} +13 -6
- package/dist/chunk-D7372LQX.js.map +1 -0
- package/dist/chunk-EZYXYWNF.js +131 -0
- package/dist/chunk-EZYXYWNF.js.map +1 -0
- package/dist/{chunk-NGIMCIK2.js → chunk-GF6OVPIN.js} +2 -2
- package/dist/{chunk-GOVI6COW.js → chunk-NVSPGSKB.js} +12 -4
- package/dist/chunk-NVSPGSKB.js.map +1 -0
- package/dist/core/index.d.ts +105 -3
- package/dist/core/index.js +12 -2
- package/dist/{defineFragment-D0UTve-I.d.ts → defineFragment-CBMS7Bab.d.ts} +21 -1
- package/dist/generate-LQA2R7FN.js +461 -0
- package/dist/generate-LQA2R7FN.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/{init-KSAAS7X3.js → init-2GEGVIUQ.js} +13 -75
- package/dist/init-2GEGVIUQ.js.map +1 -0
- package/dist/mcp-bin.js +4 -3
- package/dist/mcp-bin.js.map +1 -1
- package/dist/{scan-65RH3QMM.js → scan-JGS65S7P.js} +6 -5
- package/dist/{service-A5GIGGGK.js → service-XP2EAJXD.js} +4 -3
- package/dist/{static-viewer-NSODM5VX.js → static-viewer-XCS7UJTO.js} +4 -3
- package/dist/storyFilters-3LUYAFZF.js +15 -0
- package/dist/storyFilters-3LUYAFZF.js.map +1 -0
- package/dist/{test-RPWZAYSJ.js → test-TD6TJNVY.js} +3 -3
- package/dist/{tokens-NIXSZRX7.js → tokens-2EXPCVP3.js} +5 -4
- package/dist/{tokens-NIXSZRX7.js.map → tokens-2EXPCVP3.js.map} +1 -1
- package/dist/{viewer-SBTJDMP7.js → viewer-RFA2KVBG.js} +243 -18
- package/dist/viewer-RFA2KVBG.js.map +1 -0
- package/package.json +1 -1
- package/src/build.ts +12 -2
- package/src/commands/build.ts +16 -2
- package/src/commands/generate.ts +383 -68
- package/src/commands/init.ts +9 -51
- package/src/core/config.ts +15 -2
- package/src/core/generators/typescript-extractor.ts +10 -0
- package/src/core/index.ts +15 -0
- package/src/core/schema.ts +10 -2
- package/src/core/storyFilters.test.ts +350 -0
- package/src/core/storyFilters.ts +253 -0
- package/src/core/types.ts +22 -0
- package/src/migrate/converter.ts +9 -1
- package/src/migrate/parser.ts +2 -0
- package/src/migrate/types.ts +2 -0
- package/src/setup.ts +69 -24
- package/src/viewer/__tests__/viewer-integration.test.ts +1 -1
- package/src/viewer/components/AccessibilityPanel.tsx +305 -312
- package/src/viewer/components/ActionsPanel.tsx +31 -29
- package/src/viewer/components/AllVariantsPreview.tsx +78 -0
- package/src/viewer/components/App.tsx +187 -740
- package/src/viewer/components/BottomPanel.tsx +228 -132
- package/src/viewer/components/CodePanel.tsx +1 -1
- package/src/viewer/components/CommandPalette.tsx +7 -10
- package/src/viewer/components/ComponentDocView.tsx +164 -0
- package/src/viewer/components/ComponentGraph.tsx +111 -142
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
- package/src/viewer/components/FigmaEmbed.tsx +20 -18
- package/src/viewer/components/FragmentEditor.tsx +92 -115
- package/src/viewer/components/HeaderSearch.tsx +24 -0
- package/src/viewer/components/HealthDashboard.tsx +16 -2
- package/src/viewer/components/Icons.tsx +9 -0
- package/src/viewer/components/InteractionsPanel.tsx +101 -117
- package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
- package/src/viewer/components/LandingPage.tsx +3 -3
- package/src/viewer/components/LeftSidebar.tsx +141 -63
- package/src/viewer/components/LoadErrorMessage.tsx +102 -0
- package/src/viewer/components/MultiViewportPreview.tsx +61 -142
- package/src/viewer/components/NoVariantsMessage.tsx +59 -0
- package/src/viewer/components/PanelShell.tsx +161 -0
- package/src/viewer/components/PerformancePanel.tsx +31 -28
- package/src/viewer/components/PreviewArea.tsx +1 -1
- package/src/viewer/components/PreviewAside.tsx +168 -0
- package/src/viewer/components/PreviewFrameHost.tsx +3 -3
- package/src/viewer/components/PropsEditor.tsx +70 -156
- package/src/viewer/components/ResizablePanel.tsx +103 -263
- package/src/viewer/components/RightSidebar.tsx +3 -9
- package/src/viewer/components/SkeletonLoader.tsx +13 -13
- package/src/viewer/components/TokenStylePanel.tsx +182 -209
- package/src/viewer/components/TopToolbar.tsx +159 -0
- package/src/viewer/components/VariantMatrix.tsx +42 -86
- package/src/viewer/components/VariantTabs.tsx +3 -3
- package/src/viewer/components/ViewerHeader.tsx +69 -0
- package/src/viewer/components/WebMCPDevTools.tsx +17 -23
- package/src/viewer/components/viewer-utils.ts +16 -0
- package/src/viewer/entry.tsx +5 -0
- package/src/viewer/hooks/useAppState.ts +27 -4
- package/src/viewer/hooks/usePreviewBridge.ts +2 -2
- package/src/viewer/preview-frame.html +6 -12
- package/src/viewer/server.ts +169 -2
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
- package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +6 -18
- package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
- package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +5 -16
- package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
- package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +122 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
- package/src/viewer/vendor/shared/src/index.ts +8 -0
- package/src/viewer/vendor/shared/src/types.ts +12 -0
- package/src/viewer/vite-plugin.ts +109 -4
- package/dist/chunk-2JIKCJX3.js.map +0 -1
- package/dist/chunk-CJEGT3WD.js.map +0 -1
- package/dist/chunk-GOVI6COW.js.map +0 -1
- package/dist/generate-35OIMW4Y.js +0 -252
- package/dist/generate-35OIMW4Y.js.map +0 -1
- package/dist/init-KSAAS7X3.js.map +0 -1
- package/dist/viewer-SBTJDMP7.js.map +0 -1
- /package/dist/{chunk-WI6SLMSO.js.map → chunk-5GT62FCB.js.map} +0 -0
- /package/dist/{chunk-NGIMCIK2.js.map → chunk-GF6OVPIN.js.map} +0 -0
- /package/dist/{scan-65RH3QMM.js.map → scan-JGS65S7P.js.map} +0 -0
- /package/dist/{service-A5GIGGGK.js.map → service-XP2EAJXD.js.map} +0 -0
- /package/dist/{static-viewer-NSODM5VX.js.map → static-viewer-XCS7UJTO.js.map} +0 -0
- /package/dist/{test-RPWZAYSJ.js.map → test-TD6TJNVY.js.map} +0 -0
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
import { useMemo, useState } from "react";
|
|
12
12
|
import type { FragmentDefinition, ComponentRelation, RelationshipType } from "../../core/index.js";
|
|
13
|
-
import { ChevronRightIcon,
|
|
13
|
+
import { ChevronRightIcon, WandIcon } from "./Icons.js";
|
|
14
14
|
import { detectAllRelationships, mergeRelationships } from "../utils/detectRelationships.js";
|
|
15
|
-
import { Stack, Text, Badge, Button,
|
|
15
|
+
import { Stack, Text, Badge, Button, CodeBlock, Separator, Box } from "@fragments-sdk/ui";
|
|
16
|
+
import { TreeStructure } from "@phosphor-icons/react";
|
|
17
|
+
import { PanelShell } from "./PanelShell.js";
|
|
16
18
|
|
|
17
19
|
interface ComponentGraphProps {
|
|
18
20
|
/** Current fragment definition */
|
|
@@ -148,46 +150,24 @@ export function ComponentGraph({ fragment, allFragments, onNavigate }: Component
|
|
|
148
150
|
}
|
|
149
151
|
};
|
|
150
152
|
|
|
151
|
-
// No fragment selected
|
|
152
|
-
if (!fragment) {
|
|
153
|
-
return (
|
|
154
|
-
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
155
|
-
<div style={{ padding: '16px', borderBottom: '1px solid var(--border)' }}>
|
|
156
|
-
<Text weight="medium">Component Graph</Text>
|
|
157
|
-
</div>
|
|
158
|
-
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
|
|
159
|
-
<EmptyState>
|
|
160
|
-
<EmptyState.Icon>
|
|
161
|
-
<EmptyIcon style={{ width: '24px', height: '24px' }} />
|
|
162
|
-
</EmptyState.Icon>
|
|
163
|
-
<EmptyState.Description>Select a component to view its relationships</EmptyState.Description>
|
|
164
|
-
</EmptyState>
|
|
165
|
-
</div>
|
|
166
|
-
</div>
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
153
|
const hasRelations = (groupedRelations && Object.values(groupedRelations).some(g => g.length > 0)) || reverseRelations.length > 0;
|
|
171
154
|
const detectedCount = detectedRelationships.length;
|
|
172
155
|
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
</EmptyState.Description>
|
|
189
|
-
<div style={{ marginTop: '16px', width: '100%' }}>
|
|
190
|
-
<CodeBlock language="typescript">{`relations: [
|
|
156
|
+
// Determine empty config
|
|
157
|
+
const emptyConfig = !fragment ? {
|
|
158
|
+
icon: <TreeStructure size={24} weight="regular" style={{ color: 'var(--text-tertiary)' }} />,
|
|
159
|
+
title: "No component selected",
|
|
160
|
+
description: "Select a component to view its relationships",
|
|
161
|
+
} : !hasRelations ? {
|
|
162
|
+
icon: <TreeStructure size={24} weight="regular" style={{ color: 'var(--text-tertiary)' }} />,
|
|
163
|
+
title: "No relationships defined",
|
|
164
|
+
description: (
|
|
165
|
+
<>
|
|
166
|
+
Add a <Box as="code" padding="xs" background="secondary" rounded="sm" style={{ fontSize: '12px', display: 'inline' }}>relations</Box> array to your fragment definition to visualize component connections.
|
|
167
|
+
</>
|
|
168
|
+
),
|
|
169
|
+
action: (
|
|
170
|
+
<CodeBlock language="typescript" code={`relations: [
|
|
191
171
|
{
|
|
192
172
|
component: "ButtonGroup",
|
|
193
173
|
relationship: "parent",
|
|
@@ -198,116 +178,105 @@ export function ComponentGraph({ fragment, allFragments, onNavigate }: Component
|
|
|
198
178
|
relationship: "alternative",
|
|
199
179
|
note: "Use when only an icon is needed"
|
|
200
180
|
}
|
|
201
|
-
]`}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
181
|
+
]`} />
|
|
182
|
+
),
|
|
183
|
+
} : undefined;
|
|
184
|
+
|
|
185
|
+
// Toolbar with badges + auto-detect toggle (only when we have data)
|
|
186
|
+
const toolbar = fragment && hasRelations ? (
|
|
187
|
+
<Stack direction="row" align="center" justify="between" style={{ width: '100%' }}>
|
|
188
|
+
<Stack direction="row" align="center" gap="sm">
|
|
189
|
+
<Badge size="sm">{fragment.meta.name}</Badge>
|
|
190
|
+
<Badge size="sm" variant="default">
|
|
191
|
+
{allRelationships.length + reverseRelations.length} connection{(allRelationships.length + reverseRelations.length) !== 1 ? 's' : ''}
|
|
192
|
+
</Badge>
|
|
193
|
+
</Stack>
|
|
194
|
+
{detectedCount > 0 && (
|
|
195
|
+
<AutoDetectToggle
|
|
196
|
+
showAutoDetected={showAutoDetected}
|
|
197
|
+
onToggle={() => setShowAutoDetected(!showAutoDetected)}
|
|
198
|
+
/>
|
|
199
|
+
)}
|
|
200
|
+
</Stack>
|
|
201
|
+
) : undefined;
|
|
208
202
|
|
|
209
203
|
return (
|
|
210
|
-
<
|
|
211
|
-
{/*
|
|
212
|
-
|
|
213
|
-
<Stack direction="
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
204
|
+
<PanelShell toolbar={toolbar} empty={emptyConfig}>
|
|
205
|
+
{/* Defined relations */}
|
|
206
|
+
{groupedRelations && (
|
|
207
|
+
<Stack direction="column" gap="md">
|
|
208
|
+
{(Object.entries(groupedRelations) as [RelationshipType, ComponentRelation[]][]).map(
|
|
209
|
+
([type, relations]) => {
|
|
210
|
+
if (relations.length === 0) return null;
|
|
211
|
+
const config = RELATIONSHIP_CONFIG[type];
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div key={type}>
|
|
215
|
+
<Stack direction="row" align="center" gap="sm" style={{ marginBottom: '8px' }}>
|
|
216
|
+
<Text size="xs" weight="medium" style={{ textTransform: 'uppercase', letterSpacing: '0.05em', color: config.color }}>
|
|
217
|
+
{config.label}
|
|
218
|
+
</Text>
|
|
219
|
+
<Separator style={{ flex: 1 }} />
|
|
220
|
+
</Stack>
|
|
221
|
+
<Stack direction="column" gap="sm">
|
|
222
|
+
{relations.map((relation, index) => (
|
|
223
|
+
<RelationCard
|
|
224
|
+
key={`${relation.component}-${index}`}
|
|
225
|
+
relation={relation}
|
|
226
|
+
config={config}
|
|
227
|
+
exists={componentExists(relation.component)}
|
|
228
|
+
onNavigate={handleNavigate}
|
|
229
|
+
/>
|
|
230
|
+
))}
|
|
231
|
+
</Stack>
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
220
235
|
)}
|
|
221
236
|
</Stack>
|
|
222
|
-
|
|
223
|
-
Relationships for <Text as="span" weight="medium" size="xs">{fragment.meta.name}</Text>
|
|
224
|
-
{showAutoDetected && detectedCount > 0 && (
|
|
225
|
-
<span style={{ marginLeft: '4px' }}>
|
|
226
|
-
({detectedCount} from variants)
|
|
227
|
-
</span>
|
|
228
|
-
)}
|
|
229
|
-
</Text>
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
|
-
{/* Graph visualization */}
|
|
233
|
-
<div style={{ flex: 1, overflowY: 'auto', padding: '16px' }}>
|
|
234
|
-
{/* Defined relations */}
|
|
235
|
-
{groupedRelations && (
|
|
236
|
-
<Stack direction="column" gap="md">
|
|
237
|
-
{(Object.entries(groupedRelations) as [RelationshipType, ComponentRelation[]][]).map(
|
|
238
|
-
([type, relations]) => {
|
|
239
|
-
if (relations.length === 0) return null;
|
|
240
|
-
const config = RELATIONSHIP_CONFIG[type];
|
|
237
|
+
)}
|
|
241
238
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
<Stack direction="column" gap="sm">
|
|
251
|
-
{relations.map((relation, index) => (
|
|
252
|
-
<RelationCard
|
|
253
|
-
key={`${relation.component}-${index}`}
|
|
254
|
-
relation={relation}
|
|
255
|
-
config={config}
|
|
256
|
-
exists={componentExists(relation.component)}
|
|
257
|
-
onNavigate={handleNavigate}
|
|
258
|
-
/>
|
|
259
|
-
))}
|
|
260
|
-
</Stack>
|
|
261
|
-
</div>
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
)}
|
|
239
|
+
{/* Reverse relations (inbound references) */}
|
|
240
|
+
{reverseRelations.length > 0 && (
|
|
241
|
+
<Box style={{ marginTop: '24px' }}>
|
|
242
|
+
<Stack direction="row" align="center" gap="sm" style={{ marginBottom: '8px' }}>
|
|
243
|
+
<Text size="xs" weight="medium" color="secondary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
|
244
|
+
Referenced By
|
|
245
|
+
</Text>
|
|
246
|
+
<Separator style={{ flex: 1 }} />
|
|
265
247
|
</Stack>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
relation={relation}
|
|
284
|
-
config={config}
|
|
285
|
-
exists={componentExists(relation.component)}
|
|
286
|
-
onNavigate={handleNavigate}
|
|
287
|
-
isReverse
|
|
288
|
-
/>
|
|
289
|
-
);
|
|
290
|
-
})}
|
|
291
|
-
</Stack>
|
|
292
|
-
</div>
|
|
293
|
-
)}
|
|
248
|
+
<Stack direction="column" gap="sm">
|
|
249
|
+
{reverseRelations.map(({ relation }, index) => {
|
|
250
|
+
const config = RELATIONSHIP_CONFIG[relation.relationship];
|
|
251
|
+
return (
|
|
252
|
+
<RelationCard
|
|
253
|
+
key={`reverse-${relation.component}-${index}`}
|
|
254
|
+
relation={relation}
|
|
255
|
+
config={config}
|
|
256
|
+
exists={componentExists(relation.component)}
|
|
257
|
+
onNavigate={handleNavigate}
|
|
258
|
+
isReverse
|
|
259
|
+
/>
|
|
260
|
+
);
|
|
261
|
+
})}
|
|
262
|
+
</Stack>
|
|
263
|
+
</Box>
|
|
264
|
+
)}
|
|
294
265
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
<
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
</div>
|
|
310
|
-
</div>
|
|
266
|
+
{/* Legend */}
|
|
267
|
+
<Box borderTop style={{ marginTop: '24px', paddingTop: '12px' }}>
|
|
268
|
+
<Stack direction="row" gap="md" style={{ flexWrap: 'wrap' }}>
|
|
269
|
+
{(Object.entries(RELATIONSHIP_CONFIG) as [RelationshipType, typeof RELATIONSHIP_CONFIG[RelationshipType]][]).map(
|
|
270
|
+
([type, config]) => (
|
|
271
|
+
<Stack key={type} direction="row" align="center" gap="xs">
|
|
272
|
+
<span style={{ width: '6px', height: '6px', borderRadius: '50%', backgroundColor: config.color, flexShrink: 0 }} />
|
|
273
|
+
<Text size="xs" color="tertiary">{config.label}</Text>
|
|
274
|
+
</Stack>
|
|
275
|
+
)
|
|
276
|
+
)}
|
|
277
|
+
</Stack>
|
|
278
|
+
</Box>
|
|
279
|
+
</PanelShell>
|
|
311
280
|
);
|
|
312
281
|
}
|
|
313
282
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { memo } from 'react';
|
|
7
7
|
import type { FragmentContract } from '../../core/index.js';
|
|
8
|
-
import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge } from '@fragments-sdk/ui';
|
|
8
|
+
import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge, Box } from '@fragments-sdk/ui';
|
|
9
9
|
|
|
10
10
|
interface ContractPanelProps {
|
|
11
11
|
contract?: FragmentContract;
|
|
@@ -37,7 +37,7 @@ export const ContractPanel = memo(function ContractPanel({
|
|
|
37
37
|
</EmptyState.Icon>
|
|
38
38
|
<EmptyState.Title>No contract metadata</EmptyState.Title>
|
|
39
39
|
<EmptyState.Description>
|
|
40
|
-
Add a <code
|
|
40
|
+
Add a <Box as="code" padding="xs" background="secondary" rounded="sm">contract</Box> field to the fragment definition to enable AI agent features.
|
|
41
41
|
</EmptyState.Description>
|
|
42
42
|
<EmptyState.Actions>
|
|
43
43
|
<CodeBlock
|
|
@@ -55,7 +55,7 @@ export const ContractPanel = memo(function ContractPanel({
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
return (
|
|
58
|
-
<
|
|
58
|
+
<Box padding="md" style={{ fontSize: '14px' }}>
|
|
59
59
|
<Stack gap="lg">
|
|
60
60
|
{/* Props Summary */}
|
|
61
61
|
{contract.propsSummary && contract.propsSummary.length > 0 && (
|
|
@@ -110,12 +110,11 @@ export const ContractPanel = memo(function ContractPanel({
|
|
|
110
110
|
<Section title="Accessibility Rules">
|
|
111
111
|
<Stack gap="sm">
|
|
112
112
|
{contract.a11yRules.map((rule, i) => (
|
|
113
|
+
<Box key={i} padding="sm" background="secondary" rounded="sm">
|
|
113
114
|
<Stack
|
|
114
|
-
key={i}
|
|
115
115
|
direction="row"
|
|
116
116
|
align="center"
|
|
117
117
|
gap="sm"
|
|
118
|
-
style={{ padding: '8px', background: 'var(--bg-hover)', borderRadius: '4px' }}
|
|
119
118
|
>
|
|
120
119
|
<span style={{ color: '#22c55e' }}>
|
|
121
120
|
<svg style={{ width: '16px', height: '16px' }} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
@@ -124,6 +123,7 @@ export const ContractPanel = memo(function ContractPanel({
|
|
|
124
123
|
</span>
|
|
125
124
|
<Text font="mono" size="xs" color="secondary">{rule}</Text>
|
|
126
125
|
</Stack>
|
|
126
|
+
</Box>
|
|
127
127
|
))}
|
|
128
128
|
</Stack>
|
|
129
129
|
</Section>
|
|
@@ -160,7 +160,7 @@ export const ContractPanel = memo(function ContractPanel({
|
|
|
160
160
|
</Alert>
|
|
161
161
|
)}
|
|
162
162
|
</Stack>
|
|
163
|
-
</
|
|
163
|
+
</Box>
|
|
164
164
|
);
|
|
165
165
|
});
|
|
166
166
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Stack, Text, Alert } from "@fragments-sdk/ui";
|
|
2
|
+
|
|
3
|
+
interface EmptyVariantMessageProps {
|
|
4
|
+
reason: string;
|
|
5
|
+
variantName: string;
|
|
6
|
+
hint?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function EmptyVariantMessage({ reason, variantName, hint }: EmptyVariantMessageProps) {
|
|
10
|
+
return (
|
|
11
|
+
<Alert variant="warning">
|
|
12
|
+
<Alert.Body>
|
|
13
|
+
<Alert.Title>Variant "{variantName}" rendered empty</Alert.Title>
|
|
14
|
+
<Alert.Content>
|
|
15
|
+
<Stack direction="column" gap="sm">
|
|
16
|
+
<Text size="xs" color="secondary">
|
|
17
|
+
{reason}
|
|
18
|
+
</Text>
|
|
19
|
+
{hint && (
|
|
20
|
+
<Text size="xs" color="tertiary">
|
|
21
|
+
<Text as="span" size="xs" weight="semibold">
|
|
22
|
+
Tip:
|
|
23
|
+
</Text>{" "}
|
|
24
|
+
{hint}
|
|
25
|
+
</Text>
|
|
26
|
+
)}
|
|
27
|
+
<div>
|
|
28
|
+
<Text size="xs" color="tertiary" weight="semibold">
|
|
29
|
+
Common causes:
|
|
30
|
+
</Text>
|
|
31
|
+
<ul style={{ marginTop: "4px", marginLeft: "16px", listStyleType: "disc" }}>
|
|
32
|
+
<li>
|
|
33
|
+
<Text size="xs" color="secondary">
|
|
34
|
+
Component requires props that weren't provided
|
|
35
|
+
</Text>
|
|
36
|
+
</li>
|
|
37
|
+
<li>
|
|
38
|
+
<Text size="xs" color="secondary">
|
|
39
|
+
Component renders conditionally and conditions aren't met
|
|
40
|
+
</Text>
|
|
41
|
+
</li>
|
|
42
|
+
<li>
|
|
43
|
+
<Text size="xs" color="secondary">
|
|
44
|
+
Story args reference variables that don't exist in this context
|
|
45
|
+
</Text>
|
|
46
|
+
</li>
|
|
47
|
+
</ul>
|
|
48
|
+
</div>
|
|
49
|
+
</Stack>
|
|
50
|
+
</Alert.Content>
|
|
51
|
+
</Alert.Body>
|
|
52
|
+
</Alert>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useRef, useEffect, useState, useMemo, useCallback } from "react";
|
|
2
2
|
import { FigmaIcon } from "./Icons.js";
|
|
3
|
+
import { Stack, Text, Button } from '@fragments-sdk/ui';
|
|
3
4
|
|
|
4
5
|
interface FigmaEmbedProps {
|
|
5
6
|
/** Current Figma URL to display */
|
|
@@ -124,18 +125,19 @@ export function FigmaEmbed({ figmaUrl, allFigmaUrls, zoom = 100, className, styl
|
|
|
124
125
|
// If we can't parse the URL, show error
|
|
125
126
|
if (!currentParsed) {
|
|
126
127
|
return (
|
|
127
|
-
<
|
|
128
|
-
<
|
|
128
|
+
<Stack className={className} align="center" justify="center" style={style}>
|
|
129
|
+
<Stack direction="column" align="center" gap="sm" style={{ color: 'var(--text-tertiary)', padding: '16px', textAlign: 'center' }}>
|
|
129
130
|
<FigmaIcon style={{ width: '24px', height: '24px' }} />
|
|
130
|
-
<
|
|
131
|
-
<
|
|
131
|
+
<Text size="xs">Unable to embed Figma design</Text>
|
|
132
|
+
<Button
|
|
133
|
+
variant="ghost"
|
|
134
|
+
size="sm"
|
|
132
135
|
onClick={() => window.open(figmaUrl, "_blank", "noopener,noreferrer")}
|
|
133
|
-
style={{ fontSize: '12px', color: 'var(--color-accent)', background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'underline' }}
|
|
134
136
|
>
|
|
135
137
|
Open in Figma
|
|
136
|
-
</
|
|
137
|
-
</
|
|
138
|
-
</
|
|
138
|
+
</Button>
|
|
139
|
+
</Stack>
|
|
140
|
+
</Stack>
|
|
139
141
|
);
|
|
140
142
|
}
|
|
141
143
|
|
|
@@ -143,22 +145,22 @@ export function FigmaEmbed({ figmaUrl, allFigmaUrls, zoom = 100, className, styl
|
|
|
143
145
|
<div className={className} style={{ ...style, position: "relative", overflow: "hidden" }}>
|
|
144
146
|
{/* Loading overlay - shows while current iframe is loading */}
|
|
145
147
|
{!isCurrentLoaded && (
|
|
146
|
-
<
|
|
147
|
-
<
|
|
148
|
+
<Stack align="center" justify="center" style={{ position: 'absolute', inset: 0, backgroundColor: 'var(--bg-secondary)', zIndex: 20 }}>
|
|
149
|
+
<Stack direction="column" align="center" gap="sm">
|
|
148
150
|
<FigmaIcon style={{ width: '20px', height: '20px', color: 'var(--text-tertiary)' }} />
|
|
149
|
-
<
|
|
150
|
-
</
|
|
151
|
-
</
|
|
151
|
+
<Text size="xs" color="tertiary">Loading Figma...</Text>
|
|
152
|
+
</Stack>
|
|
153
|
+
</Stack>
|
|
152
154
|
)}
|
|
153
155
|
|
|
154
156
|
{/* Error overlay */}
|
|
155
157
|
{error && (
|
|
156
|
-
<
|
|
157
|
-
<
|
|
158
|
+
<Stack align="center" justify="center" style={{ position: 'absolute', inset: 0, backgroundColor: 'var(--bg-secondary)', zIndex: 20 }}>
|
|
159
|
+
<Stack direction="column" align="center" gap="sm" style={{ color: 'var(--text-tertiary)' }}>
|
|
158
160
|
<FigmaIcon style={{ width: '24px', height: '24px' }} />
|
|
159
|
-
<
|
|
160
|
-
</
|
|
161
|
-
</
|
|
161
|
+
<Text size="xs">{error}</Text>
|
|
162
|
+
</Stack>
|
|
163
|
+
</Stack>
|
|
162
164
|
)}
|
|
163
165
|
|
|
164
166
|
{/*
|