@polymarbot/nuxt-layer-shadcn-ui 0.9.6 → 0.10.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.
- 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 +21 -0
- package/app/components/ui/Upload/index.stories.ts +381 -0
- package/app/components/ui/Upload/index.vue +598 -0
- package/app/components/ui/Upload/types.ts +36 -0
- package/i18n/messages/ar.json +21 -0
- package/i18n/messages/de.json +21 -0
- package/i18n/messages/en.json +21 -0
- package/i18n/messages/es.json +21 -0
- package/i18n/messages/fr.json +21 -0
- package/i18n/messages/hi.json +21 -0
- package/i18n/messages/id.json +21 -0
- package/i18n/messages/it.json +21 -0
- package/i18n/messages/ja.json +21 -0
- package/i18n/messages/ko.json +21 -0
- package/i18n/messages/nl.json +21 -0
- package/i18n/messages/pl.json +21 -0
- package/i18n/messages/pt.json +21 -0
- package/i18n/messages/ru.json +21 -0
- package/i18n/messages/th.json +21 -0
- package/i18n/messages/tr.json +21 -0
- package/i18n/messages/vi.json +21 -0
- package/i18n/messages/zh-CN.json +21 -0
- package/i18n/messages/zh-TW.json +21 -0
- package/package.json +2 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { Component } from 'vue'
|
|
1
2
|
import type { DropdownItem } from '../Dropdown/types'
|
|
2
3
|
|
|
3
4
|
export interface AdminLayoutSidebarMenuItem {
|
|
4
5
|
label: string
|
|
5
|
-
icon?: string
|
|
6
|
+
icon?: string | Component
|
|
6
7
|
href?: string
|
|
7
8
|
command?: () => void
|
|
8
9
|
group?: string
|
|
@@ -14,7 +15,7 @@ export interface AdminLayoutSidebarMenuItem {
|
|
|
14
15
|
export interface AdminLayoutSidebarDropdownProfile {
|
|
15
16
|
title?: string
|
|
16
17
|
subtitle?: string
|
|
17
|
-
icon?: string
|
|
18
|
+
icon?: string | Component
|
|
18
19
|
image?: string
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -54,13 +54,9 @@ const hasDescription = computed(() => Boolean(slots.default || props.description
|
|
|
54
54
|
<ShadcnAlert :class="mergedClass">
|
|
55
55
|
<slot name="icon">
|
|
56
56
|
<Icon
|
|
57
|
-
v-if="
|
|
57
|
+
v-if="icon"
|
|
58
58
|
:name="icon"
|
|
59
59
|
/>
|
|
60
|
-
<component
|
|
61
|
-
:is="icon"
|
|
62
|
-
v-else-if="icon"
|
|
63
|
-
/>
|
|
64
60
|
<Icon
|
|
65
61
|
v-else-if="defaultIconName"
|
|
66
62
|
:name="defaultIconName"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ButtonVariants } from '../../shadcn/button'
|
|
2
|
+
import type { Component } from 'vue'
|
|
2
3
|
import type { RouteLocationRaw } from 'vue-router'
|
|
3
4
|
|
|
4
5
|
export type ButtonVariant = ButtonVariants['variant']
|
|
@@ -10,7 +11,7 @@ export interface ButtonProps {
|
|
|
10
11
|
loading?: boolean
|
|
11
12
|
disabled?: boolean
|
|
12
13
|
rounded?: boolean
|
|
13
|
-
icon?: string
|
|
14
|
+
icon?: string | Component
|
|
14
15
|
iconPosition?: 'start' | 'end'
|
|
15
16
|
href?: string
|
|
16
17
|
to?: RouteLocationRaw
|
|
@@ -3,7 +3,6 @@ import { cva } from 'class-variance-authority'
|
|
|
3
3
|
import type { DropdownActionItem } from './types'
|
|
4
4
|
|
|
5
5
|
const props = defineProps<{
|
|
6
|
-
/** Icon name (lucide kebab-case) or a Vue component. */
|
|
7
6
|
icon: DropdownActionItem['icon']
|
|
8
7
|
/** Override icon color independently of the surrounding item color. */
|
|
9
8
|
iconColor?: DropdownActionItem['iconColor']
|
|
@@ -29,13 +28,8 @@ const colorClass = computed(() => iconColorVariants({ color: props.iconColor }))
|
|
|
29
28
|
|
|
30
29
|
<template>
|
|
31
30
|
<Icon
|
|
32
|
-
v-if="
|
|
31
|
+
v-if="icon"
|
|
33
32
|
:name="icon"
|
|
34
33
|
:class="colorClass"
|
|
35
34
|
/>
|
|
36
|
-
<component
|
|
37
|
-
:is="icon"
|
|
38
|
-
v-else-if="icon"
|
|
39
|
-
:class="cn('size-4', colorClass)"
|
|
40
|
-
/>
|
|
41
35
|
</template>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import { Coffee } from 'lucide-vue-next'
|
|
2
3
|
import Icon from './index.vue'
|
|
3
4
|
|
|
4
5
|
const commonIcons = [
|
|
@@ -75,6 +76,30 @@ export const CommonIcons: Story = {
|
|
|
75
76
|
}),
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
export const FromComponent: Story = {
|
|
80
|
+
parameters: {
|
|
81
|
+
...noControls,
|
|
82
|
+
docs: {
|
|
83
|
+
source: {
|
|
84
|
+
code: `
|
|
85
|
+
<script setup lang="ts">
|
|
86
|
+
import { Coffee } from 'lucide-vue-next'
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<Icon :name="Coffee" class="size-6" />
|
|
91
|
+
</template>
|
|
92
|
+
`.trim(),
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
render: () => ({
|
|
97
|
+
components: { Icon },
|
|
98
|
+
setup: () => ({ Coffee }),
|
|
99
|
+
template: '<Icon :name="Coffee" class="size-6" />',
|
|
100
|
+
}),
|
|
101
|
+
}
|
|
102
|
+
|
|
78
103
|
export const Sizes: Story = {
|
|
79
104
|
parameters: {
|
|
80
105
|
...noControls,
|
|
@@ -14,6 +14,7 @@ function toPascalCase (str: string): string {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const iconComponent = computed<Component | undefined>(() => {
|
|
17
|
+
if (typeof props.name !== 'string') return props.name
|
|
17
18
|
const pascalName = toPascalCase(props.name)
|
|
18
19
|
return (icons as Record<string, Component>)[pascalName]
|
|
19
20
|
})
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { Component } from 'vue'
|
|
2
|
+
|
|
1
3
|
export interface IconProps {
|
|
2
|
-
/**
|
|
3
|
-
name: string
|
|
4
|
+
/** Lucide icon name in kebab-case (e.g. "arrow-left") or a Vue component. */
|
|
5
|
+
name: string | Component
|
|
4
6
|
class?: ClassValue
|
|
5
7
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { VNode } from 'vue'
|
|
1
|
+
import type { Component, VNode } from 'vue'
|
|
2
2
|
|
|
3
3
|
export type ModalContentType = 'default' | 'success' | 'info' | 'help' | 'warn' | 'danger' | 'error'
|
|
4
4
|
|
|
5
5
|
export interface ModalContentProps {
|
|
6
6
|
type?: ModalContentType
|
|
7
|
-
icon?: string
|
|
7
|
+
icon?: string | Component
|
|
8
8
|
content?: string | VNode | HTMLElement
|
|
9
9
|
}
|
|
@@ -34,7 +34,7 @@ function getTriggerClass (item: TabsItem) {
|
|
|
34
34
|
const showsOnlyIcon = !!item.icon && (props.iconOnly || !item.title)
|
|
35
35
|
return cn(
|
|
36
36
|
props.rounded && 'rounded-full',
|
|
37
|
-
showsOnlyIcon && 'aspect-square flex-none
|
|
37
|
+
showsOnlyIcon && 'px-0 aspect-square flex-none',
|
|
38
38
|
props.triggerClass,
|
|
39
39
|
)
|
|
40
40
|
}
|
|
@@ -62,13 +62,9 @@ function getTriggerClass (item: TabsItem) {
|
|
|
62
62
|
:active="activeValue === item.value"
|
|
63
63
|
>
|
|
64
64
|
<Icon
|
|
65
|
-
v-if="item.icon
|
|
65
|
+
v-if="item.icon"
|
|
66
66
|
:name="item.icon"
|
|
67
67
|
/>
|
|
68
|
-
<component
|
|
69
|
-
:is="item.icon"
|
|
70
|
-
v-else-if="item.icon"
|
|
71
|
-
/>
|
|
72
68
|
<span v-if="item.title && !iconOnly">
|
|
73
69
|
{{ item.title }}
|
|
74
70
|
</span>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"drag": {
|
|
3
|
+
"title": "Click or drag file to this area to upload",
|
|
4
|
+
"titleMultiple": "Click or drag files to this area to upload"
|
|
5
|
+
},
|
|
6
|
+
"empty": "No files",
|
|
7
|
+
"error": {
|
|
8
|
+
"oversize": "{files} exceeds the {size} size limit."
|
|
9
|
+
},
|
|
10
|
+
"hint": {
|
|
11
|
+
"max": "Up to {max} files can be selected.",
|
|
12
|
+
"maxSize": "Max size per file: {size}.",
|
|
13
|
+
"multiple": "Multiple files can be selected.",
|
|
14
|
+
"single": "Only a single file can be selected."
|
|
15
|
+
},
|
|
16
|
+
"preview": "Preview",
|
|
17
|
+
"remove": "Remove",
|
|
18
|
+
"upload": "Upload",
|
|
19
|
+
"uploadDirectory": "Upload Directory",
|
|
20
|
+
"uploading": "Uploading"
|
|
21
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
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: noControls,
|
|
107
|
+
render: () => ({
|
|
108
|
+
components: { Upload },
|
|
109
|
+
setup: () => ({ variants }),
|
|
110
|
+
template: `
|
|
111
|
+
<div class="max-w-2xl space-y-6">
|
|
112
|
+
<Upload v-for="v in variants" :key="v" :variant="v" multiple />
|
|
113
|
+
</div>
|
|
114
|
+
`,
|
|
115
|
+
}),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const MaxCount: Story = {
|
|
119
|
+
parameters: noControls,
|
|
120
|
+
render: () => ({
|
|
121
|
+
components: { Upload },
|
|
122
|
+
setup: () => ({ variants }),
|
|
123
|
+
template: `
|
|
124
|
+
<div class="max-w-2xl space-y-6">
|
|
125
|
+
<Upload v-for="v in variants" :key="v" :variant="v" multiple :maxCount="3" />
|
|
126
|
+
</div>
|
|
127
|
+
`,
|
|
128
|
+
}),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const MaxSize: Story = {
|
|
132
|
+
parameters: noControls,
|
|
133
|
+
render: () => ({
|
|
134
|
+
components: { Upload },
|
|
135
|
+
setup () {
|
|
136
|
+
const errorMessage = ref('')
|
|
137
|
+
const onError = (e: unknown) => {
|
|
138
|
+
errorMessage.value = e instanceof Error ? e.message : String(e)
|
|
139
|
+
}
|
|
140
|
+
return { variants, errorMessage, onError }
|
|
141
|
+
},
|
|
142
|
+
template: `
|
|
143
|
+
<div class="max-w-2xl space-y-6">
|
|
144
|
+
<Upload
|
|
145
|
+
v-for="v in variants"
|
|
146
|
+
:key="v"
|
|
147
|
+
:variant="v"
|
|
148
|
+
multiple
|
|
149
|
+
:maxSize="50 * 1024"
|
|
150
|
+
@error="onError"
|
|
151
|
+
/>
|
|
152
|
+
<p v-if="errorMessage" class="text-sm text-danger">
|
|
153
|
+
{{ errorMessage }}
|
|
154
|
+
</p>
|
|
155
|
+
</div>
|
|
156
|
+
`,
|
|
157
|
+
}),
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const Directory: Story = {
|
|
161
|
+
parameters: noControls,
|
|
162
|
+
render: () => ({
|
|
163
|
+
components: { Upload },
|
|
164
|
+
setup: () => ({ variants }),
|
|
165
|
+
template: `
|
|
166
|
+
<div class="max-w-2xl space-y-6">
|
|
167
|
+
<Upload v-for="v in variants" :key="v" :variant="v" directory :maxCount="5" />
|
|
168
|
+
</div>
|
|
169
|
+
`,
|
|
170
|
+
}),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --- States ---
|
|
174
|
+
|
|
175
|
+
export const Disabled: Story = {
|
|
176
|
+
parameters: noControls,
|
|
177
|
+
render: () => ({
|
|
178
|
+
components: { Upload },
|
|
179
|
+
setup () {
|
|
180
|
+
const fileList = ref<UploadFile[]>([ ...sampleImages ])
|
|
181
|
+
return { variants, fileList }
|
|
182
|
+
},
|
|
183
|
+
template: `
|
|
184
|
+
<div class="max-w-2xl space-y-6">
|
|
185
|
+
<Upload
|
|
186
|
+
v-for="v in variants"
|
|
187
|
+
:key="v"
|
|
188
|
+
:variant="v"
|
|
189
|
+
disabled
|
|
190
|
+
multiple
|
|
191
|
+
:fileList="fileList"
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
`,
|
|
195
|
+
}),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const Readonly: Story = {
|
|
199
|
+
parameters: noControls,
|
|
200
|
+
render: () => ({
|
|
201
|
+
components: { Upload },
|
|
202
|
+
setup () {
|
|
203
|
+
const fileList = ref<UploadFile[]>([ ...sampleImages ])
|
|
204
|
+
return { variants, fileList }
|
|
205
|
+
},
|
|
206
|
+
template: `
|
|
207
|
+
<div class="max-w-2xl space-y-6">
|
|
208
|
+
<Upload
|
|
209
|
+
v-for="v in variants"
|
|
210
|
+
:key="v"
|
|
211
|
+
:variant="v"
|
|
212
|
+
readonly
|
|
213
|
+
multiple
|
|
214
|
+
:fileList="fileList"
|
|
215
|
+
/>
|
|
216
|
+
</div>
|
|
217
|
+
`,
|
|
218
|
+
}),
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export const ReadonlyEmpty: Story = {
|
|
222
|
+
parameters: noControls,
|
|
223
|
+
render: () => ({
|
|
224
|
+
components: { Upload },
|
|
225
|
+
setup: () => ({ variants }),
|
|
226
|
+
template: `
|
|
227
|
+
<div class="max-w-2xl space-y-6">
|
|
228
|
+
<Upload v-for="v in variants" :key="v" :variant="v" readonly />
|
|
229
|
+
</div>
|
|
230
|
+
`,
|
|
231
|
+
}),
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const Invalid: Story = {
|
|
235
|
+
parameters: noControls,
|
|
236
|
+
args: {
|
|
237
|
+
variant: 'drag',
|
|
238
|
+
invalid: true,
|
|
239
|
+
multiple: true,
|
|
240
|
+
fileList: [
|
|
241
|
+
{ uid: 'i-1', name: 'gitmoji (1).md', status: 'done' },
|
|
242
|
+
{ uid: 'i-2', name: 'cd973fbf55b88a9ebaec4f01821c552a.png', status: 'done' },
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// --- Custom behaviors ---
|
|
248
|
+
|
|
249
|
+
export const BeforeUpload: Story = {
|
|
250
|
+
parameters: {
|
|
251
|
+
...noControls,
|
|
252
|
+
docs: {
|
|
253
|
+
source: {
|
|
254
|
+
code: `
|
|
255
|
+
<template>
|
|
256
|
+
<Upload
|
|
257
|
+
v-model:fileList="fileList"
|
|
258
|
+
variant="drag"
|
|
259
|
+
multiple
|
|
260
|
+
:beforeUpload="beforeUpload"
|
|
261
|
+
/>
|
|
262
|
+
</template>
|
|
263
|
+
|
|
264
|
+
<script setup lang="ts">
|
|
265
|
+
const fileList = ref<UploadFile[]>([])
|
|
266
|
+
|
|
267
|
+
function beforeUpload (file: File) {
|
|
268
|
+
// Reject files larger than 1MB
|
|
269
|
+
if (file.size > 1024 * 1024) {
|
|
270
|
+
alert(\`\${file.name} is too large (>1MB)\`)
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
// Optionally return a Promise<File | Blob> to replace the file
|
|
274
|
+
return true
|
|
275
|
+
}
|
|
276
|
+
</script>
|
|
277
|
+
`.trim(),
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
render: () => ({
|
|
282
|
+
components: { Upload },
|
|
283
|
+
setup () {
|
|
284
|
+
const fileList = ref<UploadFile[]>([])
|
|
285
|
+
function beforeUpload (file: File) {
|
|
286
|
+
if (file.size > 1024 * 1024) {
|
|
287
|
+
alert(`${file.name} is too large (>1MB)`)
|
|
288
|
+
return false
|
|
289
|
+
}
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
return { fileList, beforeUpload }
|
|
293
|
+
},
|
|
294
|
+
template: `
|
|
295
|
+
<div class="max-w-2xl">
|
|
296
|
+
<Upload
|
|
297
|
+
v-model:fileList="fileList"
|
|
298
|
+
variant="drag"
|
|
299
|
+
multiple
|
|
300
|
+
:beforeUpload="beforeUpload"
|
|
301
|
+
/>
|
|
302
|
+
</div>
|
|
303
|
+
`,
|
|
304
|
+
}),
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export const CustomUpload: Story = {
|
|
308
|
+
parameters: {
|
|
309
|
+
...noControls,
|
|
310
|
+
docs: {
|
|
311
|
+
source: {
|
|
312
|
+
code: `
|
|
313
|
+
<template>
|
|
314
|
+
<Upload
|
|
315
|
+
v-model:fileList="fileList"
|
|
316
|
+
variant="box"
|
|
317
|
+
accept="image/*"
|
|
318
|
+
multiple
|
|
319
|
+
:maxCount="5"
|
|
320
|
+
:upload="upload"
|
|
321
|
+
/>
|
|
322
|
+
</template>
|
|
323
|
+
|
|
324
|
+
<script setup lang="ts">
|
|
325
|
+
const fileList = ref<UploadFile[]>([])
|
|
326
|
+
|
|
327
|
+
async function upload (files: (File | Blob)[]) {
|
|
328
|
+
// Simulate network latency
|
|
329
|
+
await new Promise(resolve => setTimeout(resolve, 1500))
|
|
330
|
+
// throw new Error('Upload failed') would mark items as error
|
|
331
|
+
}
|
|
332
|
+
</script>
|
|
333
|
+
`.trim(),
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
render: () => ({
|
|
338
|
+
components: { Upload },
|
|
339
|
+
setup () {
|
|
340
|
+
const fileList = ref<UploadFile[]>([])
|
|
341
|
+
async function upload () {
|
|
342
|
+
await new Promise<void>(resolve => setTimeout(resolve, 1500))
|
|
343
|
+
}
|
|
344
|
+
return { fileList, upload }
|
|
345
|
+
},
|
|
346
|
+
template: `
|
|
347
|
+
<div class="max-w-2xl">
|
|
348
|
+
<Upload
|
|
349
|
+
v-model:fileList="fileList"
|
|
350
|
+
variant="box"
|
|
351
|
+
accept="image/*"
|
|
352
|
+
multiple
|
|
353
|
+
:maxCount="5"
|
|
354
|
+
:upload="upload"
|
|
355
|
+
/>
|
|
356
|
+
</div>
|
|
357
|
+
`,
|
|
358
|
+
}),
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export const EventHandling: Story = {
|
|
362
|
+
parameters: noControls,
|
|
363
|
+
render: () => ({
|
|
364
|
+
components: { Upload, EventLog },
|
|
365
|
+
setup: () => ({ fileList: ref<UploadFile[]>([]) }),
|
|
366
|
+
template: `
|
|
367
|
+
<EventLog v-slot="{ record }">
|
|
368
|
+
<Upload
|
|
369
|
+
v-model:fileList="fileList"
|
|
370
|
+
variant="drag"
|
|
371
|
+
multiple
|
|
372
|
+
@update:fileList="(v) => record('update:fileList', v.map(f => f.name))"
|
|
373
|
+
@change="(v) => record('change', v.map(f => f.name))"
|
|
374
|
+
@remove="(f) => record('remove', f.name)"
|
|
375
|
+
@preview="(f) => record('preview', f.name)"
|
|
376
|
+
@error="(e) => record('error', String(e))"
|
|
377
|
+
/>
|
|
378
|
+
</EventLog>
|
|
379
|
+
`,
|
|
380
|
+
}),
|
|
381
|
+
}
|