Skip to content

Commit 75a1159

Browse files
committed
feat: add afcl modal component
1 parent dda5490 commit 75a1159

File tree

2 files changed

+157
-1
lines changed

2 files changed

+157
-1
lines changed

adminforth/spa/src/afcl/Modal.vue

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<template>
2+
<div
3+
v-if="$slots.trigger"
4+
@click="modal?.show()" class="inline-flex items-center cursor-pointer"
5+
>
6+
<slot name="trigger"></slot>
7+
</div>
8+
<Teleport to="body">
9+
<div ref="modalEl" tabindex="-1" aria-hidden="true" class="[scrollbar-gutter:stable] hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-full max-h-full">
10+
<div v-bind="$attrs" class="relative p-4 max-w-2xl max-h-full" :class="($attrs.class as string)?.includes('w-') ? '' : 'w-full'">
11+
<!-- Modal content -->
12+
<div class="relative bg-lightDialogBackgorund rounded-lg shadow-sm dark:bg-darkDialogBackgorund">
13+
14+
<!-- Modal body -->
15+
<div class="p-4 md:p-5 space-y-4 text-lightDialogBodyText dark:text-darkDialogBodyText">
16+
<slot></slot>
17+
</div>
18+
19+
<!-- Confirmation Modal -->
20+
<div
21+
v-if="showConfirmationOnClose"
22+
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-[60]"
23+
>
24+
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
25+
<h2 class="text-lg font-semibold mb-4 text-lightDialogHeaderText dark:text-darkDialogHeaderText">Confirm Close</h2>
26+
<p class="mb-6 text-lightDialogBodyText dark:text-darkDialogBodyText">{{ props.closeConfirmationText }}</p>
27+
<div class="flex justify-end">
28+
<Button
29+
class="me-3 !bg-gray-50 dark:!bg-gray-700 !text-lightDialogBodyText dark:!text-darkDialogBodyText hover:!bg-gray-100 dark:hover:!bg-gray-600 !border-gray-200 dark:!border-gray-600"
30+
@click="showConfirmationOnClose = false"
31+
>
32+
Cancel
33+
</Button>
34+
<Button
35+
@click="
36+
showConfirmationOnClose = false;
37+
modal?.hide();
38+
"
39+
>
40+
Confirm
41+
</Button>
42+
</div>
43+
</div>
44+
</div>
45+
</div>
46+
</div>
47+
</div>
48+
</Teleport>
49+
</template>
50+
51+
<script setup lang="ts">
52+
import Button from "./Button.vue";
53+
import { ref, onMounted, nextTick, onUnmounted, computed, type Ref } from 'vue';
54+
import { Modal } from 'flowbite';
55+
56+
const modalEl = ref(null);
57+
const modal: Ref<Modal|null> = ref(null);
58+
59+
interface DialogButton {
60+
label: string
61+
onclick: (dialog: any) => void
62+
options?: Record<string, any>
63+
}
64+
65+
interface DialogProps {
66+
buttons?: DialogButton[]
67+
clickToCloseOutside?: boolean
68+
beforeCloseFunction?: (() => void | Promise<void>) | null
69+
beforeOpenFunction?: (() => void | Promise<void>) | null
70+
closable?: boolean
71+
askForCloseConfirmation?: boolean
72+
closeConfirmationText?: string
73+
}
74+
75+
const props = withDefaults(defineProps<DialogProps>(), {
76+
buttons: () => [],
77+
clickToCloseOutside: true,
78+
beforeCloseFunction: null,
79+
beforeOpenFunction: null,
80+
closable: true,
81+
askForCloseConfirmation: false,
82+
closeConfirmationText: 'Are you sure you want to close this dialog?',
83+
})
84+
85+
const buttons = computed<DialogButton[]>(() => {
86+
if (props.buttons && props.buttons.length > 0) {
87+
return props.buttons;
88+
}
89+
return [
90+
{
91+
label: 'Close',
92+
onclick: (dialog: any) => {
93+
if (!props.askForCloseConfirmation) {
94+
dialog.hide();
95+
} else {
96+
showConfirmationOnClose.value = true;
97+
}
98+
},
99+
options: {}
100+
}
101+
];
102+
});
103+
104+
const showConfirmationOnClose = ref(false);
105+
onMounted(async () => {
106+
//await one tick when all is mounted
107+
await nextTick();
108+
modal.value = new Modal(
109+
modalEl.value,
110+
{
111+
closable: props.closable,
112+
backdrop: props.clickToCloseOutside ? 'dynamic' : 'static',
113+
onHide: async () => {
114+
if (props.beforeCloseFunction) {
115+
await props.beforeCloseFunction();
116+
}
117+
},
118+
onShow: async () => {
119+
if (props.beforeOpenFunction) {
120+
await props.beforeOpenFunction();
121+
}
122+
},
123+
}
124+
);
125+
})
126+
127+
onUnmounted(() => {
128+
//destroy tooltip
129+
modal.value?.destroy();
130+
})
131+
132+
function open() {
133+
modal.value?.show();
134+
}
135+
136+
function close() {
137+
modal.value?.hide();
138+
}
139+
140+
defineExpose({
141+
open: open,
142+
close: close,
143+
tryToHideModal: tryToHideModal,
144+
})
145+
146+
function tryToHideModal() {
147+
if (!props.askForCloseConfirmation ) {
148+
modal.value?.hide();
149+
} else {
150+
showConfirmationOnClose.value = true;
151+
}
152+
}
153+
154+
155+
</script>

adminforth/spa/src/afcl/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ export { default as Toggle } from './Toggle.vue';
2424
export { default as DatePicker } from './DatePicker.vue';
2525
export { default as Textarea } from './Textarea.vue';
2626
export { default as ButtonGroup } from './ButtonGroup.vue';
27-
export { default as Card } from './Card.vue';
27+
export { default as Card } from './Card.vue';
28+
export { default as Modal } from './Modal.vue';

0 commit comments

Comments
 (0)