Files
mydot/agents/.agents/skills/nuxt-ui/references/recipes/overlays.md
T
2026-09-27 11:51:31 +03:00

173 lines
4.2 KiB
Markdown

# Overlays
Patterns for modals, slideovers, drawers, and command palettes.
## Confirmation dialog
```vue
<script setup lang="ts">
const isOpen = ref(false)
function confirmDelete() {
// perform delete
isOpen.value = false
}
</script>
<template>
<UButton label="Delete" color="error" variant="soft" @click="isOpen = true" />
<UModal v-model:open="isOpen" title="Delete item" description="This action cannot be undone. Are you sure?" :ui="{ footer: 'justify-end' }">
<template #footer="{ close }">
<UButton label="Cancel" color="neutral" variant="outline" @click="close" />
<UButton label="Delete" color="error" @click="confirmDelete" />
</template>
</UModal>
</template>
```
## Programmatic confirmation (useOverlay)
Reusable pattern — no template state needed at the call site.
```vue [components/ConfirmModal.vue]
<script setup lang="ts">
defineProps<{
title: string
description?: string
}>()
const emit = defineEmits<{
close: [confirmed: boolean]
}>()
</script>
<template>
<UModal :close="{ onClick: () => emit('close', false) }" :title="title" :description="description">
<template #footer>
<UButton label="Cancel" color="neutral" variant="outline" @click="emit('close', false)" />
<UButton label="Confirm" color="error" @click="emit('close', true)" />
</template>
</UModal>
</template>
```
```ts
// Usage anywhere
const overlay = useOverlay()
const confirm = overlay.create(ConfirmModal)
async function deleteItem(item) {
const instance = confirm.open({
title: 'Delete item',
description: `Are you sure you want to delete "${item.name}"?`
})
if (await instance.result) {
// user confirmed
}
}
```
## Form in a slideover
```vue
<script setup lang="ts">
import * as z from 'zod'
const isOpen = ref(false)
const schema = z.object({
name: z.string().min(1),
email: z.email()
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({})
function onSave() {
// save user
isOpen.value = false
}
</script>
<template>
<UButton label="Add user" @click="isOpen = true" />
<USlideover v-model:open="isOpen" title="Add user" description="Fill in the details below.">
<template #body>
<UForm id="user-form" :schema="schema" :state="state" class="space-y-4" @submit="onSave">
<UFormField name="name" label="Name">
<UInput v-model="state.name" />
</UFormField>
<UFormField name="email" label="Email">
<UInput v-model="state.email" type="email" />
</UFormField>
</UForm>
</template>
<template #footer="{ close }">
<UButton label="Cancel" color="neutral" variant="outline" @click="close" />
<UButton type="submit" form="user-form" label="Save" />
</template>
</USlideover>
</template>
```
## Command palette
```vue
<script setup lang="ts">
const isOpen = ref(false)
defineShortcuts({
meta_k: () => { isOpen.value = true }
})
const groups = [{
id: 'actions',
label: 'Actions',
items: [
{ label: 'New file', icon: 'i-lucide-file-plus', kbds: ['meta', 'n'], onSelect: () => newFile() },
{ label: 'New folder', icon: 'i-lucide-folder-plus', onSelect: () => newFolder() }
]
}, {
id: 'navigation',
label: 'Navigation',
items: [
{ label: 'Dashboard', icon: 'i-lucide-house', to: '/dashboard' },
{ label: 'Settings', icon: 'i-lucide-settings', to: '/settings' }
]
}]
</script>
<template>
<UButton label="Search..." icon="i-lucide-search" color="neutral" variant="outline" @click="isOpen = true" />
<UCommandPalette v-model:open="isOpen" :groups="groups" placeholder="Type a command or search..." />
</template>
```
## Drawer (bottom sheet)
```vue
<script setup lang="ts">
const isOpen = ref(false)
</script>
<template>
<UButton label="Options" @click="isOpen = true" />
<UDrawer v-model:open="isOpen" title="Options">
<template #body>
<div class="space-y-2 p-4">
<UButton label="Share" icon="i-lucide-share" block variant="ghost" />
<UButton label="Export" icon="i-lucide-download" block variant="ghost" />
<USeparator />
<UButton label="Delete" icon="i-lucide-trash" block variant="ghost" color="error" />
</div>
</template>
</UDrawer>
</template>
```