208 lines
5.2 KiB
Vue
Executable File
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>
|