@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.
Files changed (137) hide show
  1. package/README.md +5 -3
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +2 -2
  4. package/dist/runtime/components/Accordion.vue +2 -2
  5. package/dist/runtime/components/Accordion.vue.d.ts +11 -7
  6. package/dist/runtime/components/Alert.vue +2 -1
  7. package/dist/runtime/components/Alert.vue.d.ts +4 -4
  8. package/dist/runtime/components/App.vue +2 -1
  9. package/dist/runtime/components/App.vue.d.ts +11 -7
  10. package/dist/runtime/components/Avatar.vue +2 -1
  11. package/dist/runtime/components/Avatar.vue.d.ts +4 -3
  12. package/dist/runtime/components/AvatarGroup.vue +2 -1
  13. package/dist/runtime/components/AvatarGroup.vue.d.ts +1 -1
  14. package/dist/runtime/components/Badge.vue +4 -3
  15. package/dist/runtime/components/Badge.vue.d.ts +2 -2
  16. package/dist/runtime/components/Breadcrumb.vue +2 -2
  17. package/dist/runtime/components/Breadcrumb.vue.d.ts +10 -6
  18. package/dist/runtime/components/Button.vue +8 -8
  19. package/dist/runtime/components/Button.vue.d.ts +1 -1
  20. package/dist/runtime/components/Calendar.vue +2 -1
  21. package/dist/runtime/components/Calendar.vue.d.ts +16 -12
  22. package/dist/runtime/components/Card.vue +2 -1
  23. package/dist/runtime/components/Card.vue.d.ts +1 -1
  24. package/dist/runtime/components/Carousel.vue +2 -1
  25. package/dist/runtime/components/Carousel.vue.d.ts +14 -10
  26. package/dist/runtime/components/Checkbox.vue +4 -2
  27. package/dist/runtime/components/Checkbox.vue.d.ts +4 -3
  28. package/dist/runtime/components/CheckboxGroup.vue +4 -3
  29. package/dist/runtime/components/CheckboxGroup.vue.d.ts +11 -7
  30. package/dist/runtime/components/Chip.vue +5 -1
  31. package/dist/runtime/components/Chip.vue.d.ts +2 -2
  32. package/dist/runtime/components/Collapsible.vue +2 -1
  33. package/dist/runtime/components/Collapsible.vue.d.ts +2 -2
  34. package/dist/runtime/components/Drawer.vue +2 -1
  35. package/dist/runtime/components/Drawer.vue.d.ts +6 -6
  36. package/dist/runtime/components/DropdownMenu.vue +3 -3
  37. package/dist/runtime/components/DropdownMenu.vue.d.ts +17 -9
  38. package/dist/runtime/components/DropdownMenuContent.vue +11 -12
  39. package/dist/runtime/components/DropdownMenuContent.vue.d.ts +11 -7
  40. package/dist/runtime/components/FieldGroup.vue +2 -1
  41. package/dist/runtime/components/FieldGroup.vue.d.ts +2 -2
  42. package/dist/runtime/components/FileUpload.vue +267 -0
  43. package/dist/runtime/components/FileUpload.vue.d.ts +178 -0
  44. package/dist/runtime/components/Form.vue +2 -1
  45. package/dist/runtime/components/Form.vue.d.ts +13 -8
  46. package/dist/runtime/components/FormField.vue +6 -4
  47. package/dist/runtime/components/FormField.vue.d.ts +7 -2
  48. package/dist/runtime/components/Icon.vue.d.ts +1 -1
  49. package/dist/runtime/components/Input.vue +4 -4
  50. package/dist/runtime/components/Input.vue.d.ts +16 -12
  51. package/dist/runtime/components/InputNumber.vue +3 -2
  52. package/dist/runtime/components/InputNumber.vue.d.ts +128 -124
  53. package/dist/runtime/components/InputTags.vue +2 -1
  54. package/dist/runtime/components/InputTags.vue.d.ts +16 -11
  55. package/dist/runtime/components/Kbd.vue +2 -1
  56. package/dist/runtime/components/Kbd.vue.d.ts +2 -2
  57. package/dist/runtime/components/Link.vue +5 -3
  58. package/dist/runtime/components/Link.vue.d.ts +16 -6
  59. package/dist/runtime/components/LinkBase.vue.d.ts +2 -2
  60. package/dist/runtime/components/Marquee.vue +2 -1
  61. package/dist/runtime/components/Marquee.vue.d.ts +3 -3
  62. package/dist/runtime/components/Modal.vue +3 -2
  63. package/dist/runtime/components/Modal.vue.d.ts +6 -6
  64. package/dist/runtime/components/NavigationMenu.vue +2 -2
  65. package/dist/runtime/components/NavigationMenu.vue.d.ts +11 -7
  66. package/dist/runtime/components/Pagination.vue +2 -1
  67. package/dist/runtime/components/Pagination.vue.d.ts +4 -4
  68. package/dist/runtime/components/PinInput.vue +4 -4
  69. package/dist/runtime/components/PinInput.vue.d.ts +14 -10
  70. package/dist/runtime/components/Popover.vue +1 -1
  71. package/dist/runtime/components/Popover.vue.d.ts +11 -7
  72. package/dist/runtime/components/Progress.vue +2 -1
  73. package/dist/runtime/components/Progress.vue.d.ts +2 -2
  74. package/dist/runtime/components/RadioGroup.vue +3 -3
  75. package/dist/runtime/components/RadioGroup.vue.d.ts +11 -7
  76. package/dist/runtime/components/ScrollArea.vue +2 -1
  77. package/dist/runtime/components/ScrollArea.vue.d.ts +2 -2
  78. package/dist/runtime/components/Select.vue +282 -131
  79. package/dist/runtime/components/Select.vue.d.ts +103 -123
  80. package/dist/runtime/components/Separator.vue +2 -1
  81. package/dist/runtime/components/Separator.vue.d.ts +2 -2
  82. package/dist/runtime/components/Skeleton.vue +2 -1
  83. package/dist/runtime/components/Skeleton.vue.d.ts +2 -2
  84. package/dist/runtime/components/Slider.vue +2 -1
  85. package/dist/runtime/components/Slider.vue.d.ts +11 -7
  86. package/dist/runtime/components/Stepper.vue +117 -0
  87. package/dist/runtime/components/Stepper.vue.d.ts +83 -0
  88. package/dist/runtime/components/Switch.vue +3 -4
  89. package/dist/runtime/components/Switch.vue.d.ts +4 -3
  90. package/dist/runtime/components/Table.vue +2 -1
  91. package/dist/runtime/components/Table.vue.d.ts +13 -8
  92. package/dist/runtime/components/Tabs.vue +2 -2
  93. package/dist/runtime/components/Tabs.vue.d.ts +12 -8
  94. package/dist/runtime/components/Textarea.vue +3 -3
  95. package/dist/runtime/components/Textarea.vue.d.ts +16 -11
  96. package/dist/runtime/components/Timeline.vue +2 -1
  97. package/dist/runtime/components/Timeline.vue.d.ts +11 -7
  98. package/dist/runtime/components/Toast.vue +2 -1
  99. package/dist/runtime/components/Toast.vue.d.ts +5 -5
  100. package/dist/runtime/components/ToastProvider.vue +2 -2
  101. package/dist/runtime/components/ToastProvider.vue.d.ts +3 -3
  102. package/dist/runtime/components/Tooltip.vue +4 -4
  103. package/dist/runtime/components/Tooltip.vue.d.ts +2 -2
  104. package/dist/runtime/components/Tree.vue +241 -0
  105. package/dist/runtime/components/Tree.vue.d.ts +121 -0
  106. package/dist/runtime/composables/defineShortcuts.d.ts +1 -0
  107. package/dist/runtime/composables/defineShortcuts.js +44 -8
  108. package/dist/runtime/composables/useFileUpload.d.ts +19 -0
  109. package/dist/runtime/composables/useFileUpload.js +79 -0
  110. package/dist/runtime/composables/useLocale.d.ts +18 -0
  111. package/dist/runtime/locale/en.d.ts +9 -0
  112. package/dist/runtime/locale/en.js +9 -0
  113. package/dist/runtime/locale/zh_tw.d.ts +9 -0
  114. package/dist/runtime/locale/zh_tw.js +9 -0
  115. package/dist/runtime/types/html.d.ts +8 -0
  116. package/dist/runtime/types/html.js +0 -0
  117. package/dist/runtime/types/index.d.ts +3 -0
  118. package/dist/runtime/types/index.js +3 -0
  119. package/dist/runtime/types/input.d.ts +5 -5
  120. package/dist/runtime/types/locale.d.ts +9 -0
  121. package/dist/runtime/types/utils.d.ts +4 -4
  122. package/dist/runtime/utils/index.d.ts +3 -3
  123. package/dist/runtime/utils/link.d.ts +2 -1
  124. package/dist/runtime/utils/link.js +40 -29
  125. package/dist/runtime/vue/components/Icon.vue.d.ts +1 -1
  126. package/dist/runtime/vue/components/Link.vue +7 -12
  127. package/dist/runtime/vue/components/Link.vue.d.ts +11 -40
  128. package/dist/setup.d.mts +1 -1
  129. package/dist/shared/{ui.DpbffTXs.d.mts → ui.CGCKYv7g.d.mts} +6 -2
  130. package/dist/shared/{ui.CzIlLITK.mjs → ui.DYMXCXO6.mjs} +9 -5
  131. package/dist/shared/{ui.DLOxhmP0.mjs → ui.DcEKQd0n.mjs} +490 -20
  132. package/dist/unocss.mjs +1 -1
  133. package/dist/unplugin.d.mts +1 -1
  134. package/dist/unplugin.mjs +2 -2
  135. package/dist/vite.d.mts +1 -1
  136. package/dist/vite.mjs +2 -2
  137. 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 __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_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
63
- props: __VLS_PrettifyLocal<FormProps<S, T, N> & {
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
- }> & import("vue").PublicProps;
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
- declare const _default: typeof __VLS_export;
100
- export default _default;
101
- type __VLS_PrettifyLocal<T> = {
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
- * @default "md"
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);