@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.
- package/dist/bin.js +996 -79
- 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-7OPWMLOE.js +1625 -0
- package/dist/chunk-7OPWMLOE.js.map +1 -0
- package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
- package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
- package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
- package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
- package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
- package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
- package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
- package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
- package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.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-7CHRKQ7P.js} +5 -5
- package/dist/mcp-bin.js +8 -220
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-WY23TJCP.js +12 -0
- package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
- package/dist/static-viewer-GBR7YNF3.js +12 -0
- package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
- package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
- package/dist/viewer-SUFOISZM.js +1822 -0
- package/dist/viewer-SUFOISZM.js.map +1 -0
- package/package.json +6 -5
- package/src/bin.ts +31 -0
- package/src/build.ts +147 -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/commands/graph.ts +274 -0
- package/src/core/auto-props.ts +464 -0
- package/src/core/composition.ts +64 -1
- package/src/core/graph-extractor.test.ts +542 -0
- package/src/core/graph-extractor.ts +601 -0
- package/src/core/importAnalyzer.ts +5 -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 +276 -183
- 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 +151 -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 +12 -10
- package/src/viewer/components/LeftSidebar.tsx +103 -155
- package/src/viewer/components/MultiViewportPreview.tsx +254 -63
- package/src/viewer/components/PreviewArea.tsx +113 -44
- package/src/viewer/components/PreviewFrameHost.tsx +36 -6
- package/src/viewer/components/PreviewPane.tsx +2 -3
- package/src/viewer/components/PreviewToolbar.tsx +109 -105
- 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 +121 -114
- package/src/viewer/constants/ui.ts +23 -22
- package/src/viewer/entry.tsx +8 -3
- package/src/viewer/index.ts +3 -6
- package/src/viewer/preview-frame.html +43 -18
- package/src/viewer/server.ts +7 -16
- package/src/viewer/styles/globals.css +46 -85
- package/src/viewer/utils/a11y-fixes.ts +53 -30
- package/dist/chunk-ICAIQ57V.js.map +0 -1
- package/dist/chunk-U4GQ2JTD.js +0 -832
- 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-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
- /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
- /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
- /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
- /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
- /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
- /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
- /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
- /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
- /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
- /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
|
|
13
13
|
import { useState, useMemo, useRef, useCallback } from "react";
|
|
14
14
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
15
|
-
import clsx from "clsx";
|
|
16
15
|
import type { SegmentVariant } from "../../core/index.js";
|
|
17
16
|
import { ErrorBoundary } from "./ErrorBoundary.js";
|
|
18
17
|
import { StoryRenderer, LoaderIndicator } from "./StoryRenderer.js";
|
|
@@ -42,7 +41,7 @@ interface VariantMatrixProps {
|
|
|
42
41
|
type GridSize = "small" | "medium" | "large";
|
|
43
42
|
|
|
44
43
|
interface GridConfig {
|
|
45
|
-
|
|
44
|
+
gridTemplateColumns: string;
|
|
46
45
|
minHeight: string;
|
|
47
46
|
heightPx: number; // For virtualization
|
|
48
47
|
scale: number;
|
|
@@ -50,9 +49,9 @@ interface GridConfig {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
const GRID_SIZES: Record<GridSize, GridConfig> = {
|
|
53
|
-
small: {
|
|
54
|
-
medium: {
|
|
55
|
-
large: {
|
|
52
|
+
small: { gridTemplateColumns: "repeat(4, 1fr)", minHeight: "150px", heightPx: 150, scale: 0.5, colCount: 4 },
|
|
53
|
+
medium: { gridTemplateColumns: "repeat(3, 1fr)", minHeight: "200px", heightPx: 200, scale: 0.75, colCount: 3 },
|
|
54
|
+
large: { gridTemplateColumns: "repeat(2, 1fr)", minHeight: "300px", heightPx: 300, scale: 1, colCount: 2 },
|
|
56
55
|
};
|
|
57
56
|
|
|
58
57
|
/** Threshold for enabling virtualization */
|
|
@@ -70,6 +69,7 @@ export function VariantMatrix({
|
|
|
70
69
|
}: VariantMatrixProps) {
|
|
71
70
|
const [gridSize, setGridSize] = useState<GridSize>("medium");
|
|
72
71
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
72
|
+
const [hoveredButton, setHoveredButton] = useState<GridSize | null>(null);
|
|
73
73
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
74
74
|
|
|
75
75
|
const gridConfig = GRID_SIZES[gridSize];
|
|
@@ -94,35 +94,68 @@ export function VariantMatrix({
|
|
|
94
94
|
|
|
95
95
|
if (variants.length === 0) {
|
|
96
96
|
return (
|
|
97
|
-
<div
|
|
97
|
+
<div style={{
|
|
98
|
+
display: 'flex',
|
|
99
|
+
alignItems: 'center',
|
|
100
|
+
justifyContent: 'center',
|
|
101
|
+
height: '100%',
|
|
102
|
+
color: 'var(--text-muted)',
|
|
103
|
+
}}>
|
|
98
104
|
No variants to display
|
|
99
105
|
</div>
|
|
100
106
|
);
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
return (
|
|
104
|
-
<div
|
|
110
|
+
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
105
111
|
{/* Toolbar */}
|
|
106
|
-
<div
|
|
107
|
-
|
|
112
|
+
<div style={{
|
|
113
|
+
flexShrink: 0,
|
|
114
|
+
padding: '8px 16px',
|
|
115
|
+
borderBottom: '1px solid var(--border)',
|
|
116
|
+
background: 'var(--bg-secondary)',
|
|
117
|
+
display: 'flex',
|
|
118
|
+
alignItems: 'center',
|
|
119
|
+
justifyContent: 'space-between',
|
|
120
|
+
}}>
|
|
121
|
+
<div style={{ fontSize: 14, color: 'var(--text-secondary)' }}>
|
|
108
122
|
{variants.length} variant{variants.length !== 1 ? "s" : ""}
|
|
109
123
|
{useVirtualization && (
|
|
110
|
-
<span
|
|
124
|
+
<span style={{ marginLeft: 8, fontSize: 12, color: 'var(--text-tertiary)' }}>(virtualized)</span>
|
|
111
125
|
)}
|
|
112
126
|
</div>
|
|
113
|
-
<div
|
|
114
|
-
<span
|
|
115
|
-
<div
|
|
127
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
128
|
+
<span style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Grid size:</span>
|
|
129
|
+
<div style={{
|
|
130
|
+
display: 'flex',
|
|
131
|
+
borderRadius: 6,
|
|
132
|
+
border: '1px solid var(--border)',
|
|
133
|
+
overflow: 'hidden',
|
|
134
|
+
}}>
|
|
116
135
|
{(["small", "medium", "large"] as GridSize[]).map((size) => (
|
|
117
136
|
<button
|
|
118
137
|
key={size}
|
|
119
138
|
onClick={() => setGridSize(size)}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
onMouseEnter={() => setHoveredButton(size)}
|
|
140
|
+
onMouseLeave={() => setHoveredButton(null)}
|
|
141
|
+
style={{
|
|
142
|
+
padding: '4px 8px',
|
|
143
|
+
fontSize: 12,
|
|
144
|
+
textTransform: 'capitalize',
|
|
145
|
+
transition: 'background-color 0.15s, color 0.15s',
|
|
146
|
+
border: 'none',
|
|
147
|
+
cursor: 'pointer',
|
|
148
|
+
background: gridSize === size
|
|
149
|
+
? 'var(--bg-hover)'
|
|
150
|
+
: hoveredButton === size
|
|
151
|
+
? 'var(--bg-hover)'
|
|
152
|
+
: 'transparent',
|
|
153
|
+
color: gridSize === size
|
|
154
|
+
? 'var(--text-primary)'
|
|
155
|
+
: hoveredButton === size
|
|
156
|
+
? 'var(--text-secondary)'
|
|
157
|
+
: 'var(--text-tertiary)',
|
|
158
|
+
}}
|
|
126
159
|
>
|
|
127
160
|
{size}
|
|
128
161
|
</button>
|
|
@@ -133,7 +166,7 @@ export function VariantMatrix({
|
|
|
133
166
|
|
|
134
167
|
{/* Grid - Virtualized or Regular */}
|
|
135
168
|
{useVirtualization ? (
|
|
136
|
-
<div ref={scrollRef}
|
|
169
|
+
<div ref={scrollRef} style={{ flex: 1, overflow: 'auto', padding: 16 }}>
|
|
137
170
|
<div
|
|
138
171
|
style={{
|
|
139
172
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
@@ -157,7 +190,12 @@ export function VariantMatrix({
|
|
|
157
190
|
transform: `translateY(${virtualRow.start}px)`,
|
|
158
191
|
}}
|
|
159
192
|
>
|
|
160
|
-
<div
|
|
193
|
+
<div style={{
|
|
194
|
+
display: 'grid',
|
|
195
|
+
gap: 16,
|
|
196
|
+
gridTemplateColumns: gridConfig.gridTemplateColumns,
|
|
197
|
+
height: gridConfig.minHeight,
|
|
198
|
+
}}>
|
|
161
199
|
{rowVariants.map((variant, colIndex) => {
|
|
162
200
|
const index = startIndex + colIndex;
|
|
163
201
|
return (
|
|
@@ -186,8 +224,12 @@ export function VariantMatrix({
|
|
|
186
224
|
</div>
|
|
187
225
|
</div>
|
|
188
226
|
) : (
|
|
189
|
-
<div
|
|
190
|
-
<div
|
|
227
|
+
<div style={{ flex: 1, overflow: 'auto', padding: 16 }}>
|
|
228
|
+
<div style={{
|
|
229
|
+
display: 'grid',
|
|
230
|
+
gap: 16,
|
|
231
|
+
gridTemplateColumns: gridConfig.gridTemplateColumns,
|
|
232
|
+
}}>
|
|
191
233
|
{variants.map((variant, index) => (
|
|
192
234
|
<VariantCard
|
|
193
235
|
key={variant.name}
|
|
@@ -248,24 +290,46 @@ function VariantCard({
|
|
|
248
290
|
|
|
249
291
|
return (
|
|
250
292
|
<div
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
293
|
+
style={{
|
|
294
|
+
position: 'relative',
|
|
295
|
+
borderRadius: 8,
|
|
296
|
+
border: isHovered
|
|
297
|
+
? '2px solid #3b82f6'
|
|
298
|
+
: '1px solid var(--border)',
|
|
299
|
+
overflow: 'hidden',
|
|
300
|
+
transition: 'border-color 0.2s, box-shadow 0.2s',
|
|
301
|
+
cursor: 'pointer',
|
|
302
|
+
minHeight,
|
|
303
|
+
boxShadow: isHovered
|
|
304
|
+
? '0 10px 15px -3px rgba(0,0,0,0.1), 0 0 0 3px rgba(59,130,246,0.2)'
|
|
305
|
+
: 'none',
|
|
306
|
+
}}
|
|
258
307
|
onMouseEnter={onHover}
|
|
259
308
|
onMouseLeave={onLeave}
|
|
260
309
|
onClick={onClick}
|
|
261
310
|
>
|
|
262
311
|
{/* Header */}
|
|
263
|
-
<div
|
|
264
|
-
|
|
265
|
-
|
|
312
|
+
<div style={{
|
|
313
|
+
position: 'absolute',
|
|
314
|
+
top: 0,
|
|
315
|
+
left: 0,
|
|
316
|
+
right: 0,
|
|
317
|
+
zIndex: 10,
|
|
318
|
+
padding: '4px 8px',
|
|
319
|
+
background: 'linear-gradient(to bottom, rgba(0,0,0,0.6), transparent)',
|
|
320
|
+
}}>
|
|
321
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
322
|
+
<span style={{
|
|
323
|
+
fontSize: 12,
|
|
324
|
+
fontWeight: 500,
|
|
325
|
+
color: '#ffffff',
|
|
326
|
+
overflow: 'hidden',
|
|
327
|
+
textOverflow: 'ellipsis',
|
|
328
|
+
whiteSpace: 'nowrap',
|
|
329
|
+
}}>
|
|
266
330
|
{variant.name}
|
|
267
331
|
</span>
|
|
268
|
-
<span
|
|
332
|
+
<span style={{ fontSize: 10, color: 'rgba(255,255,255,0.7)' }}>
|
|
269
333
|
#{index + 1}
|
|
270
334
|
</span>
|
|
271
335
|
</div>
|
|
@@ -273,21 +337,44 @@ function VariantCard({
|
|
|
273
337
|
|
|
274
338
|
{/* Click to view overlay */}
|
|
275
339
|
<div
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
340
|
+
style={{
|
|
341
|
+
position: 'absolute',
|
|
342
|
+
inset: 0,
|
|
343
|
+
zIndex: 10,
|
|
344
|
+
display: 'flex',
|
|
345
|
+
alignItems: 'center',
|
|
346
|
+
justifyContent: 'center',
|
|
347
|
+
background: 'rgba(0,0,0,0.4)',
|
|
348
|
+
transition: 'opacity 0.2s',
|
|
349
|
+
opacity: isHovered ? 1 : 0,
|
|
350
|
+
pointerEvents: isHovered ? 'auto' : 'none',
|
|
351
|
+
}}
|
|
280
352
|
>
|
|
281
|
-
<span
|
|
353
|
+
<span style={{
|
|
354
|
+
padding: '6px 12px',
|
|
355
|
+
background: '#2563eb',
|
|
356
|
+
color: '#ffffff',
|
|
357
|
+
fontSize: 12,
|
|
358
|
+
fontWeight: 500,
|
|
359
|
+
borderRadius: 9999,
|
|
360
|
+
boxShadow: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)',
|
|
361
|
+
}}>
|
|
282
362
|
Click to focus
|
|
283
363
|
</span>
|
|
284
364
|
</div>
|
|
285
365
|
|
|
286
366
|
{/* Preview content */}
|
|
287
367
|
<div
|
|
288
|
-
className="h-full w-full overflow-hidden flex items-center justify-center"
|
|
289
368
|
data-theme={previewTheme}
|
|
290
|
-
style={
|
|
369
|
+
style={{
|
|
370
|
+
height: '100%',
|
|
371
|
+
width: '100%',
|
|
372
|
+
overflow: 'hidden',
|
|
373
|
+
display: 'flex',
|
|
374
|
+
alignItems: 'center',
|
|
375
|
+
justifyContent: 'center',
|
|
376
|
+
...backgroundStyle,
|
|
377
|
+
}}
|
|
291
378
|
>
|
|
292
379
|
{useIframeIsolation ? (
|
|
293
380
|
<IsolatedPreviewFrame
|
|
@@ -300,15 +387,15 @@ function VariantCard({
|
|
|
300
387
|
/>
|
|
301
388
|
) : (
|
|
302
389
|
<div
|
|
303
|
-
className="p-4"
|
|
304
390
|
style={{
|
|
391
|
+
padding: 16,
|
|
305
392
|
transform: `scale(${scale})`,
|
|
306
393
|
}}
|
|
307
394
|
>
|
|
308
395
|
<ErrorBoundary
|
|
309
396
|
componentName={componentName}
|
|
310
397
|
fallback={
|
|
311
|
-
<div
|
|
398
|
+
<div style={{ fontSize: 12, color: '#ef4444', padding: 8 }}>
|
|
312
399
|
Error rendering variant
|
|
313
400
|
</div>
|
|
314
401
|
}
|
|
@@ -317,14 +404,14 @@ function VariantCard({
|
|
|
317
404
|
{(content, isLoading, error) => {
|
|
318
405
|
if (isLoading) {
|
|
319
406
|
return (
|
|
320
|
-
<div
|
|
407
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16 }}>
|
|
321
408
|
<LoaderIndicator />
|
|
322
409
|
</div>
|
|
323
410
|
);
|
|
324
411
|
}
|
|
325
412
|
if (error) {
|
|
326
413
|
return (
|
|
327
|
-
<div
|
|
414
|
+
<div style={{ fontSize: 12, color: '#ef4444', padding: 8 }}>
|
|
328
415
|
{error.message}
|
|
329
416
|
</div>
|
|
330
417
|
);
|
|
@@ -339,8 +426,14 @@ function VariantCard({
|
|
|
339
426
|
|
|
340
427
|
{/* Tags/badges */}
|
|
341
428
|
{variant.hasPlayFunction && (
|
|
342
|
-
<div
|
|
343
|
-
<span
|
|
429
|
+
<div style={{ position: 'absolute', bottom: 8, right: 8, zIndex: 10 }}>
|
|
430
|
+
<span style={{
|
|
431
|
+
padding: '2px 6px',
|
|
432
|
+
fontSize: 10,
|
|
433
|
+
background: '#9333ea',
|
|
434
|
+
color: '#ffffff',
|
|
435
|
+
borderRadius: 4,
|
|
436
|
+
}}>
|
|
344
437
|
play
|
|
345
438
|
</span>
|
|
346
439
|
</div>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Tabs } from '@fragments/ui';
|
|
2
2
|
import type { SegmentVariant } from '../../core/index.js';
|
|
3
|
-
import clsx from 'clsx';
|
|
4
3
|
import { PlayIcon } from './Icons.js';
|
|
5
4
|
|
|
6
5
|
interface VariantTabsProps {
|
|
@@ -10,75 +9,32 @@ interface VariantTabsProps {
|
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export function VariantTabs({ variants, activeIndex, onSelect }: VariantTabsProps) {
|
|
13
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
14
|
-
const buttonRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
|
|
15
|
-
|
|
16
|
-
// Scroll active tab into view when it changes
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
const button = buttonRefs.current.get(activeIndex);
|
|
19
|
-
if (button && containerRef.current) {
|
|
20
|
-
button.scrollIntoView({
|
|
21
|
-
behavior: 'smooth',
|
|
22
|
-
block: 'nearest',
|
|
23
|
-
inline: 'center',
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
}, [activeIndex]);
|
|
27
|
-
|
|
28
|
-
// Keyboard navigation for variant tabs
|
|
29
|
-
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
30
|
-
if (e.key === 'ArrowLeft') {
|
|
31
|
-
e.preventDefault();
|
|
32
|
-
const newIndex = activeIndex > 0 ? activeIndex - 1 : variants.length - 1;
|
|
33
|
-
onSelect(newIndex);
|
|
34
|
-
} else if (e.key === 'ArrowRight') {
|
|
35
|
-
e.preventDefault();
|
|
36
|
-
const newIndex = activeIndex < variants.length - 1 ? activeIndex + 1 : 0;
|
|
37
|
-
onSelect(newIndex);
|
|
38
|
-
}
|
|
39
|
-
}, [activeIndex, variants.length, onSelect]);
|
|
40
|
-
|
|
41
12
|
if (variants.length === 0) return null;
|
|
42
13
|
|
|
14
|
+
const activeValue = variants[activeIndex]?.name ?? '';
|
|
15
|
+
|
|
43
16
|
return (
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
17
|
+
<Tabs
|
|
18
|
+
value={activeValue}
|
|
19
|
+
onValueChange={(value: string | number) => {
|
|
20
|
+
const index = variants.findIndex(v => v.name === value);
|
|
21
|
+
if (index >= 0) onSelect(index);
|
|
22
|
+
}}
|
|
50
23
|
>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'px-3 py-1 text-xs font-medium rounded whitespace-nowrap flex-shrink-0',
|
|
67
|
-
'focus:outline-none focus-visible:ring-2 focus-visible:ring-[--color-accent]',
|
|
68
|
-
'flex items-center gap-1.5',
|
|
69
|
-
isActive
|
|
70
|
-
? 'text-primary bg-[--bg-hover]'
|
|
71
|
-
: 'text-tertiary hover:text-secondary'
|
|
72
|
-
)}
|
|
73
|
-
title={variant.hasPlayFunction ? `${variant.name} (has interaction test)` : variant.name}
|
|
74
|
-
>
|
|
75
|
-
{variant.name}
|
|
76
|
-
{variant.hasPlayFunction && (
|
|
77
|
-
<PlayIcon className="w-3 h-3 text-[--color-accent]" />
|
|
78
|
-
)}
|
|
79
|
-
</button>
|
|
80
|
-
);
|
|
81
|
-
})}
|
|
82
|
-
</div>
|
|
24
|
+
<Tabs.List variant="pills">
|
|
25
|
+
{variants.map((variant) => (
|
|
26
|
+
<Tabs.Tab key={variant.name} value={variant.name}>
|
|
27
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
28
|
+
{variant.name}
|
|
29
|
+
{variant.hasPlayFunction && (
|
|
30
|
+
<span style={{ display: 'inline-flex', width: '12px', height: '12px', color: 'var(--color-accent)' }}>
|
|
31
|
+
<PlayIcon />
|
|
32
|
+
</span>
|
|
33
|
+
)}
|
|
34
|
+
</span>
|
|
35
|
+
</Tabs.Tab>
|
|
36
|
+
))}
|
|
37
|
+
</Tabs.List>
|
|
38
|
+
</Tabs>
|
|
83
39
|
);
|
|
84
40
|
}
|