用法
在 Modal 的默认插槽中使用 Button 或任何其他组件。
然后,使用 #content 插槽来添加 Modal 打开时显示的内容。
<template>
<UModal>
<UButton label="Open" color="neutral" variant="subtle" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UModal>
</template>
你也可以使用 #header、#body 和 #footer 插槽来自定义 Modal 的内容。
标题
使用 title 属性来设置 Modal 页眉的标题。
<template>
<UModal title="Modal with title">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>
描述
使用 description 属性来设置 Modal 页眉的描述。
<template>
<UModal
title="Modal with description"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
>
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>
关闭
使用 close 属性来自定义或隐藏(设为 false)在 Modal 页眉中显示的关闭按钮。
您可以将 Button 组件的任何属性传递过来以进行自定义。
<template>
<UModal
title="Modal with close button"
:close="{
color: 'primary',
variant: 'outline',
class: 'rounded-full'
}"
>
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>
#content 插槽,则不会显示关闭按钮,因为它是页眉的一部分。关闭图标
使用 close-icon 属性来定制关闭按钮的 Icon。默认为 i-lucide-x。
<template>
<UModal title="Modal with close button" close-icon="i-lucide-arrow-right">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>
过渡动画
使用 transition 属性来控制 Modal 是否启用动画。默认为 true。
<template>
<UModal :transition="false" title="Modal without transition">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>
遮罩层
使用 overlay 属性来控制 Modal 是否具有遮罩层。默认为 true。
<template>
<UModal :overlay="false" title="Modal without overlay">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>
Modal
使用 modal 属性来控制 Modal 是否阻止与外部内容的交互。默认为 true。
modal设置为false时,叠加层会自动禁用,外部内容变得可交互。<template>
<UModal :modal="false" title="Modal interactive">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>
可关闭
使用 dismissible 属性来控制在点击外部或按 Esc 键时是否可以关闭 Modal。默认为 true。
close:prevent事件。modal: false 和 dismissible: false,使 Modal 的背景在不关闭窗口的情况下可交互。<template>
<UModal :dismissible="false" title="Modal non-dismissible">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>
可滚动 4.2+
使用 scrollable 属性使 Modal 的内容在遮罩层内可滚动。
modal: false 是不兼容的,而 overlay: false 仅会移除背景颜色。<template>
<UModal scrollable title="Modal scrollable">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-full" />
</template>
</UModal>
</template>
全屏
使用 fullscreen 属性使 Modal 全屏显示。
<template>
<UModal fullscreen title="Modal fullscreen">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-full" />
</template>
</UModal>
</template>
示例
控制打开状态
您可以通过使用default-open prop 或v-model:open指令来控制打开状态。
<script setup lang="ts">
const open = ref(false)
defineShortcuts({
o: () => open.value = !open.value
})
</script>
<template>
<UModal v-model:open="open">
<UButton label="Open" color="neutral" variant="subtle" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UModal>
</template>
defineShortcuts,你可以通过按 O 键来切换 Modal 的显示。编程式用法
你可以使用 useOverlay 组合式函数以编程方式打开 Modal。
App 组件中,该组件使用了OverlayProvider组件的任何属性。首先,创建一个将以编程方式打开的 Modal 组件
<script setup lang="ts">
defineProps<{
count: number
}>()
const emit = defineEmits<{ close: [boolean] }>()
</script>
<template>
<UModal
:close="{ onClick: () => emit('close', false) }"
:title="`This modal was opened programmatically ${count} times`"
>
<template #footer>
<div class="flex gap-2">
<UButton color="neutral" label="Dismiss" @click="emit('close', false)" />
<UButton label="Success" @click="emit('close', true)" />
</div>
</template>
</UModal>
</template>
close 事件。你可以通过 close 事件传递任何数据,但是必须触发该事件才能捕获返回值。然后,在你的应用中使用它
<script setup lang="ts">
import { LazyModalExample } from '#components'
const count = ref(0)
const toast = useToast()
const overlay = useOverlay()
const modal = overlay.create(LazyModalExample)
async function open() {
const instance = modal.open({
count: count.value
})
const shouldIncrement = await instance.result
if (shouldIncrement) {
count.value++
toast.add({
title: `Success: ${shouldIncrement}`,
color: 'success',
id: 'modal-success'
})
// Update the count
modal.patch({
count: count.value
})
return
}
toast.add({
title: `Dismissed: ${shouldIncrement}`,
color: 'error',
id: 'modal-dismiss'
})
}
</script>
<template>
<UButton label="Open" color="neutral" variant="subtle" @click="open" />
</template>
emit('close') 在 Modal 组件内部关闭它。嵌套 Modal
你可以将 Modal 相互嵌套。
<script setup lang="ts">
const first = ref(false)
const second = ref(false)
</script>
<template>
<UModal v-model:open="first" title="First modal" :ui="{ footer: 'justify-end' }">
<UButton color="neutral" variant="subtle" label="Open" />
<template #footer>
<UButton label="Close" color="neutral" variant="outline" @click="first = false" />
<UModal v-model:open="second" title="Second modal" :ui="{ footer: 'justify-end' }">
<UButton label="Open second" color="neutral" />
<template #footer>
<UButton label="Close" color="neutral" variant="outline" @click="second = false" />
</template>
</UModal>
</template>
</UModal>
</template>
带页脚插槽
使用 #footer 插槽在 Modal 的正文之后添加内容。
<script setup lang="ts">
const open = ref(false)
</script>
<template>
<UModal v-model:open="open" title="Modal with footer" description="This is useful when you want a form in a Modal." :ui="{ footer: 'justify-end' }">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
<template #footer="{ close }">
<UButton label="Cancel" color="neutral" variant="outline" @click="close" />
<UButton label="Submit" color="neutral" />
</template>
</UModal>
</template>
带命令面板
你可以在 Modal 的内容中使用 CommandPalette 组件。
<script setup lang="ts">
const searchTerm = ref('')
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: '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}` } })) || []
},
lazy: true
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
ignoreFilter: true
}])
</script>
<template>
<UModal>
<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>
</UModal>
</template>
API
属性
| 属性 | 默认值 | 类型 |
|---|---|---|
title | string | |
description | string | |
内容 | DialogContentProps & Partial<EmitsToProps<DialogContentImplEmits>>模态框的内容。
| |
叠加层 | true | boolean在模态框后面渲染一个叠加层。 |
scrollable | false | boolean当为 |
过渡动画 | true | boolean在打开或关闭时为模态框添加动画效果。 |
全屏 | false | boolean当为 |
portal | true | string | false | true | HTMLElement在传送门中渲染模态框。
|
close | true | boolean | Omit<ButtonProps, LinkPropsKeys>显示一个关闭按钮以关闭 Modal。 |
closeIcon | appConfig.ui.icons.close | any关闭按钮中显示的图标。 |
可关闭的 | true | boolean当为 |
open | boolean对话框的受控打开状态。可以绑定为 | |
defaultOpen | boolean对话框初始渲染时的打开状态。当您不需要控制其打开状态时使用。 | |
modal | true | boolean对话框的模态性。当设置为 |
ui | { overlay?: ClassNameValue; content?: ClassNameValue; header?: ClassNameValue; wrapper?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; close?: ClassNameValue; } |
插槽
| 插槽 | 类型 |
|---|---|
default | { open: boolean; } |
内容 | { close: () => void; } |
页头 | { close: () => void; } |
title | {} |
description | {} |
actions | {} |
close | { ui: object; } |
主体 | { close: () => void; } |
页脚 | { close: () => void; } |
事件
| 事件 | 类型 |
|---|---|
after:leave | [] |
after:enter | [] |
close:prevent | [] |
update:open | [value: boolean] |
主题
export default defineAppConfig({
ui: {
modal: {
slots: {
overlay: 'fixed inset-0',
content: 'bg-default divide-y divide-default flex flex-col focus:outline-none',
header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-16',
wrapper: '',
body: 'flex-1 p-4 sm:p-6',
footer: 'flex items-center gap-1.5 p-4 sm:px-6',
title: 'text-highlighted font-semibold',
description: 'mt-1 text-muted text-sm',
close: 'absolute top-4 end-4'
},
variants: {
transition: {
true: {
overlay: 'data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_200ms_ease-in]',
content: 'data-[state=open]:animate-[scale-in_200ms_ease-out] data-[state=closed]:animate-[scale-out_200ms_ease-in]'
}
},
fullscreen: {
true: {
content: 'inset-0'
},
false: {
content: 'w-[calc(100vw-2rem)] max-w-lg rounded-lg shadow-lg ring ring-default'
}
},
overlay: {
true: {
overlay: 'bg-elevated/75'
}
},
scrollable: {
true: {
overlay: 'overflow-y-auto',
content: 'relative'
},
false: {
content: 'fixed',
body: 'overflow-y-auto'
}
}
},
compoundVariants: [
{
scrollable: true,
fullscreen: false,
class: {
overlay: 'grid place-items-center p-4 sm:py-8'
}
},
{
scrollable: false,
fullscreen: false,
class: {
content: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-4rem)] overflow-hidden'
}
}
]
}
}
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
modal: {
slots: {
overlay: 'fixed inset-0',
content: 'bg-default divide-y divide-default flex flex-col focus:outline-none',
header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-16',
wrapper: '',
body: 'flex-1 p-4 sm:p-6',
footer: 'flex items-center gap-1.5 p-4 sm:px-6',
title: 'text-highlighted font-semibold',
description: 'mt-1 text-muted text-sm',
close: 'absolute top-4 end-4'
},
variants: {
transition: {
true: {
overlay: 'data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_200ms_ease-in]',
content: 'data-[state=open]:animate-[scale-in_200ms_ease-out] data-[state=closed]:animate-[scale-out_200ms_ease-in]'
}
},
fullscreen: {
true: {
content: 'inset-0'
},
false: {
content: 'w-[calc(100vw-2rem)] max-w-lg rounded-lg shadow-lg ring ring-default'
}
},
overlay: {
true: {
overlay: 'bg-elevated/75'
}
},
scrollable: {
true: {
overlay: 'overflow-y-auto',
content: 'relative'
},
false: {
content: 'fixed',
body: 'overflow-y-auto'
}
}
},
compoundVariants: [
{
scrollable: true,
fullscreen: false,
class: {
overlay: 'grid place-items-center p-4 sm:py-8'
}
},
{
scrollable: false,
fullscreen: false,
class: {
content: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-4rem)] overflow-hidden'
}
}
]
}
}
})
]
})