@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.
Files changed (124) hide show
  1. package/dist/bin.js +996 -79
  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-7OPWMLOE.js +1625 -0
  6. package/dist/chunk-7OPWMLOE.js.map +1 -0
  7. package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
  8. package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
  9. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
  12. package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
  13. package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
  14. package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
  15. package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +15 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
  20. package/dist/mcp-bin.js +8 -220
  21. package/dist/mcp-bin.js.map +1 -1
  22. package/dist/scan-WY23TJCP.js +12 -0
  23. package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
  24. package/dist/static-viewer-GBR7YNF3.js +12 -0
  25. package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
  26. package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
  27. package/dist/viewer-SUFOISZM.js +1822 -0
  28. package/dist/viewer-SUFOISZM.js.map +1 -0
  29. package/package.json +6 -5
  30. package/src/bin.ts +31 -0
  31. package/src/build.ts +147 -13
  32. package/src/cli-commands.ts +18 -0
  33. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  34. package/src/commands/a11y-report.ts +625 -0
  35. package/src/commands/a11y.ts +168 -14
  36. package/src/commands/build.ts +16 -0
  37. package/src/commands/graph.ts +274 -0
  38. package/src/core/auto-props.ts +464 -0
  39. package/src/core/composition.ts +64 -1
  40. package/src/core/graph-extractor.test.ts +542 -0
  41. package/src/core/graph-extractor.ts +601 -0
  42. package/src/core/importAnalyzer.ts +5 -0
  43. package/src/core/schema.ts +2 -0
  44. package/src/core/types.ts +3 -1
  45. package/src/index.ts +4 -0
  46. package/src/mcp/server.ts +13 -220
  47. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  48. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  49. package/src/theme/contrast.test.ts +331 -0
  50. package/src/theme/contrast.ts +246 -0
  51. package/src/theme/generator.ts +213 -1
  52. package/src/theme/index.ts +16 -0
  53. package/src/theme/types.ts +51 -0
  54. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  55. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  56. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  57. package/src/viewer/components/ActionCapture.tsx +1 -1
  58. package/src/viewer/components/ActionsPanel.tsx +142 -183
  59. package/src/viewer/components/App.tsx +276 -183
  60. package/src/viewer/components/BottomPanel.tsx +40 -80
  61. package/src/viewer/components/CodePanel.tsx +9 -87
  62. package/src/viewer/components/CommandPalette.tsx +117 -74
  63. package/src/viewer/components/ComponentGraph.tsx +143 -126
  64. package/src/viewer/components/ComponentHeader.tsx +46 -43
  65. package/src/viewer/components/ContractPanel.tsx +124 -117
  66. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  67. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  68. package/src/viewer/components/FragmentEditor.tsx +126 -63
  69. package/src/viewer/components/HealthDashboard.tsx +146 -171
  70. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  71. package/src/viewer/components/Icons.tsx +151 -98
  72. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  73. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  74. package/src/viewer/components/IsolatedRender.tsx +12 -6
  75. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  76. package/src/viewer/components/LandingPage.tsx +285 -305
  77. package/src/viewer/components/Layout.tsx +12 -10
  78. package/src/viewer/components/LeftSidebar.tsx +103 -155
  79. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  80. package/src/viewer/components/PreviewArea.tsx +113 -44
  81. package/src/viewer/components/PreviewFrameHost.tsx +36 -6
  82. package/src/viewer/components/PreviewPane.tsx +2 -3
  83. package/src/viewer/components/PreviewToolbar.tsx +109 -105
  84. package/src/viewer/components/PropsEditor.tsx +154 -74
  85. package/src/viewer/components/PropsTable.tsx +95 -82
  86. package/src/viewer/components/RelationsSection.tsx +71 -40
  87. package/src/viewer/components/ResizablePanel.tsx +158 -55
  88. package/src/viewer/components/RightSidebar.tsx +46 -56
  89. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  90. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  91. package/src/viewer/components/StoryRenderer.tsx +4 -11
  92. package/src/viewer/components/Toast.tsx +3 -67
  93. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  94. package/src/viewer/components/UsageSection.tsx +26 -26
  95. package/src/viewer/components/VariantMatrix.tsx +140 -47
  96. package/src/viewer/components/VariantTabs.tsx +24 -68
  97. package/src/viewer/components/ViewportSelector.tsx +121 -114
  98. package/src/viewer/constants/ui.ts +23 -22
  99. package/src/viewer/entry.tsx +8 -3
  100. package/src/viewer/index.ts +3 -6
  101. package/src/viewer/preview-frame.html +43 -18
  102. package/src/viewer/server.ts +7 -16
  103. package/src/viewer/styles/globals.css +46 -85
  104. package/src/viewer/utils/a11y-fixes.ts +53 -30
  105. package/dist/chunk-ICAIQ57V.js.map +0 -1
  106. package/dist/chunk-U4GQ2JTD.js +0 -832
  107. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  108. package/dist/scan-ESEXV7LF.js +0 -12
  109. package/dist/static-viewer-O37MJ5B6.js +0 -12
  110. package/dist/viewer-YDGFDTK5.js +0 -11104
  111. package/dist/viewer-YDGFDTK5.js.map +0 -1
  112. package/src/viewer/postcss.config.js +0 -6
  113. package/src/viewer/tailwind.config.js +0 -37
  114. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  115. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  116. /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
  117. /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
  118. /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
  119. /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
  120. /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
  121. /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
  122. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
  123. /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
  124. /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 clsx from "clsx";
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 className="flex h-screen bg-[--bg-primary]">
14
+ <div style={{ display: 'flex', height: '100vh', backgroundColor: 'var(--bg-primary)' }}>
30
15
  {/* Sidebar skeleton */}
31
- <div className="w-60 border-r border-[--border] bg-[--bg-secondary] flex flex-col">
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 className="flex items-center justify-between px-4 py-2 border-b border-[--border]">
34
- <Skeleton className="w-20 h-5" />
35
- <Skeleton className="w-6 h-6 rounded-md" />
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 className="px-3 py-3">
40
- <Skeleton className="w-full h-8 rounded-md" />
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 className="flex-1 px-2 space-y-4 overflow-hidden">
41
+ <div style={{ flex: 1, padding: '0 8px', overflow: 'hidden' }}>
45
42
  {/* Category 1 */}
46
- <div>
47
- <Skeleton className="w-16 h-3 mx-2 mb-2" />
48
- <div className="space-y-1">
49
- <Skeleton className="w-full h-7 rounded-md" />
50
- <Skeleton className="w-full h-7 rounded-md" />
51
- <Skeleton className="w-3/4 h-7 rounded-md" />
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 className="w-20 h-3 mx-2 mb-2" />
58
- <div className="space-y-1">
59
- <Skeleton className="w-full h-7 rounded-md" />
60
- <Skeleton className="w-5/6 h-7 rounded-md" />
61
- <Skeleton className="w-full h-7 rounded-md" />
62
- <Skeleton className="w-2/3 h-7 rounded-md" />
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 className="w-24 h-3 mx-2 mb-2" />
69
- <div className="space-y-1">
70
- <Skeleton className="w-4/5 h-7 rounded-md" />
71
- <Skeleton className="w-full h-7 rounded-md" />
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 className="px-4 py-3 border-t border-[--border-subtle]">
78
- <Skeleton className="w-24 h-3" />
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 className="flex-1 flex flex-col">
80
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
84
81
  {/* Toolbar */}
85
- <div className="flex items-center justify-between px-4 py-2 border-b border-[--border] bg-[--bg-secondary]">
86
- <div className="flex items-center gap-3">
87
- <Skeleton className="w-24 h-5" />
88
- <Skeleton className="w-16 h-4" />
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 className="flex items-center gap-2">
91
- <Skeleton className="w-16 h-7 rounded" />
92
- <Skeleton className="w-24 h-7 rounded" />
93
- <Skeleton className="w-20 h-7 rounded" />
94
- <Skeleton className="w-6 h-6 rounded" />
95
- <Skeleton className="w-6 h-6 rounded" />
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 className="px-4 py-2 border-b border-[--border] bg-[--bg-primary]">
101
- <div className="flex items-center gap-2">
102
- <Skeleton className="w-16 h-7 rounded-md" />
103
- <Skeleton className="w-20 h-7 rounded-md" />
104
- <Skeleton className="w-14 h-7 rounded-md" />
105
- <Skeleton className="w-18 h-7 rounded-md" />
106
- <Skeleton className="w-12 h-7 rounded-md" />
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 className="flex-1 flex items-center justify-center p-8">
112
- <Skeleton className="w-64 h-32 rounded-lg" />
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 className="h-64 border-t border-[--border] bg-[--bg-secondary]">
117
- <div className="flex items-center gap-1 px-4 h-10 border-b border-[--border-subtle]">
118
- <Skeleton className="w-12 h-6 rounded" />
119
- <Skeleton className="w-10 h-6 rounded" />
120
- <Skeleton className="w-12 h-6 rounded" />
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 className="p-4 space-y-3">
123
- <div className="flex items-center gap-4">
124
- <Skeleton className="w-20 h-4" />
125
- <Skeleton className="w-32 h-8 rounded" />
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 className="flex items-center gap-4">
128
- <Skeleton className="w-16 h-4" />
129
- <Skeleton className="w-24 h-8 rounded" />
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 className="flex items-center gap-4">
132
- <Skeleton className="w-24 h-4" />
133
- <Skeleton className="w-20 h-8 rounded" />
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 className="flex items-center justify-center p-8">
148
- <div className="text-center space-y-3">
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 className="flex items-center gap-2 text-secondary text-sm">
97
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', color: 'var(--text-secondary)', fontSize: '14px' }}>
105
98
  <svg
106
- className="animate-spin h-4 w-4"
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
- className="opacity-25"
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
- className="opacity-75"
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
- import { useEffect, useState } from 'react';
2
- import { CheckIcon, WarningIcon, RefreshIcon } from './Icons.js';
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';