chore(Modal): refactor component with headlessui

This commit is contained in:
Benjamin Canac
2021-11-17 18:19:15 +01:00
parent 192b5fafda
commit 2f83c18c78
2 changed files with 120 additions and 154 deletions

View File

@@ -1,8 +1,36 @@
<template> <template>
<div class="mx-auto max-w-80em"> <div class="mx-auto max-w-7xl flex flex-col">
Welcome Welcome
<NuButton class="ml-3" variant="primary" icon="heroicons-outline:check-circle"> <UButton class="ml-3" variant="primary" icon="mdi:bed-single">
toto toto
</NuButton> </UButton>
<UIcon name="mdi:bullhorn" class="w-4 h-4 text-black" />
<UAvatar class="ml-3" src="https://picsum.photos/200/300" />
<UButton @click="toggleModalIsOpen()">
Toggle modal!
</UButton>
{{ isModalOpen }}
<UModal v-model="isModalOpen" title="Modal">
Body
</UModal>
<!-- <UPopover v-slot="{ open }">
<UButton trailing variant="white" :icon="open ? 'heroicons-outline:chevron-up' : 'heroicons-outline:chevron-down'">
toto
</UButton>
</UPopover> -->
</div> </div>
</template> </template>
<script setup>
const isModalOpen = ref(false)
function toggleModalIsOpen () {
isModalOpen.value = !isModalOpen.value
}
</script>

View File

@@ -1,179 +1,117 @@
<template> <template>
<div v-if="showModal" class="fixed inset-0 z-50 overflow-hidden modal"> <TransitionRoot :show="isOpen" as="template">
<div class="flex items-start justify-center min-h-screen"> <Dialog @close="setIsOpen">
<transition <div class="fixed z-10 inset-0 overflow-y-auto">
appear <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
enter-class="opacity-0" <TransitionChild
enter-active-class="duration-300 ease-out" as="template"
enter-to-class="opacity-100" enter="ease-out duration-300"
leave-class="opacity-100" enter-from="opacity-0"
leave-active-class="duration-200 ease-in" enter-to="opacity-75"
leave-to-class="opacity-0" leave="ease-in duration-200"
@before-leave="backdropLeaving = true" leave-from="opacity-75"
@after-leave="backdropLeaving = false" leave-to="opacity-0"
> entered="opacity-75"
<div v-if="showBackdrop" class="fixed inset-0 transition-opacity bg-gray-800 sm:bg-opacity-75" @click="open = false" /> >
</transition> <DialogOverlay class="fixed inset-0 bg-gray-500 transition-opacity" />
</TransitionChild>
<transition <TransitionChild
appear enter="ease-out transform duration-300"
enter-class="translate-y-4 opacity-0 sm:translate-y-0 sm:scale-95" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-active-class="duration-300 ease-out" enter-to="opacity-100 translate-y-0 sm:scale-100"
enter-to-class="translate-y-0 opacity-100 sm:scale-100" leave="ease-in transform duration-200"
leave-class="translate-y-0 opacity-100 sm:scale-100" leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-active-class="duration-200 ease-in" leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
leave-to-class="translate-y-4 opacity-0 sm:translate-y-0 sm:scale-95" >
@before-leave="contentLeaving = true" <!-- This element is to trick the browser into centering the modal contents. -->
@after-leave="contentLeaving = false" <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
> &#8203;
<Card </span>
v-if="showContent"
v-bind="$attrs" <Card
class="z-50 flex flex-col flex-1 w-screen h-screen mx-auto overflow-hidden shadow-xl" class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
:class="modalClass" v-bind="$attrs"
variant="white" variant="white"
ring-class="sm:ring-1 ring-transparent dark:ring-gray-700" ring-class="sm:ring-1 sm:ring-transparent dark:ring-gray-700"
aria-modal="true" >
> <template v-if="$slots.header" #header>
<template v-if="$slots.header" #header> <slot name="header" />
<slot name="header" /> </template>
</template> <template v-else-if="title" #header>
<template v-else-if="title" #header> <div class="flex items-center justify-between">
<div class="flex items-center justify-between"> <h2 class="font-medium sm:text-lg sm:leading-6 text-tw-gray-900">
<h2 class="font-medium sm:text-lg sm:leading-6 text-tw-gray-900"> {{ title }}
{{ title }} </h2>
</h2> <div class="flex items-center">
<div class="flex items-center"> <button
<button type="button"
type="button" aria-label="Close panel"
aria-label="Close panel" class="rounded-md text-tw-gray-400 hover:text-tw-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500"
class="rounded-md text-tw-gray-400 hover:text-tw-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500" @click="isOpen = false"
@click="open = false" >
> <Icon name="heroicons-outline:x" class="w-6 h-6" />
<Icon name="outline/x" class="w-6 h-6" /> </button>
</button> </div>
</div> </div>
</div> </template>
</template> <slot />
<slot /> <template v-if="$slots.footer" #footer>
<template v-if="$slots.footer" #footer> <slot name="footer" />
<slot name="footer" /> </template>
</template> </Card>
</Card> </TransitionChild>
</transition> </div>
</div> </div>
</div> </Dialog>
</TransitionRoot>
</template> </template>
<script> <script>
import Icon from '../elements/Icon' import { Dialog, DialogOverlay, TransitionRoot, TransitionChild } from '@headlessui/vue'
import Card from '../layout/Card' import Card from '../layout/Card'
import Icon from '../elements/Icon'
export default { export default {
components: { components: {
Icon, Dialog,
Card DialogOverlay,
TransitionRoot,
TransitionChild,
Card,
Icon
}, },
props: { props: {
value: { modelValue: {
type: Boolean, type: Boolean,
default: false default: false
}, },
title: { title: {
type: String, type: String,
default: null default: null
},
fullscreen: {
type: Boolean,
default: false
},
baseClass: {
type: String,
default: 'sm:my-20 sm:max-w-xl sm:h-auto'
} }
}, },
data () { emits: ['update:modelValue'],
return { setup (props, { emit }) {
showModal: false, const isOpen = computed({
showBackdrop: false,
showContent: false,
backdropLeaving: false,
contentLeaving: false
}
},
head () {
if (this.open) {
return {
bodyAttrs: {
class: ['overflow-hidden']
},
htmlAttrs: {
style: ['touch-action: none;']
}
}
}
return undefined
},
computed: {
modalClass () {
return this.fullscreen ? 'sm:m-10 sm:h-[calc(100vh-5rem)]' : this.baseClass
},
leaving () {
return this.backdropLeaving || this.contentLeaving
},
open: {
get () { get () {
return this.value return props.modelValue
}, },
set (open) { set (value) {
this.$emit('input', open) emit('update:modelValue', value)
} }
} })
},
watch: { return {
open: { isOpen,
handler (newValue) { setIsOpen (value) {
if (newValue) { isOpen.value = value
this.show()
} else {
this.close()
}
}, },
immediate: true toggleIsOpen () {
}, isOpen.value = !isOpen.value
leaving (newValue) {
if (newValue === false) {
this.showModal = false
this.open = false
} }
} }
},
shortcuts: {
disabled () {
return !this.open
},
esc: 'esc'
},
methods: {
show () {
this.showModal = true
this.showBackdrop = true
this.showContent = true
},
close () {
this.showBackdrop = false
this.showContent = false
},
esc () {
this.$listeners.close ? this.$listeners.close() : this.close()
}
} }
} }
</script> </script>
<style scoped>
.modal {
padding: env(safe-area-inset-top) 0 0 env(safe-area-inset-left);
}
</style>