mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-25 17:30:37 +01:00
up
This commit is contained in:
@@ -73,9 +73,9 @@ async function onSubmit(event: FormSubmitEvent<schema>) {
|
|||||||
<div class="flex flex-col items-center gap-8">
|
<div class="flex flex-col items-center gap-8">
|
||||||
<UForm :schema="schema" :state="state" class="space-y-4 w-80" @submit="onSubmit">
|
<UForm :schema="schema" :state="state" class="space-y-4 w-80" @submit="onSubmit">
|
||||||
<UFormField name="avatar" label="Avatar" description="JPG, GIF or PNG. 1MB Max.">
|
<UFormField name="avatar" label="Avatar" description="JPG, GIF or PNG. 1MB Max.">
|
||||||
<UFileUpload v-slot="{ open, reset, previewUrls }" v-model="state.avatar" accept="image/*">
|
<UFileUpload v-slot="{ open, reset, urls }" v-model="state.avatar" accept="image/*">
|
||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
<UAvatar size="lg" :src="previewUrls?.[0]" icon="i-lucide-image" />
|
<UAvatar size="lg" :src="urls?.[0]" icon="i-lucide-image" />
|
||||||
|
|
||||||
<UButton :label="state.avatar ? 'Change image' : 'Upload image'" color="neutral" @click="open()" />
|
<UButton :label="state.avatar ? 'Change image' : 'Upload image'" color="neutral" @click="open()" />
|
||||||
</div>
|
</div>
|
||||||
@@ -105,6 +105,7 @@ async function onSubmit(event: FormSubmitEvent<schema>) {
|
|||||||
:size="size"
|
:size="size"
|
||||||
label="Drop your image here"
|
label="Drop your image here"
|
||||||
description="SVG, PNG, JPG or GIF (max. 2MB)"
|
description="SVG, PNG, JPG or GIF (max. 2MB)"
|
||||||
|
multiple
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,10 +24,16 @@ export interface FileUploadProps<M extends boolean = false> {
|
|||||||
label?: string
|
label?: string
|
||||||
description?: string
|
description?: string
|
||||||
actions?: ButtonProps[]
|
actions?: ButtonProps[]
|
||||||
|
/**
|
||||||
|
* @defaultValue 'primary'
|
||||||
|
*/
|
||||||
|
color?: FileUpload['variants']['color']
|
||||||
/**
|
/**
|
||||||
* @defaultValue 'md'
|
* @defaultValue 'md'
|
||||||
*/
|
*/
|
||||||
size?: FileUpload['variants']['size']
|
size?: FileUpload['variants']['size']
|
||||||
|
/** Highlight the ring color like a focus state. */
|
||||||
|
highlight?: boolean
|
||||||
required?: boolean
|
required?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
multiple?: M & boolean
|
multiple?: M & boolean
|
||||||
@@ -57,7 +63,11 @@ export interface FileUploadEmits<M extends boolean = false> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FileUploadSlots {
|
export interface FileUploadSlots {
|
||||||
default(props: { open: UseFileDialogReturn['open'], reset: UseFileDialogReturn['reset'] }): any
|
default(props: {
|
||||||
|
open: UseFileDialogReturn['open']
|
||||||
|
reset: UseFileDialogReturn['reset']
|
||||||
|
urls: string[]
|
||||||
|
}): any
|
||||||
leading(props?: {}): any
|
leading(props?: {}): any
|
||||||
label(props?: {}): any
|
label(props?: {}): any
|
||||||
description(props?: {}): any
|
description(props?: {}): any
|
||||||
@@ -105,10 +115,14 @@ const { isOverDropZone } = useDropZone(dropZoneRef, {
|
|||||||
})
|
})
|
||||||
const { emitFormInput, id, name, disabled, ariaAttrs } = useFormField<FileUploadProps>(props, { deferInputValidation: true })
|
const { emitFormInput, id, name, disabled, ariaAttrs } = useFormField<FileUploadProps>(props, { deferInputValidation: true })
|
||||||
|
|
||||||
|
const urls = computed(() => Array.from(files.value || []).map(file => URL.createObjectURL(file)))
|
||||||
|
|
||||||
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.fileUpload || {}) })({
|
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.fileUpload || {}) })({
|
||||||
dropzone: props.dropzone,
|
dropzone: props.dropzone,
|
||||||
multiple: props.multiple,
|
multiple: props.multiple,
|
||||||
size: props.size
|
color: props.color,
|
||||||
|
size: props.size,
|
||||||
|
highlight: props.highlight
|
||||||
}))
|
}))
|
||||||
|
|
||||||
onChange((files) => {
|
onChange((files) => {
|
||||||
@@ -121,6 +135,17 @@ function onDrop(files: File[] | null) {
|
|||||||
modelValue.value = (props.multiple ? files : files?.[0]) as (M extends true ? File[] : File) | null
|
modelValue.value = (props.multiple ? files : files?.[0]) as (M extends true ? File[] : File) | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeFile(index: number) {
|
||||||
|
const file = files.value?.[index]
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
URL.revokeObjectURL(URL.createObjectURL(file))
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileListArr = Array.from(files.value!)
|
||||||
|
fileListArr.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
inputRef
|
inputRef
|
||||||
})
|
})
|
||||||
@@ -128,12 +153,13 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
|
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
|
||||||
<slot :open="open" :reset="reset">
|
<slot :open="open" :reset="reset" :urls="urls">
|
||||||
<div
|
<div
|
||||||
ref="dropZoneRef"
|
ref="dropZoneRef"
|
||||||
role="button"
|
role="button"
|
||||||
:data-dragging="isOverDropZone"
|
:data-dragging="isOverDropZone"
|
||||||
:class="ui.base({ class: props.ui?.base })"
|
:class="ui.base({ class: props.ui?.base })"
|
||||||
|
tabindex="0"
|
||||||
@click="open()"
|
@click="open()"
|
||||||
>
|
>
|
||||||
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
|
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
|
||||||
@@ -164,7 +190,11 @@ defineExpose({
|
|||||||
|
|
||||||
<div v-if="files && files.length > 0" :class="ui.files({ class: props.ui?.files })">
|
<div v-if="files && files.length > 0" :class="ui.files({ class: props.ui?.files })">
|
||||||
<slot name="files" :files="files">
|
<slot name="files" :files="files">
|
||||||
{{ files }}
|
<div v-for="(file, index) in files" :key="file.name" class="flex items-center gap-2 border border-default rounded-md p-2">
|
||||||
|
<UAvatar :src="urls[index]" :icon="appConfig.ui.icons.file" />
|
||||||
|
<span class="text-sm">{{ file.name }}</span>
|
||||||
|
<UButton size="xs" color="neutral" variant="link" :trailing-icon="appConfig.ui.icons.close" @click="removeFile(index)" />
|
||||||
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import type { ModuleOptions } from '../module'
|
|||||||
|
|
||||||
export default (options: Required<ModuleOptions>) => ({
|
export default (options: Required<ModuleOptions>) => ({
|
||||||
slots: {
|
slots: {
|
||||||
root: 'relative',
|
root: 'relative flex flex-col gap-2',
|
||||||
base: ['w-full bg-default hover:bg-elevated/25 border border-default p-4 flex flex-col items-center justify-center rounded-lg focus-visible:outline-primary', options.theme.transitions && 'transition-colors'],
|
base: ['w-full bg-default hover:bg-elevated/25 border border-default p-4 flex flex-col items-center justify-center rounded-lg focus-visible:outline-2', options.theme.transitions && 'transition-[background]'],
|
||||||
wrapper: 'flex flex-col items-center justify-center text-center px-4 py-3',
|
wrapper: 'flex flex-col items-center justify-center text-center px-4 py-3',
|
||||||
leading: 'inline-flex items-center rounded-full ring ring-default',
|
leading: 'inline-flex items-center rounded-full ring ring-default',
|
||||||
leadingIcon: 'shrink-0 text-default',
|
leadingIcon: 'shrink-0 text-default',
|
||||||
label: 'font-medium text-default mt-2',
|
label: 'font-medium text-default mt-2',
|
||||||
description: 'text-muted mt-1',
|
description: 'text-muted mt-1',
|
||||||
actions: 'flex flex-wrap gap-1.5 shrink-0',
|
actions: 'flex flex-wrap gap-1.5 shrink-0',
|
||||||
preview: 'absolute inset-0'
|
files: 'flex flex-col gap-2'
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
color: {
|
color: {
|
||||||
@@ -47,26 +47,30 @@ export default (options: Required<ModuleOptions>) => ({
|
|||||||
dropzone: {
|
dropzone: {
|
||||||
true: 'border-dashed data-[dragging=true]:bg-elevated/25'
|
true: 'border-dashed data-[dragging=true]:bg-elevated/25'
|
||||||
},
|
},
|
||||||
|
highlight: {
|
||||||
|
true: ''
|
||||||
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
true: 'cursor-not-allowed opacity-75'
|
true: 'cursor-not-allowed opacity-75'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
compoundVariants: [...(options.theme.colors || []).map((color: string) => ({
|
compoundVariants: [...(options.theme.colors || []).map((color: string) => ({
|
||||||
color,
|
color,
|
||||||
class: `has-focus-visible:ring-2 has-focus-visible:ring-inset has-focus-visible:ring-${color}`
|
class: `focus-visible:outline-${color}`
|
||||||
})), ...(options.theme.colors || []).map((color: string) => ({
|
})), ...(options.theme.colors || []).map((color: string) => ({
|
||||||
color,
|
color,
|
||||||
highlight: true,
|
highlight: true,
|
||||||
class: `ring ring-inset ring-${color}`
|
class: `ring ring-inset ring-${color}`
|
||||||
})), {
|
})), {
|
||||||
color: 'neutral',
|
color: 'neutral',
|
||||||
class: 'has-focus-visible:ring-2 has-focus-visible:ring-inset has-focus-visible:ring-inverted'
|
class: 'focus-visible:outline-inverted'
|
||||||
}, {
|
}, {
|
||||||
color: 'neutral',
|
color: 'neutral',
|
||||||
highlight: true,
|
highlight: true,
|
||||||
class: 'ring ring-inset ring-inverted'
|
class: 'ring ring-inset ring-inverted'
|
||||||
}],
|
}],
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
color: 'primary',
|
||||||
size: 'md'
|
size: 'md'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default {
|
|||||||
close: 'i-lucide-x',
|
close: 'i-lucide-x',
|
||||||
ellipsis: 'i-lucide-ellipsis',
|
ellipsis: 'i-lucide-ellipsis',
|
||||||
external: 'i-lucide-arrow-up-right',
|
external: 'i-lucide-arrow-up-right',
|
||||||
|
file: 'i-lucide-file',
|
||||||
folder: 'i-lucide-folder',
|
folder: 'i-lucide-folder',
|
||||||
folderOpen: 'i-lucide-folder-open',
|
folderOpen: 'i-lucide-folder-open',
|
||||||
loading: 'i-lucide-loader-circle',
|
loading: 'i-lucide-loader-circle',
|
||||||
|
|||||||
Reference in New Issue
Block a user