mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
feat(useOverlay)!: handle programmatic modals and slideovers (#3279)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -1,23 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
const modal = useModal()
|
||||
|
||||
defineProps<{
|
||||
count: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
function onSuccess() {
|
||||
emit('success')
|
||||
}
|
||||
const emit = defineEmits<{ close: [boolean] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal :title="`This modal was opened programmatically ${count} times`">
|
||||
<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="Close" @click="modal.close()" />
|
||||
<UButton label="Success" @click="onSuccess" />
|
||||
<UButton color="neutral" label="Dismiss" @click="emit('close', false)" />
|
||||
<UButton label="Success" @click="emit('close', true)" />
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
@@ -4,20 +4,37 @@ import { LazyModalExample } from '#components'
|
||||
const count = ref(0)
|
||||
|
||||
const toast = useToast()
|
||||
const modal = useModal()
|
||||
const overlay = useOverlay()
|
||||
|
||||
function open() {
|
||||
count.value++
|
||||
const modal = overlay.create(LazyModalExample, {
|
||||
props: {
|
||||
count: count.value
|
||||
}
|
||||
})
|
||||
|
||||
modal.open(LazyModalExample, {
|
||||
description: 'And you can even provide a description!',
|
||||
count: count.value,
|
||||
onSuccess() {
|
||||
toast.add({
|
||||
title: 'Success !',
|
||||
id: 'modal-success'
|
||||
})
|
||||
}
|
||||
async function open() {
|
||||
const shouldIncrement = await modal.open()
|
||||
|
||||
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>
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
const slideover = useSlideover()
|
||||
|
||||
defineProps<{
|
||||
count: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
function onSuccess() {
|
||||
emit('success')
|
||||
}
|
||||
const emit = defineEmits<{ close: [boolean] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USlideover :description="`This slideover was opened programmatically ${count} times`">
|
||||
<USlideover :close="{ onClick: () => emit('close', false) }" :description="`This slideover was opened programmatically ${count} times`">
|
||||
<template #body>
|
||||
<Placeholder class="h-full" />
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex gap-2">
|
||||
<UButton color="neutral" label="Close" @click="slideover.close()" />
|
||||
<UButton label="Success" @click="onSuccess" />
|
||||
<UButton color="neutral" label="Dismiss" @click="emit('close', false)" />
|
||||
<UButton label="Success" @click="emit('close', true)" />
|
||||
</div>
|
||||
</template>
|
||||
</USlideover>
|
||||
|
||||
@@ -4,20 +4,37 @@ import { LazySlideoverExample } from '#components'
|
||||
const count = ref(0)
|
||||
|
||||
const toast = useToast()
|
||||
const slideover = useSlideover()
|
||||
const overlay = useOverlay()
|
||||
|
||||
function open() {
|
||||
count.value++
|
||||
const slideover = overlay.create(LazySlideoverExample, {
|
||||
props: {
|
||||
count: count.value
|
||||
}
|
||||
})
|
||||
|
||||
slideover.open(LazySlideoverExample, {
|
||||
title: 'Slideover',
|
||||
count: count.value,
|
||||
onSuccess() {
|
||||
toast.add({
|
||||
title: 'Success !',
|
||||
id: 'modal-success'
|
||||
})
|
||||
}
|
||||
async function open() {
|
||||
const shouldIncrement = await slideover.open()
|
||||
|
||||
if (shouldIncrement) {
|
||||
count.value++
|
||||
|
||||
toast.add({
|
||||
title: `Success: ${shouldIncrement}`,
|
||||
color: 'success',
|
||||
id: 'slideover-success'
|
||||
})
|
||||
|
||||
// Update the count
|
||||
slideover.patch({
|
||||
count: count.value
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
toast.add({
|
||||
title: `Dismissed: ${shouldIncrement}`,
|
||||
color: 'error',
|
||||
id: 'slideover-dismiss'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -102,7 +102,7 @@ It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.
|
||||
```
|
||||
|
||||
::note{to="/components/app"}
|
||||
The `App` component provides global configurations and is required for **Toast** and **Tooltip** components to work.
|
||||
The `App` component provides global configurations and is required for **Toast**, **Tooltip** components to work as well as **Programmatic Overlays**.
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
@@ -161,7 +161,7 @@ It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.
|
||||
```
|
||||
|
||||
::note{to="/components/app"}
|
||||
The `App` component provides global configurations and is required for **Toast** and **Tooltip** components to work.
|
||||
The `App` component provides global configurations and is required for **Toast**, **Tooltip** components to work as well as **Programmatic Overlays**.
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
---
|
||||
title: useModal
|
||||
description: 'A composable to programmatically control a Modal component.'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the auto-imported `useModal` composable to programmatically control a [Modal](/components/modal) component.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const modal = useModal()
|
||||
</script>
|
||||
```
|
||||
|
||||
- The `useModal` composable is created using `createSharedComposable`, ensuring that the same modal state is shared across your entire application.
|
||||
|
||||
::tip{to="/components/modal"}
|
||||
Learn how to customize the appearance and behavior of modals in the **Modal** component documentation.
|
||||
::
|
||||
|
||||
## API
|
||||
|
||||
### `open(component: Component, props?: ModalProps & ComponentProps<T>)`
|
||||
|
||||
Opens a modal with the specified component and props.
|
||||
|
||||
- Parameters:
|
||||
- `component`: The Vue component to render inside the modal.
|
||||
- `props`: An optional object of props to pass to both the Modal and the rendered component.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const modal = useModal()
|
||||
|
||||
function openModal() {
|
||||
modal.open(MyModalContent, { title: 'Welcome' })
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `close()`
|
||||
|
||||
Closes the currently open modal.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const modal = useModal()
|
||||
|
||||
async function closeModal() {
|
||||
await modal.close()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `reset()`
|
||||
|
||||
Resets the modal state to its default values.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const modal = useModal()
|
||||
|
||||
function resetModal() {
|
||||
modal.reset()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `patch(props: Partial<ModalProps & ComponentProps<T>>)`
|
||||
|
||||
Updates the props of the currently open modal.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const modal = useModal()
|
||||
|
||||
function updateModalTitle() {
|
||||
modal.patch({ title: 'Updated Title' })
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Here's a complete example of how to use the `useModal` composable:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<button @click="openModal">Open Modal</button>
|
||||
<button @click="closeModal">Close Modal</button>
|
||||
<button @click="updateModalTitle">Update Title</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const modal = useModal()
|
||||
|
||||
const openModal = () => {
|
||||
modal.open(MyModalContent, { title: 'Welcome' })
|
||||
}
|
||||
|
||||
const closeModal = async () => {
|
||||
await modal.close()
|
||||
}
|
||||
|
||||
const updateModalTitle = () => {
|
||||
modal.patch({ title: 'Updated Welcome' })
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
In this example, we're using the `useModal` composable to control a modal. We can open it with a specific component and props, close it, and update its props.
|
||||
166
docs/content/2.composables/use-overlay.md
Normal file
166
docs/content/2.composables/use-overlay.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: useOverlay
|
||||
description: "A composable to programmatically control overlays."
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the auto-imported `useOverlay` composable to programmatically control [Modal](/components/modal) and [Slideover](/components/slideover) components.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const overlay = useOverlay()
|
||||
|
||||
const modal = overlay.create(MyModal)
|
||||
|
||||
async function openModal() {
|
||||
modal.open()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
- The `useOverlay` composable is created using `createSharedComposable`, ensuring that the same overlay state is shared across your entire application.
|
||||
|
||||
::note
|
||||
In order to return a value from the overlay, the `overlay.open()` can be awaited. In order for this to work, however, the **overlay component must emit a `close` event**. See example below for details.
|
||||
::
|
||||
|
||||
## API
|
||||
|
||||
### `create(component: T, options: OverlayOptions): OverlayInstance`
|
||||
|
||||
Creates an overlay, and returns its instance
|
||||
|
||||
- Parameters:
|
||||
- `component`: The overlay component
|
||||
- `options` The overlay options
|
||||
- `defaultOpen?: boolean` Opens the overlay immediately after being created `default: false`
|
||||
- `props?: ComponentProps`: An optional object of props to pass to the rendered component.
|
||||
- `destroyOnClose?: boolean` Removes the overlay from memory when closed `default: false`
|
||||
|
||||
### `open(id: symbol, props?: ComponentProps<T>): Promise<any>`
|
||||
|
||||
Opens the overlay using its `id`
|
||||
|
||||
- Parameters:
|
||||
- `id`: The identifier of the overlay
|
||||
- `props`: An optional object of props to pass to the rendered component.
|
||||
|
||||
### `close(id: symbol, value?: any): void`
|
||||
|
||||
Close an overlay using its `id`
|
||||
|
||||
- Parameters:
|
||||
- `id`: The identifier of the overlay
|
||||
- `value`: A value to resolve the overlay promise with
|
||||
|
||||
### `patch(id: symbol, props: ComponentProps<T>): void`
|
||||
|
||||
Update an overlay using its `id`
|
||||
|
||||
- Parameters:
|
||||
- `id`: The identifier of the overlay
|
||||
- `props`: An object of props to update on the rendered component.
|
||||
|
||||
### `unmount(id: symbol): void`
|
||||
|
||||
Removes the overlay from the DOM using its `id`
|
||||
|
||||
- Parameters:
|
||||
- `id`: The identifier of the overlay
|
||||
|
||||
### `overlays: Overlay[]`
|
||||
|
||||
In-memory list of overlays that were created
|
||||
|
||||
## Overlay Instance API
|
||||
|
||||
### `open(props?: ComponentProps<T>): Promise<any>`
|
||||
|
||||
Opens the overlay
|
||||
|
||||
- Parameters:
|
||||
- `props`: An optional object of props to pass to the rendered component.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const overlay = useOverlay()
|
||||
|
||||
const modal = overlay.create(MyModalContent)
|
||||
|
||||
function openModal() {
|
||||
modal.open({
|
||||
title: 'Welcome'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `close(value?: any): void`
|
||||
|
||||
Close the overlay
|
||||
|
||||
- Parameters:
|
||||
- `value`: A value to resolve the overlay promise with
|
||||
|
||||
### `patch(props: ComponentProps<T>)`
|
||||
|
||||
Updates the props of the overlay.
|
||||
|
||||
- Parameters:
|
||||
- `props`: An object of props to update on the rendered component.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const overlay = useOverlay()
|
||||
|
||||
const modal = overlay.create(MyModal, {
|
||||
title: 'Welcome'
|
||||
})
|
||||
|
||||
function openModal() {
|
||||
modal.open()
|
||||
}
|
||||
|
||||
function updateModalTitle() {
|
||||
modal.patch({ title: 'Updated Title' })
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Here's a complete example of how to use the `useOverlay` composable:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const overlay = useOverlay()
|
||||
|
||||
// Create with default props
|
||||
const modalA = overlay.create(ModalA, { title: 'Welcome' })
|
||||
const modalB = overlay.create(modalB)
|
||||
|
||||
const slideoverA = overlay.create(SlideoverA)
|
||||
|
||||
const openModalA = () => {
|
||||
// Open Modal A, but override the title prop
|
||||
modalA.open({ title: 'Hello' })
|
||||
}
|
||||
|
||||
const openModalB = async () => {
|
||||
// Open modalB, and wait for its result
|
||||
const input = await modalB.open()
|
||||
|
||||
// Pass the result from modalB to the slideover, and open it.
|
||||
slideoverA.open({ input })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button @click="openModal">Open Modal</button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
In this example, we're using the `useOverlay` composable to control multiple modals and slideovers.
|
||||
@@ -1,114 +0,0 @@
|
||||
---
|
||||
title: useSlideover
|
||||
description: 'A composable to programmatically control a Slideover component.'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the auto-imported `useSlideover` composable to programmatically control a [Slideover](/components/slideover) component.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const slideover = useSlideover()
|
||||
</script>
|
||||
```
|
||||
|
||||
- The `useSlideover` composable is created using `createSharedComposable`, ensuring that the same slideover state is shared across your entire application.
|
||||
|
||||
::tip{to="/components/slideover"}
|
||||
Learn how to customize the appearance and behavior of slideovers in the **Slideover** component documentation.
|
||||
::
|
||||
|
||||
## API
|
||||
|
||||
### `open(component: Component, props?: SlideoverProps & ComponentProps<T>)`
|
||||
|
||||
Opens a slideover with the specified component and props.
|
||||
|
||||
- Parameters:
|
||||
- `component`: The Vue component to render inside the slideover.
|
||||
- `props`: An optional object of props to pass to both the Slideover and the rendered component.
|
||||
|
||||
````vue
|
||||
<script setup lang="ts">
|
||||
const slideover = useSlideover()
|
||||
|
||||
function openSlideover() {
|
||||
slideover.open(MySlideoverContent, { title: 'Welcome' })
|
||||
}
|
||||
</script>
|
||||
````
|
||||
|
||||
### `close(): Promise<void>`
|
||||
|
||||
Closes the currently open slideover.
|
||||
|
||||
````vue
|
||||
<script setup lang="ts">
|
||||
const slideover = useSlideover()
|
||||
|
||||
async function closeSlideover() {
|
||||
await slideover.close()
|
||||
}
|
||||
</script>
|
||||
````
|
||||
|
||||
### `reset()`
|
||||
|
||||
Resets the slideover state to its default values.
|
||||
|
||||
````vue
|
||||
<script setup lang="ts">
|
||||
const slideover = useSlideover()
|
||||
|
||||
function resetSlideover() {
|
||||
slideover.reset()
|
||||
}
|
||||
</script>
|
||||
````
|
||||
|
||||
### `patch(props: Partial<SlideoverProps & ComponentProps<T>>)`
|
||||
|
||||
Updates the props of the currently open slideover.
|
||||
|
||||
````vue
|
||||
<script setup lang="ts">
|
||||
const slideover = useSlideover()
|
||||
|
||||
function updateSlideoverTitle() {
|
||||
slideover.patch({ title: 'Updated Title' })
|
||||
}
|
||||
</script>
|
||||
````
|
||||
|
||||
## Example
|
||||
|
||||
Here's a complete example of how to use the `useSlideover` composable:
|
||||
|
||||
````vue
|
||||
<template>
|
||||
<div>
|
||||
<button @click="openSlideover">Open Slideover</button>
|
||||
<button @click="closeSlideover">Close Slideover</button>
|
||||
<button @click="updateSlideoverTitle">Update Title</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const slideover = useSlideover()
|
||||
|
||||
const openSlideover = () => {
|
||||
slideover.open(MySlideoverContent, { title: 'Welcome' })
|
||||
}
|
||||
|
||||
const closeSlideover = async () => {
|
||||
await slideover.close()
|
||||
}
|
||||
|
||||
const updateSlideoverTitle = () => {
|
||||
slideover.patch({ title: 'Updated Welcome' })
|
||||
}
|
||||
</script>
|
||||
````
|
||||
|
||||
In this example, we're using the `useSlideover` composable to control a slideover. We can open it with a specific component and props, close it, and update its props.
|
||||
@@ -305,21 +305,26 @@ slots:
|
||||
|
||||
### Programmatic usage
|
||||
|
||||
You can use the [`useModal`](/composables/use-modal) composable to open a Modal programatically.
|
||||
You can use the [`useOverlay`](/composables/use-overlay) composable to open a Modal programatically.
|
||||
|
||||
::warning
|
||||
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`ModalProvider`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/ModalProvider.vue) component.
|
||||
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`OverlayProvider`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/OverlayProvider.vue) component.
|
||||
::
|
||||
|
||||
First, create a modal component that will be opened programatically:
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
name: 'modal-example'
|
||||
preview: false
|
||||
---
|
||||
::
|
||||
|
||||
::note
|
||||
We are emitting a `close` event when the modal is closed or dismissed here. You can emit any data through the `close` event, however, the event must be emitted in order to capture the return value.
|
||||
::
|
||||
|
||||
Then, use it in your app:
|
||||
|
||||
::component-example
|
||||
|
||||
@@ -304,21 +304,26 @@ slots:
|
||||
|
||||
### Programmatic usage
|
||||
|
||||
You can use the [`useSlideover`](/composables/use-slideover) composable to open a Slideover programatically.
|
||||
You can use the [`useOverlay`](/composables/use-overlay) composable to open a Slideover programatically.
|
||||
|
||||
::warning
|
||||
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`SlideoverProvider`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/SlideoverProvider.vue) component.
|
||||
Make sure to wrap your app with the [`App`](/components/app) component which uses the [`OverlayProvider`](https://github.com/nuxt/ui/blob/v3/src/runtime/components/OverlayProvider.vue) component.
|
||||
::
|
||||
|
||||
First, create a slideover component that will be opened programatically:
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
name: 'slideover-example'
|
||||
preview: false
|
||||
---
|
||||
::
|
||||
|
||||
::note
|
||||
We are emitting a `close` event when the slideover is closed or dismissed here. You can emit any data through the `close` event, however, the event must be emitted in order to capture the return value.
|
||||
::
|
||||
|
||||
Then, use it in your app:
|
||||
|
||||
::component-example
|
||||
|
||||
Reference in New Issue
Block a user