@djangocfg/ui-tools 2.1.382 → 2.1.383
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/DictationField-U25MEYAL.mjs +4 -0
- package/dist/{DictationField-2ZLQWLYV.mjs.map → DictationField-U25MEYAL.mjs.map} +1 -1
- package/dist/DictationField-XWR5VOID.cjs +13 -0
- package/dist/{DictationField-IPPJ54CU.cjs.map → DictationField-XWR5VOID.cjs.map} +1 -1
- package/dist/{chunk-KMSBGNVC.cjs → chunk-4PFW7MIJ.cjs} +4 -2
- package/dist/chunk-4PFW7MIJ.cjs.map +1 -0
- package/dist/{chunk-4LXG3NBV.mjs → chunk-C2YN6WEO.mjs} +3 -3
- package/dist/chunk-C2YN6WEO.mjs.map +1 -0
- package/dist/index.cjs +139 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.mjs +141 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -13
- package/src/tools/Chat/index.ts +15 -0
- package/dist/DictationField-2ZLQWLYV.mjs +0 -4
- package/dist/DictationField-IPPJ54CU.cjs +0 -13
- package/dist/chunk-4LXG3NBV.mjs.map +0 -1
- package/dist/chunk-KMSBGNVC.cjs.map +0 -1
- package/src/components/markdown/MarkdownMessage/MarkdownMessage.story.tsx +0 -771
- package/src/stories/index.ts +0 -63
- package/src/tools/AudioPlayer/AudioPlayer.story.tsx +0 -481
- package/src/tools/Chat/stories/01-basic.story.tsx +0 -64
- package/src/tools/Chat/stories/02-bubbles.story.tsx +0 -21
- package/src/tools/Chat/stories/03-tool-calls.story.tsx +0 -59
- package/src/tools/Chat/stories/04-personas.story.tsx +0 -78
- package/src/tools/Chat/stories/05-launcher.story.tsx +0 -321
- package/src/tools/Chat/stories/06-header.story.tsx +0 -147
- package/src/tools/Chat/stories/07-audio-actions.story.tsx +0 -112
- package/src/tools/Chat/stories/shared/Frame.tsx +0 -21
- package/src/tools/Chat/stories/shared/index.ts +0 -5
- package/src/tools/Chat/stories/shared/messages.ts +0 -39
- package/src/tools/Chat/stories/shared/personas.ts +0 -13
- package/src/tools/Chat/stories/shared/seeds.ts +0 -92
- package/src/tools/Chat/stories/shared/transports.ts +0 -36
- package/src/tools/CodeEditor/CodeEditor.story.tsx +0 -202
- package/src/tools/CronScheduler/CronScheduler.story.tsx +0 -300
- package/src/tools/Gallery/Gallery.story.tsx +0 -237
- package/src/tools/ImageViewer/ImageViewer.story.tsx +0 -85
- package/src/tools/JsonForm/JsonForm.story.tsx +0 -350
- package/src/tools/JsonTree/JsonTree.story.tsx +0 -141
- package/src/tools/LottiePlayer/LottiePlayer.story.tsx +0 -95
- package/src/tools/Map/Map.story.tsx +0 -458
- package/src/tools/MarkdownEditor/MarkdownEditor.story.tsx +0 -225
- package/src/tools/Mermaid/Mermaid.story.tsx +0 -251
- package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +0 -230
- package/src/tools/PrettyCode/PrettyCode.story.tsx +0 -304
- package/src/tools/SpeechRecognition/stories/01-basic.story.tsx +0 -32
- package/src/tools/SpeechRecognition/stories/02-dictation-field.story.tsx +0 -32
- package/src/tools/SpeechRecognition/stories/03-push-to-talk.story.tsx +0 -27
- package/src/tools/SpeechRecognition/stories/04-mic-meter.story.tsx +0 -35
- package/src/tools/SpeechRecognition/stories/05-custom-engine-http.story.tsx +0 -40
- package/src/tools/SpeechRecognition/stories/06-custom-engine-ws.story.tsx +0 -48
- package/src/tools/SpeechRecognition/stories/07-language-device.story.tsx +0 -57
- package/src/tools/SpeechRecognition/stories/08-errors-permissions.story.tsx +0 -25
- package/src/tools/SpeechRecognition/stories/09-chat-voice.story.tsx +0 -90
- package/src/tools/SpeechRecognition/stories/shared.tsx +0 -123
- package/src/tools/Tour/Tour.story.tsx +0 -279
- package/src/tools/Tree/Tree.story.tsx +0 -620
- package/src/tools/Uploader/Uploader.story.tsx +0 -415
- package/src/tools/VideoPlayer/VideoPlayer.story.tsx +0 -87
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { defineStory, useBoolean, useNumber } from '@djangocfg/playground';
|
|
3
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@djangocfg/ui-core/components';
|
|
4
|
-
import { ImageIcon, ClipboardPaste } from 'lucide-react';
|
|
5
|
-
import { Uploader } from './components/Uploader';
|
|
6
|
-
import { logger } from './utils';
|
|
7
|
-
import { UploadProvider } from './context';
|
|
8
|
-
import { UploadDropzone } from './components/UploadDropzone';
|
|
9
|
-
import { UploadPreviewList } from './components/UploadPreviewList';
|
|
10
|
-
import { UploadAddButton } from './components/UploadAddButton';
|
|
11
|
-
import { useUploadEvents } from './hooks/useUploadEvents';
|
|
12
|
-
import { useClipboardPaste } from './hooks/useClipboardPaste';
|
|
13
|
-
import { useUploadProvider } from './hooks/useUploadProvider';
|
|
14
|
-
import type { UploadedAsset, AssetType } from './types';
|
|
15
|
-
|
|
16
|
-
export default defineStory({
|
|
17
|
-
title: 'Tools/Uploader',
|
|
18
|
-
component: Uploader,
|
|
19
|
-
description: 'Drag-drop file uploader with progress tracking and preview.',
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// Mock upload endpoint that simulates server response
|
|
23
|
-
const MOCK_DESTINATION = 'https://httpbin.org/post';
|
|
24
|
-
|
|
25
|
-
export const Interactive = () => {
|
|
26
|
-
const [compact] = useBoolean('compact', {
|
|
27
|
-
defaultValue: false,
|
|
28
|
-
label: 'Compact Mode',
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const [showPreview] = useBoolean('showPreview', {
|
|
32
|
-
defaultValue: true,
|
|
33
|
-
label: 'Show Preview',
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const [multiple] = useBoolean('multiple', {
|
|
37
|
-
defaultValue: true,
|
|
38
|
-
label: 'Multiple Files',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const [maxSizeMB] = useNumber('maxSizeMB', {
|
|
42
|
-
defaultValue: 10,
|
|
43
|
-
min: 1,
|
|
44
|
-
max: 100,
|
|
45
|
-
label: 'Max Size (MB)',
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const [concurrent] = useNumber('concurrent', {
|
|
49
|
-
defaultValue: 3,
|
|
50
|
-
min: 1,
|
|
51
|
-
max: 10,
|
|
52
|
-
label: 'Concurrent Uploads',
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<div className="max-w-2xl space-y-4">
|
|
59
|
-
<Uploader
|
|
60
|
-
destination={MOCK_DESTINATION}
|
|
61
|
-
compact={compact}
|
|
62
|
-
showPreview={showPreview}
|
|
63
|
-
multiple={multiple}
|
|
64
|
-
maxSizeMB={maxSizeMB}
|
|
65
|
-
concurrent={concurrent}
|
|
66
|
-
accept={['image', 'document']}
|
|
67
|
-
onUploadComplete={(asset) => {
|
|
68
|
-
setUploadedFiles((prev) => [...prev, asset.name]);
|
|
69
|
-
}}
|
|
70
|
-
/>
|
|
71
|
-
|
|
72
|
-
{uploadedFiles.length > 0 && (
|
|
73
|
-
<Card>
|
|
74
|
-
<CardHeader>
|
|
75
|
-
<CardTitle className="text-sm">Uploaded Files</CardTitle>
|
|
76
|
-
</CardHeader>
|
|
77
|
-
<CardContent>
|
|
78
|
-
<ul className="text-sm space-y-1">
|
|
79
|
-
{uploadedFiles.map((name, i) => (
|
|
80
|
-
<li key={i} className="text-muted-foreground">
|
|
81
|
-
{name}
|
|
82
|
-
</li>
|
|
83
|
-
))}
|
|
84
|
-
</ul>
|
|
85
|
-
</CardContent>
|
|
86
|
-
</Card>
|
|
87
|
-
)}
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export const Default = () => (
|
|
93
|
-
<div className="max-w-2xl">
|
|
94
|
-
<Uploader
|
|
95
|
-
destination={MOCK_DESTINATION}
|
|
96
|
-
accept={['image', 'video', 'document']}
|
|
97
|
-
/>
|
|
98
|
-
</div>
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
export const ImagesOnly = () => (
|
|
102
|
-
<div className="max-w-2xl">
|
|
103
|
-
<Uploader
|
|
104
|
-
destination={MOCK_DESTINATION}
|
|
105
|
-
accept={['image']}
|
|
106
|
-
maxSizeMB={5}
|
|
107
|
-
/>
|
|
108
|
-
</div>
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
export const Compact = () => (
|
|
112
|
-
<div className="max-w-md">
|
|
113
|
-
<Uploader
|
|
114
|
-
destination={MOCK_DESTINATION}
|
|
115
|
-
compact
|
|
116
|
-
accept={['image', 'document']}
|
|
117
|
-
/>
|
|
118
|
-
</div>
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
export const NoPreview = () => (
|
|
122
|
-
<div className="max-w-2xl">
|
|
123
|
-
<Uploader
|
|
124
|
-
destination={MOCK_DESTINATION}
|
|
125
|
-
showPreview={false}
|
|
126
|
-
accept={['image', 'document']}
|
|
127
|
-
/>
|
|
128
|
-
</div>
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
export const SingleFile = () => (
|
|
132
|
-
<div className="max-w-2xl">
|
|
133
|
-
<Uploader
|
|
134
|
-
destination={MOCK_DESTINATION}
|
|
135
|
-
multiple={false}
|
|
136
|
-
accept={['image']}
|
|
137
|
-
/>
|
|
138
|
-
</div>
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
// Custom composition example
|
|
142
|
-
function CustomUploaderContent() {
|
|
143
|
-
const [assets, setAssets] = useState<UploadedAsset[]>([]);
|
|
144
|
-
|
|
145
|
-
useUploadEvents({
|
|
146
|
-
onFileComplete: (asset) => {
|
|
147
|
-
setAssets((prev) => [...prev, asset]);
|
|
148
|
-
},
|
|
149
|
-
onError: (error, fileName) => {
|
|
150
|
-
logger.error(`Error uploading ${fileName}: ${error}`);
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
return (
|
|
155
|
-
<div className="grid grid-cols-2 gap-4">
|
|
156
|
-
<div>
|
|
157
|
-
<h3 className="text-sm font-medium mb-2">Drop Zone</h3>
|
|
158
|
-
<UploadDropzone accept={['image']} />
|
|
159
|
-
</div>
|
|
160
|
-
<div>
|
|
161
|
-
<h3 className="text-sm font-medium mb-2">Upload Queue</h3>
|
|
162
|
-
<UploadPreviewList />
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export const CustomComposition = () => (
|
|
169
|
-
<div className="max-w-3xl">
|
|
170
|
-
<UploadProvider destination={{ url: MOCK_DESTINATION }}>
|
|
171
|
-
<CustomUploaderContent />
|
|
172
|
-
</UploadProvider>
|
|
173
|
-
</div>
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
// With add button
|
|
177
|
-
function WithAddButtonContent() {
|
|
178
|
-
return (
|
|
179
|
-
<div className="space-y-4">
|
|
180
|
-
<div className="flex items-center gap-2">
|
|
181
|
-
<UploadAddButton accept={['image', 'document']} />
|
|
182
|
-
<span className="text-sm text-muted-foreground">
|
|
183
|
-
Click to add files
|
|
184
|
-
</span>
|
|
185
|
-
</div>
|
|
186
|
-
<UploadPreviewList />
|
|
187
|
-
</div>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export const WithAddButton = () => (
|
|
192
|
-
<div className="max-w-2xl">
|
|
193
|
-
<UploadProvider destination={{ url: MOCK_DESTINATION }}>
|
|
194
|
-
<WithAddButtonContent />
|
|
195
|
-
</UploadProvider>
|
|
196
|
-
</div>
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
export const DocumentsOnly = () => (
|
|
200
|
-
<div className="max-w-2xl">
|
|
201
|
-
<Uploader
|
|
202
|
-
destination={MOCK_DESTINATION}
|
|
203
|
-
accept={['document']}
|
|
204
|
-
maxSizeMB={20}
|
|
205
|
-
>
|
|
206
|
-
<div className="text-center">
|
|
207
|
-
<p className="text-muted-foreground">Drop PDF, DOC, or XLS files</p>
|
|
208
|
-
<p className="text-xs text-muted-foreground/60 mt-1">
|
|
209
|
-
Max 20MB per file
|
|
210
|
-
</p>
|
|
211
|
-
</div>
|
|
212
|
-
</Uploader>
|
|
213
|
-
</div>
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
export const CustomContent = () => (
|
|
217
|
-
<div className="max-w-2xl">
|
|
218
|
-
<Uploader
|
|
219
|
-
destination={MOCK_DESTINATION}
|
|
220
|
-
accept={['image']}
|
|
221
|
-
compact
|
|
222
|
-
>
|
|
223
|
-
<div className="flex items-center gap-2">
|
|
224
|
-
<span className="text-2xl">+</span>
|
|
225
|
-
<span className="text-sm">Add images</span>
|
|
226
|
-
</div>
|
|
227
|
-
</Uploader>
|
|
228
|
-
</div>
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
// Paste to upload
|
|
232
|
-
export const PasteToUpload = () => {
|
|
233
|
-
const [pasteEnabled] = useBoolean('pasteEnabled', {
|
|
234
|
-
defaultValue: true,
|
|
235
|
-
label: 'Paste Enabled (Ctrl+V)',
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
return (
|
|
239
|
-
<div className="max-w-2xl space-y-4">
|
|
240
|
-
<Card>
|
|
241
|
-
<CardContent className="pt-4">
|
|
242
|
-
<p className="text-sm text-muted-foreground flex items-center gap-2">
|
|
243
|
-
<ClipboardPaste className="h-4 w-4" />
|
|
244
|
-
Copy an image anywhere, then press <kbd className="px-1 py-0.5 rounded bg-muted text-xs font-mono">Ctrl+V</kbd> to upload it.
|
|
245
|
-
Supports: files, screenshots, copied images, base64 URLs, remote image URLs.
|
|
246
|
-
</p>
|
|
247
|
-
</CardContent>
|
|
248
|
-
</Card>
|
|
249
|
-
<Uploader
|
|
250
|
-
destination={MOCK_DESTINATION}
|
|
251
|
-
accept={['image']}
|
|
252
|
-
pasteEnabled={pasteEnabled}
|
|
253
|
-
onPasteNoMatch={() => logger.info('Paste: no uploadable content found')}
|
|
254
|
-
/>
|
|
255
|
-
</div>
|
|
256
|
-
);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
// useClipboardPaste hook standalone
|
|
260
|
-
function ClipboardPasteHookContent() {
|
|
261
|
-
const { upload } = useUploadProvider();
|
|
262
|
-
const [lastPaste, setLastPaste] = useState<string | null>(null);
|
|
263
|
-
|
|
264
|
-
useClipboardPaste({
|
|
265
|
-
enabled: true,
|
|
266
|
-
acceptTypes: ['image'],
|
|
267
|
-
onFiles: (files) => {
|
|
268
|
-
setLastPaste(`Pasted ${files.length} file(s): ${files.map((f) => f.name).join(', ')}`);
|
|
269
|
-
upload(files);
|
|
270
|
-
},
|
|
271
|
-
onNoMatch: () => setLastPaste('Paste detected but no image found in clipboard'),
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
return (
|
|
275
|
-
<div className="space-y-4">
|
|
276
|
-
<Card>
|
|
277
|
-
<CardContent className="pt-4 space-y-2">
|
|
278
|
-
<p className="text-sm text-muted-foreground flex items-center gap-2">
|
|
279
|
-
<ClipboardPaste className="h-4 w-4" />
|
|
280
|
-
Using <code className="text-xs bg-muted px-1 rounded">useClipboardPaste</code> hook directly — paste anywhere on the page.
|
|
281
|
-
</p>
|
|
282
|
-
{lastPaste && (
|
|
283
|
-
<p className="text-xs font-mono bg-muted rounded p-2">{lastPaste}</p>
|
|
284
|
-
)}
|
|
285
|
-
</CardContent>
|
|
286
|
-
</Card>
|
|
287
|
-
<UploadPreviewList />
|
|
288
|
-
</div>
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
export const ClipboardPasteHook = () => (
|
|
293
|
-
<div className="max-w-2xl">
|
|
294
|
-
<UploadProvider destination={{ url: MOCK_DESTINATION }}>
|
|
295
|
-
<ClipboardPasteHookContent />
|
|
296
|
-
</UploadProvider>
|
|
297
|
-
</div>
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
// Page-level drop zone
|
|
301
|
-
function PageDropContent() {
|
|
302
|
-
return (
|
|
303
|
-
<div className="space-y-4">
|
|
304
|
-
<Card>
|
|
305
|
-
<CardHeader>
|
|
306
|
-
<CardTitle className="text-sm">Page Drop Enabled</CardTitle>
|
|
307
|
-
</CardHeader>
|
|
308
|
-
<CardContent>
|
|
309
|
-
<p className="text-sm text-muted-foreground mb-4">
|
|
310
|
-
Drag files anywhere on the page to upload. An overlay will appear when dragging.
|
|
311
|
-
</p>
|
|
312
|
-
<UploadPreviewList />
|
|
313
|
-
</CardContent>
|
|
314
|
-
</Card>
|
|
315
|
-
</div>
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export const PageDrop = () => (
|
|
320
|
-
<div className="max-w-2xl">
|
|
321
|
-
<UploadProvider
|
|
322
|
-
destination={{ url: MOCK_DESTINATION }}
|
|
323
|
-
pageDropEnabled
|
|
324
|
-
pageDropProps={{
|
|
325
|
-
accept: ['image', 'document'],
|
|
326
|
-
maxSizeMB: 10,
|
|
327
|
-
}}
|
|
328
|
-
>
|
|
329
|
-
<PageDropContent />
|
|
330
|
-
</UploadProvider>
|
|
331
|
-
</div>
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
// Page drop with custom overlay
|
|
335
|
-
function PageDropCustomOverlayContent() {
|
|
336
|
-
return (
|
|
337
|
-
<div className="space-y-4">
|
|
338
|
-
<Card>
|
|
339
|
-
<CardHeader>
|
|
340
|
-
<CardTitle className="text-sm">Custom Page Drop Overlay</CardTitle>
|
|
341
|
-
</CardHeader>
|
|
342
|
-
<CardContent>
|
|
343
|
-
<p className="text-sm text-muted-foreground mb-4">
|
|
344
|
-
This example uses a custom overlay design.
|
|
345
|
-
</p>
|
|
346
|
-
<UploadPreviewList />
|
|
347
|
-
</CardContent>
|
|
348
|
-
</Card>
|
|
349
|
-
</div>
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export const PageDropCustomOverlay = () => (
|
|
354
|
-
<div className="max-w-2xl">
|
|
355
|
-
<UploadProvider
|
|
356
|
-
destination={{ url: MOCK_DESTINATION }}
|
|
357
|
-
pageDropEnabled
|
|
358
|
-
pageDropProps={{
|
|
359
|
-
accept: ['image'],
|
|
360
|
-
}}
|
|
361
|
-
pageDropOverlay={
|
|
362
|
-
<div className="text-center p-12 bg-primary/10 rounded-2xl border-4 border-dashed border-primary">
|
|
363
|
-
<ImageIcon className="h-16 w-16 text-primary mx-auto mb-4" />
|
|
364
|
-
<p className="text-xl font-bold text-primary">Drop your images here!</p>
|
|
365
|
-
</div>
|
|
366
|
-
}
|
|
367
|
-
>
|
|
368
|
-
<PageDropCustomOverlayContent />
|
|
369
|
-
</UploadProvider>
|
|
370
|
-
</div>
|
|
371
|
-
);
|
|
372
|
-
|
|
373
|
-
// Standalone — uploadFn instead of UploadProvider (custom API hooks, no rpldy)
|
|
374
|
-
export const StandaloneWithUploadFn = () => {
|
|
375
|
-
const [files, setFiles] = useState<string[]>([]);
|
|
376
|
-
|
|
377
|
-
return (
|
|
378
|
-
<div className="max-w-2xl space-y-4">
|
|
379
|
-
<Card>
|
|
380
|
-
<CardContent className="pt-4">
|
|
381
|
-
<p className="text-sm text-muted-foreground flex items-center gap-2">
|
|
382
|
-
<ClipboardPaste className="h-4 w-4" />
|
|
383
|
-
No <code className="text-xs bg-muted px-1 rounded">UploadProvider</code> needed.
|
|
384
|
-
Pass <code className="text-xs bg-muted px-1 rounded">uploadFn</code> to handle files yourself.
|
|
385
|
-
Drag, click, or paste (Ctrl+V).
|
|
386
|
-
</p>
|
|
387
|
-
</CardContent>
|
|
388
|
-
</Card>
|
|
389
|
-
<UploadDropzone
|
|
390
|
-
accept={['image', 'document']}
|
|
391
|
-
maxSizeMB={10}
|
|
392
|
-
pasteEnabled
|
|
393
|
-
uploadFn={(selected) => {
|
|
394
|
-
setFiles((prev) => [...prev, ...selected.map((f) => f.name)]);
|
|
395
|
-
logger.info('Custom uploadFn received: ' + selected.map((f) => f.name).join(', '));
|
|
396
|
-
}}
|
|
397
|
-
onPasteNoMatch={() => logger.info('Paste: no uploadable content found')}
|
|
398
|
-
/>
|
|
399
|
-
{files.length > 0 && (
|
|
400
|
-
<Card>
|
|
401
|
-
<CardHeader>
|
|
402
|
-
<CardTitle className="text-sm">Received by uploadFn</CardTitle>
|
|
403
|
-
</CardHeader>
|
|
404
|
-
<CardContent>
|
|
405
|
-
<ul className="text-sm space-y-1">
|
|
406
|
-
{files.map((name, i) => (
|
|
407
|
-
<li key={i} className="text-muted-foreground">{name}</li>
|
|
408
|
-
))}
|
|
409
|
-
</ul>
|
|
410
|
-
</CardContent>
|
|
411
|
-
</Card>
|
|
412
|
-
)}
|
|
413
|
-
</div>
|
|
414
|
-
);
|
|
415
|
-
};
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { defineStory, useBoolean, useSelect } from '@djangocfg/playground';
|
|
2
|
-
import { VideoPlayer } from './index';
|
|
3
|
-
import type { VideoSourceUnion } from './types';
|
|
4
|
-
|
|
5
|
-
export default defineStory({
|
|
6
|
-
title: 'Tools/Video Player',
|
|
7
|
-
component: VideoPlayer,
|
|
8
|
-
description: 'Video player with HLS support, quality selection, and custom controls.',
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const VIDEO_SOURCES: Record<string, VideoSourceUnion> = {
|
|
12
|
-
mp4: {
|
|
13
|
-
type: 'url',
|
|
14
|
-
url: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
|
15
|
-
poster: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg',
|
|
16
|
-
title: 'Big Buck Bunny',
|
|
17
|
-
},
|
|
18
|
-
hls: {
|
|
19
|
-
type: 'hls',
|
|
20
|
-
url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
|
|
21
|
-
title: 'HLS Stream',
|
|
22
|
-
},
|
|
23
|
-
elephants: {
|
|
24
|
-
type: 'url',
|
|
25
|
-
url: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
|
|
26
|
-
poster: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ElephantsDream.jpg',
|
|
27
|
-
title: "Elephant's Dream",
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const Interactive = () => {
|
|
32
|
-
const [sourceKey] = useSelect('source', {
|
|
33
|
-
options: ['mp4', 'hls', 'elephants'] as const,
|
|
34
|
-
defaultValue: 'mp4',
|
|
35
|
-
label: 'Video Source',
|
|
36
|
-
description: 'Select video source type',
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const [autoplay] = useBoolean('autoplay', {
|
|
40
|
-
defaultValue: false,
|
|
41
|
-
label: 'Autoplay',
|
|
42
|
-
description: 'Auto-start video playback',
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const [muted] = useBoolean('muted', {
|
|
46
|
-
defaultValue: false,
|
|
47
|
-
label: 'Muted',
|
|
48
|
-
description: 'Mute video audio',
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const [loop] = useBoolean('loop', {
|
|
52
|
-
defaultValue: false,
|
|
53
|
-
label: 'Loop',
|
|
54
|
-
description: 'Loop video playback',
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const source = VIDEO_SOURCES[sourceKey];
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className="max-w-3xl" key={sourceKey}>
|
|
61
|
-
<VideoPlayer
|
|
62
|
-
source={source}
|
|
63
|
-
autoPlay={autoplay}
|
|
64
|
-
muted={muted}
|
|
65
|
-
loop={loop}
|
|
66
|
-
/>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export const MP4 = () => (
|
|
72
|
-
<div className="max-w-3xl">
|
|
73
|
-
<VideoPlayer source={VIDEO_SOURCES.mp4} />
|
|
74
|
-
</div>
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
export const HLS = () => (
|
|
78
|
-
<div className="max-w-3xl">
|
|
79
|
-
<VideoPlayer source={VIDEO_SOURCES.hls} />
|
|
80
|
-
</div>
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
export const WithPoster = () => (
|
|
84
|
-
<div className="max-w-3xl">
|
|
85
|
-
<VideoPlayer source={VIDEO_SOURCES.elephants} />
|
|
86
|
-
</div>
|
|
87
|
-
);
|