All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Reviewed-on: #1 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
127 lines
3.7 KiB
Vue
127 lines
3.7 KiB
Vue
<template>
|
|
<Teleport v-if="isOpen" to="body">
|
|
<Transition name="modal" appear>
|
|
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
<!-- Backdrop -->
|
|
<div
|
|
class="absolute inset-0 bg-slate-900/40 backdrop-blur-sm"
|
|
@click="close"
|
|
/>
|
|
|
|
<!-- Modal -->
|
|
<div
|
|
class="relative z-10 flex w-full flex-col overflow-hidden rounded-2xl bg-white shadow-2xl ring-1 ring-black/5"
|
|
:class="maxWidthClass"
|
|
style="max-height: min(90vh, 900px)"
|
|
>
|
|
<!-- Header -->
|
|
<div class="border-b border-neutral-100 bg-neutral-50/80 px-4 py-4 sm:px-8 sm:py-5">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-lg font-bold tracking-tight text-neutral-900">
|
|
<slot name="title" />
|
|
</h2>
|
|
<MalioButtonIcon
|
|
icon="mdi:close"
|
|
aria-label="Fermer"
|
|
variant="ghost"
|
|
icon-size="20"
|
|
@click="close"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div class="overflow-y-auto px-4 py-4 sm:px-8 sm:py-6">
|
|
<slot />
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="border-t border-neutral-100 px-4 py-4 sm:px-8">
|
|
<div class="flex justify-end gap-3">
|
|
<slot name="footer">
|
|
<MalioButton
|
|
:label="cancelLabel"
|
|
variant="tertiary"
|
|
@click="close"
|
|
/>
|
|
<MalioButton
|
|
:label="submitLabel"
|
|
:loading="loading"
|
|
@click="$emit('submit')"
|
|
/>
|
|
</slot>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const props = withDefaults(defineProps<{
|
|
modelValue: boolean
|
|
submitLabel?: string
|
|
cancelLabel?: string
|
|
loading?: boolean
|
|
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
}>(), {
|
|
submitLabel: 'Enregistrer',
|
|
cancelLabel: 'Annuler',
|
|
loading: false,
|
|
maxWidth: '2xl',
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', value: boolean): void
|
|
(e: 'submit'): void
|
|
}>()
|
|
|
|
const isOpen = computed({
|
|
get: () => props.modelValue,
|
|
set: (v) => emit('update:modelValue', v),
|
|
})
|
|
|
|
function close() {
|
|
isOpen.value = false
|
|
}
|
|
|
|
const maxWidthClass = computed(() => {
|
|
const map = {
|
|
sm: 'max-w-sm',
|
|
md: 'max-w-md',
|
|
lg: 'max-w-lg',
|
|
xl: 'max-w-xl',
|
|
'2xl': 'max-w-2xl',
|
|
}
|
|
return map[props.maxWidth]
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.modal-enter-active,
|
|
.modal-leave-active {
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.modal-enter-active > div:last-child,
|
|
.modal-leave-active > div:last-child {
|
|
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.2s ease;
|
|
}
|
|
|
|
.modal-enter-from,
|
|
.modal-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
.modal-enter-from > div:last-child {
|
|
transform: scale(0.95) translateY(8px);
|
|
opacity: 0;
|
|
}
|
|
|
|
.modal-leave-to > div:last-child {
|
|
transform: scale(0.97);
|
|
opacity: 0;
|
|
}
|
|
</style>
|