Bearbeiten
Verwenden Sie einen Button oder eine andere Komponente im Standardsteckplatz der Schublade.
Verwenden Sie dann den Steckplatz #content, um den Inhalt hinzuzufügen, der angezeigt wird, wenn die Schublade geöffnet ist.
<template>
<UDrawer>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UDrawer>
</template>Sie können auch die Slots #header, #body und #footer verwenden, um den Inhalt der Schublade anzupassen.
title
Verwenden Sie die title-prop, um den Titel des Schubladenkopfes festzulegen.
<template>
<UDrawer title="Drawer with title">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="h-48" />
</template>
</UDrawer>
</template>Beschreibung
Verwenden Sie die description-prop, um die Beschreibung des Schubladenkopfes festzulegen.
<template>
<UDrawer
title="Drawer with description"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="h-48" />
</template>
</UDrawer>
</template>Close: badge{label="4.10+" class="align-text-top"} (geschlossen)
Verwenden Sie die close-Stütze, um eine Schließen-Taste in der Schublade anzuzeigen. Standardmäßig ist false.
Sie können jede Eigenschaft der Komponente Button übergeben, um sie anzupassen.
<template>
<UDrawer
title="Drawer with close button"
:close="{
color: 'primary',
variant: 'outline',
class: 'rounded-full'
}"
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="h-48" />
</template>
</UDrawer>
</template>Schließen Icon: badge{label="4.10+" class="align-text-top"}
Verwenden Sie die close-icon-Prop, um die Schließen-Taste Icon. Defaults auf i-lucide-x anzupassen.
<template>
<UDrawer title="Drawer with close button" close close-icon="i-lucide-arrow-right">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="h-48" />
</template>
</UDrawer>
</template>Direction Bearbeiten
Verwenden Sie die direction-Stütze, um die Richtung der Schublade zu steuern. Standardmäßig bottom.
<template>
<UDrawer direction="right">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="min-w-96 min-h-96 size-full m-4" />
</template>
</UDrawer>
</template>Inset ist
Verwenden Sie die inset-Stütze, um die Schublade von den Kanten zu setzen.
<template>
<UDrawer direction="right" inset>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="min-w-96 min-h-96 size-full m-4" />
</template>
</UDrawer>
</template>Handle Bearbeiten
Verwenden Sie die handle-Stütze, um zu steuern, ob die Schublade einen Griff hat oder nicht. Standardmäßig true.
<template>
<UDrawer :handle="false">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UDrawer>
</template>Handle Nur für die
Verwenden Sie die handle-only-Stütze, um die Schublade nur am Griff ziehen zu lassen.
<template>
<UDrawer handle-only>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UDrawer>
</template>Overlay (Überladen)
Verwenden Sie die overlay-Stütze, um zu steuern, ob die Schublade eine Überlagerung hat oder nicht. Standardmäßig true.
<template>
<UDrawer :overlay="false">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UDrawer>
</template>Modal Bearbeiten
Verwenden Sie die modal-prop, um zu steuern, ob die Schublade die Interaktion mit externen Inhalten blockiert. Standardmäßig ist true.
modal auf false gesetzt ist, wird das Overlay automatisch deaktiviert und externe Inhalte werden interaktiv.<template>
<UDrawer :modal="false">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UDrawer>
</template>Dismissible (nicht zulässig)
Verwenden Sie die dismissible-Stütze, um zu steuern, ob die Schublade deaktiviert werden kann, wenn Sie außerhalb der Schublade klicken oder escape drücken.
close:prevent-Ereignis wird ausgegeben, wenn der Benutzer versucht, es zu schließen.modal: false mit dismissible: false kombinieren, um den Hintergrund der Schublade interaktiv zu gestalten, ohne sie zu schließen.<script setup lang="ts">
const open = ref(false)
</script>
<template>
<UDrawer
v-model:open="open"
title="Drawer non-dismissible"
close
:dismissible="false"
:modal="false"
:handle="false"
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="size-full min-h-48" />
</template>
</UDrawer>
</template><script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<UDrawer
v-model:open="open"
title="Drawer non-dismissible"
close
:dismissible="false"
:modal="false"
:handle="false"
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="size-full min-h-48" />
</template>
</UDrawer>
</template>Scale Hintergrundbilder
Verwenden Sie die should-scale-background prop, um den Hintergrund zu skalieren, wenn die Schublade geöffnet ist, wodurch ein visueller Tiefeneffekt entsteht. Sie können die set-background-color-on-scale prop auf false setzen, um eine Änderung der Hintergrundfarbe zu verhindern.
<template>
<UDrawer should-scale-background set-background-color-on-scale>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UDrawer>
</template>data-vaul-drawer-wrapper-Direktive zu einem übergeordneten Element Ihrer App hinzufügen, damit dies funktioniert.<template>
<UApp>
<div class="bg-default" data-vaul-drawer-wrapper>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</UApp>
</template>
export default defineNuxtConfig({
app: {
rootAttrs: {
'data-vaul-drawer-wrapper': '',
'class': 'bg-default'
}
}
})
Examples Bearbeiten
Control im Open State
Sie können den offenen Zustand mit der default-open-prop-oder der v-model:open-Direktive steuern.
<script setup lang="ts">
const open = ref(false)
defineShortcuts({
o: () => (open.value = !open.value)
})
</script>
<template>
<UDrawer v-model:open="open">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UDrawer>
</template><script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
defineShortcuts({
o: () => (open.value = !open.value)
})
</script>
<template>
<UDrawer v-model:open="open">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UDrawer>
</template>defineShortcuts die Schublade umschalten, indem Sie: kbd{value="O"} drücken.Responsive Drawer für iOS
Sie können beispielsweise eine Modal-Komponente auf dem Desktop und eine Schublade auf dem Handy rendern.
<script lang="ts" setup>
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
const [DefineFormTemplate, ReuseFormTemplate] = createReusableTemplate()
const isDesktop = useMediaQuery('(min-width: 768px)')
const open = ref(false)
const state = reactive({
email: undefined
})
const title = 'Edit profile'
const description = "Make changes to your profile here. Click save when you're done."
</script>
<template>
<DefineFormTemplate>
<UForm :state="state" class="space-y-4">
<UFormField label="Email" name="email" required>
<UInput v-model="state.email" placeholder="shadcn@example.com" required />
</UFormField>
<UButton label="Save changes" type="submit" />
</UForm>
</DefineFormTemplate>
<UModal v-if="isDesktop" v-model:open="open" :title="title" :description="description">
<UButton label="Edit profile" color="neutral" variant="outline" />
<template #body>
<ReuseFormTemplate />
</template>
</UModal>
<UDrawer v-else v-model:open="open" :title="title" :description="description">
<UButton label="Edit profile" color="neutral" variant="outline" />
<template #body>
<ReuseFormTemplate />
</template>
</UDrawer>
</template>Verschachtelte Schubladen
Sie können Schubladen miteinander verschachteln, indem Sie die nested-Stütze verwenden.
<template>
<UDrawer :ui="{ content: 'h-full', overlay: 'bg-inverted/30' }">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #footer>
<UDrawer nested :ui="{ content: 'h-full', overlay: 'bg-inverted/30' }">
<UButton color="neutral" variant="outline" label="Open nested" />
<template #content>
<Placeholder class="flex-1 m-4" />
</template>
</UDrawer>
</template>
</UDrawer>
</template>Mit Footer-Slot
Verwenden Sie den #footer-Steckplatz, um Inhalt nach dem Schubladenkörper hinzuzufügen.
<script setup lang="ts">
const open = ref(false)
</script>
<template>
<UDrawer
v-model:open="open"
title="Drawer with footer"
description="This is useful when you want a form in a Drawer."
:ui="{ container: 'max-w-xl mx-auto' }"
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="h-48" />
</template>
<template #footer>
<UButton label="Submit" color="neutral" class="justify-center" />
<UButton
label="Cancel"
color="neutral"
variant="outline"
class="justify-center"
@click="open = false"
/>
</template>
</UDrawer>
</template><script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<UDrawer
v-model:open="open"
title="Drawer with footer"
description="This is useful when you want a form in a Drawer."
:ui="{ container: 'max-w-xl mx-auto' }"
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="h-48" />
</template>
<template #footer>
<UButton label="Submit" color="neutral" class="justify-center" />
<UButton
label="Cancel"
color="neutral"
variant="outline"
class="justify-center"
@click="open = false"
/>
</template>
</UDrawer>
</template>Mit der Befehlspalette
Sie können eine CommandPalette-Komponente innerhalb des Inhalts der Schublade verwenden.
<script setup lang="ts">
const searchTerm = ref('')
const { data: users, status, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
key: 'drawer-command-palette-users',
params: { q: searchTerm },
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, loading: 'lazy' as const } })) || []
},
immediate: false
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
ignoreFilter: true
}])
function onOpen() {
if (!users.value?.length) {
execute()
}
}
</script>
<template>
<UDrawer :handle="false" @update:open="onOpen">
<UButton
label="Search users..."
color="neutral"
variant="subtle"
icon="i-lucide-search"
/>
<template #content>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
placeholder="Search users..."
class="h-80"
/>
</template>
</UDrawer>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
const searchTerm = ref('')
const { data: users, status, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
key: 'drawer-command-palette-users',
params: { q: searchTerm },
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, loading: 'lazy' as const } })) || []
},
immediate: false
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
ignoreFilter: true
}])
function onOpen() {
if (!users.value?.length) {
execute()
}
}
</script>
<template>
<UDrawer :handle="false" @update:open="onOpen">
<UButton
label="Search users..."
color="neutral"
variant="subtle"
icon="i-lucide-search"
/>
<template #content>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
placeholder="Search users..."
class="h-80"
/>
</template>
</UDrawer>
</template>
useLazyFetch mit immediate: false verwendet, um Daten nur beim Öffnen der Schublade abzurufen.API
Props Bearbeiten
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
title | string | |
description | string | |
inset | false | boolean Whether to inset the drawer from the edges. |
content | DialogContentProps & Partial<EmitsToProps<DialogContentImplEmits>>The content of the drawer. | |
overlay | true | boolean Render an overlay behind the drawer. |
handle | true | boolean Render a handle on the drawer. |
portal | true | string | false | true | HTMLElementRender the drawer in a portal. |
nested | false | boolean Whether the drawer is nested in another drawer. |
close | false | boolean | Omit<ButtonProps, LinkPropsKeys> Display a close button to dismiss the drawer.
|
closeIcon | appConfig.ui.icons.close | anyThe icon displayed in the close button. |
activeSnapPoint | null | string | number | |
closeThreshold | numberNumber between 0 and 1 that determines when the drawer should be closed. Example: threshold of 0.5 would close the drawer if the user swiped for 50% of the height of the drawer or more. | |
shouldScaleBackground | boolean | |
setBackgroundColorOnScale | boolean When | |
scrollLockTimeout | numberDuration for which the drawer is not draggable after scrolling content inside of the drawer. | |
fixed | boolean When | |
dismissible | true | boolean When |
modal | true | boolean When |
open | boolean | |
defaultOpen | boolean Opened by default, skips initial enter animation. Still reacts to | |
direction | 'bottom' | "bottom" | "top" | "left" | "right"Direction of the drawer. Can be |
noBodyStyles | boolean When | |
handleOnly | boolean When | |
preventScrollRestoration | boolean | |
snapPoints | (string | number)[]Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up.
Should go from least visible. Example | |
ui | { overlay?: SlotClass; content?: SlotClass; handle?: SlotClass; container?: SlotClass; header?: SlotClass; wrapper?: SlotClass; title?: SlotClass; description?: SlotClass; actions?: SlotClass; body?: SlotClass; footer?: SlotClass; close?: SlotClass; } |
Slots Bearbeiten
| Slot | Type |
|---|---|
default | {} |
content | {} |
header | {} |
title | {} |
description | {} |
actions | {} |
close | { ui: object; } |
body | {} |
footer | {} |
Emits (englisch)
| Event | Type |
|---|---|
close | [] |
drag | [percentageDragged: number] |
close:prevent | [] |
release | [open: boolean] |
update:open | [open: boolean] |
update:activeSnapPoint | [val: string | number] |
animationEnd | [open: boolean] |
Theme Bearbeiten
export default defineAppConfig({
ui: {
drawer: {
slots: {
overlay: 'fixed inset-0 bg-elevated/75',
content: 'fixed bg-default ring ring-default flex focus:outline-none',
handle: [
'shrink-0 !bg-accented',
'transition-opacity ease-out'
],
container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto',
header: 'flex items-center gap-1.5 min-h-8',
wrapper: 'min-w-0 flex-1',
title: 'text-highlighted font-semibold',
description: 'mt-1 text-muted text-sm',
actions: 'flex items-center gap-1.5 shrink-0 ms-auto',
body: 'flex-1',
footer: 'flex flex-col gap-1.5',
close: ''
},
variants: {
direction: {
top: {
content: 'mb-24 flex-col-reverse',
handle: 'mb-4'
},
right: {
content: 'flex-row rtl:flex-row-reverse',
handle: '!ml-4'
},
bottom: {
content: 'mt-24 flex-col',
handle: 'mt-4'
},
left: {
content: 'flex-row-reverse rtl:flex-row',
handle: '!mr-4'
}
},
inset: {
true: {
content: 'rounded-lg after:hidden overflow-hidden [--initial-transform:calc(100%+1.5rem)]'
}
},
snapPoints: {
true: ''
}
},
compoundVariants: [
{
direction: [
'top',
'bottom'
],
class: {
content: 'h-auto max-h-[96%]',
handle: '!w-12 !h-1.5 mx-auto'
}
},
{
direction: [
'top',
'bottom'
],
snapPoints: true,
class: {
content: 'h-full'
}
},
{
direction: [
'right',
'left'
],
class: {
content: 'w-auto max-w-[calc(100%-2rem)]',
handle: '!h-12 !w-1.5 mt-auto mb-auto'
}
},
{
direction: [
'right',
'left'
],
snapPoints: true,
class: {
content: 'w-full'
}
},
{
direction: 'top',
inset: true,
class: {
content: 'inset-x-4 top-4'
}
},
{
direction: 'top',
inset: false,
class: {
content: 'inset-x-0 top-0 rounded-b-lg'
}
},
{
direction: 'bottom',
inset: true,
class: {
content: 'inset-x-4 bottom-4'
}
},
{
direction: 'bottom',
inset: false,
class: {
content: 'inset-x-0 bottom-0 rounded-t-lg'
}
},
{
direction: 'left',
inset: true,
class: {
content: 'inset-y-4 left-4'
}
},
{
direction: 'left',
inset: false,
class: {
content: 'inset-y-0 left-0 rounded-r-lg'
}
},
{
direction: 'right',
inset: true,
class: {
content: 'inset-y-4 right-4'
}
},
{
direction: 'right',
inset: false,
class: {
content: 'inset-y-0 right-0 rounded-l-lg'
}
}
]
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
drawer: {
slots: {
overlay: 'fixed inset-0 bg-elevated/75',
content: 'fixed bg-default ring ring-default flex focus:outline-none',
handle: [
'shrink-0 !bg-accented',
'transition-opacity ease-out'
],
container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto',
header: 'flex items-center gap-1.5 min-h-8',
wrapper: 'min-w-0 flex-1',
title: 'text-highlighted font-semibold',
description: 'mt-1 text-muted text-sm',
actions: 'flex items-center gap-1.5 shrink-0 ms-auto',
body: 'flex-1',
footer: 'flex flex-col gap-1.5',
close: ''
},
variants: {
direction: {
top: {
content: 'mb-24 flex-col-reverse',
handle: 'mb-4'
},
right: {
content: 'flex-row rtl:flex-row-reverse',
handle: '!ml-4'
},
bottom: {
content: 'mt-24 flex-col',
handle: 'mt-4'
},
left: {
content: 'flex-row-reverse rtl:flex-row',
handle: '!mr-4'
}
},
inset: {
true: {
content: 'rounded-lg after:hidden overflow-hidden [--initial-transform:calc(100%+1.5rem)]'
}
},
snapPoints: {
true: ''
}
},
compoundVariants: [
{
direction: [
'top',
'bottom'
],
class: {
content: 'h-auto max-h-[96%]',
handle: '!w-12 !h-1.5 mx-auto'
}
},
{
direction: [
'top',
'bottom'
],
snapPoints: true,
class: {
content: 'h-full'
}
},
{
direction: [
'right',
'left'
],
class: {
content: 'w-auto max-w-[calc(100%-2rem)]',
handle: '!h-12 !w-1.5 mt-auto mb-auto'
}
},
{
direction: [
'right',
'left'
],
snapPoints: true,
class: {
content: 'w-full'
}
},
{
direction: 'top',
inset: true,
class: {
content: 'inset-x-4 top-4'
}
},
{
direction: 'top',
inset: false,
class: {
content: 'inset-x-0 top-0 rounded-b-lg'
}
},
{
direction: 'bottom',
inset: true,
class: {
content: 'inset-x-4 bottom-4'
}
},
{
direction: 'bottom',
inset: false,
class: {
content: 'inset-x-0 bottom-0 rounded-t-lg'
}
},
{
direction: 'left',
inset: true,
class: {
content: 'inset-y-4 left-4'
}
},
{
direction: 'left',
inset: false,
class: {
content: 'inset-y-0 left-0 rounded-r-lg'
}
},
{
direction: 'right',
inset: true,
class: {
content: 'inset-y-4 right-4'
}
},
{
direction: 'right',
inset: false,
class: {
content: 'inset-y-0 right-0 rounded-l-lg'
}
}
]
}
}
})
]
})