-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduced
ModalRoot
component to prevent rendering modals inside `…
…ModalLink` (#4)
- Loading branch information
1 parent
a9cbc87
commit c862022
Showing
10 changed files
with
278 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,175 @@ | ||
<script setup> | ||
import { inject, onBeforeUnmount, ref, provide } from 'vue' | ||
import { inject, onBeforeUnmount, ref, computed, useAttrs, onMounted } from 'vue' | ||
import { TransitionRoot, TransitionChild, Dialog } from '@headlessui/vue' | ||
import { getConfig, getConfigByType } from './config' | ||
import { modalPropNames } from './modalStack' | ||
import { only } from './helpers' | ||
import { useModalStack } from './modalStack' | ||
import ModalContent from './ModalContent.vue' | ||
import ModalWrapper from './ModalWrapper.vue' | ||
import ModalRenderer from './ModalRenderer.vue' | ||
import SlideoverContent from './SlideoverContent.vue' | ||
import { useModalStack } from './modalStack' | ||
const props = defineProps({ | ||
name: { | ||
type: String, | ||
required: false, | ||
}, | ||
// The slideover prop in on top because we need to know if it's a slideover | ||
// before we can determine the defaule value of other props | ||
slideover: { | ||
type: Boolean, | ||
default: () => getConfig('type') === 'slideover', | ||
}, | ||
closeButton: { | ||
type: Boolean, | ||
default: (props) => getConfigByType(props.slideover, 'closeButton'), | ||
}, | ||
closeExplicitly: { | ||
type: Boolean, | ||
default: (props) => getConfigByType(props.slideover, 'closeExplicitly'), | ||
}, | ||
maxWidth: { | ||
type: String, | ||
default: (props) => getConfigByType(props.slideover, 'maxWidth'), | ||
}, | ||
paddingClasses: { | ||
type: [Boolean, String], | ||
default: (props) => getConfigByType(props.slideover, 'paddingClasses'), | ||
}, | ||
panelClasses: { | ||
type: [Boolean, String], | ||
default: (props) => getConfigByType(props.slideover, 'panelClasses'), | ||
}, | ||
position: { | ||
type: String, | ||
default: (props) => getConfigByType(props.slideover, 'position'), | ||
}, | ||
}) | ||
const modalStack = useModalStack() | ||
const injectedModalContext = props.name ? ref({}) : inject('modalContext') | ||
const modalContext = props.name ? ref({}) : inject('modalContext') | ||
const modalProps = computed(() => { | ||
return { | ||
...only(props, modalPropNames), | ||
...modalContext.value.modalProps, | ||
} | ||
}) | ||
// Local Modals... | ||
if (props.name) { | ||
modalStack.registerLocalModal(props.name, function (context) { | ||
injectedModalContext.value = context | ||
modalContext.value = context | ||
registerEventListeners() | ||
}) | ||
// Now this component is the provider instead of ModalLink | ||
provide('modalContext', injectedModalContext) | ||
onBeforeUnmount(() => { | ||
modalStack.removeLocalModal(props.name) | ||
}) | ||
} | ||
const emits = defineEmits(['emit']) | ||
onMounted(() => { | ||
modalStack.verifyRoot() | ||
if (!props.name) { | ||
registerEventListeners() | ||
} | ||
}) | ||
function closeDialog() { | ||
if (!modalProps.value.closeExplicitly) { | ||
modalContext.value.close() | ||
} | ||
} | ||
const unsubscribeEventListeners = ref(null) | ||
onBeforeUnmount(() => unsubscribeEventListeners.value?.()) | ||
const $attrs = useAttrs() | ||
function registerEventListeners() { | ||
unsubscribeEventListeners.value = modalContext.value.registerEventListenersFromAttrs($attrs) | ||
} | ||
const emits = defineEmits(['modal-event']) | ||
function emit(event, ...args) { | ||
emits('emit', event, ...args) | ||
emits('modal-event', event, ...args) | ||
} | ||
defineExpose({ | ||
close: injectedModalContext.value.close, | ||
close: modalContext.value.close, | ||
emit, | ||
getChildModal: injectedModalContext.value.getChildModal, | ||
getParentModal: injectedModalContext.value.getParentModal, | ||
modalContext: injectedModalContext.value, | ||
reload: injectedModalContext.value.reload, | ||
getChildModal: modalContext.value.getChildModal, | ||
getParentModal: modalContext.value.getParentModal, | ||
modalContext: modalContext.value, | ||
reload: modalContext.value.reload, | ||
}) | ||
</script> | ||
<template> | ||
<ModalWrapper v-slot="{ modalContext, modalProps }"> | ||
<component | ||
:is="modalProps.slideover ? SlideoverContent : ModalContent" | ||
:modal-context="modalContext" | ||
:modal-props="modalProps" | ||
<TransitionRoot | ||
:unmount="false" | ||
:show="modalContext.open ?? false" | ||
enter="transition transform ease-in-out duration-300" | ||
enter-from="opacity-0 scale-95" | ||
enter-to="opacity-100 scale-100" | ||
leave="transition transform ease-in-out duration-300" | ||
leave-from="opacity-100 scale-100" | ||
leave-to="opacity-0 scale-95" | ||
> | ||
<Dialog | ||
:data-inertiaui-modal-id="modalContext.id" | ||
:data-inertiaui-modal-index="modalContext.index" | ||
class="im-dialog relative z-20" | ||
@close="closeDialog" | ||
> | ||
<slot | ||
:close="modalContext.close" | ||
:emit="emit" | ||
:get-child-modal="modalContext.getChildModal" | ||
:get-parent-modal="modalContext.getParentModal" | ||
<!-- Only transition the backdrop for the first modal in the stack --> | ||
<TransitionChild | ||
v-if="modalContext.index === 0" | ||
as="template" | ||
enter="transition transform ease-in-out duration-300" | ||
enter-from="opacity-0" | ||
enter-to="opacity-100" | ||
leave="transition transform ease-in-out duration-300" | ||
leave-from="opacity-100" | ||
leave-to="opacity-0" | ||
> | ||
<div | ||
v-show="modalContext.onTopOfStack" | ||
class="im-backdrop fixed inset-0 z-30 bg-black/75" | ||
aria-hidden="true" | ||
/> | ||
</TransitionChild> | ||
<!-- On multiple modals, only show a backdrop for the modal that is on top of the stack --> | ||
<div | ||
v-if="modalContext.index > 0 && modalContext.onTopOfStack" | ||
class="im-backdrop fixed inset-0 z-30 bg-black/75" | ||
/> | ||
<!-- The modal/slideover content itself --> | ||
<component | ||
:is="modalProps.slideover ? SlideoverContent : ModalContent" | ||
:modal-context="modalContext" | ||
:modal-props="modalProps" | ||
:reload="modalContext.reload" | ||
> | ||
<slot | ||
:close="modalContext.close" | ||
:emit="emit" | ||
:get-child-modal="modalContext.getChildModal" | ||
:get-parent-modal="modalContext.getParentModal" | ||
:modal-context="modalContext" | ||
:modal-props="modalProps" | ||
:reload="modalContext.reload" | ||
/> | ||
</component> | ||
<!-- The next modal in the stack --> | ||
<ModalRenderer | ||
v-if="modalStack.stack.value[modalContext.index + 1]" | ||
:index="modalContext.index + 1" | ||
/> | ||
</component> | ||
</ModalWrapper> | ||
</Dialog> | ||
</TransitionRoot> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<script setup> | ||
import { useModalStack } from './modalStack' | ||
import { computed, provide } from 'vue' | ||
const props = defineProps({ | ||
index: { | ||
type: Number, | ||
required: true, | ||
}, | ||
}) | ||
const modalStack = useModalStack() | ||
const modalContext = computed(() => { | ||
return modalStack.stack.value[props.index] | ||
}) | ||
provide('modalContext', modalContext) | ||
</script> | ||
|
||
<template> | ||
<modalContext.component | ||
v-if="modalContext?.component" | ||
v-bind="modalContext.componentProps" | ||
@modal-event="(event, ...args) => modalContext.emit(event, ...args)" | ||
/> | ||
</template> |
Oops, something went wrong.