@fragments-sdk/cli 0.2.2

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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. package/src/viewer/vite-plugin.ts +2143 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Skeleton Loading Components
3
+ *
4
+ * Shows animated placeholders while the app is loading.
5
+ */
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
+ }
23
+
24
+ /**
25
+ * Full app skeleton shown during initial load
26
+ */
27
+ export function AppSkeleton() {
28
+ return (
29
+ <div className="flex h-screen bg-[--bg-primary]">
30
+ {/* Sidebar skeleton */}
31
+ <div className="w-60 border-r border-[--border] bg-[--bg-secondary] flex flex-col">
32
+ {/* 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" />
36
+ </div>
37
+
38
+ {/* Search */}
39
+ <div className="px-3 py-3">
40
+ <Skeleton className="w-full h-8 rounded-md" />
41
+ </div>
42
+
43
+ {/* Component list */}
44
+ <div className="flex-1 px-2 space-y-4 overflow-hidden">
45
+ {/* 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" />
52
+ </div>
53
+ </div>
54
+
55
+ {/* 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" />
63
+ </div>
64
+ </div>
65
+
66
+ {/* Category 3 */}
67
+ <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" />
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ {/* Footer */}
77
+ <div className="px-4 py-3 border-t border-[--border-subtle]">
78
+ <Skeleton className="w-24 h-3" />
79
+ </div>
80
+ </div>
81
+
82
+ {/* Main content skeleton */}
83
+ <div className="flex-1 flex flex-col">
84
+ {/* 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" />
89
+ </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" />
96
+ </div>
97
+ </div>
98
+
99
+ {/* 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" />
107
+ </div>
108
+ </div>
109
+
110
+ {/* Preview area */}
111
+ <div className="flex-1 flex items-center justify-center p-8">
112
+ <Skeleton className="w-64 h-32 rounded-lg" />
113
+ </div>
114
+
115
+ {/* 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" />
121
+ </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" />
126
+ </div>
127
+ <div className="flex items-center gap-4">
128
+ <Skeleton className="w-16 h-4" />
129
+ <Skeleton className="w-24 h-8 rounded" />
130
+ </div>
131
+ <div className="flex items-center gap-4">
132
+ <Skeleton className="w-24 h-4" />
133
+ <Skeleton className="w-20 h-8 rounded" />
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ );
140
+ }
141
+
142
+ /**
143
+ * Component preview skeleton
144
+ */
145
+ export function PreviewSkeleton() {
146
+ 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>
152
+ </div>
153
+ );
154
+ }
155
+
156
+ export default AppSkeleton;
@@ -0,0 +1,128 @@
1
+ import { useState, useEffect, useMemo, type ReactNode } from "react";
2
+ import type { SegmentVariant } from "../../core/index.js";
3
+
4
+ interface StoryRendererProps {
5
+ /** The variant to render */
6
+ variant: SegmentVariant;
7
+ /** Children render function - receives rendered content */
8
+ children: (content: ReactNode | null, isLoading: boolean, error: Error | null) => ReactNode;
9
+ }
10
+
11
+ /**
12
+ * Component that handles async loader execution before rendering a story variant.
13
+ *
14
+ * If the variant has loaders:
15
+ * 1. Shows loading state while loaders execute
16
+ * 2. Merges all loaded data
17
+ * 3. Passes loaded data to render function
18
+ *
19
+ * If no loaders, renders immediately.
20
+ */
21
+ export function StoryRenderer({ variant, children }: StoryRendererProps) {
22
+ const [loadedData, setLoadedData] = useState<Record<string, unknown> | null>(null);
23
+ const [isLoading, setIsLoading] = useState(false);
24
+ const [error, setError] = useState<Error | null>(null);
25
+
26
+ const hasLoaders = variant.loaders && variant.loaders.length > 0;
27
+
28
+ useEffect(() => {
29
+ // Reset state when variant changes
30
+ setLoadedData(null);
31
+ setError(null);
32
+
33
+ if (!hasLoaders) {
34
+ return;
35
+ }
36
+
37
+ let cancelled = false;
38
+ setIsLoading(true);
39
+
40
+ async function executeLoaders() {
41
+ try {
42
+ // Execute all loaders in parallel
43
+ const results = await Promise.all(
44
+ variant.loaders!.map((loader) => loader())
45
+ );
46
+
47
+ if (cancelled) return;
48
+
49
+ // Merge all loaded data (later loaders override earlier ones)
50
+ const merged = results.reduce(
51
+ (acc, result) => ({ ...acc, ...result }),
52
+ {}
53
+ );
54
+
55
+ setLoadedData(merged);
56
+ setError(null);
57
+ } catch (err) {
58
+ if (cancelled) return;
59
+ setError(err instanceof Error ? err : new Error(String(err)));
60
+ } finally {
61
+ if (!cancelled) {
62
+ setIsLoading(false);
63
+ }
64
+ }
65
+ }
66
+
67
+ executeLoaders();
68
+
69
+ return () => {
70
+ cancelled = true;
71
+ };
72
+ }, [variant, hasLoaders]);
73
+
74
+ // If loading, pass null content with loading flag
75
+ if (hasLoaders && isLoading) {
76
+ return <>{children(null, true, null)}</>;
77
+ }
78
+
79
+ // If error during loading, pass error
80
+ if (error) {
81
+ return <>{children(null, false, error)}</>;
82
+ }
83
+
84
+ // Build render options with loaded data only
85
+ const renderOptions = useMemo(() => ({
86
+ loadedData: hasLoaders ? loadedData ?? undefined : undefined,
87
+ }), [hasLoaders, loadedData]);
88
+
89
+ // Render the variant with options
90
+ try {
91
+ const content = variant.render(renderOptions);
92
+ return <>{children(content, false, null)}</>;
93
+ } catch (err) {
94
+ const renderError = err instanceof Error ? err : new Error(String(err));
95
+ return <>{children(null, false, renderError)}</>;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Loading indicator for stories with loaders
101
+ */
102
+ export function LoaderIndicator() {
103
+ return (
104
+ <div className="flex items-center gap-2 text-secondary text-sm">
105
+ <svg
106
+ className="animate-spin h-4 w-4"
107
+ xmlns="http://www.w3.org/2000/svg"
108
+ fill="none"
109
+ viewBox="0 0 24 24"
110
+ >
111
+ <circle
112
+ className="opacity-25"
113
+ cx="12"
114
+ cy="12"
115
+ r="10"
116
+ stroke="currentColor"
117
+ strokeWidth="4"
118
+ />
119
+ <path
120
+ className="opacity-75"
121
+ fill="currentColor"
122
+ 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
+ />
124
+ </svg>
125
+ <span>Running loaders...</span>
126
+ </div>
127
+ );
128
+ }
@@ -0,0 +1,96 @@
1
+ import { createContext, useContext, useEffect, useState, useCallback, type ReactNode } from 'react';
2
+ import { BRAND } from '../../core/index.js';
3
+
4
+ type Theme = 'light' | 'dark' | 'system';
5
+
6
+ interface ThemeContextValue {
7
+ theme: Theme;
8
+ resolvedTheme: 'light' | 'dark';
9
+ setTheme: (theme: Theme) => void;
10
+ }
11
+
12
+ const ThemeContext = createContext<ThemeContextValue | null>(null);
13
+
14
+ const STORAGE_KEY = `${BRAND.storagePrefix}theme`;
15
+
16
+ function getSystemTheme(): 'light' | 'dark' {
17
+ if (typeof window === 'undefined') return 'light';
18
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
19
+ }
20
+
21
+ function getStoredTheme(): Theme {
22
+ if (typeof window === 'undefined') return 'system';
23
+ const stored = localStorage.getItem(STORAGE_KEY);
24
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
25
+ return stored;
26
+ }
27
+ return 'system';
28
+ }
29
+
30
+ export function ThemeProvider({ children }: { children: ReactNode }) {
31
+ const [theme, setThemeState] = useState<Theme>(getStoredTheme);
32
+ const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(() => {
33
+ const stored = getStoredTheme();
34
+ return stored === 'system' ? getSystemTheme() : stored;
35
+ });
36
+
37
+ const setTheme = useCallback((newTheme: Theme) => {
38
+ setThemeState(newTheme);
39
+ localStorage.setItem(STORAGE_KEY, newTheme);
40
+
41
+ // Immediately update resolved theme to avoid timing issues
42
+ const resolved = newTheme === 'system' ? getSystemTheme() : newTheme;
43
+ setResolvedTheme(resolved);
44
+
45
+ // Immediately apply to document
46
+ if (resolved === 'dark') {
47
+ document.documentElement.classList.add('dark');
48
+ } else {
49
+ document.documentElement.classList.remove('dark');
50
+ }
51
+ }, []);
52
+
53
+ // Update resolved theme and apply to document
54
+ useEffect(() => {
55
+ const resolved = theme === 'system' ? getSystemTheme() : theme;
56
+ setResolvedTheme(resolved);
57
+
58
+ if (resolved === 'dark') {
59
+ document.documentElement.classList.add('dark');
60
+ } else {
61
+ document.documentElement.classList.remove('dark');
62
+ }
63
+ }, [theme]);
64
+
65
+ // Listen for system theme changes
66
+ useEffect(() => {
67
+ if (theme !== 'system') return;
68
+
69
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
70
+ const handler = (e: MediaQueryListEvent) => {
71
+ setResolvedTheme(e.matches ? 'dark' : 'light');
72
+ if (e.matches) {
73
+ document.documentElement.classList.add('dark');
74
+ } else {
75
+ document.documentElement.classList.remove('dark');
76
+ }
77
+ };
78
+
79
+ mediaQuery.addEventListener('change', handler);
80
+ return () => mediaQuery.removeEventListener('change', handler);
81
+ }, [theme]);
82
+
83
+ return (
84
+ <ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
85
+ {children}
86
+ </ThemeContext.Provider>
87
+ );
88
+ }
89
+
90
+ export function useTheme() {
91
+ const context = useContext(ThemeContext);
92
+ if (!context) {
93
+ throw new Error('useTheme must be used within a ThemeProvider');
94
+ }
95
+ return context;
96
+ }
@@ -0,0 +1,67 @@
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
+ }