conflict-nuxt-4/app/components/auto-import/BaseModal.vue
2026-02-12 11:24:27 +03:30

208 lines
5.2 KiB
Vue
Executable File

<template>
<UModal
v-model:open="isModalOpen"
:title="localModalSchema.title || ''"
:description="localModalSchema.description || ''"
:ui="modalUi"
:overlay="localModalSchema.overlay !== false"
:dismissible="localModalSchema.dismissible !== false"
:scrollable="localModalSchema.scrollable || false"
:fullscreen="localModalSchema.fullscreen || false"
>
<!-- Body -->
<template #body>
<slot name="modal-body-content" />
</template>
<!-- Footer -->
<template v-if="hasFooter" #footer>
<div class="flex w-full flex-wrap items-center justify-between gap-3">
<!-- right actions -->
<div class="flex flex-wrap gap-2">
<UButton
v-for="action in rightActions"
:key="action.key"
v-bind="resolveActionProps(action)"
@click="onActionClick(action)"
size="lg"
class="dark:text-white"
/>
</div>
<!-- left actions -->
<div class="flex flex-wrap items-center gap-2">
<UButton
v-for="action in leftActions"
:key="action.key"
v-bind="resolveActionProps(action)"
@click="onActionClick(action)"
size="lg"
class="dark:text-white"
/>
</div>
</div>
<div v-if="footerText" class="mt-2 text-sm text-gray-600">
{{ footerText }}
</div>
</template>
</UModal>
</template>
<script setup>
import { ref, computed, watch } from "vue";
/* ---------------- props & emits ---------------- */
const props = defineProps({
modalSchema: {
type: Object,
default: () => ({
title: "مدال",
description: "",
size: "lg",
footerDescription: "",
actions: {
left: [
{
key: "close",
label: "بستن",
variant: "outline",
closeOnClick: true,
},
{
key: "save",
label: "ذخیره",
color: "primary",
closeOnClick: false,
},
],
},
}),
},
isOpen: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["modal-action", "update-isOpen", "modal-close"]);
/* ---------------- state ---------------- */
const isModalOpen = ref(props.isOpen);
const footerText = ref("");
/* ---------------- computed ---------------- */
const localModalSchema = computed(() => props.modalSchema || {});
const actions = computed(() => localModalSchema.value.actions || {});
const leftActions = computed(() => {
const actionsList = actions.value.left || [];
return actionsList.filter((action) => action && action.key); // فیلتر کردن actions خالی
});
const rightActions = computed(() => {
const actionsList = actions.value.right || [];
return actionsList.filter((action) => action && action.key); // فیلتر کردن actions خالی
});
const hasFooter = computed(
() => leftActions.value.length > 0 || rightActions.value.length > 0,
);
/* -------- modal width by size -------- */
const modalWidthMap = {
sm: "max-w-[400px]",
md: "max-w-[600px]",
lg: "max-w-[900px]",
xl: "max-w-[1200px]",
};
const modalUi = computed(() => {
const size = localModalSchema.value.size || "md";
const widthClass = modalWidthMap[size] || modalWidthMap.md;
return {
...localModalSchema.value.ui,
content: [widthClass, localModalSchema.value.ui?.content]
.filter(Boolean)
.join(" "),
header: [
localModalSchema.value.ui?.header,
"bg-gray-50 dark:bg-dark-primary",
]
.filter(Boolean)
.join(" "),
footer: [
localModalSchema.value.ui?.footer,
"bg-gray-50 dark:bg-dark-primary",
]
.filter(Boolean)
.join(" "),
};
});
/* ---------------- watchers ---------------- */
/* sync parent → modal */
watch(
() => props.isOpen,
(val) => {
isModalOpen.value = val;
},
{ immediate: true },
);
/* sync modal → parent (❌ / ESC / overlay) */
watch(isModalOpen, (newVal) => {
emit("update-isOpen", newVal);
// اگر مودال بسته شد، event مخصوص close هم emit کن
if (newVal === false) {
emit("modal-close");
}
});
// /* وقتی overlay یا ESC بزند */
// function onOverlayClick() {
// emit("modal-action", {
// action: "overlay-close",
// });
// }
/* footer description */
watch(
() => localModalSchema.value.footerDescription,
(val) => {
footerText.value = val || "";
},
{ immediate: true },
);
/* ---------------- methods ---------------- */
function onActionClick(action) {
// همیشه event را emit کن (حتی برای close)
emit("modal-action", action.key);
// اگر دکمه باید مودال را ببندد
if (action.closeOnClick) {
isModalOpen.value = false;
}
}
function resolveActionProps(action) {
return {
label: action.label,
icon: action.icon,
color: action.color || "gray",
variant: action.variant || "solid",
size: action.size || "sm",
loading: action.loading || false,
disabled: action.disabled || false,
};
}
</script>