Skip to content

Commit 4a7cac3

Browse files
committed
refactor: keep local state for open value, so it can be modified internally
1 parent 9d2fb6f commit 4a7cac3

File tree

2 files changed

+77
-86
lines changed

2 files changed

+77
-86
lines changed

src/runtime/components/Modal.vue

Lines changed: 77 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { AppConfig } from '@nuxt/schema'
55
import theme from '#build/ui/modal'
66
import type { ButtonProps } from '../types'
77
import type { EmitsToProps, ComponentConfig } from '../types/utils'
8-
import ModalContext from './ModalContext.vue'
98
109
type Modal = ComponentConfig<typeof theme, AppConfig, 'modal'>
1110
@@ -59,6 +58,7 @@ export interface ModalEmits extends DialogRootEmits {
5958
'after:leave': []
6059
'after:enter': []
6160
'close:prevent': []
61+
'close': []
6262
}
6363
6464
export interface ModalSlots {
@@ -74,8 +74,8 @@ export interface ModalSlots {
7474
</script>
7575

7676
<script setup lang="ts">
77-
import { computed, toRef } from 'vue'
78-
import { reactivePick } from '@vueuse/core'
77+
import { computed, toRef, watch } from 'vue'
78+
import { reactivePick, useVModel } from '@vueuse/core'
7979
import { useAppConfig } from '#imports'
8080
import { useLocale } from '../composables/useLocale'
8181
import { usePortal } from '../composables/usePortal'
@@ -96,7 +96,9 @@ const slots = defineSlots<ModalSlots>()
9696
const { t } = useLocale()
9797
const appConfig = useAppConfig() as Modal['AppConfig']
9898
99-
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
99+
const open = useVModel(props, 'open', emits, { passive: true })
100+
const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'modal'), emits)
101+
100102
const portalProps = usePortal(toRef(() => props.portal))
101103
const contentProps = toRef(() => props.content)
102104
const contentEvents = computed(() => {
@@ -123,77 +125,80 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.modal || {})
123125
transition: props.transition,
124126
fullscreen: props.fullscreen
125127
}))
128+
129+
function closeModal() {
130+
open.value = false
131+
emits('close')
132+
}
126133
</script>
127134

128135
<template>
129-
<DialogRoot v-slot="{ open }" v-bind="rootProps">
130-
<ModalContext v-slot="{ onOpenChange }">
131-
<DialogTrigger v-if="!!slots.default" as-child :class="props.class">
132-
<slot :open="open" />
133-
</DialogTrigger>
134-
135-
<DialogPortal v-bind="portalProps">
136-
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
137-
138-
<DialogContent :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" @after-enter="emits('after:enter')" @after-leave="emits('after:leave')" v-on="contentEvents">
139-
<VisuallyHidden v-if="!!slots.content && ((title || !!slots.title) || (description || !!slots.description))">
140-
<DialogTitle v-if="title || !!slots.title">
141-
<slot name="title" :close="() => onOpenChange(false)">
142-
{{ title }}
143-
</slot>
144-
</DialogTitle>
145-
146-
<DialogDescription v-if="description || !!slots.description">
147-
<slot name="description" :close="() => onOpenChange(false)">
148-
{{ description }}
149-
</slot>
150-
</DialogDescription>
151-
</VisuallyHidden>
152-
153-
<slot name="content" :close="() => onOpenChange(false)">
154-
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
155-
<slot name="header" :close="() => onOpenChange(false)">
156-
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
157-
<DialogTitle v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
158-
<slot name="title" :close="() => onOpenChange(false)">
159-
{{ title }}
160-
</slot>
161-
</DialogTitle>
162-
163-
<DialogDescription v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
164-
<slot name="description" :close="() => onOpenChange(false)">
165-
{{ description }}
166-
</slot>
167-
</DialogDescription>
168-
</div>
169-
170-
<DialogClose v-if="close || !!slots.close" as-child>
171-
<slot name="close" :ui="ui">
172-
<UButton
173-
v-if="close"
174-
:icon="closeIcon || appConfig.ui.icons.close"
175-
size="md"
176-
color="neutral"
177-
variant="ghost"
178-
:aria-label="t('modal.close')"
179-
v-bind="(typeof close === 'object' ? close as Partial<ButtonProps> : {})"
180-
:class="ui.close({ class: props.ui?.close })"
181-
/>
136+
<DialogRoot v-model:open="open" v-bind="rootProps">
137+
<DialogTrigger v-if="!!slots.default" as-child :class="props.class">
138+
<slot :open="open" />
139+
</DialogTrigger>
140+
141+
<DialogPortal v-bind="portalProps">
142+
<DialogOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
143+
144+
<DialogContent :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" @after-enter="emits('after:enter')" @after-leave="emits('after:leave')" v-on="contentEvents">
145+
<VisuallyHidden v-if="!!slots.content && ((title || !!slots.title) || (description || !!slots.description))">
146+
<DialogTitle v-if="title || !!slots.title">
147+
<slot name="title" :close="closeModal">
148+
{{ title }}
149+
</slot>
150+
</DialogTitle>
151+
152+
<DialogDescription v-if="description || !!slots.description">
153+
<slot name="description" :close="closeModal">
154+
{{ description }}
155+
</slot>
156+
</DialogDescription>
157+
</VisuallyHidden>
158+
159+
<slot name="content" :close="closeModal">
160+
<div v-if="!!slots.header || (title || !!slots.title) || (description || !!slots.description) || (close || !!slots.close)" :class="ui.header({ class: props.ui?.header })">
161+
<slot name="header" :close="closeModal">
162+
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
163+
<DialogTitle v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
164+
<slot name="title" :close="closeModal">
165+
{{ title }}
166+
</slot>
167+
</DialogTitle>
168+
169+
<DialogDescription v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
170+
<slot name="description" :close="closeModal">
171+
{{ description }}
182172
</slot>
183-
</DialogClose>
184-
</slot>
185-
</div>
186-
187-
<div v-if="!!slots.body" :class="ui.body({ class: props.ui?.body })">
188-
<slot name="body" :close="() => onOpenChange(false)" />
189-
</div>
190-
191-
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
192-
<slot name="footer" :close="() => onOpenChange(false)" />
193-
</div>
194-
</slot>
195-
</DialogContent>
196-
</DialogPortal>
197-
</ModalContext>
173+
</DialogDescription>
174+
</div>
175+
176+
<DialogClose v-if="close || !!slots.close" as-child>
177+
<slot name="close" :ui="ui">
178+
<UButton
179+
v-if="close"
180+
:icon="closeIcon || appConfig.ui.icons.close"
181+
size="md"
182+
color="neutral"
183+
variant="ghost"
184+
:aria-label="t('modal.close')"
185+
v-bind="(typeof close === 'object' ? close as Partial<ButtonProps> : {})"
186+
:class="ui.close({ class: props.ui?.close })"
187+
/>
188+
</slot>
189+
</DialogClose>
190+
</slot>
191+
</div>
192+
193+
<div v-if="!!slots.body" :class="ui.body({ class: props.ui?.body })">
194+
<slot name="body" :close="closeModal" />
195+
</div>
196+
197+
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
198+
<slot name="footer" :close="closeModal" />
199+
</div>
200+
</slot>
201+
</DialogContent>
202+
</DialogPortal>
198203
</DialogRoot>
199204
</template>

src/runtime/components/ModalContext.vue

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)