@fragments-sdk/cli 0.5.2 → 0.7.0

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 (124) hide show
  1. package/dist/bin.js +996 -79
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
  4. package/dist/chunk-6JBGU74P.js.map +1 -0
  5. package/dist/chunk-7OPWMLOE.js +1625 -0
  6. package/dist/chunk-7OPWMLOE.js.map +1 -0
  7. package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
  8. package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
  9. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
  12. package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
  13. package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
  14. package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
  15. package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +15 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
  20. package/dist/mcp-bin.js +8 -220
  21. package/dist/mcp-bin.js.map +1 -1
  22. package/dist/scan-WY23TJCP.js +12 -0
  23. package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
  24. package/dist/static-viewer-GBR7YNF3.js +12 -0
  25. package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
  26. package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
  27. package/dist/viewer-SUFOISZM.js +1822 -0
  28. package/dist/viewer-SUFOISZM.js.map +1 -0
  29. package/package.json +6 -5
  30. package/src/bin.ts +31 -0
  31. package/src/build.ts +147 -13
  32. package/src/cli-commands.ts +18 -0
  33. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  34. package/src/commands/a11y-report.ts +625 -0
  35. package/src/commands/a11y.ts +168 -14
  36. package/src/commands/build.ts +16 -0
  37. package/src/commands/graph.ts +274 -0
  38. package/src/core/auto-props.ts +464 -0
  39. package/src/core/composition.ts +64 -1
  40. package/src/core/graph-extractor.test.ts +542 -0
  41. package/src/core/graph-extractor.ts +601 -0
  42. package/src/core/importAnalyzer.ts +5 -0
  43. package/src/core/schema.ts +2 -0
  44. package/src/core/types.ts +3 -1
  45. package/src/index.ts +4 -0
  46. package/src/mcp/server.ts +13 -220
  47. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  48. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  49. package/src/theme/contrast.test.ts +331 -0
  50. package/src/theme/contrast.ts +246 -0
  51. package/src/theme/generator.ts +213 -1
  52. package/src/theme/index.ts +16 -0
  53. package/src/theme/types.ts +51 -0
  54. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  55. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  56. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  57. package/src/viewer/components/ActionCapture.tsx +1 -1
  58. package/src/viewer/components/ActionsPanel.tsx +142 -183
  59. package/src/viewer/components/App.tsx +276 -183
  60. package/src/viewer/components/BottomPanel.tsx +40 -80
  61. package/src/viewer/components/CodePanel.tsx +9 -87
  62. package/src/viewer/components/CommandPalette.tsx +117 -74
  63. package/src/viewer/components/ComponentGraph.tsx +143 -126
  64. package/src/viewer/components/ComponentHeader.tsx +46 -43
  65. package/src/viewer/components/ContractPanel.tsx +124 -117
  66. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  67. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  68. package/src/viewer/components/FragmentEditor.tsx +126 -63
  69. package/src/viewer/components/HealthDashboard.tsx +146 -171
  70. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  71. package/src/viewer/components/Icons.tsx +151 -98
  72. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  73. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  74. package/src/viewer/components/IsolatedRender.tsx +12 -6
  75. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  76. package/src/viewer/components/LandingPage.tsx +285 -305
  77. package/src/viewer/components/Layout.tsx +12 -10
  78. package/src/viewer/components/LeftSidebar.tsx +103 -155
  79. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  80. package/src/viewer/components/PreviewArea.tsx +113 -44
  81. package/src/viewer/components/PreviewFrameHost.tsx +36 -6
  82. package/src/viewer/components/PreviewPane.tsx +2 -3
  83. package/src/viewer/components/PreviewToolbar.tsx +109 -105
  84. package/src/viewer/components/PropsEditor.tsx +154 -74
  85. package/src/viewer/components/PropsTable.tsx +95 -82
  86. package/src/viewer/components/RelationsSection.tsx +71 -40
  87. package/src/viewer/components/ResizablePanel.tsx +158 -55
  88. package/src/viewer/components/RightSidebar.tsx +46 -56
  89. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  90. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  91. package/src/viewer/components/StoryRenderer.tsx +4 -11
  92. package/src/viewer/components/Toast.tsx +3 -67
  93. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  94. package/src/viewer/components/UsageSection.tsx +26 -26
  95. package/src/viewer/components/VariantMatrix.tsx +140 -47
  96. package/src/viewer/components/VariantTabs.tsx +24 -68
  97. package/src/viewer/components/ViewportSelector.tsx +121 -114
  98. package/src/viewer/constants/ui.ts +23 -22
  99. package/src/viewer/entry.tsx +8 -3
  100. package/src/viewer/index.ts +3 -6
  101. package/src/viewer/preview-frame.html +43 -18
  102. package/src/viewer/server.ts +7 -16
  103. package/src/viewer/styles/globals.css +46 -85
  104. package/src/viewer/utils/a11y-fixes.ts +53 -30
  105. package/dist/chunk-ICAIQ57V.js.map +0 -1
  106. package/dist/chunk-U4GQ2JTD.js +0 -832
  107. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  108. package/dist/scan-ESEXV7LF.js +0 -12
  109. package/dist/static-viewer-O37MJ5B6.js +0 -12
  110. package/dist/viewer-YDGFDTK5.js +0 -11104
  111. package/dist/viewer-YDGFDTK5.js.map +0 -1
  112. package/src/viewer/postcss.config.js +0 -6
  113. package/src/viewer/tailwind.config.js +0 -37
  114. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  115. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  116. /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
  117. /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
  118. /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
  119. /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
  120. /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
  121. /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
  122. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
  123. /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
  124. /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { memo } from 'react';
7
7
  import type { SegmentContract } from '../../core/index.js';
8
+ import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge } from '@fragments/ui';
8
9
 
9
10
  interface ContractPanelProps {
10
11
  contract?: SegmentContract;
@@ -18,10 +19,10 @@ export const ContractPanel = memo(function ContractPanel({
18
19
  // Empty state when no contract metadata
19
20
  if (!contract || isContractEmpty(contract)) {
20
21
  return (
21
- <div className="p-6 text-center">
22
- <div className="text-tertiary mb-4">
22
+ <EmptyState>
23
+ <EmptyState.Icon>
23
24
  <svg
24
- className="w-12 h-12 mx-auto mb-4 opacity-50"
25
+ style={{ width: '48px', height: '48px', opacity: 0.5 }}
25
26
  fill="none"
26
27
  viewBox="0 0 24 24"
27
28
  stroke="currentColor"
@@ -33,126 +34,132 @@ export const ContractPanel = memo(function ContractPanel({
33
34
  d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
34
35
  />
35
36
  </svg>
36
- <p className="text-sm font-medium">No contract metadata</p>
37
- </div>
38
- <p className="text-xs text-quaternary mb-4">
39
- Add a <code className="bg-[--bg-hover] px-1 py-0.5 rounded">contract</code> field to the segment definition to enable AI agent features.
40
- </p>
41
- <div className="text-left bg-[--bg-hover] rounded-lg p-4 text-xs font-mono">
42
- <pre className="text-quaternary">{`contract: {
37
+ </EmptyState.Icon>
38
+ <EmptyState.Title>No contract metadata</EmptyState.Title>
39
+ <EmptyState.Description>
40
+ Add a <code style={{ background: 'var(--bg-hover)', padding: '2px 4px', borderRadius: '4px' }}>contract</code> field to the segment definition to enable AI agent features.
41
+ </EmptyState.Description>
42
+ <EmptyState.Actions>
43
+ <CodeBlock
44
+ code={`contract: {
43
45
  propsSummary: ['variant: primary|secondary'],
44
46
  scenarioTags: ['form.submit', 'action.primary'],
45
47
  a11yRules: ['A11Y_BTN_LABEL'],
46
- }`}</pre>
47
- </div>
48
- </div>
48
+ }`}
49
+ language="typescript"
50
+ compact
51
+ />
52
+ </EmptyState.Actions>
53
+ </EmptyState>
49
54
  );
50
55
  }
51
56
 
52
57
  return (
53
- <div className="p-4 space-y-6 text-sm">
54
- {/* Props Summary */}
55
- {contract.propsSummary && contract.propsSummary.length > 0 && (
56
- <Section title="Props Summary">
57
- <div className="overflow-x-auto">
58
- <table className="w-full text-xs border-collapse">
59
- <thead>
60
- <tr className="border-b border-[--border]">
61
- <th className="text-left py-2 pr-4 font-medium text-tertiary">Name</th>
62
- <th className="text-left py-2 pr-4 font-medium text-tertiary">Type</th>
63
- <th className="text-left py-2 font-medium text-tertiary">Description</th>
64
- </tr>
65
- </thead>
66
- <tbody>
67
- {contract.propsSummary.map((prop, i) => {
68
- const parsed = parsePropSummary(prop);
69
- return (
70
- <tr key={i} className="border-b border-[--border-subtle] last:border-b-0">
71
- <td className="py-2 pr-4 align-top">
72
- <code className="text-xs font-medium text-primary">{parsed.name}</code>
73
- </td>
74
- <td className="py-2 pr-4 align-top">
75
- <code className="text-xs text-purple-600 dark:text-purple-400">{parsed.type}</code>
76
- </td>
77
- <td className="py-2 align-top text-secondary">{parsed.description}</td>
58
+ <div style={{ padding: '16px', fontSize: '14px' }}>
59
+ <Stack gap="lg">
60
+ {/* Props Summary */}
61
+ {contract.propsSummary && contract.propsSummary.length > 0 && (
62
+ <Section title="Props Summary">
63
+ <Card>
64
+ <Card.Body>
65
+ <table style={{ width: '100%', fontSize: '12px', borderCollapse: 'collapse' }}>
66
+ <thead>
67
+ <tr style={{ borderBottom: '1px solid var(--border)' }}>
68
+ <th style={{ textAlign: 'left', padding: '8px 16px 8px 0', fontWeight: 500, color: 'var(--text-tertiary)' }}>Name</th>
69
+ <th style={{ textAlign: 'left', padding: '8px 16px 8px 0', fontWeight: 500, color: 'var(--text-tertiary)' }}>Type</th>
70
+ <th style={{ textAlign: 'left', padding: '8px 0', fontWeight: 500, color: 'var(--text-tertiary)' }}>Description</th>
78
71
  </tr>
79
- );
80
- })}
81
- </tbody>
82
- </table>
83
- </div>
84
- </Section>
85
- )}
86
-
87
- {/* Scenario Tags */}
88
- {contract.scenarioTags && contract.scenarioTags.length > 0 && (
89
- <Section title="Scenario Tags" subtitle="Used by fragments_suggest for AI queries">
90
- <div className="flex flex-wrap gap-2">
91
- {contract.scenarioTags.map((tag, i) => (
92
- <span
93
- key={i}
94
- className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-blue-500/10 text-blue-500 border border-blue-500/20"
95
- >
96
- {tag}
97
- </span>
98
- ))}
99
- </div>
100
- </Section>
101
- )}
102
-
103
- {/* Accessibility Rules */}
104
- {contract.a11yRules && contract.a11yRules.length > 0 && (
105
- <Section title="Accessibility Rules">
106
- <div className="space-y-2">
107
- {contract.a11yRules.map((rule, i) => (
108
- <div
109
- key={i}
110
- className="flex items-center gap-2 p-2 bg-[--bg-hover] rounded"
111
- >
112
- <span className="text-green-500">
113
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
114
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
115
- </svg>
116
- </span>
117
- <code className="text-xs text-secondary">{rule}</code>
118
- </div>
119
- ))}
120
- </div>
121
- </Section>
122
- )}
123
-
124
- {/* Bans */}
125
- {contract.bans && contract.bans.length > 0 && (
126
- <Section title="Usage Bans" subtitle="Patterns to avoid">
127
- <div className="space-y-2">
128
- {contract.bans.map((ban, i) => (
129
- <div
130
- key={i}
131
- className="p-3 bg-amber-500/10 border border-amber-500/20 rounded"
132
- >
133
- <div className="flex items-center gap-2 mb-1">
134
- <span className="text-amber-500">
135
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
136
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
72
+ </thead>
73
+ <tbody>
74
+ {contract.propsSummary.map((prop, i) => {
75
+ const parsed = parsePropSummary(prop);
76
+ return (
77
+ <tr key={i} style={{ borderBottom: i < contract.propsSummary!.length - 1 ? '1px solid var(--border)' : undefined }}>
78
+ <td style={{ padding: '8px 16px 8px 0', verticalAlign: 'top' }}>
79
+ <Text font="mono" size="xs" weight="medium">{parsed.name}</Text>
80
+ </td>
81
+ <td style={{ padding: '8px 16px 8px 0', verticalAlign: 'top' }}>
82
+ <Badge size="sm">{parsed.type}</Badge>
83
+ </td>
84
+ <td style={{ padding: '8px 0', verticalAlign: 'top' }}>
85
+ <Text size="xs" color="secondary">{parsed.description}</Text>
86
+ </td>
87
+ </tr>
88
+ );
89
+ })}
90
+ </tbody>
91
+ </table>
92
+ </Card.Body>
93
+ </Card>
94
+ </Section>
95
+ )}
96
+
97
+ {/* Scenario Tags */}
98
+ {contract.scenarioTags && contract.scenarioTags.length > 0 && (
99
+ <Section title="Scenario Tags" subtitle="Used by fragments_suggest for AI queries">
100
+ <Stack direction="row" gap="sm" style={{ flexWrap: 'wrap' }}>
101
+ {contract.scenarioTags.map((tag, i) => (
102
+ <Chip key={i}>{tag}</Chip>
103
+ ))}
104
+ </Stack>
105
+ </Section>
106
+ )}
107
+
108
+ {/* Accessibility Rules */}
109
+ {contract.a11yRules && contract.a11yRules.length > 0 && (
110
+ <Section title="Accessibility Rules">
111
+ <Stack gap="sm">
112
+ {contract.a11yRules.map((rule, i) => (
113
+ <Stack
114
+ key={i}
115
+ direction="row"
116
+ align="center"
117
+ gap="sm"
118
+ style={{ padding: '8px', background: 'var(--bg-hover)', borderRadius: '4px' }}
119
+ >
120
+ <span style={{ color: '#22c55e' }}>
121
+ <svg style={{ width: '16px', height: '16px' }} fill="none" viewBox="0 0 24 24" stroke="currentColor">
122
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
137
123
  </svg>
138
124
  </span>
139
- <code className="text-xs text-amber-600 dark:text-amber-400">
140
- {ban.pattern}
141
- </code>
142
- </div>
143
- <p className="text-xs text-tertiary pl-6">{ban.message}</p>
144
- </div>
145
- ))}
146
- </div>
147
- </Section>
148
- )}
149
-
150
- {/* Empty sections message */}
151
- {isPartialContract(contract) && (
152
- <div className="text-xs text-quaternary p-3 bg-[--bg-hover] rounded">
153
- Tip: Add more contract fields to help AI agents better understand this component.
154
- </div>
155
- )}
125
+ <Text font="mono" size="xs" color="secondary">{rule}</Text>
126
+ </Stack>
127
+ ))}
128
+ </Stack>
129
+ </Section>
130
+ )}
131
+
132
+ {/* Bans */}
133
+ {contract.bans && contract.bans.length > 0 && (
134
+ <Section title="Usage Bans" subtitle="Patterns to avoid">
135
+ <Stack gap="sm">
136
+ {contract.bans.map((ban, i) => (
137
+ <Alert key={i} severity="warning">
138
+ <Alert.Body>
139
+ <Alert.Title>
140
+ <Text font="mono" size="xs">{ban.pattern}</Text>
141
+ </Alert.Title>
142
+ <Alert.Content>
143
+ <Text size="xs" color="tertiary">{ban.message}</Text>
144
+ </Alert.Content>
145
+ </Alert.Body>
146
+ </Alert>
147
+ ))}
148
+ </Stack>
149
+ </Section>
150
+ )}
151
+
152
+ {/* Empty sections message */}
153
+ {isPartialContract(contract) && (
154
+ <Alert severity="info">
155
+ <Alert.Body>
156
+ <Alert.Content>
157
+ Tip: Add more contract fields to help AI agents better understand this component.
158
+ </Alert.Content>
159
+ </Alert.Body>
160
+ </Alert>
161
+ )}
162
+ </Stack>
156
163
  </div>
157
164
  );
158
165
  });
@@ -167,12 +174,12 @@ interface SectionProps {
167
174
  function Section({ title, subtitle, children }: SectionProps) {
168
175
  return (
169
176
  <div>
170
- <h3 className="text-xs font-semibold text-primary mb-2 flex items-center gap-2">
171
- {title}
177
+ <Stack direction="row" align="center" gap="sm" style={{ marginBottom: '8px' }}>
178
+ <Text as="h3" size="xs" weight="semibold">{title}</Text>
172
179
  {subtitle && (
173
- <span className="font-normal text-quaternary">({subtitle})</span>
180
+ <Text size="xs" color="tertiary">({subtitle})</Text>
174
181
  )}
175
- </h3>
182
+ </Stack>
176
183
  {children}
177
184
  </div>
178
185
  );
@@ -1,9 +1,11 @@
1
1
  import { Component, type ReactNode, type ErrorInfo } from 'react';
2
+ import { Button, Alert, CodeBlock, Collapsible, Stack, Text } from '@fragments/ui';
2
3
  import { ErrorIcon, RefreshIcon } from './Icons.js';
3
4
 
4
5
  interface ErrorBoundaryProps {
5
6
  children: ReactNode;
6
7
  componentName?: string;
8
+ fallback?: ReactNode;
7
9
  onRetry?: () => void;
8
10
  }
9
11
 
@@ -24,10 +26,8 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
24
26
  }
25
27
 
26
28
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
27
- // Log error to console with full stack trace
28
29
  console.error('Component Error:', error);
29
30
  console.error('Component Stack:', errorInfo.componentStack);
30
-
31
31
  this.setState({ errorInfo });
32
32
  }
33
33
 
@@ -38,45 +38,57 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
38
38
 
39
39
  render() {
40
40
  if (this.state.hasError) {
41
+ if (this.props.fallback) {
42
+ return this.props.fallback;
43
+ }
44
+
41
45
  const { componentName } = this.props;
42
46
  const { error, errorInfo } = this.state;
43
47
 
44
48
  return (
45
- <div className="p-6 rounded-xl border-2 border-red-300 dark:border-red-700 bg-red-50 dark:bg-red-950">
46
- <div className="flex items-start gap-3">
47
- <ErrorIcon className="w-6 h-6 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
48
- <div className="flex-1 min-w-0">
49
- <h3 className="text-sm font-semibold text-red-900 dark:text-red-100 mb-1">
50
- {componentName ? `Error rendering ${componentName}` : 'Component Error'}
51
- </h3>
52
-
53
- {error && (
54
- <p className="text-sm text-red-800 dark:text-red-200 mb-3 font-mono">
55
- {error.message}
56
- </p>
57
- )}
49
+ <Alert variant="error">
50
+ <Alert.Icon>
51
+ <ErrorIcon style={{ width: '24px', height: '24px' }} />
52
+ </Alert.Icon>
53
+ <Alert.Body>
54
+ <Alert.Title>
55
+ {componentName ? `Error rendering ${componentName}` : 'Component Error'}
56
+ </Alert.Title>
57
+ <Alert.Content>
58
+ <Stack direction="column" gap="sm">
59
+ {error && (
60
+ <Text size="sm" font="mono" style={{ color: '#dc2626' }}>
61
+ {error.message}
62
+ </Text>
63
+ )}
58
64
 
59
- {errorInfo?.componentStack && (
60
- <details className="mb-4">
61
- <summary className="text-xs text-red-700 dark:text-red-300 cursor-pointer hover:text-red-900 dark:hover:text-red-100 transition-colors">
62
- Show stack trace
63
- </summary>
64
- <pre className="mt-2 p-3 text-[10px] font-mono bg-red-100 dark:bg-red-900 rounded-lg overflow-x-auto text-red-800 dark:text-red-200 whitespace-pre-wrap">
65
- {error?.stack || errorInfo.componentStack}
66
- </pre>
67
- </details>
68
- )}
65
+ {errorInfo?.componentStack && (
66
+ <Collapsible>
67
+ <Collapsible.Trigger>
68
+ <Text size="xs" color="tertiary" style={{ cursor: 'pointer' }}>
69
+ Show stack trace
70
+ </Text>
71
+ </Collapsible.Trigger>
72
+ <Collapsible.Content>
73
+ <div style={{ marginTop: '8px' }}>
74
+ <CodeBlock language="plaintext">
75
+ {error?.stack || errorInfo.componentStack}
76
+ </CodeBlock>
77
+ </div>
78
+ </Collapsible.Content>
79
+ </Collapsible>
80
+ )}
69
81
 
70
- <button
71
- onClick={this.handleRetry}
72
- className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md bg-red-600 text-white hover:bg-red-700 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
73
- >
74
- <RefreshIcon className="w-3.5 h-3.5" />
75
- Retry
76
- </button>
77
- </div>
78
- </div>
79
- </div>
82
+ <div>
83
+ <Button variant="danger" size="sm" onClick={this.handleRetry}>
84
+ <RefreshIcon style={{ width: '14px', height: '14px' }} />
85
+ Retry
86
+ </Button>
87
+ </div>
88
+ </Stack>
89
+ </Alert.Content>
90
+ </Alert.Body>
91
+ </Alert>
80
92
  );
81
93
  }
82
94
 
@@ -125,12 +125,12 @@ export function FigmaEmbed({ figmaUrl, allFigmaUrls, zoom = 100, className, styl
125
125
  if (!currentParsed) {
126
126
  return (
127
127
  <div className={className} style={{ ...style, display: "flex", alignItems: "center", justifyContent: "center" }}>
128
- <div className="flex flex-col items-center gap-2 text-tertiary p-4 text-center">
129
- <FigmaIcon className="w-6 h-6" />
130
- <span className="text-xs">Unable to embed Figma design</span>
128
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px', color: 'var(--text-tertiary)', padding: '16px', textAlign: 'center' }}>
129
+ <FigmaIcon style={{ width: '24px', height: '24px' }} />
130
+ <span style={{ fontSize: '12px' }}>Unable to embed Figma design</span>
131
131
  <button
132
132
  onClick={() => window.open(figmaUrl, "_blank", "noopener,noreferrer")}
133
- className="text-xs text-[--color-accent] hover:underline"
133
+ style={{ fontSize: '12px', color: 'var(--color-accent)', background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'underline' }}
134
134
  >
135
135
  Open in Figma
136
136
  </button>
@@ -143,20 +143,20 @@ export function FigmaEmbed({ figmaUrl, allFigmaUrls, zoom = 100, className, styl
143
143
  <div className={className} style={{ ...style, position: "relative", overflow: "hidden" }}>
144
144
  {/* Loading overlay - shows while current iframe is loading */}
145
145
  {!isCurrentLoaded && (
146
- <div className="absolute inset-0 flex items-center justify-center bg-[--bg-secondary] z-20">
147
- <div className="flex flex-col items-center gap-2">
148
- <FigmaIcon className="w-5 h-5 text-tertiary animate-pulse" />
149
- <span className="text-xs text-tertiary">Loading Figma...</span>
146
+ <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'var(--bg-secondary)', zIndex: 20 }}>
147
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px' }}>
148
+ <FigmaIcon style={{ width: '20px', height: '20px', color: 'var(--text-tertiary)' }} />
149
+ <span style={{ fontSize: '12px', color: 'var(--text-tertiary)' }}>Loading Figma...</span>
150
150
  </div>
151
151
  </div>
152
152
  )}
153
153
 
154
154
  {/* Error overlay */}
155
155
  {error && (
156
- <div className="absolute inset-0 flex items-center justify-center bg-[--bg-secondary] z-20">
157
- <div className="flex flex-col items-center gap-2 text-tertiary">
158
- <FigmaIcon className="w-6 h-6" />
159
- <span className="text-xs">{error}</span>
156
+ <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'var(--bg-secondary)', zIndex: 20 }}>
157
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px', color: 'var(--text-tertiary)' }}>
158
+ <FigmaIcon style={{ width: '24px', height: '24px' }} />
159
+ <span style={{ fontSize: '12px' }}>{error}</span>
160
160
  </div>
161
161
  </div>
162
162
  )}
@@ -182,8 +182,13 @@ export function FigmaEmbed({ figmaUrl, allFigmaUrls, zoom = 100, className, styl
182
182
  <iframe
183
183
  key={key}
184
184
  src={embedUrl}
185
- className="absolute inset-0 w-full h-full border-0 transition-opacity duration-150"
186
185
  style={{
186
+ position: 'absolute',
187
+ inset: 0,
188
+ width: '100%',
189
+ height: '100%',
190
+ border: 'none',
191
+ transition: 'opacity 150ms',
187
192
  ...zoomStyle,
188
193
  opacity: isActive && isLoaded ? 1 : 0,
189
194
  zIndex: isActive ? 10 : 1,