@byyuurin/ui 0.2.0 → 0.4.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/README.md +5 -3
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -2
- package/dist/runtime/components/Accordion.vue +2 -2
- package/dist/runtime/components/Accordion.vue.d.ts +11 -7
- package/dist/runtime/components/Alert.vue +2 -1
- package/dist/runtime/components/Alert.vue.d.ts +4 -4
- package/dist/runtime/components/App.vue +2 -1
- package/dist/runtime/components/App.vue.d.ts +11 -7
- package/dist/runtime/components/Avatar.vue +2 -1
- package/dist/runtime/components/Avatar.vue.d.ts +4 -3
- package/dist/runtime/components/AvatarGroup.vue +2 -1
- package/dist/runtime/components/AvatarGroup.vue.d.ts +1 -1
- package/dist/runtime/components/Badge.vue +4 -3
- package/dist/runtime/components/Badge.vue.d.ts +2 -2
- package/dist/runtime/components/Breadcrumb.vue +2 -2
- package/dist/runtime/components/Breadcrumb.vue.d.ts +10 -6
- package/dist/runtime/components/Button.vue +8 -8
- package/dist/runtime/components/Button.vue.d.ts +1 -1
- package/dist/runtime/components/Calendar.vue +2 -1
- package/dist/runtime/components/Calendar.vue.d.ts +16 -12
- package/dist/runtime/components/Card.vue +2 -1
- package/dist/runtime/components/Card.vue.d.ts +1 -1
- package/dist/runtime/components/Carousel.vue +2 -1
- package/dist/runtime/components/Carousel.vue.d.ts +14 -10
- package/dist/runtime/components/Checkbox.vue +4 -2
- package/dist/runtime/components/Checkbox.vue.d.ts +4 -3
- package/dist/runtime/components/CheckboxGroup.vue +4 -3
- package/dist/runtime/components/CheckboxGroup.vue.d.ts +11 -7
- package/dist/runtime/components/Chip.vue +5 -1
- package/dist/runtime/components/Chip.vue.d.ts +2 -2
- package/dist/runtime/components/Collapsible.vue +2 -1
- package/dist/runtime/components/Collapsible.vue.d.ts +2 -2
- package/dist/runtime/components/Drawer.vue +2 -1
- package/dist/runtime/components/Drawer.vue.d.ts +6 -6
- package/dist/runtime/components/DropdownMenu.vue +3 -3
- package/dist/runtime/components/DropdownMenu.vue.d.ts +17 -9
- package/dist/runtime/components/DropdownMenuContent.vue +11 -12
- package/dist/runtime/components/DropdownMenuContent.vue.d.ts +11 -7
- package/dist/runtime/components/FieldGroup.vue +2 -1
- package/dist/runtime/components/FieldGroup.vue.d.ts +2 -2
- package/dist/runtime/components/FileUpload.vue +267 -0
- package/dist/runtime/components/FileUpload.vue.d.ts +178 -0
- package/dist/runtime/components/Form.vue +2 -1
- package/dist/runtime/components/Form.vue.d.ts +13 -8
- package/dist/runtime/components/FormField.vue +6 -4
- package/dist/runtime/components/FormField.vue.d.ts +7 -2
- package/dist/runtime/components/Icon.vue.d.ts +1 -1
- package/dist/runtime/components/Input.vue +4 -4
- package/dist/runtime/components/Input.vue.d.ts +16 -12
- package/dist/runtime/components/InputNumber.vue +3 -2
- package/dist/runtime/components/InputNumber.vue.d.ts +128 -124
- package/dist/runtime/components/InputTags.vue +2 -1
- package/dist/runtime/components/InputTags.vue.d.ts +16 -11
- package/dist/runtime/components/Kbd.vue +2 -1
- package/dist/runtime/components/Kbd.vue.d.ts +2 -2
- package/dist/runtime/components/Link.vue +5 -3
- package/dist/runtime/components/Link.vue.d.ts +16 -6
- package/dist/runtime/components/LinkBase.vue.d.ts +2 -2
- package/dist/runtime/components/Marquee.vue +2 -1
- package/dist/runtime/components/Marquee.vue.d.ts +3 -3
- package/dist/runtime/components/Modal.vue +3 -2
- package/dist/runtime/components/Modal.vue.d.ts +6 -6
- package/dist/runtime/components/NavigationMenu.vue +2 -2
- package/dist/runtime/components/NavigationMenu.vue.d.ts +11 -7
- package/dist/runtime/components/Pagination.vue +2 -1
- package/dist/runtime/components/Pagination.vue.d.ts +4 -4
- package/dist/runtime/components/PinInput.vue +4 -4
- package/dist/runtime/components/PinInput.vue.d.ts +14 -10
- package/dist/runtime/components/Popover.vue +1 -1
- package/dist/runtime/components/Popover.vue.d.ts +11 -7
- package/dist/runtime/components/Progress.vue +2 -1
- package/dist/runtime/components/Progress.vue.d.ts +2 -2
- package/dist/runtime/components/RadioGroup.vue +3 -3
- package/dist/runtime/components/RadioGroup.vue.d.ts +11 -7
- package/dist/runtime/components/ScrollArea.vue +2 -1
- package/dist/runtime/components/ScrollArea.vue.d.ts +2 -2
- package/dist/runtime/components/Select.vue +282 -131
- package/dist/runtime/components/Select.vue.d.ts +103 -123
- package/dist/runtime/components/Separator.vue +2 -1
- package/dist/runtime/components/Separator.vue.d.ts +2 -2
- package/dist/runtime/components/Skeleton.vue +2 -1
- package/dist/runtime/components/Skeleton.vue.d.ts +2 -2
- package/dist/runtime/components/Slider.vue +2 -1
- package/dist/runtime/components/Slider.vue.d.ts +11 -7
- package/dist/runtime/components/Stepper.vue +117 -0
- package/dist/runtime/components/Stepper.vue.d.ts +83 -0
- package/dist/runtime/components/Switch.vue +3 -4
- package/dist/runtime/components/Switch.vue.d.ts +4 -3
- package/dist/runtime/components/Table.vue +2 -1
- package/dist/runtime/components/Table.vue.d.ts +13 -8
- package/dist/runtime/components/Tabs.vue +2 -2
- package/dist/runtime/components/Tabs.vue.d.ts +12 -8
- package/dist/runtime/components/Textarea.vue +3 -3
- package/dist/runtime/components/Textarea.vue.d.ts +16 -11
- package/dist/runtime/components/Timeline.vue +2 -1
- package/dist/runtime/components/Timeline.vue.d.ts +11 -7
- package/dist/runtime/components/Toast.vue +2 -1
- package/dist/runtime/components/Toast.vue.d.ts +5 -5
- package/dist/runtime/components/ToastProvider.vue +2 -2
- package/dist/runtime/components/ToastProvider.vue.d.ts +3 -3
- package/dist/runtime/components/Tooltip.vue +4 -4
- package/dist/runtime/components/Tooltip.vue.d.ts +2 -2
- package/dist/runtime/components/Tree.vue +241 -0
- package/dist/runtime/components/Tree.vue.d.ts +121 -0
- package/dist/runtime/composables/defineShortcuts.d.ts +1 -0
- package/dist/runtime/composables/defineShortcuts.js +44 -8
- package/dist/runtime/composables/useFileUpload.d.ts +19 -0
- package/dist/runtime/composables/useFileUpload.js +79 -0
- package/dist/runtime/composables/useLocale.d.ts +18 -0
- package/dist/runtime/locale/en.d.ts +9 -0
- package/dist/runtime/locale/en.js +9 -0
- package/dist/runtime/locale/zh_tw.d.ts +9 -0
- package/dist/runtime/locale/zh_tw.js +9 -0
- package/dist/runtime/types/html.d.ts +8 -0
- package/dist/runtime/types/html.js +0 -0
- package/dist/runtime/types/index.d.ts +3 -0
- package/dist/runtime/types/index.js +3 -0
- package/dist/runtime/types/input.d.ts +5 -5
- package/dist/runtime/types/locale.d.ts +9 -0
- package/dist/runtime/types/utils.d.ts +4 -4
- package/dist/runtime/utils/index.d.ts +3 -3
- package/dist/runtime/utils/link.d.ts +2 -1
- package/dist/runtime/utils/link.js +40 -29
- package/dist/runtime/vue/components/Icon.vue.d.ts +1 -1
- package/dist/runtime/vue/components/Link.vue +7 -12
- package/dist/runtime/vue/components/Link.vue.d.ts +11 -40
- package/dist/setup.d.mts +1 -1
- package/dist/shared/{ui.DpbffTXs.d.mts → ui.CGCKYv7g.d.mts} +6 -2
- package/dist/shared/{ui.CzIlLITK.mjs → ui.DYMXCXO6.mjs} +9 -5
- package/dist/shared/{ui.DLOxhmP0.mjs → ui.DcEKQd0n.mjs} +490 -20
- package/dist/unocss.mjs +1 -1
- package/dist/unplugin.d.mts +1 -1
- package/dist/unplugin.mjs +2 -2
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +2 -2
- package/package.json +31 -31
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import theme from "#build/ui/file-upload";
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script setup>
|
|
6
|
+
import { createReusableTemplate } from "@vueuse/core";
|
|
7
|
+
import { Primitive, VisuallyHidden } from "reka-ui";
|
|
8
|
+
import { computed, toRef, watch } from "vue";
|
|
9
|
+
import { useAppConfig } from "#imports";
|
|
10
|
+
import { useFileUpload } from "../composables/useFileUpload";
|
|
11
|
+
import { useFormField } from "../composables/useFormField";
|
|
12
|
+
import { useLocale } from "../composables/useLocale";
|
|
13
|
+
import { pick } from "../utils";
|
|
14
|
+
import { cv, merge } from "../utils/style";
|
|
15
|
+
import Avatar from "./Avatar.vue";
|
|
16
|
+
import Button from "./Button.vue";
|
|
17
|
+
import Icon from "./Icon.vue";
|
|
18
|
+
defineOptions({ inheritAttrs: false });
|
|
19
|
+
const props = defineProps({
|
|
20
|
+
as: { type: null, required: false },
|
|
21
|
+
id: { type: String, required: false },
|
|
22
|
+
name: { type: String, required: false },
|
|
23
|
+
modelValue: { type: null, required: false },
|
|
24
|
+
icon: { type: [String, Object], required: false },
|
|
25
|
+
label: { type: String, required: false },
|
|
26
|
+
description: { type: String, required: false },
|
|
27
|
+
color: { type: null, required: false },
|
|
28
|
+
variant: { type: null, required: false },
|
|
29
|
+
size: { type: null, required: false },
|
|
30
|
+
layout: { type: null, required: false, default: "grid" },
|
|
31
|
+
position: { type: null, required: false, default: "outside" },
|
|
32
|
+
highlight: { type: Boolean, required: false },
|
|
33
|
+
accept: { type: String, required: false, default: "*" },
|
|
34
|
+
multiple: { type: Boolean, required: false, default: false },
|
|
35
|
+
reset: { type: Boolean, required: false, default: false },
|
|
36
|
+
dropzone: { type: Boolean, required: false, default: true },
|
|
37
|
+
interactive: { type: Boolean, required: false, default: true },
|
|
38
|
+
required: { type: Boolean, required: false },
|
|
39
|
+
disabled: { type: Boolean, required: false },
|
|
40
|
+
fileIcon: { type: [String, Object], required: false },
|
|
41
|
+
fileDelete: { type: [Boolean, Object], required: false, default: true },
|
|
42
|
+
fileDeleteIcon: { type: [String, Object], required: false },
|
|
43
|
+
preview: { type: Boolean, required: false, default: true },
|
|
44
|
+
ui: { type: null, required: false },
|
|
45
|
+
class: { type: [Object, String, Number, Boolean, null, Array], required: false, skipCheck: true }
|
|
46
|
+
});
|
|
47
|
+
const emit = defineEmits(["change"]);
|
|
48
|
+
const slots = defineSlots();
|
|
49
|
+
const modelValue = defineModel({ type: null });
|
|
50
|
+
const { t } = useLocale();
|
|
51
|
+
const [DefineFilesTemplate, ReuseFilesTemplate] = createReusableTemplate();
|
|
52
|
+
const { isDragging, open, inputRef, dropzoneRef } = useFileUpload({
|
|
53
|
+
accept: toRef(() => props.accept),
|
|
54
|
+
reset: props.reset,
|
|
55
|
+
multiple: toRef(() => props.multiple),
|
|
56
|
+
dropzone: toRef(() => props.dropzone),
|
|
57
|
+
onUpdate
|
|
58
|
+
});
|
|
59
|
+
const { id, name, disabled, ariaAttrs, emitFormInput, emitFormChange } = useFormField(props);
|
|
60
|
+
const variant = computed(() => props.multiple ? "area" : props.variant);
|
|
61
|
+
const layout = computed(() => props.variant === "button" && !props.multiple ? "grid" : props.layout);
|
|
62
|
+
const position = computed(() => {
|
|
63
|
+
if (layout.value === "grid" && props.multiple)
|
|
64
|
+
return "inside";
|
|
65
|
+
if (variant.value === "button")
|
|
66
|
+
return "outside";
|
|
67
|
+
return props.position;
|
|
68
|
+
});
|
|
69
|
+
const appConfig = useAppConfig();
|
|
70
|
+
const ui = computed(() => {
|
|
71
|
+
const styler = cv(merge(theme, appConfig.ui.fileUpload));
|
|
72
|
+
return styler({
|
|
73
|
+
...pick(props, ["dropzone", "interactive", "color", "size", "multiple", "highlight"]),
|
|
74
|
+
variant: variant.value,
|
|
75
|
+
layout: layout.value,
|
|
76
|
+
position: position.value,
|
|
77
|
+
disabled: disabled.value
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
function createObjectUrl(file) {
|
|
81
|
+
return URL.createObjectURL(file);
|
|
82
|
+
}
|
|
83
|
+
function formatFileSize(bytes) {
|
|
84
|
+
if (bytes === 0)
|
|
85
|
+
return "0B";
|
|
86
|
+
const k = 1024;
|
|
87
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
88
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
89
|
+
const size = bytes / k ** i;
|
|
90
|
+
const formattedSize = i === 0 ? size.toString() : size.toFixed(0);
|
|
91
|
+
return `${formattedSize}${sizes[i]}`;
|
|
92
|
+
}
|
|
93
|
+
function onUpdate(files, reset = false) {
|
|
94
|
+
if (props.multiple) {
|
|
95
|
+
if (reset) {
|
|
96
|
+
modelValue.value = files;
|
|
97
|
+
} else {
|
|
98
|
+
const existingFiles = modelValue.value || [];
|
|
99
|
+
modelValue.value = [...existingFiles, ...files || []];
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
modelValue.value = files?.[0];
|
|
103
|
+
}
|
|
104
|
+
const event = new Event("change", { target: { value: modelValue.value } });
|
|
105
|
+
emit("change", event);
|
|
106
|
+
emitFormChange();
|
|
107
|
+
emitFormInput();
|
|
108
|
+
}
|
|
109
|
+
function removeFile(index) {
|
|
110
|
+
if (!modelValue.value)
|
|
111
|
+
return;
|
|
112
|
+
if (!props.multiple || index === void 0) {
|
|
113
|
+
onUpdate([], true);
|
|
114
|
+
dropzoneRef.value?.focus();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const files = [...modelValue.value];
|
|
118
|
+
files.splice(index, 1);
|
|
119
|
+
onUpdate(files, true);
|
|
120
|
+
dropzoneRef.value?.focus();
|
|
121
|
+
}
|
|
122
|
+
watch(modelValue, (newValue) => {
|
|
123
|
+
const hasModelReset = props.multiple ? !newValue?.length : !newValue;
|
|
124
|
+
if (hasModelReset && inputRef.value?.$el)
|
|
125
|
+
inputRef.value.$el.value = "";
|
|
126
|
+
});
|
|
127
|
+
defineExpose({
|
|
128
|
+
inputRef: toRef(() => inputRef.value?.$el),
|
|
129
|
+
dropzoneRef
|
|
130
|
+
});
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<template>
|
|
134
|
+
<DefineFilesTemplate>
|
|
135
|
+
<template v-if="props.preview && modelValue && (Array.isArray(modelValue) ? modelValue.length : true)">
|
|
136
|
+
<slot name="files-top" :files="modelValue" :open="open" :remove-file="removeFile"></slot>
|
|
137
|
+
|
|
138
|
+
<div :class="ui.files({ class: props.ui?.files })" data-part="files">
|
|
139
|
+
<slot name="files" :files="modelValue">
|
|
140
|
+
<div v-for="(file, index) in Array.isArray(modelValue) ? modelValue : [modelValue]" :key="index" :class="ui.file({ class: props.ui?.file })" data-part="file">
|
|
141
|
+
<slot name="file" :file="file" :index="index">
|
|
142
|
+
<slot name="file-leading" :file="file" :index="index" :ui="ui">
|
|
143
|
+
<Avatar
|
|
144
|
+
:as="{ img: 'img' }"
|
|
145
|
+
:src="createObjectUrl(file)"
|
|
146
|
+
:icon="props.fileIcon || appConfig.ui.icons.file"
|
|
147
|
+
:size="props.size"
|
|
148
|
+
:class="ui.fileLeadingAvatar({ class: props.ui?.fileLeadingAvatar })"
|
|
149
|
+
data-part="fileLeadingAvatar"
|
|
150
|
+
/>
|
|
151
|
+
</slot>
|
|
152
|
+
|
|
153
|
+
<div :class="ui.fileWrapper({ class: props.ui?.fileWrapper })" data-part="fileWrapper">
|
|
154
|
+
<span :class="ui.fileName({ class: props.ui?.fileName })" data-part="fileName">
|
|
155
|
+
<slot name="file-name" :file="file" :index="index">
|
|
156
|
+
{{ file.name }}
|
|
157
|
+
</slot>
|
|
158
|
+
</span>
|
|
159
|
+
|
|
160
|
+
<span :class="ui.fileSize({ class: props.ui?.fileSize })" data-part="fileSize">
|
|
161
|
+
<slot name="file-size" :file="file" :index="index">
|
|
162
|
+
{{ formatFileSize(file.size) }}
|
|
163
|
+
</slot>
|
|
164
|
+
</span>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<slot name="file-trailing" :file="file" :index="index" :ui="ui">
|
|
168
|
+
<Button
|
|
169
|
+
v-if="props.fileDelete"
|
|
170
|
+
color="neutral"
|
|
171
|
+
v-bind="{
|
|
172
|
+
...layout === 'grid' ? { variant: 'solid', size: 'xs' } : { variant: 'link', size: props.size },
|
|
173
|
+
...typeof props.fileDelete === 'object' ? props.fileDelete : void 0
|
|
174
|
+
}"
|
|
175
|
+
:aria-label="t('fileUpload.removeFile', { filename: file.name })"
|
|
176
|
+
:trailing-icon="props.fileDeleteIcon || appConfig.ui.icons.close"
|
|
177
|
+
:class="ui.fileTrailingButton({ class: props.ui?.fileTrailingButton })"
|
|
178
|
+
data-part="fileTrailingButton"
|
|
179
|
+
@click.stop.prevent="removeFile(index)"
|
|
180
|
+
/>
|
|
181
|
+
</slot>
|
|
182
|
+
</slot>
|
|
183
|
+
</div>
|
|
184
|
+
</slot>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<slot name="files-bottom" :files="modelValue" :open="open" :remove-file="removeFile"></slot>
|
|
188
|
+
</template>
|
|
189
|
+
</DefineFilesTemplate>
|
|
190
|
+
|
|
191
|
+
<Primitive :as="props.as" :class="ui.root({ class: [props.ui?.root, props.class] })" data-part="root">
|
|
192
|
+
<slot :open="open" :remove-file="removeFile" :ui="ui">
|
|
193
|
+
<component
|
|
194
|
+
:is="variant === 'button' ? 'button' : 'div'"
|
|
195
|
+
ref="dropzoneRef"
|
|
196
|
+
:type="variant === 'button' ? 'button' : void 0"
|
|
197
|
+
:role="variant === 'button' ? void 0 : 'button'"
|
|
198
|
+
:tabindex="props.interactive && disabled ? 0 : -1"
|
|
199
|
+
:data-dragging="isDragging"
|
|
200
|
+
:class="ui.base({ class: props.ui?.base })"
|
|
201
|
+
data-part="base"
|
|
202
|
+
@click="props.interactive && !disabled && open()"
|
|
203
|
+
@keydown.space.prevent
|
|
204
|
+
@keyup.enter.space="props.interactive && !disabled && open()"
|
|
205
|
+
>
|
|
206
|
+
<ReuseFilesTemplate v-if="position === 'inside'" />
|
|
207
|
+
|
|
208
|
+
<div
|
|
209
|
+
v-if="props.preview && position === 'inside' ? props.multiple ? !modelValue?.length : !modelValue : true"
|
|
210
|
+
:class="ui.wrapper({ class: props.ui?.wrapper })"
|
|
211
|
+
data-part="wrapper"
|
|
212
|
+
>
|
|
213
|
+
<slot name="leading" :ui="ui">
|
|
214
|
+
<Icon
|
|
215
|
+
v-if="variant === 'button'"
|
|
216
|
+
:name="props.icon || appConfig.ui.icons.upload"
|
|
217
|
+
:class="ui.icon({ class: props.ui?.icon })"
|
|
218
|
+
data-part="icon"
|
|
219
|
+
/>
|
|
220
|
+
<Avatar
|
|
221
|
+
v-else
|
|
222
|
+
:icon="props.icon || appConfig.ui.icons.upload"
|
|
223
|
+
:size="props.size"
|
|
224
|
+
:ui="{ icon: props.ui?.icon }"
|
|
225
|
+
:class="ui.avatar({ class: props.ui?.avatar })"
|
|
226
|
+
data-part="avatar"
|
|
227
|
+
/>
|
|
228
|
+
</slot>
|
|
229
|
+
|
|
230
|
+
<template v-if="variant !== 'button'">
|
|
231
|
+
<div v-if="props.label || slots.label" :class="ui.label({ class: props.ui?.label })" data-part="label">
|
|
232
|
+
<slot name="label">
|
|
233
|
+
{{ props.label }}
|
|
234
|
+
</slot>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div v-if="props.description || slots.description" :class="ui.description({ class: props.ui?.description })">
|
|
238
|
+
<slot name="description">
|
|
239
|
+
{{ props.description }}
|
|
240
|
+
</slot>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<div v-if="slots.actions">
|
|
244
|
+
<slot name="actions" :files="modelValue || null" :open="open" :remove-file="removeFile"></slot>
|
|
245
|
+
</div>
|
|
246
|
+
</template>
|
|
247
|
+
</div>
|
|
248
|
+
</component>
|
|
249
|
+
|
|
250
|
+
<ReuseFilesTemplate v-if="position === 'outside'" />
|
|
251
|
+
</slot>
|
|
252
|
+
|
|
253
|
+
<VisuallyHidden
|
|
254
|
+
:id="id"
|
|
255
|
+
ref="inputRef"
|
|
256
|
+
as="input"
|
|
257
|
+
type="file"
|
|
258
|
+
feature="fully-hidden"
|
|
259
|
+
:name="name"
|
|
260
|
+
:accept="props.accept"
|
|
261
|
+
:multiple="props.multiple"
|
|
262
|
+
:required="props.required"
|
|
263
|
+
:disabled="disabled"
|
|
264
|
+
v-bind="{ ...$attrs, ...ariaAttrs }"
|
|
265
|
+
/>
|
|
266
|
+
</Primitive>
|
|
267
|
+
</template>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { VariantProps } from '@byyuurin/ui-kit';
|
|
2
|
+
import type { UseFileDialogReturn } from '@vueuse/core';
|
|
3
|
+
import type { PrimitiveProps } from 'reka-ui';
|
|
4
|
+
import theme from '#build/ui/file-upload';
|
|
5
|
+
import type { ButtonProps, ComponentBaseProps, ComponentStyler, ComponentUIProps, IconProps, LinkPropsKeys } from '../types';
|
|
6
|
+
import type { InputHTMLAttributes } from '../types/html';
|
|
7
|
+
import type { MaybeNull, StaticSlot } from '../types/utils';
|
|
8
|
+
type ThemeVariants = VariantProps<typeof theme>;
|
|
9
|
+
export interface FileUploadProps<M extends boolean = false> extends ComponentBaseProps, /** @vue-ignore */ Pick<InputHTMLAttributes, 'form' | 'formaction' | 'formenctype' | 'formmethod' | 'formnovalidate' | 'formtarget'> {
|
|
10
|
+
/**
|
|
11
|
+
* The element or component this component should render as.
|
|
12
|
+
* @default "div"
|
|
13
|
+
*/
|
|
14
|
+
as?: PrimitiveProps['as'];
|
|
15
|
+
id?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
modelValue?: MaybeNull<M extends true ? File[] : File>;
|
|
18
|
+
/**
|
|
19
|
+
* The icon to display.
|
|
20
|
+
* @default app.icons.upload
|
|
21
|
+
*/
|
|
22
|
+
icon?: IconProps['name'];
|
|
23
|
+
label?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
/** @default "primary" */
|
|
26
|
+
color?: ThemeVariants['color'];
|
|
27
|
+
/**
|
|
28
|
+
* The `button` variant is only available when `multiple` is `false`.
|
|
29
|
+
* @default "area"
|
|
30
|
+
*/
|
|
31
|
+
variant?: ThemeVariants['variant'];
|
|
32
|
+
/** @default "md" */
|
|
33
|
+
size?: ThemeVariants['size'];
|
|
34
|
+
/**
|
|
35
|
+
* The layout of how files are displayed.
|
|
36
|
+
* Only works when `variant` is `area`.
|
|
37
|
+
* @default "list"
|
|
38
|
+
*/
|
|
39
|
+
layout?: ThemeVariants['layout'];
|
|
40
|
+
/**
|
|
41
|
+
* The position of the files.
|
|
42
|
+
* Only works when `variant` is `area` and when `layout` is `list`.
|
|
43
|
+
* @default "outside"
|
|
44
|
+
*/
|
|
45
|
+
position?: ThemeVariants['position'];
|
|
46
|
+
/** Highlight the ring color like a focus state. */
|
|
47
|
+
highlight?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Specifies the allowed file types for the input. Provide a comma-separated list of MIME types or file extensions (e.g., "image/png,application/pdf,.jpg").
|
|
50
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept
|
|
51
|
+
* @default "*"
|
|
52
|
+
*/
|
|
53
|
+
accept?: string;
|
|
54
|
+
multiple?: M & boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Reset the file input when the dialog is opened.
|
|
57
|
+
* @default false
|
|
58
|
+
*/
|
|
59
|
+
reset?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Create a zone that allows the user to drop files onto it.
|
|
62
|
+
* @default true
|
|
63
|
+
*/
|
|
64
|
+
dropzone?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Make the dropzone interactive when the user is clicking on it.
|
|
67
|
+
* @default true
|
|
68
|
+
*/
|
|
69
|
+
interactive?: boolean;
|
|
70
|
+
required?: boolean;
|
|
71
|
+
disabled?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* The icon to display for the file.
|
|
74
|
+
* @default app.icons.file
|
|
75
|
+
*/
|
|
76
|
+
fileIcon?: IconProps['name'];
|
|
77
|
+
/**
|
|
78
|
+
* Configure the delete button for the file.
|
|
79
|
+
*
|
|
80
|
+
* When `layout` is `grid`, the default is `{ color: 'neutral', variant: 'solid', size: 'xs' }`
|
|
81
|
+
*
|
|
82
|
+
* When `layout` is `list`, the default is `{ color: 'neutral', variant: 'link' }`
|
|
83
|
+
*/
|
|
84
|
+
fileDelete?: boolean | Omit<ButtonProps, LinkPropsKeys>;
|
|
85
|
+
/**
|
|
86
|
+
* The icon displayed to delete a file.
|
|
87
|
+
* @default app.icons.close
|
|
88
|
+
*/
|
|
89
|
+
fileDeleteIcon?: IconProps['name'];
|
|
90
|
+
/**
|
|
91
|
+
* Show the file preview/list after upload.
|
|
92
|
+
* @default true
|
|
93
|
+
*/
|
|
94
|
+
preview?: boolean;
|
|
95
|
+
ui?: ComponentUIProps<typeof theme>;
|
|
96
|
+
}
|
|
97
|
+
export interface FileUploadEmits {
|
|
98
|
+
change: [event: Event];
|
|
99
|
+
}
|
|
100
|
+
type FileUploadFiles<M> = (M extends true ? File[] : File) | null;
|
|
101
|
+
export interface FileUploadSlots<M extends boolean = false> {
|
|
102
|
+
'default': StaticSlot<{
|
|
103
|
+
open: UseFileDialogReturn['open'];
|
|
104
|
+
removeFile: (index?: number) => void;
|
|
105
|
+
ui: ComponentStyler<typeof theme>;
|
|
106
|
+
}>;
|
|
107
|
+
'leading': StaticSlot<{
|
|
108
|
+
ui: ComponentStyler<typeof theme>;
|
|
109
|
+
}>;
|
|
110
|
+
'label': StaticSlot;
|
|
111
|
+
'description': StaticSlot;
|
|
112
|
+
'actions': StaticSlot<{
|
|
113
|
+
files: FileUploadFiles<M>;
|
|
114
|
+
open: UseFileDialogReturn['open'];
|
|
115
|
+
removeFile: (index?: number) => void;
|
|
116
|
+
}>;
|
|
117
|
+
'files': StaticSlot<{
|
|
118
|
+
files: FileUploadFiles<M>;
|
|
119
|
+
}>;
|
|
120
|
+
'files-top': StaticSlot<{
|
|
121
|
+
files: FileUploadFiles<M>;
|
|
122
|
+
open: UseFileDialogReturn['open'];
|
|
123
|
+
removeFile: (index?: number) => void;
|
|
124
|
+
}>;
|
|
125
|
+
'files-bottom': StaticSlot<{
|
|
126
|
+
files: FileUploadFiles<M>;
|
|
127
|
+
open: UseFileDialogReturn['open'];
|
|
128
|
+
removeFile: (index?: number) => void;
|
|
129
|
+
}>;
|
|
130
|
+
'file': StaticSlot<{
|
|
131
|
+
file: File;
|
|
132
|
+
index: number;
|
|
133
|
+
}>;
|
|
134
|
+
'file-leading': StaticSlot<{
|
|
135
|
+
file: File;
|
|
136
|
+
index: number;
|
|
137
|
+
ui: ComponentStyler<typeof theme>;
|
|
138
|
+
}>;
|
|
139
|
+
'file-name': StaticSlot<{
|
|
140
|
+
file: File;
|
|
141
|
+
index: number;
|
|
142
|
+
}>;
|
|
143
|
+
'file-size': StaticSlot<{
|
|
144
|
+
file: File;
|
|
145
|
+
index: number;
|
|
146
|
+
}>;
|
|
147
|
+
'file-trailing': StaticSlot<{
|
|
148
|
+
file: File;
|
|
149
|
+
index: number;
|
|
150
|
+
ui: ComponentStyler<typeof theme>;
|
|
151
|
+
}>;
|
|
152
|
+
}
|
|
153
|
+
declare const _default: typeof __VLS_export;
|
|
154
|
+
export default _default;
|
|
155
|
+
declare const __VLS_export: <M extends boolean = false>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
156
|
+
props: import("vue").PublicProps & __VLS_PrettifyLocal<(FileUploadProps<M> & {
|
|
157
|
+
modelValue?: MaybeNull<(M extends true ? File[] : File)>;
|
|
158
|
+
}) & {
|
|
159
|
+
onChange?: ((event: Event) => any) | undefined;
|
|
160
|
+
"onUpdate:modelValue"?: ((value: MaybeNull<M extends true ? File[] : File> | undefined) => any) | undefined;
|
|
161
|
+
}> & (typeof globalThis extends {
|
|
162
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
163
|
+
} ? P : {});
|
|
164
|
+
expose: (exposed: import("vue").ShallowUnwrapRef<{
|
|
165
|
+
inputRef: Readonly<import("vue").Ref<HTMLInputElement, HTMLInputElement>>;
|
|
166
|
+
dropzoneRef: import("vue").ShallowRef<HTMLDivElement | undefined, HTMLDivElement | undefined>;
|
|
167
|
+
}>) => void;
|
|
168
|
+
attrs: any;
|
|
169
|
+
slots: FileUploadSlots<M>;
|
|
170
|
+
emit: ((evt: "change", event: Event) => void) & ((evt: "update:modelValue", value: MaybeNull<M extends true ? File[] : File> | undefined) => void);
|
|
171
|
+
}>) => import("vue").VNode & {
|
|
172
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
173
|
+
};
|
|
174
|
+
type __VLS_PrettifyLocal<T> = (T extends any ? {
|
|
175
|
+
[K in keyof T]: T[K];
|
|
176
|
+
} : {
|
|
177
|
+
[K in keyof T as K]: T[K];
|
|
178
|
+
}) & {};
|
|
@@ -8,6 +8,7 @@ import { computed, nextTick, onMounted, onUnmounted, reactive, readonly, ref, sh
|
|
|
8
8
|
import { useAppConfig } from "#imports";
|
|
9
9
|
import { injectFormBus, injectFormState, provideFormBus, provideFormErrors, provideFormInputs, provideFormLoading, provideFormOptions, provideFormState } from "../composables/useFormField";
|
|
10
10
|
import { FormValidationException } from "../types/form";
|
|
11
|
+
import { pick } from "../utils";
|
|
11
12
|
import { getAtPath, setAtPath, validateSchema } from "../utils/form";
|
|
12
13
|
import { cv, merge } from "../utils/style";
|
|
13
14
|
const props = defineProps({
|
|
@@ -264,7 +265,7 @@ defineExpose(api);
|
|
|
264
265
|
const appConfig = useAppConfig();
|
|
265
266
|
const ui = computed(() => {
|
|
266
267
|
const styler = cv(merge(theme, appConfig.ui.form));
|
|
267
|
-
return styler(props);
|
|
268
|
+
return styler(pick(props, []));
|
|
268
269
|
});
|
|
269
270
|
</script>
|
|
270
271
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ComponentBaseProps, FormData, FormError, FormErrorEvent, FormErrorWithId, FormInputEvents, FormSchema, FormSubmitEvent, InferInput } from '../types';
|
|
2
|
+
import type { FormHTMLAttributes } from '../types/html';
|
|
2
3
|
import type { StaticSlot } from '../types/utils';
|
|
3
|
-
export interface FormProps<S extends FormSchema, T extends boolean = true, N extends boolean = false> extends ComponentBaseProps {
|
|
4
|
+
export interface FormProps<S extends FormSchema, T extends boolean = true, N extends boolean = false> extends ComponentBaseProps, /** @vue-ignore */ Omit<FormHTMLAttributes, 'name'> {
|
|
4
5
|
id?: string | number;
|
|
5
6
|
/** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
|
|
6
7
|
schema?: S;
|
|
@@ -59,11 +60,15 @@ export interface FormSlots {
|
|
|
59
60
|
loading: boolean;
|
|
60
61
|
}>;
|
|
61
62
|
}
|
|
62
|
-
declare const
|
|
63
|
-
|
|
63
|
+
declare const _default: typeof __VLS_export;
|
|
64
|
+
export default _default;
|
|
65
|
+
declare const __VLS_export: <S extends FormSchema, T extends boolean = true, N extends boolean = false>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
66
|
+
props: import("vue").PublicProps & __VLS_PrettifyLocal<FormProps<S, T, N> & {
|
|
64
67
|
onError?: ((event: FormErrorEvent) => any) | undefined;
|
|
65
68
|
onSubmit?: ((event: FormSubmitEvent<FormData<S, T>>) => any) | undefined;
|
|
66
|
-
}> &
|
|
69
|
+
}> & (typeof globalThis extends {
|
|
70
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
71
|
+
} ? P : {});
|
|
67
72
|
expose: (exposed: import("vue").ShallowUnwrapRef<{
|
|
68
73
|
validate: any;
|
|
69
74
|
errors: import("vue").Ref<{
|
|
@@ -96,8 +101,8 @@ declare const __VLS_export: <S extends FormSchema, T extends boolean = true, N e
|
|
|
96
101
|
}>) => import("vue").VNode & {
|
|
97
102
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
98
103
|
};
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
type __VLS_PrettifyLocal<T> = (T extends any ? {
|
|
105
|
+
[K in keyof T]: T[K];
|
|
106
|
+
} : {
|
|
102
107
|
[K in keyof T as K]: T[K];
|
|
103
|
-
} & {};
|
|
108
|
+
}) & {};
|
|
@@ -7,6 +7,7 @@ import { Label, Primitive } from "reka-ui";
|
|
|
7
7
|
import { computed, ref, useId, watch } from "vue";
|
|
8
8
|
import { useAppConfig } from "#imports";
|
|
9
9
|
import { injectFormErrors, injectFormInputs, provideFormField, provideFormInputId } from "../composables/useFormField";
|
|
10
|
+
import { pick } from "../utils";
|
|
10
11
|
import { cv, merge } from "../utils/style";
|
|
11
12
|
const props = defineProps({
|
|
12
13
|
as: { type: null, required: false },
|
|
@@ -15,8 +16,9 @@ const props = defineProps({
|
|
|
15
16
|
label: { type: String, required: false },
|
|
16
17
|
description: { type: String, required: false },
|
|
17
18
|
help: { type: String, required: false },
|
|
18
|
-
error: { type: [String, Boolean], required: false },
|
|
19
|
+
error: { type: [String, Boolean], required: false, default: void 0 },
|
|
19
20
|
hint: { type: String, required: false },
|
|
21
|
+
orientation: { type: null, required: false },
|
|
20
22
|
size: { type: null, required: false },
|
|
21
23
|
required: { type: Boolean, required: false },
|
|
22
24
|
eagerValidation: { type: Boolean, required: false },
|
|
@@ -63,12 +65,12 @@ provideFormField(computed(() => ({
|
|
|
63
65
|
const appConfig = useAppConfig();
|
|
64
66
|
const ui = computed(() => {
|
|
65
67
|
const styler = cv(merge(theme, appConfig.ui.formField));
|
|
66
|
-
return styler(props);
|
|
68
|
+
return styler(pick(props, ["size", "required", "orientation"]));
|
|
67
69
|
});
|
|
68
70
|
</script>
|
|
69
71
|
|
|
70
72
|
<template>
|
|
71
|
-
<Primitive :as="props.as" :class="ui.root({ class: [props.ui?.root, props.class] })" data-part="root">
|
|
73
|
+
<Primitive :as="props.as" :class="ui.root({ class: [props.ui?.root, props.class] })" :data-orientation="props.orientation" data-part="root">
|
|
72
74
|
<div :class="ui.wrapper({ class: props.ui?.wrapper })" data-part="wrapper">
|
|
73
75
|
<div v-if="props.label || !!slots.label" :class="ui.labelWrapper({ class: props.ui?.labelWrapper })" data-part="labelWrapper">
|
|
74
76
|
<Label :for="id" :class="ui.label({ class: props.ui?.label })" data-part="label">
|
|
@@ -93,7 +95,7 @@ const ui = computed(() => {
|
|
|
93
95
|
<div :class="(props.label || !!slots.label || props.description || !!slots.description) && ui.container({ class: props.ui?.container })" data-part="container">
|
|
94
96
|
<slot :error="error"></slot>
|
|
95
97
|
|
|
96
|
-
<div v-if="typeof error === 'string' && error || !!slots.error" :id="`${ariaId}-error`" :class="ui.error({ class: props.ui?.error })" data-part="error">
|
|
98
|
+
<div v-if="props.error !== false && (typeof error === 'string' && error || !!slots.error)" :id="`${ariaId}-error`" :class="ui.error({ class: props.ui?.error })" data-part="error">
|
|
97
99
|
<slot name="error" :error="error">
|
|
98
100
|
{{ error }}
|
|
99
101
|
</slot>
|
|
@@ -20,8 +20,11 @@ export interface FormFieldProps extends ComponentBaseProps {
|
|
|
20
20
|
error?: string | boolean;
|
|
21
21
|
hint?: string;
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* The orientation of the form field.
|
|
24
|
+
* @default "vertical"
|
|
24
25
|
*/
|
|
26
|
+
orientation?: ThemeVariants['orientation'];
|
|
27
|
+
/** @default "md" */
|
|
25
28
|
size?: ThemeVariants['size'];
|
|
26
29
|
required?: boolean;
|
|
27
30
|
/** If true, validation on input will be active immediately instead of waiting for a blur event. */
|
|
@@ -53,9 +56,11 @@ export interface FormFieldSlots {
|
|
|
53
56
|
error?: boolean | string;
|
|
54
57
|
}>;
|
|
55
58
|
}
|
|
56
|
-
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<FormFieldProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FormFieldProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, FormFieldSlots>;
|
|
57
59
|
declare const _default: typeof __VLS_export;
|
|
58
60
|
export default _default;
|
|
61
|
+
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<FormFieldProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FormFieldProps> & Readonly<{}>, {
|
|
62
|
+
error: string | boolean;
|
|
63
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, FormFieldSlots>;
|
|
59
64
|
type __VLS_WithSlots<T, S> = T & {
|
|
60
65
|
new (): {
|
|
61
66
|
$slots: S;
|
|
@@ -4,6 +4,6 @@ export interface IconProps {
|
|
|
4
4
|
size?: string | number;
|
|
5
5
|
customize?: (content: string, name?: string, prefix?: string, provider?: string) => string;
|
|
6
6
|
}
|
|
7
|
-
declare const __VLS_export: import("vue").DefineComponent<IconProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<IconProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
7
|
declare const _default: typeof __VLS_export;
|
|
9
8
|
export default _default;
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<IconProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<IconProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -10,7 +10,7 @@ import { useAppConfig } from "#imports";
|
|
|
10
10
|
import { useComponentIcons } from "../composables/useComponentIcons";
|
|
11
11
|
import { useFieldGroup } from "../composables/useFieldGroup";
|
|
12
12
|
import { useFormField } from "../composables/useFormField";
|
|
13
|
-
import { looseToNumber } from "../utils";
|
|
13
|
+
import { looseToNumber, pick } from "../utils";
|
|
14
14
|
import { cv, merge } from "../utils/style";
|
|
15
15
|
import Avatar from "./Avatar.vue";
|
|
16
16
|
import Icon from "./Icon.vue";
|
|
@@ -54,14 +54,14 @@ const appConfig = useAppConfig();
|
|
|
54
54
|
const ui = computed(() => {
|
|
55
55
|
const styler = cv(merge(theme, appConfig.ui.input));
|
|
56
56
|
return styler({
|
|
57
|
-
...props,
|
|
57
|
+
...pick(props, ["variant", "loading"]),
|
|
58
58
|
type: props.type,
|
|
59
59
|
color: color.value,
|
|
60
60
|
size: fieldGroupSize.value || formFieldSize.value,
|
|
61
61
|
highlight: highlight.value,
|
|
62
|
-
fieldGroup: orientation.value,
|
|
63
62
|
leading: isLeading.value || !!props.avatar || !!slots.leading,
|
|
64
|
-
trailing: isTrailing.value || !!slots.trailing
|
|
63
|
+
trailing: isTrailing.value || !!slots.trailing,
|
|
64
|
+
fieldGroup: orientation.value
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
67
|
const inputRef = ref(null);
|