How to use:
useOverlay(CreateNewBookModal)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
<script setup> | |
import Button from '@/Components/Button.vue'; | |
import Card from '@/Components/Card.vue'; | |
import InputError from '@/Components/InputError.vue'; | |
import InputLabel from '@/Components/InputLabel.vue'; | |
import Modal from '@/Components/Modal.vue'; | |
import TextInput from '@/Components/TextInput.vue'; | |
import { useAPIForm } from '@/Composables/useAPIForm'; | |
const form = useAPIForm({ | |
url: '', | |
}); | |
const submit = (resolve) => { | |
form.post(route('api.missions.store'), { | |
onSuccess: function (response) { | |
resolve(response); | |
}, | |
}); | |
}; | |
</script> | |
<template> | |
<Modal title="New Book" description="Create a new book"> | |
<template #default="{ resolve }"> | |
<form @submit.prevent="submit(resolve)"> | |
</form> | |
</template> | |
<template #footer="{ resolve, reject }"> | |
<Button @click.prevent="submit(resolve)" :class="{ 'opacity-25': form.processing }">Create</Button> | |
</template> | |
</Modal> | |
</template> |
<script setup> | |
import CloseButton from '@/Components/CloseButton.vue'; | |
import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'; | |
import { ExclamationTriangleIcon } from '@heroicons/vue/24/outline'; | |
import { ref } from 'vue'; | |
const props = defineProps({ | |
title: { | |
type: String, | |
required: true, | |
}, | |
description: { | |
type: String, | |
default: '', | |
}, | |
}); | |
const emit = defineEmits(['resolve', 'reject']); | |
const open = ref(true); | |
const close = () => { | |
open.value = false; | |
}; | |
const resolve = (response) => { | |
emit('resolve', response); | |
close(); | |
}; | |
const reject = (error) => { | |
emit('reject', error); | |
close(); | |
}; | |
</script> | |
<template> | |
<TransitionRoot as="template" :show="open"> | |
<Dialog as="div" class="relative z-10" @close="open = false"> | |
<TransitionChild | |
as="template" | |
enter="ease-out duration-300" | |
enter-from="opacity-0" | |
enter-to="opacity-100" | |
leave="ease-in duration-200" | |
leave-from="opacity-100" | |
leave-to="opacity-0" | |
> | |
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div> | |
</TransitionChild> | |
<div class="fixed inset-0 z-10 overflow-y-auto"> | |
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> | |
<TransitionChild | |
as="template" | |
enter="ease-out duration-300" | |
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" | |
enter-to="opacity-100 translate-y-0 sm:scale-100" | |
leave="ease-in duration-200" | |
leave-from="opacity-100 translate-y-0 sm:scale-100" | |
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" | |
> | |
<DialogPanel | |
class="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg" | |
> | |
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4"> | |
<div class="sm:flex sm:items-start"> | |
<div | |
class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10" | |
> | |
<ExclamationTriangleIcon class="h-6 w-6 text-red-600" aria-hidden="true" /> | |
</div> | |
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left"> | |
<DialogTitle as="h3" class="text-base font-semibold leading-6 text-gray-900"> | |
{{ title }} | |
</DialogTitle> | |
<div class="absolute right-0 top-0 hidden pr-4 pt-4 sm:block"> | |
<CloseButton @close="reject()"></CloseButton> | |
</div> | |
<div class="mt-2"> | |
<p class="text-sm text-gray-500"> | |
{{ description }} | |
</p> | |
</div> | |
<div class="mt-2"> | |
<slot :resolve="resolve" :reject="reject"></slot> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div | |
class="space-y-2 bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:space-x-2 sm:space-y-0 sm:px-6" | |
> | |
<slot name="footer" :resolve="resolve" :reject="reject"></slot> | |
</div> | |
</DialogPanel> | |
</TransitionChild> | |
</div> | |
</div> | |
</Dialog> | |
</TransitionRoot> | |
</template> |
import { h, render } from 'vue'; | |
class Overlay { | |
constructor(component, props = {}) { | |
this.component = component; | |
this.props = props; | |
return this.show(); | |
} | |
show() { | |
let mountEl = document.createElement('div'); | |
document.body.appendChild(mountEl); | |
const destroy = () => { | |
mountEl.remove(); | |
}; | |
return new Promise((resolve, reject) => { | |
this.props.onResolve = (response) => { | |
destroy(); | |
resolve(response); | |
}; | |
this.props.onReject = (error) => { | |
destroy(); | |
reject(error); | |
}; | |
let overlay = h(this.component, this.props); | |
render(overlay, mountEl); | |
}); | |
} | |
} | |
export function useOverlay(component, props = {}) { | |
return new Overlay(component, props); | |
} |