Files
CRM_Montzar/apps/web/src/components/ui/UiModal.vue
T

220 lines
4.3 KiB
Vue

<script setup lang="ts">
import { onUnmounted, watch } from 'vue';
import UiIcon from '@/components/UiIcon.vue';
const props = defineProps<{
open: boolean;
title: string;
}>();
const emit = defineEmits<{
close: [];
}>();
watch(
() => props.open,
(open) => {
document.body.style.overflow = open ? 'hidden' : '';
},
);
onUnmounted(() => {
document.body.style.overflow = '';
});
function onBackdrop(event: MouseEvent) {
if (event.target === event.currentTarget) emit('close');
}
</script>
<template>
<Teleport to="body">
<div v-if="open" class="backdrop" @click="onBackdrop">
<section
class="dialog"
role="dialog"
aria-modal="true"
:aria-label="title"
@click.stop
>
<header class="head">
<h2>{{ title }}</h2>
<button type="button" class="close" aria-label="بستن" @click="emit('close')">
<UiIcon name="x" :size="18" />
</button>
</header>
<div class="body">
<slot />
</div>
<footer v-if="$slots.footer" class="foot">
<slot name="footer" />
</footer>
</section>
</div>
</Teleport>
</template>
<style scoped>
.backdrop {
position: fixed;
inset: 0;
z-index: 80;
display: grid;
place-items: center;
padding: 16px;
background: rgba(15, 23, 42, 0.35);
}
.dialog {
width: min(880px, 100%);
max-height: min(88dvh, 88vh, 880px);
min-height: 0;
display: flex;
flex-direction: column;
background: #fff;
border: 1px solid var(--line);
border-radius: var(--radius, 10px);
box-shadow: 0 18px 48px rgba(15, 23, 42, 0.16);
overflow: hidden;
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
padding: 16px;
border-bottom: 1px solid var(--line);
flex-shrink: 0;
background: #fff;
z-index: 10;
}
.head h2 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: var(--ink);
line-height: 1.4;
}
.close {
width: 36px;
height: 36px;
border: 1px solid var(--line);
border-radius: var(--radius, 10px);
background: #fff;
color: var(--muted);
display: grid;
place-items: center;
cursor: pointer;
transition: all var(--transition-fast);
}
.close:hover {
background: var(--surface-subtle);
color: var(--ink);
border-color: var(--line-strong);
}
.body {
flex: 1 1 auto;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
padding: 16px;
box-sizing: border-box;
}
.body :deep(.crm-page.embedded),
.body :deep(.page.embedded),
.body :deep(.product-form-page.embedded),
.body :deep(.company-form-page.embedded) {
min-height: 0;
max-width: 100%;
}
.body :deep(.page.embedded > .form),
.body :deep(.page.embedded > form),
.body :deep(.crm-page.embedded > .form),
.body :deep(.crm-page.embedded > form),
.body :deep(.crm-page.embedded > .crm-form) {
min-height: 0;
}
.body :deep(.embed-actions),
.body :deep(.embedded-actions),
.body :deep(.form-bottom-actions),
.body :deep(.form-footer-actions) {
order: 99;
flex-shrink: 0;
position: sticky;
bottom: -16px;
margin-top: auto;
margin-inline: -16px;
margin-bottom: -16px;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 12px;
padding: 16px;
background: #fff;
border-top: 1px solid var(--line);
z-index: 10;
box-shadow: 0 -4px 14px rgba(15, 23, 42, 0.05);
}
.body :deep(.embed-actions .btn),
.body :deep(.embedded-actions .btn),
.body :deep(.embed-actions .ui-btn),
.body :deep(.embedded-actions .ui-btn),
.body :deep(.form-bottom-actions .btn),
.body :deep(.form-bottom-actions .ui-btn),
.body :deep(.form-footer-actions .btn),
.body :deep(.form-footer-actions .ui-btn) {
border-radius: var(--radius, 10px);
}
.foot {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 12px;
padding: 16px;
border-top: 1px solid var(--line);
flex-shrink: 0;
background: #fff;
z-index: 10;
}
@media (max-width: 900px) {
.backdrop {
padding: 0;
place-items: stretch;
}
.dialog {
width: 100%;
height: 100%;
max-height: 100dvh;
border-radius: 0;
border: 0;
}
.head {
padding: 16px;
}
.body {
flex: 1 1 0%;
padding: 16px;
}
.foot {
padding: 16px calc(16px + env(safe-area-inset-bottom));
}
}
</style>