@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,4 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
+ import { AppShell } from '@fragments/ui';
2
3
 
3
4
  interface LayoutProps {
4
5
  leftSidebar: ReactNode;
@@ -7,16 +8,13 @@ interface LayoutProps {
7
8
 
8
9
  export function Layout({ leftSidebar, children }: LayoutProps) {
9
10
  return (
10
- <div className="flex h-screen overflow-hidden bg-[--bg-primary]">
11
- {/* Left Sidebar */}
12
- <aside className="hidden md:flex md:w-56 lg:w-64 flex-col border-r border-[--border] bg-[--bg-secondary] flex-shrink-0">
11
+ <AppShell layout="sidebar-inset">
12
+ <AppShell.Sidebar width="256px">
13
13
  {leftSidebar}
14
- </aside>
15
-
16
- {/* Main Content - Full width */}
17
- <main className="flex-1 flex flex-col overflow-hidden min-w-0">
14
+ </AppShell.Sidebar>
15
+ <AppShell.Main padding="none">
18
16
  {children}
19
- </main>
20
- </div>
17
+ </AppShell.Main>
18
+ </AppShell>
21
19
  );
22
20
  }
@@ -2,8 +2,8 @@ import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
2
2
  import type { SegmentDefinition } from '../../core/index.js';
3
3
  import { BRAND } from '../../core/index.js';
4
4
  import { useTheme } from './ThemeProvider.js';
5
- import clsx from 'clsx';
6
- import { SearchIcon, SunIcon, MoonIcon, DashboardIcon } from './Icons.js';
5
+ import { SunIcon, MoonIcon, DashboardIcon } from './Icons.js';
6
+ import { Sidebar, Input, Button, Badge, Text } from '@fragments/ui';
7
7
 
8
8
  // Fuzzy matching utility
9
9
  interface FuzzyMatch {
@@ -105,7 +105,7 @@ function HighlightedText({ text, indices }: { text: string; indices: number[] })
105
105
  result.push(text.slice(lastIndex, matchIndex));
106
106
  }
107
107
  result.push(
108
- <span key={matchIndex} className="text-primary font-medium">
108
+ <span key={matchIndex} style={{ color: 'var(--text-primary)', fontWeight: 500 }}>
109
109
  {text[matchIndex]}
110
110
  </span>
111
111
  );
@@ -132,7 +132,7 @@ export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onH
132
132
  const [focusedIndex, setFocusedIndex] = useState(-1);
133
133
  const { theme, setTheme, resolvedTheme } = useTheme();
134
134
  const searchInputRef = useRef<HTMLInputElement>(null);
135
- const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
135
+ const navRef = useRef<HTMLDivElement>(null);
136
136
 
137
137
  const debouncedSearch = useDebounce(search, 150);
138
138
 
@@ -197,8 +197,12 @@ export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onH
197
197
  }, [grouped]);
198
198
 
199
199
  useEffect(() => {
200
- if (focusedIndex >= 0 && focusedIndex < flatItems.length) {
201
- itemRefs.current.get(focusedIndex)?.focus();
200
+ if (focusedIndex >= 0 && focusedIndex < flatItems.length && navRef.current) {
201
+ // Query all nav item buttons rendered by Sidebar.Item inside the nav
202
+ const buttons = navRef.current.querySelectorAll<HTMLButtonElement>('li > button[type="button"]');
203
+ if (buttons[focusedIndex]) {
204
+ buttons[focusedIndex].focus();
205
+ }
202
206
  }
203
207
  }, [focusedIndex, flatItems.length]);
204
208
 
@@ -252,140 +256,106 @@ export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onH
252
256
  return () => document.removeEventListener('keydown', handleKeyDown);
253
257
  }, [handleKeyDown]);
254
258
 
255
- const setItemRef = useCallback((index: number, el: HTMLButtonElement | null) => {
256
- if (el) itemRefs.current.set(index, el);
257
- else itemRefs.current.delete(index);
258
- }, []);
259
-
260
- let globalIndex = 0;
261
259
  const sortedEntries = Object.entries(grouped).sort(([a], [b]) =>
262
260
  a.toLowerCase().localeCompare(b.toLowerCase())
263
261
  );
264
262
 
265
263
  return (
266
- <div className="flex flex-col h-full bg-[--bg-secondary]">
267
- {/* Header - matches toolbar height (py-2 + border) */}
268
- <div className="flex items-center justify-between px-4 py-2 border-b border-[--border]">
269
- <span className="text-sm font-medium text-primary">{BRAND.name}</span>
270
- <button
271
- onClick={toggleTheme}
272
- className={clsx(
273
- 'p-1.5 rounded-md',
274
- 'text-tertiary hover:text-secondary hover:bg-[--bg-hover]',
275
- 'focus:outline-none'
276
- )}
277
- title={`Theme: ${resolvedTheme}`}
278
- >
279
- {resolvedTheme === 'dark' ? (
280
- <MoonIcon className="w-4 h-4" />
281
- ) : (
282
- <SunIcon className="w-4 h-4" />
283
- )}
284
- </button>
285
- </div>
286
-
287
- {/* Search */}
288
- <div className="px-3 py-3">
289
- <div className="relative">
290
- <SearchIcon className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-tertiary pointer-events-none" />
291
- <input
292
- ref={searchInputRef}
293
- type="text"
294
- placeholder="Search"
295
- value={search}
296
- onChange={(e) => setSearch(e.target.value)}
297
- className={clsx(
298
- 'w-full pl-8 pr-3 py-1.5 text-sm rounded-md',
299
- 'bg-[--bg-primary] text-primary placeholder:text-tertiary',
300
- 'border border-[--border-subtle]',
301
- 'focus:outline-none focus:border-[--border-strong]'
264
+ <Sidebar collapsible="none" style={{ height: '100%', backgroundColor: 'var(--bg-secondary)' }}>
265
+ {/* Header */}
266
+ <Sidebar.Header>
267
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
268
+ <Text size="sm" weight="medium">{BRAND.name}</Text>
269
+ <Button
270
+ variant="ghost"
271
+ size="sm"
272
+ icon
273
+ onClick={toggleTheme}
274
+ aria-label={`Theme: ${resolvedTheme}`}
275
+ >
276
+ {resolvedTheme === 'dark' ? (
277
+ <MoonIcon />
278
+ ) : (
279
+ <SunIcon />
302
280
  )}
303
- />
281
+ </Button>
304
282
  </div>
283
+ </Sidebar.Header>
284
+
285
+ {/* Search */}
286
+ <div style={{ padding: '12px' }}>
287
+ <Input
288
+ ref={searchInputRef}
289
+ value={search}
290
+ onChange={(value: string) => setSearch(value)}
291
+ placeholder="Search"
292
+ size="sm"
293
+ />
305
294
  </div>
306
295
 
307
296
  {/* Dashboard Link */}
308
297
  {onHealthClick && (
309
- <div className="px-2 pb-3">
310
- <button
298
+ <div style={{ padding: '0 8px 12px' }}>
299
+ <Button
300
+ variant="ghost"
301
+ size="sm"
311
302
  onClick={onHealthClick}
312
- className={clsx(
313
- 'w-full flex items-center gap-2 px-2 py-2 rounded-md text-sm',
314
- 'focus:outline-none',
315
- showHealth
316
- ? 'bg-[--bg-hover] text-primary'
317
- : 'text-secondary hover:bg-[--bg-hover] hover:text-primary'
318
- )}
303
+ style={{
304
+ width: '100%',
305
+ justifyContent: 'flex-start',
306
+ ...(showHealth ? { backgroundColor: 'var(--bg-hover)', color: 'var(--text-primary)' } : {}),
307
+ }}
319
308
  >
320
- <DashboardIcon className="w-4 h-4" />
321
- <span>Dashboard</span>
322
- </button>
309
+ <DashboardIcon />
310
+ <span style={{ marginLeft: '8px' }}>Dashboard</span>
311
+ </Button>
323
312
  </div>
324
313
  )}
325
314
 
326
315
  {/* Component list */}
327
- <nav className="flex-1 overflow-y-auto px-2 pb-4">
328
- {sortedEntries.map(([category, items]) => {
329
- const sortedItems = [...items].sort((a, b) =>
330
- a.segment.meta.name.toLowerCase().localeCompare(b.segment.meta.name.toLowerCase())
331
- );
332
-
333
- return (
334
- <div key={category} className="mb-1">
335
- {/* Category divider */}
336
- <div className="px-2 py-1.5 text-xs font-medium text-tertiary mt-2 first:mt-0">
337
- {category}
338
- </div>
339
-
340
- {/* Items */}
341
- <div className="space-y-0.5">
316
+ <div ref={navRef}>
317
+ <Sidebar.Nav aria-label="Components">
318
+ {sortedEntries.map(([category, items]) => {
319
+ const sortedItems = [...items]
320
+ .filter(item => item.segment?.meta?.name)
321
+ .sort((a, b) =>
322
+ a.segment.meta.name.toLowerCase().localeCompare(b.segment.meta.name.toLowerCase())
323
+ );
324
+
325
+ return (
326
+ <Sidebar.Section key={category} label={category}>
342
327
  {sortedItems.map((item) => {
343
- const isActive = activeSegment === item.path;
344
- const currentIndex = globalIndex++;
345
- const isFocused = focusedIndex === currentIndex;
346
328
  const nameIndices = highlightMap.get(item.path) || [];
347
329
 
348
330
  return (
349
- <button
331
+ <Sidebar.Item
350
332
  key={item.path}
351
- ref={(el) => setItemRef(currentIndex, el)}
333
+ active={activeSegment === item.path}
352
334
  onClick={() => onSelect(item.path)}
353
- onFocus={() => setFocusedIndex(currentIndex)}
354
- className={clsx(
355
- 'w-full text-left px-2 py-1.5 rounded-md text-sm',
356
- 'focus:outline-none',
357
- isActive
358
- ? 'bg-[--bg-hover] text-primary'
359
- : isFocused
360
- ? 'bg-[--bg-hover] text-primary'
361
- : 'text-secondary hover:bg-[--bg-hover] hover:text-primary'
362
- )}
363
335
  >
364
336
  <HighlightedText
365
337
  text={item.segment.meta.name}
366
338
  indices={nameIndices}
367
339
  />
368
- </button>
340
+ </Sidebar.Item>
369
341
  );
370
342
  })}
371
- </div>
372
- </div>
373
- );
374
- })}
343
+ </Sidebar.Section>
344
+ );
345
+ })}
375
346
 
376
- {Object.keys(grouped).length === 0 && (
377
- <div className="px-2 py-8 text-center text-tertiary text-sm">
378
- No results
379
- </div>
380
- )}
381
- </nav>
347
+ {Object.keys(grouped).length === 0 && (
348
+ <div style={{ padding: '8px 32px', textAlign: 'center' }}>
349
+ <Text size="sm" color="tertiary">No results</Text>
350
+ </div>
351
+ )}
352
+ </Sidebar.Nav>
353
+ </div>
382
354
 
383
355
  {/* Footer */}
384
- <div className="px-4 py-3 border-t border-[--border-subtle]">
385
- <div className="text-xs text-tertiary">
386
- {segments.length} components
387
- </div>
388
- </div>
389
- </div>
356
+ <Sidebar.Footer>
357
+ <Badge size="sm">{segments.length} components</Badge>
358
+ </Sidebar.Footer>
359
+ </Sidebar>
390
360
  );
391
361
  }