mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-31 04:07:56 +01:00
chore(Modal): refactor component with headlessui
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
>
|
​
|
||||||
<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>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user