prev-cli 0.24.11 → 0.24.13

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.
@@ -0,0 +1,297 @@
1
+ import React, { useState } from 'react'
2
+ import type { PreviewUnit } from '../../vite/preview-types'
3
+
4
+ interface ScreenPreviewProps {
5
+ unit: PreviewUnit
6
+ initialState?: string
7
+ }
8
+
9
+ type Viewport = 'mobile' | 'tablet' | 'desktop'
10
+
11
+ const viewports: Record<Viewport, { width: number; label: string }> = {
12
+ mobile: { width: 375, label: 'Mobile' },
13
+ tablet: { width: 768, label: 'Tablet' },
14
+ desktop: { width: 1280, label: 'Desktop' },
15
+ }
16
+
17
+ export function ScreenPreview({ unit, initialState }: ScreenPreviewProps) {
18
+ const states = ['index', ...(unit.files.states || []).map(s => s.replace(/\.(tsx|jsx)$/, ''))]
19
+ const [activeState, setActiveState] = useState(initialState || 'index')
20
+ const [viewport, setViewport] = useState<Viewport>('desktop')
21
+ const [isFullscreen, setIsFullscreen] = useState(false)
22
+
23
+ const iframeUrl = `/_preview-runtime?preview=screens/${unit.name}&state=${activeState}`
24
+
25
+ // Fullscreen mode
26
+ if (isFullscreen) {
27
+ return (
28
+ <div style={{
29
+ position: 'fixed',
30
+ inset: 0,
31
+ zIndex: 50,
32
+ backgroundColor: 'var(--fd-background)',
33
+ display: 'flex',
34
+ flexDirection: 'column',
35
+ }}>
36
+ <div style={{
37
+ display: 'flex',
38
+ alignItems: 'center',
39
+ justifyContent: 'space-between',
40
+ padding: '8px 16px',
41
+ borderBottom: '1px solid var(--fd-border)',
42
+ backgroundColor: 'var(--fd-muted)',
43
+ }}>
44
+ <span style={{
45
+ fontWeight: 500,
46
+ fontSize: '14px',
47
+ color: 'var(--fd-foreground)',
48
+ }}>
49
+ {unit.name} / {activeState === 'index' ? 'default' : activeState}
50
+ </span>
51
+ <button
52
+ onClick={() => setIsFullscreen(false)}
53
+ style={{
54
+ padding: '8px 12px',
55
+ backgroundColor: 'transparent',
56
+ border: 'none',
57
+ borderRadius: '4px',
58
+ cursor: 'pointer',
59
+ fontSize: '14px',
60
+ color: 'var(--fd-foreground)',
61
+ }}
62
+ onMouseEnter={(e) => e.currentTarget.style.backgroundColor = 'var(--fd-secondary)'}
63
+ onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'}
64
+ >
65
+ Close
66
+ </button>
67
+ </div>
68
+ <iframe
69
+ src={iframeUrl}
70
+ style={{
71
+ width: '100%',
72
+ flex: 1,
73
+ border: 'none',
74
+ }}
75
+ title={`Screen: ${unit.name}`}
76
+ />
77
+ </div>
78
+ )
79
+ }
80
+
81
+ return (
82
+ <div style={{
83
+ display: 'flex',
84
+ flexDirection: 'column',
85
+ border: '1px solid var(--fd-border)',
86
+ borderRadius: '8px',
87
+ overflow: 'hidden',
88
+ backgroundColor: 'var(--fd-background)',
89
+ }}>
90
+ {/* Header with state tabs */}
91
+ <div style={{
92
+ display: 'flex',
93
+ alignItems: 'center',
94
+ justifyContent: 'space-between',
95
+ padding: '12px 16px',
96
+ backgroundColor: 'var(--fd-muted)',
97
+ borderBottom: '1px solid var(--fd-border)',
98
+ }}>
99
+ <div style={{
100
+ display: 'flex',
101
+ alignItems: 'center',
102
+ gap: '16px',
103
+ }}>
104
+ <h2 style={{
105
+ margin: 0,
106
+ fontSize: '18px',
107
+ fontWeight: 600,
108
+ color: 'var(--fd-foreground)',
109
+ }}>
110
+ {unit.config?.title || unit.name}
111
+ </h2>
112
+
113
+ {/* State tabs */}
114
+ <div style={{
115
+ display: 'flex',
116
+ gap: '4px',
117
+ }}>
118
+ {states.map(state => (
119
+ <button
120
+ key={state}
121
+ onClick={() => setActiveState(state)}
122
+ style={{
123
+ padding: '4px 12px',
124
+ fontSize: '13px',
125
+ border: 'none',
126
+ borderRadius: '4px',
127
+ cursor: 'pointer',
128
+ backgroundColor: activeState === state ? 'var(--fd-primary)' : 'transparent',
129
+ color: activeState === state ? 'var(--fd-primary-foreground)' : 'var(--fd-muted-foreground)',
130
+ fontWeight: activeState === state ? 500 : 400,
131
+ transition: 'background-color 0.15s, color 0.15s',
132
+ }}
133
+ onMouseEnter={(e) => {
134
+ if (activeState !== state) {
135
+ e.currentTarget.style.backgroundColor = 'var(--fd-secondary)'
136
+ e.currentTarget.style.color = 'var(--fd-foreground)'
137
+ }
138
+ }}
139
+ onMouseLeave={(e) => {
140
+ if (activeState !== state) {
141
+ e.currentTarget.style.backgroundColor = 'transparent'
142
+ e.currentTarget.style.color = 'var(--fd-muted-foreground)'
143
+ }
144
+ }}
145
+ >
146
+ {state === 'index' ? 'default' : state}
147
+ </button>
148
+ ))}
149
+ </div>
150
+ </div>
151
+
152
+ <button
153
+ onClick={() => setIsFullscreen(true)}
154
+ style={{
155
+ padding: '6px 12px',
156
+ backgroundColor: 'transparent',
157
+ border: '1px solid var(--fd-border)',
158
+ borderRadius: '4px',
159
+ cursor: 'pointer',
160
+ fontSize: '13px',
161
+ color: 'var(--fd-muted-foreground)',
162
+ }}
163
+ onMouseEnter={(e) => {
164
+ e.currentTarget.style.backgroundColor = 'var(--fd-secondary)'
165
+ e.currentTarget.style.color = 'var(--fd-foreground)'
166
+ }}
167
+ onMouseLeave={(e) => {
168
+ e.currentTarget.style.backgroundColor = 'transparent'
169
+ e.currentTarget.style.color = 'var(--fd-muted-foreground)'
170
+ }}
171
+ title="Fullscreen"
172
+ >
173
+ Fullscreen
174
+ </button>
175
+ </div>
176
+
177
+ {/* Description if available */}
178
+ {unit.config?.description && (
179
+ <div style={{
180
+ padding: '12px 16px',
181
+ borderBottom: '1px solid var(--fd-border)',
182
+ backgroundColor: 'var(--fd-background)',
183
+ }}>
184
+ <p style={{
185
+ margin: 0,
186
+ fontSize: '14px',
187
+ color: 'var(--fd-muted-foreground)',
188
+ }}>
189
+ {unit.config.description}
190
+ </p>
191
+ </div>
192
+ )}
193
+
194
+ {/* Preview with viewport */}
195
+ <div style={{
196
+ padding: '24px',
197
+ backgroundColor: 'var(--fd-muted)',
198
+ display: 'flex',
199
+ justifyContent: 'center',
200
+ overflow: 'auto',
201
+ minHeight: '400px',
202
+ }}>
203
+ <div
204
+ style={{
205
+ width: viewports[viewport].width,
206
+ maxWidth: '100%',
207
+ backgroundColor: 'var(--fd-background)',
208
+ boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
209
+ borderRadius: '4px',
210
+ overflow: 'hidden',
211
+ transition: 'width 0.3s ease',
212
+ }}
213
+ >
214
+ <iframe
215
+ src={iframeUrl}
216
+ style={{
217
+ width: '100%',
218
+ height: '600px',
219
+ border: 'none',
220
+ display: 'block',
221
+ }}
222
+ title={`Screen: ${unit.name} - ${activeState}`}
223
+ />
224
+ </div>
225
+ </div>
226
+
227
+ {/* Viewport toggle */}
228
+ <div style={{
229
+ display: 'flex',
230
+ justifyContent: 'center',
231
+ gap: '8px',
232
+ padding: '12px',
233
+ borderTop: '1px solid var(--fd-border)',
234
+ backgroundColor: 'var(--fd-muted)',
235
+ }}>
236
+ {(Object.entries(viewports) as [Viewport, typeof viewports[Viewport]][]).map(([key, { width, label }]) => (
237
+ <button
238
+ key={key}
239
+ onClick={() => setViewport(key)}
240
+ style={{
241
+ padding: '6px 12px',
242
+ fontSize: '13px',
243
+ border: 'none',
244
+ borderRadius: '4px',
245
+ cursor: 'pointer',
246
+ backgroundColor: viewport === key ? 'var(--fd-primary)' : 'transparent',
247
+ color: viewport === key ? 'var(--fd-primary-foreground)' : 'var(--fd-muted-foreground)',
248
+ fontWeight: viewport === key ? 500 : 400,
249
+ transition: 'background-color 0.15s, color 0.15s',
250
+ }}
251
+ onMouseEnter={(e) => {
252
+ if (viewport !== key) {
253
+ e.currentTarget.style.backgroundColor = 'var(--fd-secondary)'
254
+ e.currentTarget.style.color = 'var(--fd-foreground)'
255
+ }
256
+ }}
257
+ onMouseLeave={(e) => {
258
+ if (viewport !== key) {
259
+ e.currentTarget.style.backgroundColor = 'transparent'
260
+ e.currentTarget.style.color = 'var(--fd-muted-foreground)'
261
+ }
262
+ }}
263
+ title={`${label} (${width}px)`}
264
+ >
265
+ {label}
266
+ </button>
267
+ ))}
268
+ </div>
269
+
270
+ {/* Tags */}
271
+ {unit.config?.tags && unit.config.tags.length > 0 && (
272
+ <div style={{
273
+ padding: '12px 16px',
274
+ borderTop: '1px solid var(--fd-border)',
275
+ display: 'flex',
276
+ gap: '8px',
277
+ flexWrap: 'wrap',
278
+ }}>
279
+ {unit.config.tags.map(tag => (
280
+ <span
281
+ key={tag}
282
+ style={{
283
+ padding: '2px 8px',
284
+ fontSize: '12px',
285
+ backgroundColor: 'var(--fd-secondary)',
286
+ color: 'var(--fd-secondary-foreground)',
287
+ borderRadius: '4px',
288
+ }}
289
+ >
290
+ {tag}
291
+ </span>
292
+ ))}
293
+ </div>
294
+ )}
295
+ </div>
296
+ )
297
+ }
@@ -0,0 +1,5 @@
1
+ export { ComponentPreview } from './ComponentPreview'
2
+ export { ScreenPreview } from './ScreenPreview'
3
+ export { FlowPreview } from './FlowPreview'
4
+ export { AtlasPreview } from './AtlasPreview'
5
+ export { PreviewRouter, PreviewList } from './PreviewRouter'