@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.
- package/dist/bin.js +712 -39
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
- package/dist/chunk-6JBGU74P.js.map +1 -0
- package/dist/{chunk-U4GQ2JTD.js → chunk-D35RGPAG.js} +412 -35
- package/dist/chunk-D35RGPAG.js.map +1 -0
- package/dist/{chunk-XNWDI6UT.js → chunk-F7ITZPDJ.js} +5 -5
- package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
- package/dist/{chunk-V7YLRR4C.js → chunk-Q7GOHVOK.js} +3 -3
- package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
- package/dist/{chunk-2H2JAA3U.js → chunk-SSLQXHNX.js} +3 -3
- package/dist/{core-DKHB7FYV.js → core-SKRPJQZG.js} +4 -4
- package/dist/{generate-KL24VZVD.js → generate-7AF7WRVK.js} +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/{init-NION5S3M.js → init-WKGDPYI4.js} +5 -5
- package/dist/mcp-bin.js +8 -220
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-K6JNMCGM.js +12 -0
- package/dist/{service-RWUMZ3EW.js → service-F3E4JJM7.js} +5 -5
- package/dist/static-viewer-4LQZ5AGA.js +12 -0
- package/dist/{test-ECPEXFDN.js → test-CJDNJTPZ.js} +4 -4
- package/dist/{tokens-ITADYVPF.js → tokens-JAJABYXP.js} +6 -6
- package/dist/viewer-R3Q6WAMJ.js +1822 -0
- package/dist/viewer-R3Q6WAMJ.js.map +1 -0
- package/package.json +5 -4
- package/src/bin.ts +8 -0
- package/src/build.ts +104 -13
- package/src/cli-commands.ts +18 -0
- package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
- package/src/commands/a11y-report.ts +625 -0
- package/src/commands/a11y.ts +168 -14
- package/src/commands/build.ts +16 -0
- package/src/core/auto-props.ts +464 -0
- package/src/core/schema.ts +2 -0
- package/src/core/types.ts +3 -1
- package/src/index.ts +4 -0
- package/src/mcp/server.ts +13 -220
- package/src/theme/__tests__/component-contrast.test.ts +338 -0
- package/src/theme/__tests__/contrast-validation.test.ts +326 -0
- package/src/theme/contrast.test.ts +331 -0
- package/src/theme/contrast.ts +246 -0
- package/src/theme/generator.ts +213 -1
- package/src/theme/index.ts +16 -0
- package/src/theme/types.ts +51 -0
- package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
- package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
- package/src/viewer/components/AccessibilityPanel.tsx +493 -433
- package/src/viewer/components/ActionCapture.tsx +1 -1
- package/src/viewer/components/ActionsPanel.tsx +142 -183
- package/src/viewer/components/App.tsx +159 -164
- package/src/viewer/components/BottomPanel.tsx +40 -80
- package/src/viewer/components/CodePanel.tsx +9 -87
- package/src/viewer/components/CommandPalette.tsx +117 -74
- package/src/viewer/components/ComponentGraph.tsx +143 -126
- package/src/viewer/components/ComponentHeader.tsx +46 -43
- package/src/viewer/components/ContractPanel.tsx +124 -117
- package/src/viewer/components/ErrorBoundary.tsx +47 -35
- package/src/viewer/components/FigmaEmbed.tsx +18 -13
- package/src/viewer/components/FragmentEditor.tsx +126 -63
- package/src/viewer/components/HealthDashboard.tsx +146 -171
- package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
- package/src/viewer/components/Icons.tsx +99 -98
- package/src/viewer/components/InteractionsPanel.tsx +317 -264
- package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
- package/src/viewer/components/IsolatedRender.tsx +12 -6
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
- package/src/viewer/components/LandingPage.tsx +285 -305
- package/src/viewer/components/Layout.tsx +7 -9
- package/src/viewer/components/LeftSidebar.tsx +78 -108
- package/src/viewer/components/MultiViewportPreview.tsx +254 -63
- package/src/viewer/components/PreviewArea.tsx +113 -44
- package/src/viewer/components/PreviewFrameHost.tsx +6 -5
- package/src/viewer/components/PreviewPane.tsx +2 -3
- package/src/viewer/components/PreviewToolbar.tsx +61 -104
- package/src/viewer/components/PropsEditor.tsx +154 -74
- package/src/viewer/components/PropsTable.tsx +95 -82
- package/src/viewer/components/RelationsSection.tsx +71 -40
- package/src/viewer/components/ResizablePanel.tsx +158 -55
- package/src/viewer/components/RightSidebar.tsx +46 -56
- package/src/viewer/components/ScreenshotButton.tsx +12 -12
- package/src/viewer/components/SkeletonLoader.tsx +99 -83
- package/src/viewer/components/StoryRenderer.tsx +4 -11
- package/src/viewer/components/Toast.tsx +3 -67
- package/src/viewer/components/TokenStylePanel.tsx +136 -118
- package/src/viewer/components/UsageSection.tsx +26 -26
- package/src/viewer/components/VariantMatrix.tsx +140 -47
- package/src/viewer/components/VariantTabs.tsx +24 -68
- package/src/viewer/components/ViewportSelector.tsx +106 -110
- package/src/viewer/constants/ui.ts +19 -18
- package/src/viewer/entry.tsx +8 -3
- package/src/viewer/index.ts +3 -6
- package/src/viewer/preview-frame.html +21 -5
- package/src/viewer/server.ts +7 -16
- package/src/viewer/styles/globals.css +4 -4
- package/src/viewer/utils/a11y-fixes.ts +53 -30
- package/dist/chunk-ICAIQ57V.js.map +0 -1
- package/dist/chunk-U4GQ2JTD.js.map +0 -1
- package/dist/scan-ESEXV7LF.js +0 -12
- package/dist/static-viewer-O37MJ5B6.js +0 -12
- package/dist/viewer-YDGFDTK5.js +0 -11104
- package/dist/viewer-YDGFDTK5.js.map +0 -1
- package/src/viewer/postcss.config.js +0 -6
- package/src/viewer/tailwind.config.js +0 -37
- /package/dist/{chunk-XNWDI6UT.js.map → chunk-F7ITZPDJ.js.map} +0 -0
- /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
- /package/dist/{chunk-V7YLRR4C.js.map → chunk-Q7GOHVOK.js.map} +0 -0
- /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
- /package/dist/{chunk-2H2JAA3U.js.map → chunk-SSLQXHNX.js.map} +0 -0
- /package/dist/{core-DKHB7FYV.js.map → core-SKRPJQZG.js.map} +0 -0
- /package/dist/{generate-KL24VZVD.js.map → generate-7AF7WRVK.js.map} +0 -0
- /package/dist/{init-NION5S3M.js.map → init-WKGDPYI4.js.map} +0 -0
- /package/dist/{scan-ESEXV7LF.js.map → scan-K6JNMCGM.js.map} +0 -0
- /package/dist/{service-RWUMZ3EW.js.map → service-F3E4JJM7.js.map} +0 -0
- /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-4LQZ5AGA.js.map} +0 -0
- /package/dist/{test-ECPEXFDN.js.map → test-CJDNJTPZ.js.map} +0 -0
- /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
|
-
<
|
|
11
|
-
|
|
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
|
-
</
|
|
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
|
-
</
|
|
20
|
-
</
|
|
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
|
|
6
|
-
import {
|
|
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}
|
|
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
|
|
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
|
-
|
|
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
|
-
<
|
|
267
|
-
{/* Header
|
|
268
|
-
<
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
310
|
-
<
|
|
298
|
+
<div style={{ padding: '0 8px 12px' }}>
|
|
299
|
+
<Button
|
|
300
|
+
variant="ghost"
|
|
301
|
+
size="sm"
|
|
311
302
|
onClick={onHealthClick}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
'
|
|
315
|
-
showHealth
|
|
316
|
-
|
|
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
|
|
321
|
-
<span>Dashboard</span>
|
|
322
|
-
</
|
|
309
|
+
<DashboardIcon />
|
|
310
|
+
<span style={{ marginLeft: '8px' }}>Dashboard</span>
|
|
311
|
+
</Button>
|
|
323
312
|
</div>
|
|
324
313
|
)}
|
|
325
314
|
|
|
326
315
|
{/* Component list */}
|
|
327
|
-
<
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
<
|
|
331
|
+
<Sidebar.Item
|
|
350
332
|
key={item.path}
|
|
351
|
-
|
|
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
|
-
</
|
|
340
|
+
</Sidebar.Item>
|
|
369
341
|
);
|
|
370
342
|
})}
|
|
371
|
-
</
|
|
372
|
-
|
|
373
|
-
)
|
|
374
|
-
})}
|
|
343
|
+
</Sidebar.Section>
|
|
344
|
+
);
|
|
345
|
+
})}
|
|
375
346
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
<
|
|
385
|
-
<
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
356
|
+
<Sidebar.Footer>
|
|
357
|
+
<Badge size="sm">{segments.length} components</Badge>
|
|
358
|
+
</Sidebar.Footer>
|
|
359
|
+
</Sidebar>
|
|
390
360
|
);
|
|
391
361
|
}
|