Skip to content

Instantly share code, notes, and snippets.

@mohitmamoria
Last active June 27, 2023 12:34
Show Gist options
  • Save mohitmamoria/f1b1c97c5f021f74c627deb197cc28af to your computer and use it in GitHub Desktop.
Save mohitmamoria/f1b1c97c5f021f74c627deb197cc28af to your computer and use it in GitHub Desktop.
Vue3 Composable Overlays/Modals/Slide-overs

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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment