prev-cli 0.24.20 → 0.25.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 (150) hide show
  1. package/dist/cli.js +2006 -1714
  2. package/dist/previews/components/cart-item/index.d.ts +5 -0
  3. package/dist/previews/components/price-tag/index.d.ts +6 -0
  4. package/dist/previews/screens/cart/empty.d.ts +1 -0
  5. package/dist/previews/screens/cart/index.d.ts +1 -0
  6. package/dist/previews/screens/payment/error.d.ts +1 -0
  7. package/dist/previews/screens/payment/index.d.ts +1 -0
  8. package/dist/previews/screens/payment/processing.d.ts +1 -0
  9. package/dist/previews/screens/receipt/index.d.ts +1 -0
  10. package/dist/previews/shared/data.d.ts +30 -0
  11. package/dist/src/content/config-parser.d.ts +30 -0
  12. package/dist/src/content/flow-verifier.d.ts +21 -0
  13. package/dist/src/content/preview-types.d.ts +288 -0
  14. package/dist/{vite → src/content}/previews.d.ts +3 -11
  15. package/dist/{preview-runtime → src/preview-runtime}/build-optimized.d.ts +2 -0
  16. package/dist/{preview-runtime → src/preview-runtime}/build.d.ts +1 -1
  17. package/dist/src/preview-runtime/region-bridge.d.ts +1 -0
  18. package/dist/{preview-runtime → src/preview-runtime}/types.d.ts +18 -0
  19. package/dist/src/preview-runtime/vendors.d.ts +11 -0
  20. package/dist/{renderers → src/renderers}/index.d.ts +1 -1
  21. package/dist/{renderers → src/renderers}/types.d.ts +3 -31
  22. package/dist/src/server/build.d.ts +6 -0
  23. package/dist/src/server/dev.d.ts +13 -0
  24. package/dist/src/server/plugins/aliases.d.ts +5 -0
  25. package/dist/src/server/plugins/mdx.d.ts +5 -0
  26. package/dist/src/server/plugins/virtual-modules.d.ts +8 -0
  27. package/dist/src/server/preview.d.ts +10 -0
  28. package/dist/src/server/routes/component-bundle.d.ts +1 -0
  29. package/dist/src/server/routes/jsx-bundle.d.ts +3 -0
  30. package/dist/src/server/routes/og-image.d.ts +15 -0
  31. package/dist/src/server/routes/preview-bundle.d.ts +1 -0
  32. package/dist/src/server/routes/preview-config.d.ts +1 -0
  33. package/dist/src/server/routes/tokens.d.ts +1 -0
  34. package/dist/{vite → src/server}/start.d.ts +5 -2
  35. package/dist/{ui → src/ui}/button.d.ts +1 -1
  36. package/dist/{validators → src/validators}/index.d.ts +0 -5
  37. package/dist/{validators → src/validators}/semantic-validator.d.ts +2 -3
  38. package/package.json +8 -11
  39. package/src/jsx/CLAUDE.md +18 -0
  40. package/src/jsx/jsx-runtime.ts +1 -1
  41. package/src/preview-runtime/CLAUDE.md +21 -0
  42. package/src/preview-runtime/build-optimized.ts +189 -73
  43. package/src/preview-runtime/build.ts +75 -79
  44. package/src/preview-runtime/fast-template.html +5 -1
  45. package/src/preview-runtime/region-bridge.test.ts +41 -0
  46. package/src/preview-runtime/region-bridge.ts +101 -0
  47. package/src/preview-runtime/types.ts +6 -0
  48. package/src/preview-runtime/vendors.ts +215 -22
  49. package/src/primitives/CLAUDE.md +17 -0
  50. package/src/theme/CLAUDE.md +20 -0
  51. package/src/theme/Preview.tsx +10 -4
  52. package/src/theme/Toolbar.tsx +2 -2
  53. package/src/theme/entry.tsx +247 -121
  54. package/src/theme/hooks/useAnnotations.ts +77 -0
  55. package/src/theme/hooks/useApprovalStatus.ts +50 -0
  56. package/src/theme/hooks/useSnapshots.ts +147 -0
  57. package/src/theme/hooks/useStorage.ts +26 -0
  58. package/src/theme/hooks/useTokenOverrides.ts +56 -0
  59. package/src/theme/hooks/useViewport.ts +23 -0
  60. package/src/theme/icons.tsx +39 -1
  61. package/src/theme/index.html +18 -0
  62. package/src/theme/mdx-components.tsx +1 -1
  63. package/src/theme/previews/AnnotationLayer.tsx +285 -0
  64. package/src/theme/previews/AnnotationPin.tsx +61 -0
  65. package/src/theme/previews/AnnotationThread.tsx +257 -0
  66. package/src/theme/previews/CLAUDE.md +18 -0
  67. package/src/theme/previews/ComponentPreview.tsx +487 -107
  68. package/src/theme/previews/FlowDiagram.tsx +111 -0
  69. package/src/theme/previews/FlowPreview.tsx +938 -174
  70. package/src/theme/previews/PreviewRouter.tsx +1 -4
  71. package/src/theme/previews/ScreenPreview.tsx +515 -175
  72. package/src/theme/previews/SnapshotButton.tsx +68 -0
  73. package/src/theme/previews/SnapshotCompare.tsx +216 -0
  74. package/src/theme/previews/SnapshotPanel.tsx +274 -0
  75. package/src/theme/previews/StatusBadge.tsx +66 -0
  76. package/src/theme/previews/StatusDropdown.tsx +158 -0
  77. package/src/theme/previews/TokenPlayground.tsx +438 -0
  78. package/src/theme/previews/ViewportControls.tsx +67 -0
  79. package/src/theme/previews/flow-diagram.test.ts +141 -0
  80. package/src/theme/previews/flow-diagram.ts +109 -0
  81. package/src/theme/previews/flow-navigation.test.ts +90 -0
  82. package/src/theme/previews/flow-navigation.ts +47 -0
  83. package/src/theme/previews/machines/derived.test.ts +225 -0
  84. package/src/theme/previews/machines/derived.ts +73 -0
  85. package/src/theme/previews/machines/flow-machine.test.ts +379 -0
  86. package/src/theme/previews/machines/flow-machine.ts +207 -0
  87. package/src/theme/previews/machines/screen-machine.test.ts +149 -0
  88. package/src/theme/previews/machines/screen-machine.ts +76 -0
  89. package/src/theme/previews/stores/flow-store.test.ts +157 -0
  90. package/src/theme/previews/stores/flow-store.ts +49 -0
  91. package/src/theme/previews/stores/screen-store.test.ts +68 -0
  92. package/src/theme/previews/stores/screen-store.ts +33 -0
  93. package/src/theme/storage.test.ts +97 -0
  94. package/src/theme/storage.ts +71 -0
  95. package/src/theme/styles.css +296 -25
  96. package/src/theme/types.ts +64 -0
  97. package/src/tokens/CLAUDE.md +16 -0
  98. package/src/tokens/resolver.ts +1 -1
  99. package/dist/preview-runtime/vendors.d.ts +0 -6
  100. package/dist/vite/config-parser.d.ts +0 -13
  101. package/dist/vite/config.d.ts +0 -12
  102. package/dist/vite/plugins/config-plugin.d.ts +0 -3
  103. package/dist/vite/plugins/debug-plugin.d.ts +0 -3
  104. package/dist/vite/plugins/entry-plugin.d.ts +0 -2
  105. package/dist/vite/plugins/fumadocs-plugin.d.ts +0 -9
  106. package/dist/vite/plugins/pages-plugin.d.ts +0 -5
  107. package/dist/vite/plugins/previews-plugin.d.ts +0 -2
  108. package/dist/vite/plugins/tokens-plugin.d.ts +0 -2
  109. package/dist/vite/preview-types.d.ts +0 -70
  110. package/src/theme/previews/AtlasPreview.tsx +0 -528
  111. package/dist/{cli.d.ts → src/cli.d.ts} +0 -0
  112. package/dist/{config → src/config}/index.d.ts +0 -0
  113. package/dist/{config → src/config}/loader.d.ts +0 -0
  114. package/dist/{config → src/config}/schema.d.ts +0 -0
  115. package/dist/{vite → src/content}/pages.d.ts +0 -0
  116. package/dist/{jsx → src/jsx}/adapters/html.d.ts +0 -0
  117. package/dist/{jsx → src/jsx}/adapters/react.d.ts +0 -0
  118. package/dist/{jsx → src/jsx}/define-component.d.ts +0 -0
  119. package/dist/{jsx → src/jsx}/index.d.ts +0 -0
  120. package/dist/{jsx → src/jsx}/jsx-runtime.d.ts +0 -0
  121. package/dist/{jsx → src/jsx}/migrate.d.ts +0 -0
  122. package/dist/{jsx → src/jsx}/schemas/index.d.ts +0 -0
  123. package/dist/{jsx → src/jsx}/schemas/primitives.d.ts +10 -10
  124. package/dist/{jsx → src/jsx}/schemas/tokens.d.ts +3 -3
  125. /package/dist/{jsx → src/jsx}/validation.d.ts +0 -0
  126. /package/dist/{jsx → src/jsx}/vnode.d.ts +0 -0
  127. /package/dist/{migrate.d.ts → src/migrate.d.ts} +0 -0
  128. /package/dist/{preview-runtime → src/preview-runtime}/tailwind.d.ts +0 -0
  129. /package/dist/{primitives → src/primitives}/index.d.ts +0 -0
  130. /package/dist/{primitives → src/primitives}/migrate.d.ts +0 -0
  131. /package/dist/{primitives → src/primitives}/parser.d.ts +0 -0
  132. /package/dist/{primitives → src/primitives}/template-parser.d.ts +0 -0
  133. /package/dist/{primitives → src/primitives}/template-renderer.d.ts +0 -0
  134. /package/dist/{primitives → src/primitives}/types.d.ts +0 -0
  135. /package/dist/{renderers → src/renderers}/html/index.d.ts +0 -0
  136. /package/dist/{renderers → src/renderers}/react/index.d.ts +0 -0
  137. /package/dist/{renderers → src/renderers}/registry.d.ts +0 -0
  138. /package/dist/{renderers → src/renderers}/render.d.ts +0 -0
  139. /package/dist/{tokens → src/tokens}/defaults.d.ts +0 -0
  140. /package/dist/{tokens → src/tokens}/resolver.d.ts +0 -0
  141. /package/dist/{tokens → src/tokens}/utils.d.ts +0 -0
  142. /package/dist/{tokens → src/tokens}/validation.d.ts +0 -0
  143. /package/dist/{typecheck → src/typecheck}/index.d.ts +0 -0
  144. /package/dist/{ui → src/ui}/card.d.ts +0 -0
  145. /package/dist/{ui → src/ui}/index.d.ts +0 -0
  146. /package/dist/{ui → src/ui}/utils.d.ts +0 -0
  147. /package/dist/{utils → src/utils}/cache.d.ts +0 -0
  148. /package/dist/{utils → src/utils}/debug.d.ts +0 -0
  149. /package/dist/{utils → src/utils}/port.d.ts +0 -0
  150. /package/dist/{validators → src/validators}/schema-validator.d.ts +0 -0
@@ -1,13 +1,49 @@
1
- import React, { useState, useEffect } from 'react'
2
- import type { PreviewUnit } from '../../vite/preview-types'
1
+ import React, { useState, useEffect, useRef } from 'react'
2
+ import type { PreviewUnit } from '../../content/preview-types'
3
+ import { useViewport, VIEWPORT_WIDTHS } from '../hooks/useViewport'
4
+ import { ViewportControls } from './ViewportControls'
5
+ import { useApprovalStatus } from '../hooks/useApprovalStatus'
6
+ import { StatusDropdown } from './StatusDropdown'
7
+ import { AnnotationLayer } from './AnnotationLayer'
8
+ import { SnapshotButton } from './SnapshotButton'
9
+ import { SnapshotPanel } from './SnapshotPanel'
10
+ import { useSnapshots } from '../hooks/useSnapshots'
11
+ import { useTokenOverrides } from '../hooks/useTokenOverrides'
12
+ import { TokenPlayground } from './TokenPlayground'
13
+ import { Icon } from '../icons'
14
+ import { tokens as designTokens } from 'virtual:prev-tokens'
15
+
16
+ interface PreviewMessage {
17
+ type: 'ready' | 'init' | 'update' | 'built' | 'error'
18
+ config?: unknown
19
+ result?: unknown
20
+ error?: string
21
+ }
3
22
 
4
23
  interface ComponentPreviewProps {
5
24
  unit: PreviewUnit
6
25
  }
7
26
 
27
+ // Detect if running in static build (no dev server)
28
+ const isStaticBuild = typeof window !== 'undefined' &&
29
+ !window.location.hostname.includes('localhost') &&
30
+ !window.location.hostname.includes('127.0.0.1')
31
+
8
32
  export function ComponentPreview({ unit }: ComponentPreviewProps) {
9
- const [props, setProps] = useState<Record<string, unknown>>({})
33
+ const [props, _setProps] = useState<Record<string, unknown>>({})
10
34
  const [schema, setSchema] = useState<unknown>(null)
35
+ const [buildStatus, setBuildStatus] = useState<'loading' | 'building' | 'ready' | 'error'>('loading')
36
+ const [buildError, setBuildError] = useState<string | null>(null)
37
+ const [iframeLoaded, setIframeLoaded] = useState(false)
38
+ const [isHovered, setIsHovered] = useState(false)
39
+ const [viewport, setViewport] = useViewport()
40
+ const { status: approvalStatus, changeStatus, getAuditLog } = useApprovalStatus(`components/${unit.name}`)
41
+ const [annotationsEnabled, setAnnotationsEnabled] = useState(false)
42
+ const [showSnapshots, setShowSnapshots] = useState(false)
43
+ const [showTokens, setShowTokens] = useState(false)
44
+ const { snapshots, captureSnapshot, deleteSnapshot } = useSnapshots(`components/${unit.name}`)
45
+ const { overrides: tokenOverrides, setOverride, removeOverride, resetAll, toCssOverrides } = useTokenOverrides()
46
+ const iframeRef = useRef<HTMLIFrameElement>(null)
11
47
 
12
48
  // Load schema if available
13
49
  useEffect(() => {
@@ -18,156 +54,473 @@ export function ComponentPreview({ unit }: ComponentPreviewProps) {
18
54
  }
19
55
  }, [unit])
20
56
 
21
- const iframeUrl = `/_preview-runtime?preview=components/${unit.name}`
57
+ // Build iframe URL - use static path for production, runtime for dev
58
+ const baseUrl = typeof window !== 'undefined'
59
+ ? (import.meta.env.BASE_URL ?? '/').replace(/\/$/, '')
60
+ : ''
61
+ const iframeUrl = isStaticBuild
62
+ ? `${baseUrl}/_preview/components/${unit.name}/`
63
+ : `/_preview-runtime?preview=components/${unit.name}`
64
+
65
+ // Skip spinner immediately for static builds (runs after hydration)
66
+ useEffect(() => {
67
+ if (isStaticBuild) {
68
+ setBuildStatus('ready')
69
+ }
70
+ }, [])
71
+
72
+ // Track iframe load state for opacity hint
73
+ useEffect(() => {
74
+ const iframe = iframeRef.current
75
+ if (!iframe) return
76
+
77
+ const handleLoad = () => setIframeLoaded(true)
78
+ iframe.addEventListener('load', handleLoad)
79
+ return () => iframe.removeEventListener('load', handleLoad)
80
+ }, [])
81
+
82
+ // Initialize preview runtime - fetch config and send to iframe (dev mode only)
83
+ useEffect(() => {
84
+ if (isStaticBuild) return
85
+
86
+ const iframe = iframeRef.current
87
+ if (!iframe) return
88
+
89
+ let configSent = false
90
+
91
+ const handleMessage = (event: MessageEvent) => {
92
+ const msg = event.data as PreviewMessage
93
+
94
+ if (msg.type === 'ready' && !configSent) {
95
+ configSent = true
96
+ setBuildStatus('building')
97
+
98
+ fetch(`/_preview-config/components/${unit.name}`)
99
+ .then(res => res.json())
100
+ .then((config) => {
101
+ iframe.contentWindow?.postMessage({ type: 'init', config } as PreviewMessage, '*')
102
+ })
103
+ .catch(err => {
104
+ setBuildStatus('error')
105
+ setBuildError(`Failed to load preview config: ${err.message}`)
106
+ })
107
+ }
108
+
109
+ if (msg.type === 'built') {
110
+ const result = msg.result as { success: boolean; error?: string }
111
+ if (result.success) {
112
+ setBuildStatus('ready')
113
+ setBuildError(null)
114
+ } else {
115
+ setBuildStatus('error')
116
+ setBuildError(result.error || 'Build failed')
117
+ }
118
+ }
22
119
 
23
- // Status badge colors
120
+ if (msg.type === 'error') {
121
+ setBuildStatus('error')
122
+ setBuildError(msg.error || 'Unknown error')
123
+ }
124
+ }
125
+
126
+ window.addEventListener('message', handleMessage)
127
+ return () => window.removeEventListener('message', handleMessage)
128
+ }, [unit.name])
129
+
130
+ // Send token overrides to iframe
131
+ useEffect(() => {
132
+ const iframe = iframeRef.current
133
+ if (!iframe?.contentWindow) return
134
+ const css = toCssOverrides()
135
+ iframe.contentWindow.postMessage({ type: 'token-overrides', css }, '*')
136
+ }, [tokenOverrides, buildStatus])
137
+
138
+ // Status badge styles
24
139
  const getStatusStyle = (status: string): React.CSSProperties => {
25
140
  switch (status) {
26
141
  case 'stable':
27
- return { backgroundColor: 'oklch(0.85 0.15 145)', color: 'oklch(0.30 0.10 145)' }
142
+ return {
143
+ backgroundColor: 'oklch(0.92 0.08 155)',
144
+ color: 'oklch(0.35 0.12 155)',
145
+ borderColor: 'oklch(0.85 0.10 155)',
146
+ }
28
147
  case 'deprecated':
29
- return { backgroundColor: 'oklch(0.85 0.15 25)', color: 'oklch(0.35 0.15 25)' }
148
+ return {
149
+ backgroundColor: 'oklch(0.92 0.08 25)',
150
+ color: 'oklch(0.45 0.15 25)',
151
+ borderColor: 'oklch(0.85 0.12 25)',
152
+ }
30
153
  default: // draft
31
- return { backgroundColor: 'oklch(0.85 0.15 85)', color: 'oklch(0.35 0.10 85)' }
154
+ return {
155
+ backgroundColor: 'oklch(0.94 0.06 85)',
156
+ color: 'oklch(0.45 0.12 85)',
157
+ borderColor: 'oklch(0.88 0.08 85)',
158
+ }
32
159
  }
33
160
  }
34
161
 
35
162
  return (
36
- <div style={{
37
- display: 'flex',
38
- flexDirection: 'column',
39
- border: '1px solid var(--fd-border)',
40
- borderRadius: '8px',
41
- overflow: 'hidden',
42
- backgroundColor: 'var(--fd-background)',
43
- }}>
44
- {/* Header */}
45
- <div style={{
163
+ <div
164
+ style={{
46
165
  display: 'flex',
47
- alignItems: 'center',
48
- justifyContent: 'space-between',
49
- padding: '12px 16px',
50
- backgroundColor: 'var(--fd-muted)',
166
+ flexDirection: 'column',
167
+ borderRadius: '16px',
168
+ overflow: 'hidden',
169
+ backgroundColor: 'var(--fd-card)',
170
+ boxShadow: isHovered
171
+ ? '0 20px 40px -12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)'
172
+ : '0 4px 20px -4px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04)',
173
+ transition: 'box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
174
+ transform: isHovered ? 'translateY(-2px)' : 'none',
175
+ }}
176
+ onMouseEnter={() => setIsHovered(true)}
177
+ onMouseLeave={() => setIsHovered(false)}
178
+ >
179
+ {/* Header - Editorial style */}
180
+ <div style={{
181
+ padding: '20px 24px',
51
182
  borderBottom: '1px solid var(--fd-border)',
183
+ background: 'linear-gradient(to bottom, var(--fd-card), var(--fd-muted))',
52
184
  }}>
53
- <div>
54
- <h2 style={{
55
- margin: 0,
56
- fontSize: '18px',
57
- fontWeight: 600,
58
- color: 'var(--fd-foreground)',
59
- }}>
60
- {unit.config?.title || unit.name}
61
- </h2>
62
- {unit.config?.description && (
63
- <p style={{
64
- margin: '4px 0 0 0',
65
- fontSize: '14px',
66
- color: 'var(--fd-muted-foreground)',
67
- }}>
68
- {unit.config.description}
69
- </p>
70
- )}
185
+ <div style={{
186
+ display: 'flex',
187
+ alignItems: 'flex-start',
188
+ justifyContent: 'space-between',
189
+ gap: '16px',
190
+ }}>
191
+ <div style={{ flex: 1 }}>
192
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '6px' }}>
193
+ {/* Component icon */}
194
+ <div style={{
195
+ width: '32px',
196
+ height: '32px',
197
+ borderRadius: '8px',
198
+ background: 'linear-gradient(135deg, var(--fd-primary) 0%, oklch(0.45 0.12 280) 100%)',
199
+ display: 'flex',
200
+ alignItems: 'center',
201
+ justifyContent: 'center',
202
+ color: 'var(--fd-primary-foreground)',
203
+ fontSize: '14px',
204
+ fontWeight: 600,
205
+ boxShadow: '0 2px 8px -2px rgba(0, 0, 0, 0.2)',
206
+ }}>
207
+
208
+ </div>
209
+ <h2 style={{
210
+ margin: 0,
211
+ fontSize: '20px',
212
+ fontWeight: 600,
213
+ color: 'var(--fd-foreground)',
214
+ letterSpacing: '-0.02em',
215
+ }}>
216
+ {unit.config?.title || unit.name}
217
+ </h2>
218
+ </div>
219
+ {unit.config?.description && (
220
+ <p style={{
221
+ margin: 0,
222
+ fontSize: '14px',
223
+ color: 'var(--fd-muted-foreground)',
224
+ lineHeight: 1.5,
225
+ paddingLeft: '44px',
226
+ }}>
227
+ {unit.config.description}
228
+ </p>
229
+ )}
230
+ </div>
231
+
232
+ <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
233
+ <button
234
+ onClick={() => setAnnotationsEnabled(prev => !prev)}
235
+ style={{
236
+ display: 'flex',
237
+ alignItems: 'center',
238
+ justifyContent: 'center',
239
+ padding: '6px',
240
+ border: 'none',
241
+ borderRadius: '8px',
242
+ cursor: 'pointer',
243
+ backgroundColor: annotationsEnabled ? 'oklch(0.92 0.08 250)' : 'var(--fd-muted)',
244
+ color: annotationsEnabled ? 'oklch(0.45 0.18 250)' : 'var(--fd-muted-foreground)',
245
+ transition: 'all 0.15s ease',
246
+ }}
247
+ title={annotationsEnabled ? 'Disable annotations' : 'Enable annotations'}
248
+ >
249
+ <Icon name="pin" size={14} />
250
+ </button>
251
+ <SnapshotButton onCapture={() => captureSnapshot(iframeRef, {
252
+ previewName: `components/${unit.name}`,
253
+ stateOrStep: 'index',
254
+ viewport,
255
+ })} />
256
+ <button
257
+ onClick={() => setShowSnapshots(prev => !prev)}
258
+ style={{
259
+ display: 'flex',
260
+ alignItems: 'center',
261
+ justifyContent: 'center',
262
+ padding: '6px',
263
+ border: 'none',
264
+ borderRadius: '8px',
265
+ cursor: 'pointer',
266
+ backgroundColor: 'var(--fd-muted)',
267
+ color: 'var(--fd-muted-foreground)',
268
+ transition: 'all 0.15s ease',
269
+ position: 'relative',
270
+ }}
271
+ title="View snapshots"
272
+ >
273
+ <Icon name="camera" size={14} />
274
+ {snapshots.length > 0 && (
275
+ <span style={{
276
+ position: 'absolute',
277
+ top: '-4px',
278
+ right: '-4px',
279
+ width: '16px',
280
+ height: '16px',
281
+ borderRadius: '50%',
282
+ backgroundColor: 'var(--fd-primary)',
283
+ color: 'var(--fd-primary-foreground)',
284
+ fontSize: '9px',
285
+ fontWeight: 700,
286
+ display: 'flex',
287
+ alignItems: 'center',
288
+ justifyContent: 'center',
289
+ }}>{snapshots.length}</span>
290
+ )}
291
+ </button>
292
+ <button
293
+ onClick={() => setShowTokens(prev => !prev)}
294
+ style={{
295
+ display: 'flex',
296
+ alignItems: 'center',
297
+ justifyContent: 'center',
298
+ padding: '6px',
299
+ border: 'none',
300
+ borderRadius: '8px',
301
+ cursor: 'pointer',
302
+ backgroundColor: showTokens ? 'oklch(0.92 0.08 310)' : 'var(--fd-muted)',
303
+ color: showTokens ? 'oklch(0.45 0.18 310)' : 'var(--fd-muted-foreground)',
304
+ transition: 'all 0.15s ease',
305
+ }}
306
+ title="Token playground"
307
+ >
308
+ <Icon name="palette" size={14} />
309
+ </button>
310
+ <ViewportControls viewport={viewport} onViewportChange={setViewport} />
311
+ <StatusDropdown
312
+ previewName={`components/${unit.name}`}
313
+ status={approvalStatus}
314
+ onStatusChange={changeStatus}
315
+ getAuditLog={getAuditLog}
316
+ />
317
+ {/* Build status indicator */}
318
+ {buildStatus === 'building' && (
319
+ <div style={{
320
+ width: '8px',
321
+ height: '8px',
322
+ borderRadius: '50%',
323
+ backgroundColor: 'oklch(0.75 0.15 85)',
324
+ animation: 'pulse 1.5s ease-in-out infinite',
325
+ }} />
326
+ )}
327
+ {buildStatus === 'ready' && (
328
+ <div style={{
329
+ width: '8px',
330
+ height: '8px',
331
+ borderRadius: '50%',
332
+ backgroundColor: 'oklch(0.65 0.18 155)',
333
+ }} />
334
+ )}
335
+
336
+ {unit.config?.status && (
337
+ <span style={{
338
+ padding: '4px 10px',
339
+ fontSize: '11px',
340
+ fontWeight: 600,
341
+ borderRadius: '6px',
342
+ border: '1px solid',
343
+ textTransform: 'uppercase',
344
+ letterSpacing: '0.05em',
345
+ ...getStatusStyle(unit.config.status),
346
+ }}>
347
+ {unit.config.status}
348
+ </span>
349
+ )}
350
+ </div>
71
351
  </div>
72
- {unit.config?.status && (
73
- <span style={{
74
- padding: '4px 8px',
75
- fontSize: '12px',
76
- fontWeight: 500,
77
- borderRadius: '4px',
78
- ...getStatusStyle(unit.config.status),
79
- }}>
80
- {unit.config.status}
81
- </span>
82
- )}
83
352
  </div>
84
353
 
85
- {/* Preview area */}
354
+ {/* Canvas area - Component showcase */}
86
355
  <div style={{
87
- padding: '24px',
356
+ position: 'relative',
357
+ padding: '40px',
358
+ minHeight: '280px',
88
359
  display: 'flex',
89
360
  alignItems: 'center',
90
361
  justifyContent: 'center',
91
- minHeight: '200px',
92
- backgroundColor: 'var(--fd-background)',
93
- backgroundImage: 'linear-gradient(45deg, var(--fd-border) 25%, transparent 25%), linear-gradient(-45deg, var(--fd-border) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--fd-border) 75%), linear-gradient(-45deg, transparent 75%, var(--fd-border) 75%)',
94
- backgroundSize: '16px 16px',
95
- backgroundPosition: '0 0, 0 8px, 8px -8px, -8px 0px',
362
+ // Elegant dot grid pattern
363
+ backgroundColor: 'var(--fd-muted)',
364
+ backgroundImage: `
365
+ radial-gradient(circle at center, var(--fd-border) 1px, transparent 1px)
366
+ `,
367
+ backgroundSize: '24px 24px',
368
+ backgroundPosition: '12px 12px',
96
369
  }}>
97
- <div style={{
98
- width: '100%',
99
- height: '100%',
100
- minHeight: '200px',
101
- backgroundColor: 'var(--fd-background)',
102
- }}>
103
- <iframe
104
- src={iframeUrl}
105
- style={{
106
- border: 'none',
107
- width: '100%',
108
- height: '100%',
109
- minHeight: '200px',
110
- }}
111
- title={`Preview: ${unit.name}`}
112
- />
113
- </div>
370
+ {/* Canvas container with subtle inner shadow */}
371
+ <AnnotationLayer
372
+ previewName={`components/${unit.name}`}
373
+ stateOrStep="index"
374
+ enabled={annotationsEnabled}
375
+ >
376
+ <div style={{
377
+ position: 'relative',
378
+ width: viewport === 'desktop' ? '100%' : VIEWPORT_WIDTHS[viewport],
379
+ maxWidth: '600px',
380
+ transition: 'width 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
381
+ backgroundColor: 'var(--fd-background)',
382
+ borderRadius: '12px',
383
+ boxShadow: `
384
+ 0 0 0 1px var(--fd-border),
385
+ 0 4px 16px -4px rgba(0, 0, 0, 0.1),
386
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.1)
387
+ `,
388
+ overflow: 'hidden',
389
+ }}>
390
+ {/* Error overlay */}
391
+ {buildStatus === 'error' && buildError && (
392
+ <div style={{
393
+ position: 'absolute',
394
+ inset: 0,
395
+ backgroundColor: 'rgba(254, 242, 242, 0.95)',
396
+ backdropFilter: 'blur(4px)',
397
+ padding: '24px',
398
+ display: 'flex',
399
+ flexDirection: 'column',
400
+ alignItems: 'center',
401
+ justifyContent: 'center',
402
+ zIndex: 10,
403
+ }}>
404
+ <div style={{
405
+ width: '48px',
406
+ height: '48px',
407
+ borderRadius: '50%',
408
+ backgroundColor: '#fecaca',
409
+ display: 'flex',
410
+ alignItems: 'center',
411
+ justifyContent: 'center',
412
+ marginBottom: '16px',
413
+ fontSize: '24px',
414
+ }}>
415
+
416
+ </div>
417
+ <p style={{
418
+ margin: 0,
419
+ fontSize: '13px',
420
+ color: '#dc2626',
421
+ fontFamily: 'var(--fd-font-mono)',
422
+ textAlign: 'center',
423
+ maxWidth: '400px',
424
+ lineHeight: 1.5,
425
+ }}>
426
+ {buildError}
427
+ </p>
428
+ </div>
429
+ )}
430
+
431
+ {/* Loading state */}
432
+ {buildStatus === 'loading' && (
433
+ <div style={{
434
+ position: 'absolute',
435
+ inset: 0,
436
+ backgroundColor: 'var(--fd-background)',
437
+ display: 'flex',
438
+ alignItems: 'center',
439
+ justifyContent: 'center',
440
+ zIndex: 5,
441
+ }}>
442
+ <div style={{
443
+ width: '32px',
444
+ height: '32px',
445
+ border: '2px solid var(--fd-border)',
446
+ borderTopColor: 'var(--fd-primary)',
447
+ borderRadius: '50%',
448
+ animation: 'spin 0.8s linear infinite',
449
+ }} />
450
+ </div>
451
+ )}
452
+
453
+ <iframe
454
+ ref={iframeRef}
455
+ src={iframeUrl}
456
+ style={{
457
+ border: 'none',
458
+ width: '100%',
459
+ height: '220px',
460
+ display: 'block',
461
+ opacity: (isStaticBuild ? iframeLoaded : buildStatus === 'ready') ? 1 : 0.3,
462
+ transition: 'opacity 0.3s ease',
463
+ }}
464
+ title={`Preview: ${unit.name}`}
465
+ />
466
+ </div>
467
+ </AnnotationLayer>
114
468
  </div>
115
469
 
116
- {/* Props panel */}
470
+ {/* Props panel - Refined */}
117
471
  {schema && (
118
472
  <div style={{
119
- padding: '16px',
473
+ padding: '20px 24px',
120
474
  borderTop: '1px solid var(--fd-border)',
121
- backgroundColor: 'var(--fd-muted)',
475
+ backgroundColor: 'var(--fd-card)',
122
476
  }}>
123
477
  <h3 style={{
124
- margin: '0 0 8px 0',
125
- fontSize: '14px',
126
- fontWeight: 500,
127
- color: 'var(--fd-foreground)',
478
+ margin: '0 0 12px 0',
479
+ fontSize: '12px',
480
+ fontWeight: 600,
481
+ color: 'var(--fd-muted-foreground)',
482
+ textTransform: 'uppercase',
483
+ letterSpacing: '0.08em',
128
484
  }}>
129
- Props
485
+ Properties
130
486
  </h3>
131
- <div style={{
132
- display: 'grid',
133
- gridTemplateColumns: 'repeat(2, 1fr)',
134
- gap: '16px',
135
- fontSize: '14px',
487
+ <pre style={{
488
+ margin: 0,
489
+ padding: '16px',
490
+ fontSize: '12px',
491
+ backgroundColor: 'var(--fd-muted)',
492
+ borderRadius: '8px',
493
+ fontFamily: 'var(--fd-font-mono)',
494
+ overflow: 'auto',
495
+ border: '1px solid var(--fd-border)',
136
496
  }}>
137
- {/* TODO: Generate controls from schema */}
138
- <pre style={{
139
- margin: 0,
140
- padding: '8px',
141
- fontSize: '12px',
142
- backgroundColor: 'var(--fd-card)',
143
- borderRadius: '4px',
144
- fontFamily: 'var(--fd-font-mono)',
145
- overflow: 'auto',
146
- }}>
147
- {JSON.stringify(props, null, 2)}
148
- </pre>
149
- </div>
497
+ {JSON.stringify(props, null, 2)}
498
+ </pre>
150
499
  </div>
151
500
  )}
152
501
 
153
- {/* Tags */}
502
+ {/* Tags - Pill style */}
154
503
  {unit.config?.tags && unit.config.tags.length > 0 && (
155
504
  <div style={{
156
- padding: '12px 16px',
505
+ padding: '16px 24px',
157
506
  borderTop: '1px solid var(--fd-border)',
158
507
  display: 'flex',
159
508
  gap: '8px',
160
509
  flexWrap: 'wrap',
510
+ backgroundColor: 'var(--fd-card)',
161
511
  }}>
162
512
  {unit.config.tags.map(tag => (
163
513
  <span
164
514
  key={tag}
165
515
  style={{
166
- padding: '2px 8px',
516
+ padding: '4px 12px',
167
517
  fontSize: '12px',
168
- backgroundColor: 'var(--fd-secondary)',
169
- color: 'var(--fd-secondary-foreground)',
170
- borderRadius: '4px',
518
+ fontWeight: 500,
519
+ backgroundColor: 'var(--fd-muted)',
520
+ color: 'var(--fd-muted-foreground)',
521
+ borderRadius: '100px',
522
+ border: '1px solid var(--fd-border)',
523
+ transition: 'all 0.15s ease',
171
524
  }}
172
525
  >
173
526
  {tag}
@@ -175,6 +528,33 @@ export function ComponentPreview({ unit }: ComponentPreviewProps) {
175
528
  ))}
176
529
  </div>
177
530
  )}
531
+
532
+ {/* Panels */}
533
+ {showSnapshots && (
534
+ <SnapshotPanel
535
+ snapshots={snapshots}
536
+ onDelete={deleteSnapshot}
537
+ onClose={() => setShowSnapshots(false)}
538
+ />
539
+ )}
540
+ {showTokens && (
541
+ <TokenPlayground
542
+ tokens={designTokens}
543
+ overrides={tokenOverrides}
544
+ onSetOverride={setOverride}
545
+ onRemoveOverride={removeOverride}
546
+ onResetAll={resetAll}
547
+ onClose={() => setShowTokens(false)}
548
+ />
549
+ )}
550
+
551
+ {/* Inline styles for animations */}
552
+ <style>{`
553
+ @keyframes pulse {
554
+ 0%, 100% { opacity: 1; }
555
+ 50% { opacity: 0.4; }
556
+ }
557
+ `}</style>
178
558
  </div>
179
559
  )
180
560
  }