@fragments-sdk/cli 0.5.2 → 0.6.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 (118) hide show
  1. package/dist/bin.js +712 -39
  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-U4GQ2JTD.js → chunk-D35RGPAG.js} +412 -35
  6. package/dist/chunk-D35RGPAG.js.map +1 -0
  7. package/dist/{chunk-XNWDI6UT.js → chunk-F7ITZPDJ.js} +5 -5
  8. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  9. package/dist/{chunk-V7YLRR4C.js → chunk-Q7GOHVOK.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-2H2JAA3U.js → chunk-SSLQXHNX.js} +3 -3
  12. package/dist/{core-DKHB7FYV.js → core-SKRPJQZG.js} +4 -4
  13. package/dist/{generate-KL24VZVD.js → generate-7AF7WRVK.js} +5 -5
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +15 -7
  16. package/dist/index.js.map +1 -1
  17. package/dist/{init-NION5S3M.js → init-WKGDPYI4.js} +5 -5
  18. package/dist/mcp-bin.js +8 -220
  19. package/dist/mcp-bin.js.map +1 -1
  20. package/dist/scan-K6JNMCGM.js +12 -0
  21. package/dist/{service-RWUMZ3EW.js → service-F3E4JJM7.js} +5 -5
  22. package/dist/static-viewer-4LQZ5AGA.js +12 -0
  23. package/dist/{test-ECPEXFDN.js → test-CJDNJTPZ.js} +4 -4
  24. package/dist/{tokens-ITADYVPF.js → tokens-JAJABYXP.js} +6 -6
  25. package/dist/viewer-R3Q6WAMJ.js +1822 -0
  26. package/dist/viewer-R3Q6WAMJ.js.map +1 -0
  27. package/package.json +5 -4
  28. package/src/bin.ts +8 -0
  29. package/src/build.ts +104 -13
  30. package/src/cli-commands.ts +18 -0
  31. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  32. package/src/commands/a11y-report.ts +625 -0
  33. package/src/commands/a11y.ts +168 -14
  34. package/src/commands/build.ts +16 -0
  35. package/src/core/auto-props.ts +464 -0
  36. package/src/core/schema.ts +2 -0
  37. package/src/core/types.ts +3 -1
  38. package/src/index.ts +4 -0
  39. package/src/mcp/server.ts +13 -220
  40. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  41. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  42. package/src/theme/contrast.test.ts +331 -0
  43. package/src/theme/contrast.ts +246 -0
  44. package/src/theme/generator.ts +213 -1
  45. package/src/theme/index.ts +16 -0
  46. package/src/theme/types.ts +51 -0
  47. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  48. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  49. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  50. package/src/viewer/components/ActionCapture.tsx +1 -1
  51. package/src/viewer/components/ActionsPanel.tsx +142 -183
  52. package/src/viewer/components/App.tsx +159 -164
  53. package/src/viewer/components/BottomPanel.tsx +40 -80
  54. package/src/viewer/components/CodePanel.tsx +9 -87
  55. package/src/viewer/components/CommandPalette.tsx +117 -74
  56. package/src/viewer/components/ComponentGraph.tsx +143 -126
  57. package/src/viewer/components/ComponentHeader.tsx +46 -43
  58. package/src/viewer/components/ContractPanel.tsx +124 -117
  59. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  60. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  61. package/src/viewer/components/FragmentEditor.tsx +126 -63
  62. package/src/viewer/components/HealthDashboard.tsx +146 -171
  63. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  64. package/src/viewer/components/Icons.tsx +99 -98
  65. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  66. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  67. package/src/viewer/components/IsolatedRender.tsx +12 -6
  68. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  69. package/src/viewer/components/LandingPage.tsx +285 -305
  70. package/src/viewer/components/Layout.tsx +7 -9
  71. package/src/viewer/components/LeftSidebar.tsx +78 -108
  72. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  73. package/src/viewer/components/PreviewArea.tsx +113 -44
  74. package/src/viewer/components/PreviewFrameHost.tsx +6 -5
  75. package/src/viewer/components/PreviewPane.tsx +2 -3
  76. package/src/viewer/components/PreviewToolbar.tsx +61 -104
  77. package/src/viewer/components/PropsEditor.tsx +154 -74
  78. package/src/viewer/components/PropsTable.tsx +95 -82
  79. package/src/viewer/components/RelationsSection.tsx +71 -40
  80. package/src/viewer/components/ResizablePanel.tsx +158 -55
  81. package/src/viewer/components/RightSidebar.tsx +46 -56
  82. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  83. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  84. package/src/viewer/components/StoryRenderer.tsx +4 -11
  85. package/src/viewer/components/Toast.tsx +3 -67
  86. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  87. package/src/viewer/components/UsageSection.tsx +26 -26
  88. package/src/viewer/components/VariantMatrix.tsx +140 -47
  89. package/src/viewer/components/VariantTabs.tsx +24 -68
  90. package/src/viewer/components/ViewportSelector.tsx +106 -110
  91. package/src/viewer/constants/ui.ts +19 -18
  92. package/src/viewer/entry.tsx +8 -3
  93. package/src/viewer/index.ts +3 -6
  94. package/src/viewer/preview-frame.html +21 -5
  95. package/src/viewer/server.ts +7 -16
  96. package/src/viewer/styles/globals.css +4 -4
  97. package/src/viewer/utils/a11y-fixes.ts +53 -30
  98. package/dist/chunk-ICAIQ57V.js.map +0 -1
  99. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  100. package/dist/scan-ESEXV7LF.js +0 -12
  101. package/dist/static-viewer-O37MJ5B6.js +0 -12
  102. package/dist/viewer-YDGFDTK5.js +0 -11104
  103. package/dist/viewer-YDGFDTK5.js.map +0 -1
  104. package/src/viewer/postcss.config.js +0 -6
  105. package/src/viewer/tailwind.config.js +0 -37
  106. /package/dist/{chunk-XNWDI6UT.js.map → chunk-F7ITZPDJ.js.map} +0 -0
  107. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  108. /package/dist/{chunk-V7YLRR4C.js.map → chunk-Q7GOHVOK.js.map} +0 -0
  109. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  110. /package/dist/{chunk-2H2JAA3U.js.map → chunk-SSLQXHNX.js.map} +0 -0
  111. /package/dist/{core-DKHB7FYV.js.map → core-SKRPJQZG.js.map} +0 -0
  112. /package/dist/{generate-KL24VZVD.js.map → generate-7AF7WRVK.js.map} +0 -0
  113. /package/dist/{init-NION5S3M.js.map → init-WKGDPYI4.js.map} +0 -0
  114. /package/dist/{scan-ESEXV7LF.js.map → scan-K6JNMCGM.js.map} +0 -0
  115. /package/dist/{service-RWUMZ3EW.js.map → service-F3E4JJM7.js.map} +0 -0
  116. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-4LQZ5AGA.js.map} +0 -0
  117. /package/dist/{test-ECPEXFDN.js.map → test-CJDNJTPZ.js.map} +0 -0
  118. /package/dist/{tokens-ITADYVPF.js.map → tokens-JAJABYXP.js.map} +0 -0
@@ -1,8 +1,6 @@
1
- import { useState, useCallback, useMemo, useEffect, isValidElement, type ReactNode } from 'react';
1
+ import { useMemo, isValidElement, type ReactNode } from 'react';
2
2
  import type { SegmentVariant, PropDefinition } from '../../core/index.js';
3
- import { codeToHtml } from 'shiki';
4
- import clsx from 'clsx';
5
- import { CopyIcon, CheckIcon } from './Icons.js';
3
+ import { CodeBlock } from '@fragments/ui';
6
4
 
7
5
  interface CodePanelProps {
8
6
  variant: SegmentVariant;
@@ -213,9 +211,6 @@ function extractRenderBody(renderFn: () => ReactNode): string | null {
213
211
  }
214
212
 
215
213
  export function CodePanel({ variant, componentName, compact = false, propDefs }: CodePanelProps) {
216
- const [copied, setCopied] = useState(false);
217
- const [highlightedHtml, setHighlightedHtml] = useState<string>('');
218
-
219
214
  // Generate code - extract from render function source for accuracy
220
215
  const generatedCode = useMemo(() => {
221
216
  // Priority 1: Use variant.code if available (from compiled JSON/AST)
@@ -243,90 +238,17 @@ export function CodePanel({ variant, componentName, compact = false, propDefs }:
243
238
  return generateCombinedCode(componentName, propDefs, effectiveArgs, needsState);
244
239
  }, [componentName, variant, propDefs]);
245
240
 
246
- // Apply syntax highlighting
247
- useEffect(() => {
248
- let cancelled = false;
249
-
250
- codeToHtml(generatedCode, {
251
- lang: 'tsx',
252
- theme: 'one-dark-pro',
253
- }).then(html => {
254
- if (!cancelled) {
255
- setHighlightedHtml(html);
256
- }
257
- }).catch(err => {
258
- console.error('Syntax highlighting failed:', err);
259
- if (!cancelled) {
260
- // Fallback to plain text
261
- setHighlightedHtml(`<pre><code>${escapeHtml(generatedCode)}</code></pre>`);
262
- }
263
- });
264
-
265
- return () => { cancelled = true; };
266
- }, [generatedCode]);
267
-
268
- const handleCopy = useCallback(async () => {
269
- try {
270
- await navigator.clipboard.writeText(generatedCode);
271
- setCopied(true);
272
- setTimeout(() => setCopied(false), 2000);
273
- } catch (err) {
274
- console.error('Failed to copy:', err);
275
- }
276
- }, [generatedCode]);
277
-
278
241
  return (
279
- <div className="relative">
280
- {/* Syntax highlighted code */}
281
- <div
282
- className={clsx(
283
- 'rounded-lg overflow-auto border border-[--border] bg-[#282c34]',
284
- '[&_pre]:!bg-transparent [&_pre]:!m-0 [&_pre]:p-4',
285
- '[&_code]:!bg-transparent [&_code]:text-[13px] [&_code]:leading-relaxed',
286
- '[&_.shiki]:!bg-transparent',
287
- // Custom selection highlight for better visibility
288
- '[&_*::selection]:bg-blue-500/40 [&_*::selection]:text-white',
289
- compact && '[&_code]:text-xs'
290
- )}
291
- style={{ maxHeight: 400 }}
292
- dangerouslySetInnerHTML={{ __html: highlightedHtml || '<div class="p-4 text-gray-500 text-sm">Loading...</div>' }}
293
- />
294
-
295
- {/* Copy button - fixed to top right of editor */}
296
- <button
297
- onClick={handleCopy}
298
- className={clsx(
299
- 'absolute top-2 right-2 flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium rounded-md transition-all duration-200',
300
- copied
301
- ? 'bg-green-500/20 text-green-400 border border-green-500/30'
302
- : 'bg-white/10 text-gray-300 border border-white/10 hover:bg-white/15 hover:text-white'
303
- )}
304
- >
305
- {copied ? (
306
- <>
307
- <CheckIcon className="w-3.5 h-3.5" />
308
- <span>Copied!</span>
309
- </>
310
- ) : (
311
- <>
312
- <CopyIcon className="w-3.5 h-3.5" />
313
- <span>Copy</span>
314
- </>
315
- )}
316
- </button>
317
- </div>
242
+ <CodeBlock
243
+ code={generatedCode}
244
+ language="tsx"
245
+ showCopy
246
+ maxHeight={400}
247
+ compact={compact}
248
+ />
318
249
  );
319
250
  }
320
251
 
321
- function escapeHtml(str: string): string {
322
- return str
323
- .replace(/&/g, '&amp;')
324
- .replace(/</g, '&lt;')
325
- .replace(/>/g, '&gt;')
326
- .replace(/"/g, '&quot;')
327
- .replace(/'/g, '&#039;');
328
- }
329
-
330
252
  /**
331
253
  * Normalize indentation by removing common leading whitespace from all lines.
332
254
  * Handles JSX where first line may be at column 0 but inner content is indented.
@@ -5,10 +5,12 @@
5
5
  * - Quick navigation to components
6
6
  * - Fuzzy search across all components and variants
7
7
  * - Keyboard navigation (arrows, enter, escape)
8
+ *
9
+ * Uses Fragments UI Dialog for the overlay shell.
8
10
  */
9
11
 
10
12
  import { useState, useEffect, useRef, useMemo, useCallback } from "react";
11
- import clsx from "clsx";
13
+ import { Dialog, Stack, Text, Badge, Separator, Input } from '@fragments/ui';
12
14
  import type { SegmentDefinition } from "../../core/index.js";
13
15
  import { SearchIcon, ChevronRightIcon } from "./Icons.js";
14
16
 
@@ -168,96 +170,132 @@ export function CommandPalette({
168
170
  if (!isOpen) return null;
169
171
 
170
172
  return (
171
- <div className="fixed inset-0 z-50 flex items-start justify-center pt-[15vh]">
172
- {/* Backdrop */}
173
- <div
174
- className="absolute inset-0 bg-black/50 backdrop-blur-sm"
175
- onClick={onClose}
176
- />
177
-
178
- {/* Palette */}
179
- <div
180
- className="relative w-full max-w-xl bg-[--bg-primary] rounded-xl shadow-2xl border border-[--border] overflow-hidden"
181
- onKeyDown={handleKeyDown}
182
- >
173
+ <Dialog open={isOpen} onOpenChange={(open: boolean) => { if (!open) onClose(); }}>
174
+ <Dialog.Content size="lg">
183
175
  {/* Search input */}
184
- <div className="flex items-center px-4 py-3 border-b-2 border-blue-500">
185
- <SearchIcon className="w-5 h-5 text-blue-500 mr-3" />
176
+ <Stack direction="row" align="center" style={{
177
+ padding: '12px 16px',
178
+ borderBottom: '2px solid #3b82f6',
179
+ }}>
180
+ <SearchIcon style={{ width: 20, height: 20, color: '#3b82f6', marginRight: 12 }} />
186
181
  <input
187
182
  ref={inputRef}
188
183
  type="text"
189
184
  value={query}
190
185
  onChange={(e) => setQuery(e.target.value)}
186
+ onKeyDown={handleKeyDown}
191
187
  placeholder="Search components and variants..."
192
- className="flex-1 bg-transparent text-primary placeholder-tertiary text-base focus:outline-none"
188
+ style={{
189
+ flex: 1,
190
+ background: 'transparent',
191
+ color: 'var(--text-primary)',
192
+ fontSize: '16px',
193
+ border: 'none',
194
+ outline: 'none',
195
+ }}
193
196
  />
194
- <kbd className="hidden sm:inline-flex items-center gap-1 px-2 py-0.5 text-xs text-tertiary bg-[--bg-secondary] rounded border border-[--border]">
195
- esc
196
- </kbd>
197
- </div>
197
+ <Badge size="sm" variant="default">esc</Badge>
198
+ </Stack>
198
199
 
199
200
  {/* Results */}
200
- <div
201
- ref={listRef}
202
- className="max-h-80 overflow-y-auto py-2"
203
- >
201
+ <div ref={listRef} style={{ maxHeight: '320px', overflowY: 'auto', padding: '8px 0' }}>
204
202
  {results.length === 0 ? (
205
- <div className="px-4 py-8 text-center text-tertiary">
206
- {query ? (
207
- <>No results for "{query}"</>
208
- ) : (
209
- <>Start typing to search...</>
210
- )}
203
+ <div style={{ padding: '32px 16px', textAlign: 'center' }}>
204
+ <Text color="tertiary">
205
+ {query ? (
206
+ <>No results for &ldquo;{query}&rdquo;</>
207
+ ) : (
208
+ <>Start typing to search...</>
209
+ )}
210
+ </Text>
211
211
  </div>
212
212
  ) : (
213
213
  results.map((result, index) => (
214
214
  <button
215
215
  key={`${result.path}-${result.variantName || "component"}`}
216
216
  onClick={() => handleSelect(result)}
217
- className={clsx(
218
- "w-full px-4 py-2 flex items-center gap-3 text-left transition-colors",
219
- index === selectedIndex
220
- ? "bg-blue-500/10 text-primary"
221
- : "text-secondary hover:bg-[--bg-hover]"
222
- )}
217
+ style={{
218
+ width: '100%',
219
+ padding: '8px 16px',
220
+ display: 'flex',
221
+ alignItems: 'center',
222
+ gap: '12px',
223
+ textAlign: 'left',
224
+ transition: 'background-color 150ms',
225
+ border: 'none',
226
+ cursor: 'pointer',
227
+ backgroundColor: index === selectedIndex ? 'rgba(59, 130, 246, 0.1)' : 'transparent',
228
+ color: index === selectedIndex ? 'var(--text-primary)' : 'var(--text-secondary)',
229
+ }}
230
+ onMouseEnter={(e) => {
231
+ setSelectedIndex(index);
232
+ if (index !== selectedIndex) {
233
+ e.currentTarget.style.backgroundColor = 'var(--bg-hover)';
234
+ }
235
+ }}
236
+ onMouseLeave={(e) => {
237
+ if (index !== selectedIndex) {
238
+ e.currentTarget.style.backgroundColor = 'transparent';
239
+ }
240
+ }}
223
241
  >
224
242
  {/* Icon/Badge */}
225
- <div
226
- className={clsx(
227
- "flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center text-xs font-medium",
228
- result.type === "component"
229
- ? "bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300"
230
- : "bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300"
231
- )}
243
+ <Badge
244
+ size="sm"
245
+ style={{
246
+ flexShrink: 0,
247
+ width: '32px',
248
+ height: '32px',
249
+ borderRadius: '8px',
250
+ display: 'flex',
251
+ alignItems: 'center',
252
+ justifyContent: 'center',
253
+ backgroundColor: result.type === "component"
254
+ ? 'rgba(59, 130, 246, 0.15)'
255
+ : 'rgba(168, 85, 247, 0.15)',
256
+ color: result.type === "component"
257
+ ? '#3b82f6'
258
+ : '#a855f7',
259
+ }}
232
260
  >
233
261
  {result.type === "component" ? "C" : "V"}
234
- </div>
262
+ </Badge>
235
263
 
236
264
  {/* Content */}
237
- <div className="flex-1 min-w-0">
238
- <div className="flex items-center gap-2">
239
- <span className="font-medium truncate">
265
+ <div style={{ flex: 1, minWidth: 0 }}>
266
+ <Stack direction="row" align="center" gap="sm">
267
+ <Text weight="medium" style={{
268
+ overflow: 'hidden',
269
+ textOverflow: 'ellipsis',
270
+ whiteSpace: 'nowrap',
271
+ }}>
240
272
  {result.componentName}
241
- </span>
273
+ </Text>
242
274
  {result.variantName && (
243
275
  <>
244
- <ChevronRightIcon className="w-3 h-3 text-tertiary flex-shrink-0" />
245
- <span className="text-secondary truncate">
276
+ <ChevronRightIcon style={{ width: 12, height: 12, color: 'var(--text-tertiary)', flexShrink: 0 }} />
277
+ <Text color="secondary" style={{
278
+ overflow: 'hidden',
279
+ textOverflow: 'ellipsis',
280
+ whiteSpace: 'nowrap',
281
+ }}>
246
282
  {result.variantName}
247
- </span>
283
+ </Text>
248
284
  </>
249
285
  )}
250
- </div>
251
- <div className="text-xs text-tertiary truncate">
286
+ </Stack>
287
+ <Text size="xs" color="tertiary" style={{
288
+ overflow: 'hidden',
289
+ textOverflow: 'ellipsis',
290
+ whiteSpace: 'nowrap',
291
+ }}>
252
292
  {result.category}
253
- </div>
293
+ </Text>
254
294
  </div>
255
295
 
256
296
  {/* Keyboard hint for selected */}
257
297
  {index === selectedIndex && (
258
- <kbd className="hidden sm:inline-flex items-center px-1.5 py-0.5 text-[10px] text-tertiary bg-[--bg-secondary] rounded border border-[--border]">
259
- enter
260
- </kbd>
298
+ <Badge size="sm" variant="default">enter</Badge>
261
299
  )}
262
300
  </button>
263
301
  ))
@@ -265,24 +303,29 @@ export function CommandPalette({
265
303
  </div>
266
304
 
267
305
  {/* Footer */}
268
- <div className="px-4 py-2 border-t border-[--border] flex items-center justify-between text-xs text-tertiary">
269
- <div className="flex items-center gap-3">
270
- <span className="flex items-center gap-1">
271
- <kbd className="px-1.5 py-0.5 bg-[--bg-secondary] rounded border border-[--border]">↑</kbd>
272
- <kbd className="px-1.5 py-0.5 bg-[--bg-secondary] rounded border border-[--border]">↓</kbd>
273
- to navigate
274
- </span>
275
- <span className="flex items-center gap-1">
276
- <kbd className="px-1.5 py-0.5 bg-[--bg-secondary] rounded border border-[--border]">↵</kbd>
277
- to select
278
- </span>
279
- </div>
280
- <span>
281
- {results.length} result{results.length !== 1 ? "s" : ""}
282
- </span>
306
+ <div style={{
307
+ padding: '8px 16px',
308
+ borderTop: '1px solid var(--border)',
309
+ }}>
310
+ <Stack direction="row" align="center" justify="between">
311
+ <Stack direction="row" align="center" gap="md">
312
+ <Stack direction="row" align="center" gap="xs">
313
+ <Badge size="sm" variant="default">&#8593;</Badge>
314
+ <Badge size="sm" variant="default">&#8595;</Badge>
315
+ <Text size="xs" color="tertiary">to navigate</Text>
316
+ </Stack>
317
+ <Stack direction="row" align="center" gap="xs">
318
+ <Badge size="sm" variant="default">&#8629;</Badge>
319
+ <Text size="xs" color="tertiary">to select</Text>
320
+ </Stack>
321
+ </Stack>
322
+ <Text size="xs" color="tertiary">
323
+ {results.length} result{results.length !== 1 ? "s" : ""}
324
+ </Text>
325
+ </Stack>
283
326
  </div>
284
- </div>
285
- </div>
327
+ </Dialog.Content>
328
+ </Dialog>
286
329
  );
287
330
  }
288
331