@polymarbot/nuxt-layer-shadcn-ui 0.9.6 → 0.10.1

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,510 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import EventLog from '#storybook/EventLog.vue'
3
+ import type { UploadFile, UploadVariant } from './types'
4
+ import Upload from './index.vue'
5
+
6
+ const variants: UploadVariant[] = [ 'button', 'box', 'drag' ]
7
+
8
+ const sampleImage = 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&h=200&fit=crop'
9
+ const sampleImage2 = 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200&h=200&fit=crop'
10
+
11
+ const sampleImages: UploadFile[] = [
12
+ { uid: 's-img-1', name: 'avatar.png', url: sampleImage, status: 'done' },
13
+ { uid: 's-img-2', name: 'cover.png', url: sampleImage2, status: 'done' },
14
+ ]
15
+
16
+ const meta = {
17
+ title: 'UI/Upload',
18
+ component: Upload,
19
+ argTypes: {
20
+ variant: { control: 'select', options: variants },
21
+ accept: { control: 'text' },
22
+ disabled: { control: 'boolean' },
23
+ readonly: { control: 'boolean' },
24
+ invalid: { control: 'boolean' },
25
+ multiple: { control: 'boolean' },
26
+ maxCount: { control: 'number' },
27
+ maxSize: { control: 'number' },
28
+ directory: { control: 'boolean' },
29
+ text: { control: 'text' },
30
+ icon: { control: 'text' },
31
+ },
32
+ args: {
33
+ variant: 'button',
34
+ accept: '',
35
+ disabled: false,
36
+ readonly: false,
37
+ invalid: false,
38
+ multiple: false,
39
+ maxCount: undefined,
40
+ maxSize: undefined,
41
+ directory: false,
42
+ text: '',
43
+ icon: '',
44
+ },
45
+ // fileList carries `raw: File` after a user picks a file, which is not
46
+ // structured-cloneable — Storybook's `updateArgs` drops such values, so
47
+ // binding fileList through args makes uploads appear to do nothing. Use a
48
+ // local ref instead, optionally seeded from `args.fileList`.
49
+ render: args => ({
50
+ components: { Upload },
51
+ setup () {
52
+ const initial = Array.isArray(args.fileList) ? args.fileList as UploadFile[] : []
53
+ const fileList = ref<UploadFile[]>([ ...initial ])
54
+ return { args, fileList }
55
+ },
56
+ template: `
57
+ <div class="max-w-2xl">
58
+ <Upload v-bind="args" v-model:fileList="fileList" />
59
+ </div>
60
+ `,
61
+ }),
62
+ } satisfies Meta<typeof Upload>
63
+
64
+ export default meta
65
+ type Story = StoryObj<typeof meta>
66
+
67
+ const noControls = { controls: { disable: true }} satisfies Story['parameters']
68
+
69
+ export const Default: Story = {}
70
+
71
+ // --- Variant showcase ---
72
+
73
+ export const VariantButton: Story = {
74
+ parameters: noControls,
75
+ args: { variant: 'button' },
76
+ }
77
+
78
+ export const VariantBox: Story = {
79
+ parameters: noControls,
80
+ args: {
81
+ variant: 'box',
82
+ accept: 'image/*',
83
+ multiple: true,
84
+ maxCount: 5,
85
+ fileList: sampleImages,
86
+ },
87
+ }
88
+
89
+ export const VariantDrag: Story = {
90
+ parameters: noControls,
91
+ args: { variant: 'drag' },
92
+ }
93
+
94
+ export const AcceptImagesOnly: Story = {
95
+ parameters: noControls,
96
+ args: {
97
+ variant: 'drag',
98
+ accept: 'image/*',
99
+ multiple: true,
100
+ },
101
+ }
102
+
103
+ // --- Selection behavior (all three variants side by side) ---
104
+
105
+ export const Multiple: Story = {
106
+ parameters: {
107
+ ...noControls,
108
+ docs: {
109
+ source: {
110
+ code: `
111
+ <template>
112
+ <Upload v-model:fileList="fileList" variant="drag" multiple />
113
+ </template>
114
+ `.trim(),
115
+ },
116
+ },
117
+ },
118
+ render: () => ({
119
+ components: { Upload },
120
+ setup: () => ({ variants }),
121
+ template: `
122
+ <div class="max-w-2xl space-y-6">
123
+ <Upload v-for="v in variants" :key="v" :variant="v" multiple />
124
+ </div>
125
+ `,
126
+ }),
127
+ }
128
+
129
+ export const MaxCount: Story = {
130
+ parameters: {
131
+ ...noControls,
132
+ docs: {
133
+ source: {
134
+ code: `
135
+ <template>
136
+ <Upload v-model:fileList="fileList" variant="drag" multiple :maxCount="3" />
137
+ </template>
138
+ `.trim(),
139
+ },
140
+ },
141
+ },
142
+ render: () => ({
143
+ components: { Upload },
144
+ setup: () => ({ variants }),
145
+ template: `
146
+ <div class="max-w-2xl space-y-6">
147
+ <Upload v-for="v in variants" :key="v" :variant="v" multiple :maxCount="3" />
148
+ </div>
149
+ `,
150
+ }),
151
+ }
152
+
153
+ export const MaxSize: Story = {
154
+ parameters: {
155
+ ...noControls,
156
+ docs: {
157
+ source: {
158
+ code: `
159
+ <template>
160
+ <Upload v-model:fileList="fileList" variant="drag" multiple :maxSize="50 * 1024" />
161
+ </template>
162
+ `.trim(),
163
+ },
164
+ },
165
+ },
166
+ render: () => ({
167
+ components: { Upload },
168
+ setup: () => ({ variants }),
169
+ template: `
170
+ <div class="max-w-2xl space-y-6">
171
+ <Upload v-for="v in variants" :key="v" :variant="v" multiple :maxSize="50 * 1024" />
172
+ </div>
173
+ `,
174
+ }),
175
+ }
176
+
177
+ export const Directory: Story = {
178
+ parameters: {
179
+ ...noControls,
180
+ docs: {
181
+ source: {
182
+ code: `
183
+ <template>
184
+ <Upload
185
+ v-model:fileList="fileList"
186
+ variant="button"
187
+ directory
188
+ accept="image/*"
189
+ :maxCount="5"
190
+ />
191
+ </template>
192
+ `.trim(),
193
+ },
194
+ },
195
+ },
196
+ render: () => ({
197
+ components: { Upload },
198
+ setup: () => ({ variants }),
199
+ template: `
200
+ <div class="max-w-2xl space-y-6">
201
+ <Upload
202
+ v-for="v in variants"
203
+ :key="v"
204
+ :variant="v"
205
+ directory
206
+ accept="image/*"
207
+ :maxCount="5"
208
+ />
209
+ </div>
210
+ `,
211
+ }),
212
+ }
213
+
214
+ // --- States ---
215
+
216
+ export const Disabled: Story = {
217
+ parameters: {
218
+ ...noControls,
219
+ docs: {
220
+ source: {
221
+ code: `
222
+ <template>
223
+ <Upload v-model:fileList="fileList" variant="drag" disabled multiple />
224
+ </template>
225
+ `.trim(),
226
+ },
227
+ },
228
+ },
229
+ render: () => ({
230
+ components: { Upload },
231
+ setup () {
232
+ const fileList = ref<UploadFile[]>([ ...sampleImages ])
233
+ return { variants, fileList }
234
+ },
235
+ template: `
236
+ <div class="max-w-2xl space-y-6">
237
+ <Upload
238
+ v-for="v in variants"
239
+ :key="v"
240
+ :variant="v"
241
+ disabled
242
+ multiple
243
+ :fileList="fileList"
244
+ />
245
+ </div>
246
+ `,
247
+ }),
248
+ }
249
+
250
+ export const Readonly: Story = {
251
+ parameters: {
252
+ ...noControls,
253
+ docs: {
254
+ source: {
255
+ code: `
256
+ <template>
257
+ <Upload v-model:fileList="fileList" variant="drag" readonly multiple />
258
+ </template>
259
+ `.trim(),
260
+ },
261
+ },
262
+ },
263
+ render: () => ({
264
+ components: { Upload },
265
+ setup () {
266
+ const fileList = ref<UploadFile[]>([ ...sampleImages ])
267
+ return { variants, fileList }
268
+ },
269
+ template: `
270
+ <div class="max-w-2xl space-y-6">
271
+ <Upload
272
+ v-for="v in variants"
273
+ :key="v"
274
+ :variant="v"
275
+ readonly
276
+ multiple
277
+ :fileList="fileList"
278
+ />
279
+ </div>
280
+ `,
281
+ }),
282
+ }
283
+
284
+ export const ReadonlyEmpty: Story = {
285
+ parameters: {
286
+ ...noControls,
287
+ docs: {
288
+ source: {
289
+ code: `
290
+ <template>
291
+ <Upload variant="drag" readonly />
292
+ </template>
293
+ `.trim(),
294
+ },
295
+ },
296
+ },
297
+ render: () => ({
298
+ components: { Upload },
299
+ setup: () => ({ variants }),
300
+ template: `
301
+ <div class="max-w-2xl space-y-6">
302
+ <Upload v-for="v in variants" :key="v" :variant="v" readonly />
303
+ </div>
304
+ `,
305
+ }),
306
+ }
307
+
308
+ export const Invalid: Story = {
309
+ parameters: noControls,
310
+ args: {
311
+ variant: 'drag',
312
+ invalid: true,
313
+ multiple: true,
314
+ fileList: [
315
+ { uid: 'i-1', name: 'gitmoji (1).md', status: 'done' },
316
+ { uid: 'i-2', name: 'cd973fbf55b88a9ebaec4f01821c552a.png', status: 'done' },
317
+ ],
318
+ },
319
+ }
320
+
321
+ // --- Custom behaviors ---
322
+
323
+ export const BeforeUpload: Story = {
324
+ parameters: {
325
+ ...noControls,
326
+ docs: {
327
+ source: {
328
+ code: `
329
+ <template>
330
+ <Upload
331
+ v-model:fileList="fileList"
332
+ variant="drag"
333
+ multiple
334
+ :beforeUpload="beforeUpload"
335
+ />
336
+ </template>
337
+
338
+ <script setup lang="ts">
339
+ const fileList = ref<UploadFile[]>([])
340
+
341
+ function beforeUpload (file: File) {
342
+ // Reject files larger than 1MB
343
+ if (file.size > 1024 * 1024) {
344
+ alert(\`\${file.name} is too large (>1MB)\`)
345
+ return false
346
+ }
347
+ // Optionally return a Promise<File | Blob> to replace the file
348
+ return true
349
+ }
350
+ </script>
351
+ `.trim(),
352
+ },
353
+ },
354
+ },
355
+ render: () => ({
356
+ components: { Upload },
357
+ setup () {
358
+ const fileList = ref<UploadFile[]>([])
359
+ function beforeUpload (file: File) {
360
+ if (file.size > 1024 * 1024) {
361
+ alert(`${file.name} is too large (>1MB)`)
362
+ return false
363
+ }
364
+ return true
365
+ }
366
+ return { fileList, beforeUpload }
367
+ },
368
+ template: `
369
+ <div class="max-w-2xl">
370
+ <Upload
371
+ v-model:fileList="fileList"
372
+ variant="drag"
373
+ multiple
374
+ :beforeUpload="beforeUpload"
375
+ />
376
+ </div>
377
+ `,
378
+ }),
379
+ }
380
+
381
+ export const CustomUpload: Story = {
382
+ parameters: {
383
+ ...noControls,
384
+ docs: {
385
+ source: {
386
+ code: `
387
+ <template>
388
+ <Upload
389
+ v-model:fileList="fileList"
390
+ variant="box"
391
+ accept="image/*"
392
+ multiple
393
+ :maxCount="5"
394
+ :upload="upload"
395
+ />
396
+ </template>
397
+
398
+ <script setup lang="ts">
399
+ const fileList = ref<UploadFile[]>([])
400
+
401
+ async function upload (files: (File | Blob)[]) {
402
+ // Simulate network latency
403
+ await new Promise(resolve => setTimeout(resolve, 1500))
404
+ // throw new Error('Upload failed') would mark items as error
405
+ }
406
+ </script>
407
+ `.trim(),
408
+ },
409
+ },
410
+ },
411
+ render: () => ({
412
+ components: { Upload },
413
+ setup () {
414
+ const fileList = ref<UploadFile[]>([])
415
+ async function upload () {
416
+ await new Promise<void>(resolve => setTimeout(resolve, 1500))
417
+ }
418
+ return { fileList, upload }
419
+ },
420
+ template: `
421
+ <div class="max-w-2xl">
422
+ <Upload
423
+ v-model:fileList="fileList"
424
+ variant="box"
425
+ accept="image/*"
426
+ multiple
427
+ :maxCount="5"
428
+ :upload="upload"
429
+ />
430
+ </div>
431
+ `,
432
+ }),
433
+ }
434
+
435
+ export const CustomHint: Story = {
436
+ parameters: {
437
+ ...noControls,
438
+ docs: {
439
+ source: {
440
+ code: `
441
+ <template>
442
+ <Upload variant="drag" multiple accept="image/*" :maxSize="50 * 1024">
443
+ <template #hint="{ lines }">
444
+ <ul class="list-disc pl-5 text-primary inline-block text-left">
445
+ <li v-for="line in lines" :key="line">{{ line }}</li>
446
+ </ul>
447
+ </template>
448
+ </Upload>
449
+ </template>
450
+ `.trim(),
451
+ },
452
+ },
453
+ },
454
+ render: () => ({
455
+ components: { Upload },
456
+ template: `
457
+ <div class="max-w-2xl">
458
+ <Upload variant="drag" multiple accept="image/*" :maxSize="50 * 1024">
459
+ <template #hint="{ lines }">
460
+ <ul class="list-disc pl-5 text-primary inline-block text-left">
461
+ <li v-for="line in lines" :key="line">{{ line }}</li>
462
+ </ul>
463
+ </template>
464
+ </Upload>
465
+ </div>
466
+ `,
467
+ }),
468
+ }
469
+
470
+ export const EventHandling: Story = {
471
+ parameters: {
472
+ ...noControls,
473
+ docs: {
474
+ source: {
475
+ code: `
476
+ <template>
477
+ <Upload
478
+ v-model:fileList="fileList"
479
+ variant="drag"
480
+ multiple
481
+ @update:fileList="onUpdate"
482
+ @change="onChange"
483
+ @remove="onRemove"
484
+ @preview="onPreview"
485
+ @error="onError"
486
+ />
487
+ </template>
488
+ `.trim(),
489
+ },
490
+ },
491
+ },
492
+ render: () => ({
493
+ components: { Upload, EventLog },
494
+ setup: () => ({ fileList: ref<UploadFile[]>([]) }),
495
+ template: `
496
+ <EventLog v-slot="{ record }">
497
+ <Upload
498
+ v-model:fileList="fileList"
499
+ variant="drag"
500
+ multiple
501
+ @update:fileList="(v) => record('update:fileList', v.map(f => f.name))"
502
+ @change="(v) => record('change', v.map(f => f.name))"
503
+ @remove="(f) => record('remove', f.name)"
504
+ @preview="(f) => record('preview', f.name)"
505
+ @error="(e) => record('error', String(e))"
506
+ />
507
+ </EventLog>
508
+ `,
509
+ }),
510
+ }