@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.
- package/app/components/ui/AdminLayout/types.ts +3 -2
- package/app/components/ui/Alert/index.vue +1 -5
- package/app/components/ui/Breadcrumb/types.ts +3 -1
- package/app/components/ui/Button/types.ts +2 -1
- package/app/components/ui/Dropdown/ItemIcon.vue +1 -7
- package/app/components/ui/Icon/index.stories.ts +25 -0
- package/app/components/ui/Icon/index.vue +1 -0
- package/app/components/ui/Icon/types.ts +4 -2
- package/app/components/ui/ModalContent/types.ts +2 -2
- package/app/components/ui/Tabs/index.vue +2 -6
- package/app/components/ui/Upload/en.json +27 -0
- package/app/components/ui/Upload/index.stories.ts +510 -0
- package/app/components/ui/Upload/index.vue +712 -0
- package/app/components/ui/Upload/types.ts +36 -0
- package/i18n/messages/ar.json +27 -0
- package/i18n/messages/de.json +27 -0
- package/i18n/messages/en.json +27 -0
- package/i18n/messages/es.json +27 -0
- package/i18n/messages/fr.json +27 -0
- package/i18n/messages/hi.json +27 -0
- package/i18n/messages/id.json +27 -0
- package/i18n/messages/it.json +27 -0
- package/i18n/messages/ja.json +27 -0
- package/i18n/messages/ko.json +27 -0
- package/i18n/messages/nl.json +27 -0
- package/i18n/messages/pl.json +27 -0
- package/i18n/messages/pt.json +27 -0
- package/i18n/messages/ru.json +27 -0
- package/i18n/messages/th.json +27 -0
- package/i18n/messages/tr.json +27 -0
- package/i18n/messages/vi.json +27 -0
- package/i18n/messages/zh-CN.json +27 -0
- package/i18n/messages/zh-TW.json +27 -0
- package/package.json +2 -2
|
@@ -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
|
+
}
|