mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-31 04:07:56 +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">
|
<script setup lang="ts">
|
||||||
const modal = useModal()
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
count: number
|
count: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits<{ close: [boolean] }>()
|
||||||
|
|
||||||
function onSuccess() {
|
|
||||||
emit('success')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
<template #footer>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<UButton color="neutral" label="Close" @click="modal.close()" />
|
<UButton color="neutral" label="Dismiss" @click="emit('close', false)" />
|
||||||
<UButton label="Success" @click="onSuccess" />
|
<UButton label="Success" @click="emit('close', true)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
|
|||||||
@@ -4,20 +4,37 @@ import { LazyModalExample } from '#components'
|
|||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const modal = useModal()
|
const overlay = useOverlay()
|
||||||
|
|
||||||
function open() {
|
const modal = overlay.create(LazyModalExample, {
|
||||||
count.value++
|
props: {
|
||||||
|
count: count.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
modal.open(LazyModalExample, {
|
async function open() {
|
||||||
description: 'And you can even provide a description!',
|
const shouldIncrement = await modal.open()
|
||||||
count: count.value,
|
|
||||||
onSuccess() {
|
if (shouldIncrement) {
|
||||||
toast.add({
|
count.value++
|
||||||
title: 'Success !',
|
|
||||||
id: 'modal-success'
|
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>
|
</script>
|
||||||
|
|||||||
@@ -1,27 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const slideover = useSlideover()
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
count: number
|
count: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits<{ close: [boolean] }>()
|
||||||
|
|
||||||
function onSuccess() {
|
|
||||||
emit('success')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
<template #body>
|
||||||
<Placeholder class="h-full" />
|
<Placeholder class="h-full" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<UButton color="neutral" label="Close" @click="slideover.close()" />
|
<UButton color="neutral" label="Dismiss" @click="emit('close', false)" />
|
||||||
<UButton label="Success" @click="onSuccess" />
|
<UButton label="Success" @click="emit('close', true)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</USlideover>
|
</USlideover>
|
||||||
|
|||||||
@@ -4,20 +4,37 @@ import { LazySlideoverExample } from '#components'
|
|||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const slideover = useSlideover()
|
const overlay = useOverlay()
|
||||||
|
|
||||||
function open() {
|
const slideover = overlay.create(LazySlideoverExample, {
|
||||||
count.value++
|
props: {
|
||||||
|
count: count.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
slideover.open(LazySlideoverExample, {
|
async function open() {
|
||||||
title: 'Slideover',
|
const shouldIncrement = await slideover.open()
|
||||||
count: count.value,
|
|
||||||
onSuccess() {
|
if (shouldIncrement) {
|
||||||
toast.add({
|
count.value++
|
||||||
title: 'Success !',
|
|
||||||
id: 'modal-success'
|
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>
|
</script>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.
|
|||||||
```
|
```
|
||||||
|
|
||||||
::note{to="/components/app"}
|
::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"}
|
::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
|
### 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
|
::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:
|
First, create a modal component that will be opened programatically:
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
|
prettier: true
|
||||||
name: 'modal-example'
|
name: 'modal-example'
|
||||||
preview: false
|
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:
|
Then, use it in your app:
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
|
|||||||
@@ -304,21 +304,26 @@ slots:
|
|||||||
|
|
||||||
### Programmatic usage
|
### 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
|
::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:
|
First, create a slideover component that will be opened programatically:
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
|
prettier: true
|
||||||
name: 'slideover-example'
|
name: 'slideover-example'
|
||||||
preview: false
|
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:
|
Then, use it in your app:
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const modal = useModal()
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
count: number
|
count: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UModal :title="`This modal was opened programmatically ${count} times`">
|
<UModal :title="`This modal was opened programmatically ${count} times`">
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<UButton color="neutral" label="Close" @click="modal.close()" />
|
<UButton color="neutral" label="Close" @click="emit('close')" />
|
||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const slideover = useSlideover()
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
count: number
|
count: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -13,7 +13,7 @@ defineProps<{
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<UButton color="neutral" label="Close" @click="slideover.close()" />
|
<UButton color="neutral" label="Close" @click="emit('close')" />
|
||||||
</template>
|
</template>
|
||||||
</USlideover>
|
</USlideover>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,16 +5,18 @@ const LazyModalExample = defineAsyncComponent(() => import('../../components/Mod
|
|||||||
|
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
|
const overlay = useOverlay()
|
||||||
|
|
||||||
const modal = useModal()
|
const modal = overlay.create(LazyModalExample, {
|
||||||
|
props: {
|
||||||
|
count: count.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function openModal() {
|
function openModal() {
|
||||||
count.value++
|
count.value++
|
||||||
|
|
||||||
modal.open(LazyModalExample, {
|
modal.open({ count: count.value })
|
||||||
description: 'And you can even provide a description!',
|
|
||||||
count: count.value
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,18 @@ const LazySlideoverExample = defineAsyncComponent(() => import('../../components
|
|||||||
|
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
|
const overlay = useOverlay()
|
||||||
|
|
||||||
const slideover = useSlideover()
|
const slideover = overlay.create(LazySlideoverExample, {
|
||||||
|
props: {
|
||||||
|
count: count.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function openSlideover() {
|
function openSlideover() {
|
||||||
count.value++
|
count.value++
|
||||||
|
|
||||||
slideover.open(LazySlideoverExample, {
|
slideover.open({ count: count.value })
|
||||||
title: 'Slideover',
|
|
||||||
count: count.value
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -113,8 +113,6 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
addPlugin({ src: resolve('./runtime/plugins/colors') })
|
addPlugin({ src: resolve('./runtime/plugins/colors') })
|
||||||
addPlugin({ src: resolve('./runtime/plugins/modal') })
|
|
||||||
addPlugin({ src: resolve('./runtime/plugins/slideover') })
|
|
||||||
|
|
||||||
addComponentsDir({
|
addComponentsDir({
|
||||||
path: resolve('./runtime/components'),
|
path: resolve('./runtime/components'),
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ import { toRef, useId, provide } from 'vue'
|
|||||||
import { ConfigProvider, TooltipProvider, useForwardProps } from 'reka-ui'
|
import { ConfigProvider, TooltipProvider, useForwardProps } from 'reka-ui'
|
||||||
import { reactivePick } from '@vueuse/core'
|
import { reactivePick } from '@vueuse/core'
|
||||||
import UToaster from './Toaster.vue'
|
import UToaster from './Toaster.vue'
|
||||||
import UModalProvider from './ModalProvider.vue'
|
import UOverlayProvider from './OverlayProvider.vue'
|
||||||
import USlideoverProvider from './SlideoverProvider.vue'
|
|
||||||
|
|
||||||
const props = defineProps<AppProps>()
|
const props = defineProps<AppProps>()
|
||||||
defineSlots<AppSlots>()
|
defineSlots<AppSlots>()
|
||||||
@@ -48,8 +47,7 @@ provide(localeContextInjectionKey, locale)
|
|||||||
</UToaster>
|
</UToaster>
|
||||||
<slot v-else />
|
<slot v-else />
|
||||||
|
|
||||||
<UModalProvider />
|
<UOverlayProvider />
|
||||||
<USlideoverProvider />
|
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ export interface ModalProps extends DialogRootProps {
|
|||||||
ui?: Partial<typeof modal.slots>
|
ui?: Partial<typeof modal.slots>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModalEmits extends DialogRootEmits {}
|
export interface ModalEmits extends DialogRootEmits {
|
||||||
|
'after:leave': []
|
||||||
|
}
|
||||||
|
|
||||||
export interface ModalSlots {
|
export interface ModalSlots {
|
||||||
default(props: { open: boolean }): any
|
default(props: { open: boolean }): any
|
||||||
@@ -126,7 +128,7 @@ const ui = computed(() => modal({
|
|||||||
<DialogPortal :disabled="!portal">
|
<DialogPortal :disabled="!portal">
|
||||||
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
|
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
|
||||||
|
|
||||||
<DialogContent :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" v-on="contentEvents">
|
<DialogContent :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" @after-leave="emits('after:leave')" v-on="contentEvents">
|
||||||
<VisuallyHidden v-if="!!slots.content && ((title || !!slots.title) || (description || !!slots.description))">
|
<VisuallyHidden v-if="!!slots.content && ((title || !!slots.title) || (description || !!slots.description))">
|
||||||
<DialogTitle v-if="title || !!slots.title">
|
<DialogTitle v-if="title || !!slots.title">
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { inject } from 'vue'
|
|
||||||
import { useModal, modalInjectionKey } from '../composables/useModal'
|
|
||||||
|
|
||||||
const modalState = inject(modalInjectionKey)
|
|
||||||
|
|
||||||
const { isOpen } = useModal()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<component :is="modalState.component" v-if="modalState" v-bind="modalState.props" v-model:open="isOpen" />
|
|
||||||
</template>
|
|
||||||
26
src/runtime/components/OverlayProvider.vue
Normal file
26
src/runtime/components/OverlayProvider.vue
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { overlays, unMount, close } = useOverlay()
|
||||||
|
|
||||||
|
const mountedOverlays = computed(() => overlays.filter(overlay => overlay.isMounted))
|
||||||
|
|
||||||
|
const onAfterLeave = (id: symbol) => {
|
||||||
|
close(id)
|
||||||
|
unMount(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = (id: symbol, value: any) => {
|
||||||
|
close(id, value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="overlay.component"
|
||||||
|
v-for="overlay in mountedOverlays"
|
||||||
|
:key="overlay.id"
|
||||||
|
v-bind="overlay.props"
|
||||||
|
v-model:open="overlay.modelValue"
|
||||||
|
@close="(value:any) => onClose(overlay.id, value)"
|
||||||
|
@after:leave="onAfterLeave(overlay.id)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -55,7 +55,9 @@ export interface SlideoverProps extends DialogRootProps {
|
|||||||
ui?: Partial<typeof slideover.slots>
|
ui?: Partial<typeof slideover.slots>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SlideoverEmits extends DialogRootEmits {}
|
export interface SlideoverEmits extends DialogRootEmits {
|
||||||
|
'after:leave': []
|
||||||
|
}
|
||||||
|
|
||||||
export interface SlideoverSlots {
|
export interface SlideoverSlots {
|
||||||
default(props: { open: boolean }): any
|
default(props: { open: boolean }): any
|
||||||
@@ -126,7 +128,7 @@ const ui = computed(() => slideover({
|
|||||||
<DialogPortal :disabled="!portal">
|
<DialogPortal :disabled="!portal">
|
||||||
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
|
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
|
||||||
|
|
||||||
<DialogContent :data-side="side" :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" v-on="contentEvents">
|
<DialogContent :data-side="side" :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" @after-leave="emits('after:leave')" v-on="contentEvents">
|
||||||
<VisuallyHidden v-if="!!slots.content && ((title || !!slots.title) || (description || !!slots.description))">
|
<VisuallyHidden v-if="!!slots.content && ((title || !!slots.title) || (description || !!slots.description))">
|
||||||
<DialogTitle v-if="title || !!slots.title">
|
<DialogTitle v-if="title || !!slots.title">
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { inject } from 'vue'
|
|
||||||
import { slideoverInjectionKey, useSlideover } from '../composables/useSlideover'
|
|
||||||
|
|
||||||
const slideoverState = inject(slideoverInjectionKey)
|
|
||||||
|
|
||||||
const { isOpen } = useSlideover()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<component :is="slideoverState.component" v-if="slideoverState" v-bind="slideoverState.props" v-model:open="isOpen" />
|
|
||||||
</template>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { ref, inject } from 'vue'
|
|
||||||
import type { ShallowRef, Component, InjectionKey } from 'vue'
|
|
||||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
|
||||||
import { createSharedComposable } from '@vueuse/core'
|
|
||||||
import type { ModalProps } from '../types'
|
|
||||||
|
|
||||||
export interface ModalState {
|
|
||||||
component: Component | string
|
|
||||||
props: ModalProps
|
|
||||||
}
|
|
||||||
|
|
||||||
export const modalInjectionKey: InjectionKey<ShallowRef<ModalState>> = Symbol('nuxt-ui.modal')
|
|
||||||
|
|
||||||
function _useModal() {
|
|
||||||
const modalState = inject(modalInjectionKey)
|
|
||||||
|
|
||||||
const isOpen = ref(false)
|
|
||||||
|
|
||||||
function open<T extends Component>(component: T, props?: ModalProps & ComponentProps<T>) {
|
|
||||||
if (!modalState) {
|
|
||||||
throw new Error('useModal() is called without provider')
|
|
||||||
}
|
|
||||||
|
|
||||||
modalState.value = {
|
|
||||||
component,
|
|
||||||
props: props ?? {}
|
|
||||||
}
|
|
||||||
|
|
||||||
isOpen.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function close() {
|
|
||||||
if (!modalState) return
|
|
||||||
|
|
||||||
isOpen.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
if (!modalState) return
|
|
||||||
|
|
||||||
modalState.value = {
|
|
||||||
component: 'div',
|
|
||||||
props: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows updating the modal props
|
|
||||||
*/
|
|
||||||
function patch<T extends Component = Record<string, never>>(props: Partial<ModalProps & ComponentProps<T>>) {
|
|
||||||
if (!modalState) return
|
|
||||||
|
|
||||||
modalState.value = {
|
|
||||||
...modalState.value,
|
|
||||||
props: {
|
|
||||||
...modalState.value.props,
|
|
||||||
...props
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
reset,
|
|
||||||
patch,
|
|
||||||
isOpen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useModal = createSharedComposable(_useModal)
|
|
||||||
118
src/runtime/composables/useOverlay.ts
Normal file
118
src/runtime/composables/useOverlay.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import type { Component } from 'vue'
|
||||||
|
import { createSharedComposable } from '@vueuse/core'
|
||||||
|
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||||
|
|
||||||
|
export type OverlayOptions<OverlayAttrs = Record<string, any>> = {
|
||||||
|
defaultOpen?: boolean
|
||||||
|
props?: OverlayAttrs
|
||||||
|
destroyOnClose?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManagedOverlayOptionsPrivate<T extends Component> = {
|
||||||
|
component?: T
|
||||||
|
id: symbol
|
||||||
|
isMounted: boolean
|
||||||
|
modelValue: boolean
|
||||||
|
resolvePromise?: (value: unknown) => void
|
||||||
|
}
|
||||||
|
export type Overlay = OverlayOptions<Component> & ManagedOverlayOptionsPrivate<Component>
|
||||||
|
|
||||||
|
interface OverlayInstance<T> {
|
||||||
|
open: (props?: ComponentProps<T>) => Promise<any>
|
||||||
|
close: (value?: any) => void
|
||||||
|
patch: (props: Partial<ComponentProps<T>>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function _useOverlay() {
|
||||||
|
const overlays = shallowReactive<Overlay[]>([])
|
||||||
|
|
||||||
|
const create = <T extends Component>(component: T, _options?: OverlayOptions<ComponentProps<T>>): OverlayInstance<T> => {
|
||||||
|
const { props: props, defaultOpen, destroyOnClose } = _options || {}
|
||||||
|
|
||||||
|
const options = reactive<Overlay>({
|
||||||
|
id: Symbol(import.meta.dev ? 'useOverlay' : ''),
|
||||||
|
modelValue: !!defaultOpen,
|
||||||
|
component: markRaw(component!),
|
||||||
|
isMounted: !!defaultOpen,
|
||||||
|
destroyOnClose: !!destroyOnClose,
|
||||||
|
props: props || {}
|
||||||
|
})
|
||||||
|
|
||||||
|
overlays.push(options)
|
||||||
|
|
||||||
|
return {
|
||||||
|
open: <T extends Component>(props?: ComponentProps<T>) => open(options.id, props),
|
||||||
|
close: value => close(options.id, value),
|
||||||
|
patch: <T extends Component>(props: Partial<ComponentProps<T>>) => patch(options.id, props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = <T extends Component>(id: symbol, props?: ComponentProps<T>): Promise<any> => {
|
||||||
|
const overlay = getOverlay(id)
|
||||||
|
|
||||||
|
// If props are provided, update the overlay's props
|
||||||
|
if (props) {
|
||||||
|
patch(overlay.id, props)
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay.modelValue = true
|
||||||
|
overlay.isMounted = true
|
||||||
|
|
||||||
|
// Return a new promise that will be resolved when close is called
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
overlay.resolvePromise = resolve
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = (id: symbol, value?: any): void => {
|
||||||
|
const overlay = getOverlay(id)
|
||||||
|
|
||||||
|
overlay.modelValue = false
|
||||||
|
|
||||||
|
// Resolve the promise if it exists
|
||||||
|
if (overlay.resolvePromise) {
|
||||||
|
overlay.resolvePromise(value)
|
||||||
|
overlay.resolvePromise = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unMount = (id: symbol): void => {
|
||||||
|
const overlay = getOverlay(id)
|
||||||
|
|
||||||
|
overlay.isMounted = false
|
||||||
|
|
||||||
|
if (overlay.destroyOnClose) {
|
||||||
|
const index = overlays.findIndex(overlay => overlay.id === id)
|
||||||
|
overlays.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const patch = <T extends Component>(id: symbol, props: Partial<ComponentProps<T>>): void => {
|
||||||
|
const overlay = getOverlay(id)
|
||||||
|
|
||||||
|
Object.entries(props!).forEach(([key, value]) => {
|
||||||
|
(overlay.props as any)[key] = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOverlay = (id: symbol): Overlay => {
|
||||||
|
const overlay = overlays.find(overlay => overlay.id === id)
|
||||||
|
|
||||||
|
if (!overlay) {
|
||||||
|
throw new Error('Overlay not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
return overlay
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
overlays,
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
create,
|
||||||
|
patch,
|
||||||
|
unMount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useOverlay = createSharedComposable(_useOverlay)
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { ref, inject } from 'vue'
|
|
||||||
import type { ShallowRef, Component, InjectionKey } from 'vue'
|
|
||||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
|
||||||
import { createSharedComposable } from '@vueuse/core'
|
|
||||||
import type { SlideoverProps } from '../types'
|
|
||||||
|
|
||||||
export interface SlideoverState {
|
|
||||||
component: Component | string
|
|
||||||
props: SlideoverProps
|
|
||||||
}
|
|
||||||
|
|
||||||
export const slideoverInjectionKey: InjectionKey<ShallowRef<SlideoverState>> = Symbol('nuxt-ui.slideover')
|
|
||||||
|
|
||||||
function _useSlideover() {
|
|
||||||
const slideoverState = inject(slideoverInjectionKey)
|
|
||||||
|
|
||||||
const isOpen = ref(false)
|
|
||||||
|
|
||||||
function open<T extends Component>(component: T, props?: SlideoverProps & ComponentProps<T>) {
|
|
||||||
if (!slideoverState) {
|
|
||||||
throw new Error('useSlideover() is called without provider')
|
|
||||||
}
|
|
||||||
|
|
||||||
slideoverState.value = {
|
|
||||||
component,
|
|
||||||
props: props ?? {}
|
|
||||||
}
|
|
||||||
|
|
||||||
isOpen.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function close() {
|
|
||||||
if (!slideoverState) return
|
|
||||||
|
|
||||||
isOpen.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
if (!slideoverState) return
|
|
||||||
|
|
||||||
slideoverState.value = {
|
|
||||||
component: 'div',
|
|
||||||
props: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows updating the slideover props
|
|
||||||
*/
|
|
||||||
function patch<T extends Component = Record<string, never>>(props: Partial<SlideoverState & ComponentProps<T>>) {
|
|
||||||
if (!slideoverState) return
|
|
||||||
|
|
||||||
slideoverState.value = {
|
|
||||||
...slideoverState.value,
|
|
||||||
props: {
|
|
||||||
...slideoverState.value.props,
|
|
||||||
...props
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
reset,
|
|
||||||
patch,
|
|
||||||
isOpen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSlideover = createSharedComposable(_useSlideover)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { shallowRef } from 'vue'
|
|
||||||
import { defineNuxtPlugin } from '#imports'
|
|
||||||
// FIXME: https://github.com/nuxt/module-builder/issues/141#issuecomment-2078248248
|
|
||||||
import type {} from '#app'
|
|
||||||
import { modalInjectionKey, type ModalState } from '../composables/useModal'
|
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
|
||||||
const modalState = shallowRef<ModalState>({
|
|
||||||
component: 'div',
|
|
||||||
props: {}
|
|
||||||
})
|
|
||||||
nuxtApp.vueApp.provide(modalInjectionKey, modalState)
|
|
||||||
})
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { shallowRef } from 'vue'
|
|
||||||
import { defineNuxtPlugin } from '#imports'
|
|
||||||
// FIXME: https://github.com/nuxt/module-builder/issues/141#issuecomment-2078248248
|
|
||||||
import type {} from '#app'
|
|
||||||
import { slideoverInjectionKey, type SlideoverState } from '../composables/useSlideover'
|
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
|
||||||
const slideoverState = shallowRef<SlideoverState>({
|
|
||||||
component: 'div',
|
|
||||||
props: {}
|
|
||||||
})
|
|
||||||
|
|
||||||
nuxtApp.vueApp.provide(slideoverInjectionKey, slideoverState)
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user