@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
|
@@ -4,133 +4,152 @@
|
|
|
4
4
|
* Shows animated placeholders while the app is loading.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
interface SkeletonProps {
|
|
10
|
-
className?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function Skeleton({ className }: SkeletonProps) {
|
|
14
|
-
return (
|
|
15
|
-
<div
|
|
16
|
-
className={clsx(
|
|
17
|
-
"animate-pulse bg-[--bg-hover] rounded",
|
|
18
|
-
className
|
|
19
|
-
)}
|
|
20
|
-
/>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
7
|
+
import { Skeleton, Loading } from '@fragments/ui';
|
|
23
8
|
|
|
24
9
|
/**
|
|
25
10
|
* Full app skeleton shown during initial load
|
|
26
11
|
*/
|
|
27
12
|
export function AppSkeleton() {
|
|
28
13
|
return (
|
|
29
|
-
<div
|
|
14
|
+
<div style={{ display: 'flex', height: '100vh', backgroundColor: 'var(--bg-primary)' }}>
|
|
30
15
|
{/* Sidebar skeleton */}
|
|
31
|
-
<div
|
|
16
|
+
<div style={{
|
|
17
|
+
width: '240px',
|
|
18
|
+
borderRight: '1px solid var(--border)',
|
|
19
|
+
backgroundColor: 'var(--bg-secondary)',
|
|
20
|
+
display: 'flex',
|
|
21
|
+
flexDirection: 'column',
|
|
22
|
+
}}>
|
|
32
23
|
{/* Header */}
|
|
33
|
-
<div
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
<div style={{
|
|
25
|
+
display: 'flex',
|
|
26
|
+
alignItems: 'center',
|
|
27
|
+
justifyContent: 'space-between',
|
|
28
|
+
padding: '8px 16px',
|
|
29
|
+
borderBottom: '1px solid var(--border)',
|
|
30
|
+
}}>
|
|
31
|
+
<Skeleton variant="text" style={{ width: '80px' }} />
|
|
32
|
+
<Skeleton variant="circular" style={{ width: '24px', height: '24px' }} />
|
|
36
33
|
</div>
|
|
37
34
|
|
|
38
35
|
{/* Search */}
|
|
39
|
-
<div
|
|
40
|
-
<Skeleton
|
|
36
|
+
<div style={{ padding: '12px' }}>
|
|
37
|
+
<Skeleton variant="rectangular" style={{ width: '100%', height: '32px', borderRadius: '6px' }} />
|
|
41
38
|
</div>
|
|
42
39
|
|
|
43
40
|
{/* Component list */}
|
|
44
|
-
<div
|
|
41
|
+
<div style={{ flex: 1, padding: '0 8px', overflow: 'hidden' }}>
|
|
45
42
|
{/* Category 1 */}
|
|
46
|
-
<div>
|
|
47
|
-
<Skeleton
|
|
48
|
-
<div
|
|
49
|
-
<Skeleton
|
|
50
|
-
<Skeleton
|
|
51
|
-
<Skeleton
|
|
43
|
+
<div style={{ marginBottom: '16px' }}>
|
|
44
|
+
<Skeleton variant="text" style={{ width: '64px', margin: '0 8px 8px' }} />
|
|
45
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
46
|
+
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
47
|
+
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
48
|
+
<Skeleton variant="rectangular" style={{ width: '75%', height: '28px', borderRadius: '6px' }} />
|
|
52
49
|
</div>
|
|
53
50
|
</div>
|
|
54
51
|
|
|
55
52
|
{/* Category 2 */}
|
|
56
|
-
<div>
|
|
57
|
-
<Skeleton
|
|
58
|
-
<div
|
|
59
|
-
<Skeleton
|
|
60
|
-
<Skeleton
|
|
61
|
-
<Skeleton
|
|
62
|
-
<Skeleton
|
|
53
|
+
<div style={{ marginBottom: '16px' }}>
|
|
54
|
+
<Skeleton variant="text" style={{ width: '80px', margin: '0 8px 8px' }} />
|
|
55
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
56
|
+
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
57
|
+
<Skeleton variant="rectangular" style={{ width: '83%', height: '28px', borderRadius: '6px' }} />
|
|
58
|
+
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
59
|
+
<Skeleton variant="rectangular" style={{ width: '66%', height: '28px', borderRadius: '6px' }} />
|
|
63
60
|
</div>
|
|
64
61
|
</div>
|
|
65
62
|
|
|
66
63
|
{/* Category 3 */}
|
|
67
64
|
<div>
|
|
68
|
-
<Skeleton
|
|
69
|
-
<div
|
|
70
|
-
<Skeleton
|
|
71
|
-
<Skeleton
|
|
65
|
+
<Skeleton variant="text" style={{ width: '96px', margin: '0 8px 8px' }} />
|
|
66
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
67
|
+
<Skeleton variant="rectangular" style={{ width: '80%', height: '28px', borderRadius: '6px' }} />
|
|
68
|
+
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
72
69
|
</div>
|
|
73
70
|
</div>
|
|
74
71
|
</div>
|
|
75
72
|
|
|
76
73
|
{/* Footer */}
|
|
77
|
-
<div
|
|
78
|
-
<Skeleton
|
|
74
|
+
<div style={{ padding: '12px 16px', borderTop: '1px solid var(--border-subtle)' }}>
|
|
75
|
+
<Skeleton variant="text" style={{ width: '96px' }} />
|
|
79
76
|
</div>
|
|
80
77
|
</div>
|
|
81
78
|
|
|
82
79
|
{/* Main content skeleton */}
|
|
83
|
-
<div
|
|
80
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
84
81
|
{/* Toolbar */}
|
|
85
|
-
<div
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
<div style={{
|
|
83
|
+
display: 'flex',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
justifyContent: 'space-between',
|
|
86
|
+
padding: '8px 16px',
|
|
87
|
+
borderBottom: '1px solid var(--border)',
|
|
88
|
+
backgroundColor: 'var(--bg-secondary)',
|
|
89
|
+
}}>
|
|
90
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
91
|
+
<Skeleton variant="text" style={{ width: '96px', height: '20px' }} />
|
|
92
|
+
<Skeleton variant="text" style={{ width: '64px', height: '16px' }} />
|
|
89
93
|
</div>
|
|
90
|
-
<div
|
|
91
|
-
<Skeleton
|
|
92
|
-
<Skeleton
|
|
93
|
-
<Skeleton
|
|
94
|
-
<Skeleton
|
|
95
|
-
<Skeleton
|
|
94
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
95
|
+
<Skeleton variant="rectangular" style={{ width: '64px', height: '28px', borderRadius: '4px' }} />
|
|
96
|
+
<Skeleton variant="rectangular" style={{ width: '96px', height: '28px', borderRadius: '4px' }} />
|
|
97
|
+
<Skeleton variant="rectangular" style={{ width: '80px', height: '28px', borderRadius: '4px' }} />
|
|
98
|
+
<Skeleton variant="circular" style={{ width: '24px', height: '24px' }} />
|
|
99
|
+
<Skeleton variant="circular" style={{ width: '24px', height: '24px' }} />
|
|
96
100
|
</div>
|
|
97
101
|
</div>
|
|
98
102
|
|
|
99
103
|
{/* Variant tabs */}
|
|
100
|
-
<div
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<Skeleton
|
|
104
|
+
<div style={{
|
|
105
|
+
padding: '8px 16px',
|
|
106
|
+
borderBottom: '1px solid var(--border)',
|
|
107
|
+
backgroundColor: 'var(--bg-primary)',
|
|
108
|
+
}}>
|
|
109
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
110
|
+
<Skeleton variant="rectangular" style={{ width: '64px', height: '28px', borderRadius: '6px' }} />
|
|
111
|
+
<Skeleton variant="rectangular" style={{ width: '80px', height: '28px', borderRadius: '6px' }} />
|
|
112
|
+
<Skeleton variant="rectangular" style={{ width: '56px', height: '28px', borderRadius: '6px' }} />
|
|
113
|
+
<Skeleton variant="rectangular" style={{ width: '72px', height: '28px', borderRadius: '6px' }} />
|
|
114
|
+
<Skeleton variant="rectangular" style={{ width: '48px', height: '28px', borderRadius: '6px' }} />
|
|
107
115
|
</div>
|
|
108
116
|
</div>
|
|
109
117
|
|
|
110
118
|
{/* Preview area */}
|
|
111
|
-
<div
|
|
112
|
-
<Skeleton
|
|
119
|
+
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
|
|
120
|
+
<Skeleton variant="rectangular" style={{ width: '256px', height: '128px', borderRadius: '8px' }} />
|
|
113
121
|
</div>
|
|
114
122
|
|
|
115
123
|
{/* Bottom panel */}
|
|
116
|
-
<div
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
<div style={{
|
|
125
|
+
height: '256px',
|
|
126
|
+
borderTop: '1px solid var(--border)',
|
|
127
|
+
backgroundColor: 'var(--bg-secondary)',
|
|
128
|
+
}}>
|
|
129
|
+
<div style={{
|
|
130
|
+
display: 'flex',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
gap: '4px',
|
|
133
|
+
padding: '0 16px',
|
|
134
|
+
height: '40px',
|
|
135
|
+
borderBottom: '1px solid var(--border-subtle)',
|
|
136
|
+
}}>
|
|
137
|
+
<Skeleton variant="rectangular" style={{ width: '48px', height: '24px', borderRadius: '4px' }} />
|
|
138
|
+
<Skeleton variant="rectangular" style={{ width: '40px', height: '24px', borderRadius: '4px' }} />
|
|
139
|
+
<Skeleton variant="rectangular" style={{ width: '48px', height: '24px', borderRadius: '4px' }} />
|
|
121
140
|
</div>
|
|
122
|
-
<div
|
|
123
|
-
<div
|
|
124
|
-
<Skeleton
|
|
125
|
-
<Skeleton
|
|
141
|
+
<div style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
142
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
143
|
+
<Skeleton variant="text" style={{ width: '80px' }} />
|
|
144
|
+
<Skeleton variant="rectangular" style={{ width: '128px', height: '32px', borderRadius: '4px' }} />
|
|
126
145
|
</div>
|
|
127
|
-
<div
|
|
128
|
-
<Skeleton
|
|
129
|
-
<Skeleton
|
|
146
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
147
|
+
<Skeleton variant="text" style={{ width: '64px' }} />
|
|
148
|
+
<Skeleton variant="rectangular" style={{ width: '96px', height: '32px', borderRadius: '4px' }} />
|
|
130
149
|
</div>
|
|
131
|
-
<div
|
|
132
|
-
<Skeleton
|
|
133
|
-
<Skeleton
|
|
150
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
151
|
+
<Skeleton variant="text" style={{ width: '96px' }} />
|
|
152
|
+
<Skeleton variant="rectangular" style={{ width: '80px', height: '32px', borderRadius: '4px' }} />
|
|
134
153
|
</div>
|
|
135
154
|
</div>
|
|
136
155
|
</div>
|
|
@@ -144,11 +163,8 @@ export function AppSkeleton() {
|
|
|
144
163
|
*/
|
|
145
164
|
export function PreviewSkeleton() {
|
|
146
165
|
return (
|
|
147
|
-
<div
|
|
148
|
-
<
|
|
149
|
-
<div className="animate-spin w-8 h-8 border-2 border-[--border] border-t-[--color-accent] rounded-full mx-auto" />
|
|
150
|
-
<p className="text-sm text-tertiary">Loading component...</p>
|
|
151
|
-
</div>
|
|
166
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
|
|
167
|
+
<Loading size="md" />
|
|
152
168
|
</div>
|
|
153
169
|
);
|
|
154
170
|
}
|
|
@@ -26,7 +26,6 @@ export function StoryRenderer({ variant, children }: StoryRendererProps) {
|
|
|
26
26
|
const hasLoaders = variant.loaders && variant.loaders.length > 0;
|
|
27
27
|
|
|
28
28
|
useEffect(() => {
|
|
29
|
-
// Reset state when variant changes
|
|
30
29
|
setLoadedData(null);
|
|
31
30
|
setError(null);
|
|
32
31
|
|
|
@@ -39,14 +38,12 @@ export function StoryRenderer({ variant, children }: StoryRendererProps) {
|
|
|
39
38
|
|
|
40
39
|
async function executeLoaders() {
|
|
41
40
|
try {
|
|
42
|
-
// Execute all loaders in parallel
|
|
43
41
|
const results = await Promise.all(
|
|
44
42
|
variant.loaders!.map((loader) => loader())
|
|
45
43
|
);
|
|
46
44
|
|
|
47
45
|
if (cancelled) return;
|
|
48
46
|
|
|
49
|
-
// Merge all loaded data (later loaders override earlier ones)
|
|
50
47
|
const merged = results.reduce(
|
|
51
48
|
(acc, result) => ({ ...acc, ...result }),
|
|
52
49
|
{}
|
|
@@ -71,22 +68,18 @@ export function StoryRenderer({ variant, children }: StoryRendererProps) {
|
|
|
71
68
|
};
|
|
72
69
|
}, [variant, hasLoaders]);
|
|
73
70
|
|
|
74
|
-
// If loading, pass null content with loading flag
|
|
75
71
|
if (hasLoaders && isLoading) {
|
|
76
72
|
return <>{children(null, true, null)}</>;
|
|
77
73
|
}
|
|
78
74
|
|
|
79
|
-
// If error during loading, pass error
|
|
80
75
|
if (error) {
|
|
81
76
|
return <>{children(null, false, error)}</>;
|
|
82
77
|
}
|
|
83
78
|
|
|
84
|
-
// Build render options with loaded data only
|
|
85
79
|
const renderOptions = useMemo(() => ({
|
|
86
80
|
loadedData: hasLoaders ? loadedData ?? undefined : undefined,
|
|
87
81
|
}), [hasLoaders, loadedData]);
|
|
88
82
|
|
|
89
|
-
// Render the variant with options
|
|
90
83
|
try {
|
|
91
84
|
const content = variant.render(renderOptions);
|
|
92
85
|
return <>{children(content, false, null)}</>;
|
|
@@ -101,15 +94,15 @@ export function StoryRenderer({ variant, children }: StoryRendererProps) {
|
|
|
101
94
|
*/
|
|
102
95
|
export function LoaderIndicator() {
|
|
103
96
|
return (
|
|
104
|
-
<div
|
|
97
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', color: 'var(--text-secondary)', fontSize: '14px' }}>
|
|
105
98
|
<svg
|
|
106
|
-
|
|
99
|
+
style={{ animation: 'spin 1s linear infinite', width: '16px', height: '16px' }}
|
|
107
100
|
xmlns="http://www.w3.org/2000/svg"
|
|
108
101
|
fill="none"
|
|
109
102
|
viewBox="0 0 24 24"
|
|
110
103
|
>
|
|
111
104
|
<circle
|
|
112
|
-
|
|
105
|
+
style={{ opacity: 0.25 }}
|
|
113
106
|
cx="12"
|
|
114
107
|
cy="12"
|
|
115
108
|
r="10"
|
|
@@ -117,7 +110,7 @@ export function LoaderIndicator() {
|
|
|
117
110
|
strokeWidth="4"
|
|
118
111
|
/>
|
|
119
112
|
<path
|
|
120
|
-
|
|
113
|
+
style={{ opacity: 0.75 }}
|
|
121
114
|
fill="currentColor"
|
|
122
115
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
123
116
|
/>
|
|
@@ -1,67 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export interface ToastMessage {
|
|
5
|
-
id: string;
|
|
6
|
-
type: 'success' | 'error' | 'info';
|
|
7
|
-
message: string;
|
|
8
|
-
duration?: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface ToastProps {
|
|
12
|
-
messages: ToastMessage[];
|
|
13
|
-
onDismiss: (id: string) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function Toast({ messages, onDismiss }: ToastProps) {
|
|
17
|
-
return (
|
|
18
|
-
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
|
|
19
|
-
{messages.map((toast) => (
|
|
20
|
-
<ToastItem key={toast.id} toast={toast} onDismiss={onDismiss} />
|
|
21
|
-
))}
|
|
22
|
-
</div>
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function ToastItem({ toast, onDismiss }: { toast: ToastMessage; onDismiss: (id: string) => void }) {
|
|
27
|
-
const [isVisible, setIsVisible] = useState(false);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
// Animate in
|
|
31
|
-
requestAnimationFrame(() => setIsVisible(true));
|
|
32
|
-
|
|
33
|
-
// Auto-dismiss
|
|
34
|
-
const timer = setTimeout(() => {
|
|
35
|
-
setIsVisible(false);
|
|
36
|
-
setTimeout(() => onDismiss(toast.id), 200);
|
|
37
|
-
}, toast.duration || 3000);
|
|
38
|
-
|
|
39
|
-
return () => clearTimeout(timer);
|
|
40
|
-
}, [toast.id, toast.duration, onDismiss]);
|
|
41
|
-
|
|
42
|
-
const iconMap = {
|
|
43
|
-
success: <CheckIcon className="w-4 h-4 text-emerald-500" />,
|
|
44
|
-
error: <WarningIcon className="w-4 h-4 text-red-500" />,
|
|
45
|
-
info: <RefreshIcon className="w-4 h-4 text-blue-500" />,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const bgMap = {
|
|
49
|
-
success: 'bg-emerald-500/10 border-emerald-500/20',
|
|
50
|
-
error: 'bg-red-500/10 border-red-500/20',
|
|
51
|
-
info: 'bg-blue-500/10 border-blue-500/20',
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div
|
|
56
|
-
className={`
|
|
57
|
-
flex items-center gap-2 px-3 py-2 rounded-lg border backdrop-blur-sm
|
|
58
|
-
shadow-lg transition-all duration-200 ease-out
|
|
59
|
-
${bgMap[toast.type]}
|
|
60
|
-
${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'}
|
|
61
|
-
`}
|
|
62
|
-
>
|
|
63
|
-
{iconMap[toast.type]}
|
|
64
|
-
<span className="text-sm text-primary font-medium">{toast.message}</span>
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
1
|
+
// Re-export Fragments UI Toast for use in the viewer
|
|
2
|
+
export { ToastProvider, useToast } from '@fragments/ui';
|
|
3
|
+
export type { ToastData as ToastMessage } from '@fragments/ui';
|