feat(useOverlay)!: handle programmatic modals and slideovers (#3279)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Eugen Istoc
2025-02-27 11:32:48 -05:00
committed by GitHub
parent 607d9a7b4e
commit 108d36fd8a
27 changed files with 422 additions and 497 deletions

View File

@@ -26,8 +26,7 @@ import { toRef, useId, provide } from 'vue'
import { ConfigProvider, TooltipProvider, useForwardProps } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import UToaster from './Toaster.vue'
import UModalProvider from './ModalProvider.vue'
import USlideoverProvider from './SlideoverProvider.vue'
import UOverlayProvider from './OverlayProvider.vue'
const props = defineProps<AppProps>()
defineSlots<AppSlots>()
@@ -48,8 +47,7 @@ provide(localeContextInjectionKey, locale)
</UToaster>
<slot v-else />
<UModalProvider />
<USlideoverProvider />
<UOverlayProvider />
</TooltipProvider>
</ConfigProvider>
</template>

View File

@@ -56,7 +56,9 @@ export interface ModalProps extends DialogRootProps {
ui?: Partial<typeof modal.slots>
}
export interface ModalEmits extends DialogRootEmits {}
export interface ModalEmits extends DialogRootEmits {
'after:leave': []
}
export interface ModalSlots {
default(props: { open: boolean }): any
@@ -126,7 +128,7 @@ const ui = computed(() => modal({
<DialogPortal :disabled="!portal">
<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))">
<DialogTitle v-if="title || !!slots.title">
<slot name="title">

View File

@@ -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>

View 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>

View File

@@ -55,7 +55,9 @@ export interface SlideoverProps extends DialogRootProps {
ui?: Partial<typeof slideover.slots>
}
export interface SlideoverEmits extends DialogRootEmits {}
export interface SlideoverEmits extends DialogRootEmits {
'after:leave': []
}
export interface SlideoverSlots {
default(props: { open: boolean }): any
@@ -126,7 +128,7 @@ const ui = computed(() => slideover({
<DialogPortal :disabled="!portal">
<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))">
<DialogTitle v-if="title || !!slots.title">
<slot name="title">

View File

@@ -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>