Skip to content

Commit d67942a

Browse files
authored
Merge pull request #490 from devforth/feature/AdminForth/1296/add-docs-for-the-afcl-modal-an
Feature/admin forth/1296/add docs for the afcl modal an
2 parents 2f5fc55 + 92e515c commit d67942a

File tree

6 files changed

+216
-137
lines changed

6 files changed

+216
-137
lines changed

adminforth/documentation/docs/tutorial/03-Customization/15-afcl.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2317,6 +2317,44 @@ import { JsonViewer } from '@/afcl'
23172317
</div>
23182318
</div>
23192319

2320+
## Modal
2321+
2322+
<div class="split-screen" >
2323+
<div>
2324+
2325+
```ts
2326+
import { Modal, Button } from '@/afcl';
2327+
```
2328+
2329+
```html
2330+
<Modal class="w-96" :closeByClickOutside="true" :closeByEsc="true" askForCloseConfirmation >
2331+
<template #trigger>
2332+
<Button>Modal Toggle</Button>
2333+
</template>
2334+
2335+
<div class="space-y-4 p-4">
2336+
<p>This is the first paragraph of modal content.</p>
2337+
<p>And this is the second paragraph.</p>
2338+
</div>
2339+
</Modal>
2340+
```
2341+
</div>
2342+
<div>
2343+
![AFCL Dialog](image-98.png)
2344+
</div>
2345+
</div>
2346+
2347+
### Props:
2348+
```ts
2349+
closeByClickOutside?: boolean // Close on click outside of modal (default is true)
2350+
closeByEsc?: boolean // Close on Esc button click (default is true)
2351+
beforeCloseFunction?: (() => void | Promise<void>) | null // Callback, that will be executed before open
2352+
beforeOpenFunction?: (() => void | Promise<void>) | null // Callback, that will be executed before close
2353+
askForCloseConfirmation?: boolean // Show extra popup to confirm close ( to avoid close by accident)
2354+
closeConfirmationText?: string // Text that will be shown on close confirmation popup
2355+
removeFromDomOnClose?: boolean // Remove modal from DOM on close ( default is false )
2356+
```
2357+
23202358
## Date picker
23212359

23222360
```ts
13.2 KB
Loading

adminforth/spa/src/afcl/Dialog.vue

Lines changed: 155 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,188 @@
11
<template>
2-
<div
3-
v-if="$slots.trigger"
4-
@click="modal?.show()" class="inline-flex items-center cursor-pointer"
2+
<Modal
3+
ref="modalRef"
4+
v-bind="$attrs"
5+
:closeByClickOutside="clickToCloseOutside || closeByClickOutside"
6+
:closeByEsc="closeByEsc || closable"
7+
:beforeCloseFunction="beforeCloseFunction"
8+
:beforeOpenFunction="beforeOpenFunction"
9+
:askForCloseConfirmation="askForCloseConfirmation"
10+
:closeConfirmationText="closeConfirmationText"
11+
:removeFromDomOnClose="removeFromDomOnClose"
512
>
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-
<!-- Modal header -->
14-
<div
15-
v-if="header"
16-
class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-darkDialogBreakLine border-lightDialogBreakLine"
17-
>
18-
<h3 class="text-xl font-semibold text-lightDialogHeaderText dark:text-darkDialogHeaderText">
19-
{{ header }}
20-
</h3>
21-
<button
22-
v-if="headerCloseButton"
23-
type="button"
24-
class="text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
25-
@click="tryToHideModal"
26-
>
27-
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
28-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
29-
</svg>
30-
<span class="sr-only">Close modal</span>
31-
</button>
32-
</div>
33-
<!-- Modal body -->
34-
<div class="p-4 md:p-5 space-y-4 text-lightDialogBodyText dark:text-darkDialogBodyText">
35-
<slot></slot>
36-
</div>
37-
<!-- Modal footer -->
38-
<div
39-
v-if="buttons.length"
40-
class="flex items-center p-4 md:p-5 border-t border-lightDialogBreakLine rounded-b dark:border-darkDialogBreakLine"
41-
>
42-
<Button
43-
v-for="(button, buttonIndex) in buttons"
44-
:key="buttonIndex"
45-
v-bind="button.options"
46-
:class="{ 'ms-3': buttonIndex > 0 }"
47-
@click="button.onclick(modal)"
48-
>
49-
{{ button.label }}
50-
</Button>
51-
</div>
52-
</div>
53-
</div>
54-
<div>
55-
<!-- Confirmation Modal -->
56-
<div
57-
v-if="showConfirmationOnClose"
58-
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-[60]"
59-
>
60-
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
61-
<h2 class="text-lg font-semibold mb-4 text-lightDialogHeaderText dark:text-darkDialogHeaderText">Confirm Close</h2>
62-
<p class="mb-6 text-lightDialogBodyText dark:text-darkDialogBodyText">{{ props.closeConfirmationText }}</p>
63-
<div class="flex justify-end">
64-
<Button
65-
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"
66-
@click="showConfirmationOnClose = false"
67-
>
68-
Cancel
69-
</Button>
70-
<Button
71-
@click="
72-
showConfirmationOnClose = false;
73-
modal?.hide();
74-
"
75-
>
76-
Confirm
77-
</Button>
78-
</div>
79-
</div>
80-
</div>
81-
</div>
13+
<template v-if="$slots.trigger" #trigger>
14+
<slot name="trigger"></slot>
15+
</template>
16+
17+
<!-- Modal header -->
18+
<div
19+
v-if="header"
20+
class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-darkDialogBreakLine border-lightDialogBreakLine"
21+
>
22+
<h3 class="text-xl font-semibold text-lightDialogHeaderText dark:text-darkDialogHeaderText">
23+
{{ header }}
24+
</h3>
25+
<button
26+
v-if="headerCloseButton"
27+
type="button"
28+
class="text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
29+
@click="tryToHideModal"
30+
>
31+
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
32+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
33+
</svg>
34+
<span class="sr-only">{{ t('Close Modal') }}</span>
35+
</button>
36+
</div>
37+
<!-- Modal body -->
38+
<div class="p-4 md:p-5 text-lightDialogBodyText dark:text-darkDialogBodyText">
39+
<slot></slot>
40+
</div>
41+
<!-- Modal footer -->
42+
<div
43+
v-if="buttons.length"
44+
class="flex items-center p-4 md:p-5 border-t border-lightDialogBreakLine rounded-b dark:border-darkDialogBreakLine"
45+
>
46+
<Button
47+
v-for="(button, buttonIndex) in buttons"
48+
:key="buttonIndex"
49+
v-bind="button.options"
50+
:class="{ 'ms-3': buttonIndex > 0 }"
51+
@click="button.onclick(dialog)"
52+
>
53+
{{ button.label }}
54+
</Button>
8255
</div>
83-
</Teleport>
56+
</Modal>
8457
</template>
8558

8659
<script setup lang="ts">
8760
import Button from "./Button.vue";
88-
import { ref, onMounted, nextTick, onUnmounted, computed, type Ref } from 'vue';
89-
import { Modal } from 'flowbite';
61+
import { ref, computed, type Ref } from 'vue';
62+
import { Modal } from '@/afcl'
63+
import { useI18n } from "vue-i18n";
64+
65+
const { t } = useI18n();
66+
67+
68+
interface IDialogInsideButtonClickHandler {
69+
hide: () => void
70+
}
9071
91-
const modalEl = ref(null);
92-
const modal: Ref<Modal|null> = ref(null);
9372
9473
interface DialogButton {
9574
label: string
96-
onclick: (dialog: any) => void
75+
onclick: (dialog: IDialogInsideButtonClickHandler) => void
9776
options?: Record<string, any>
9877
}
9978
79+
10080
interface DialogProps {
81+
/**
82+
* The header text to display in the dialog. If not provided, no header will be displayed.
83+
*/
10184
header?: string
85+
86+
/**
87+
* If true, a close button will be displayed in the dialog header. Default is true.
88+
*/
10289
headerCloseButton?: boolean
90+
91+
/**
92+
* An array of buttons to display in the dialog footer.
93+
*/
10394
buttons?: DialogButton[]
95+
96+
/**
97+
* If true, clicking outside the dialog will close it. Default is true.
98+
*
99+
* @deprecated Use `closeByClickOutside` instead
100+
*/
104101
clickToCloseOutside?: boolean
102+
103+
/**
104+
* If true, pressing the Esc key will close the dialog. Default is true.
105+
*/
106+
closeByEsc?: boolean
107+
108+
/**
109+
* If true, clicking outside the dialog will close it. Default is true.
110+
*/
111+
closeByClickOutside?: boolean
112+
113+
/**
114+
* Function that will be called before the dialog is closed.
115+
*/
105116
beforeCloseFunction?: (() => void | Promise<void>) | null
117+
118+
/**
119+
* Function that will be called before the dialog is opened.
120+
*/
106121
beforeOpenFunction?: (() => void | Promise<void>) | null
122+
123+
/**
124+
* Disables close on Ecs button
125+
*
126+
* @deprecated Use `closeByEsc` or instead
127+
*/
107128
closable?: boolean
129+
130+
/**
131+
* If true, the dialog will ask for confirmation before closing. Default is false.
132+
*/
108133
askForCloseConfirmation?: boolean
134+
135+
/**
136+
* The text to display in the close confirmation dialog. Default is "Are you sure you want to close this dialog?".
137+
*/
109138
closeConfirmationText?: string
139+
140+
/**
141+
* If true, the dialog will be removed from the DOM when closed. Default is false.
142+
*/
143+
removeFromDomOnClose?: boolean
110144
}
111145
146+
147+
148+
149+
150+
151+
/********** for the backward compatibility ***************/
152+
class Dialog implements IDialogInsideButtonClickHandler {
153+
hide: () => void
154+
constructor( hide: () => void ) {
155+
this.hide = hide;
156+
}
157+
}
158+
const dialog: Ref<Dialog> = ref(
159+
new Dialog(
160+
() => {
161+
if (dialog.value) {
162+
tryToHideModal();
163+
}
164+
}
165+
)
166+
);
167+
/*************************************************************/
168+
169+
170+
171+
const modalRef = ref();
172+
112173
const props = withDefaults(defineProps<DialogProps>(), {
113174
header: '',
114175
headerCloseButton: true,
115176
buttons: () => [],
116-
clickToCloseOutside: true,
177+
clickToCloseOutside: false,
178+
closeByEsc: true,
179+
closeByClickOutside: true,
117180
beforeCloseFunction: null,
118181
beforeOpenFunction: null,
119-
closable: true,
182+
closable: false,
120183
askForCloseConfirmation: false,
121184
closeConfirmationText: 'Are you sure you want to close this dialog?',
185+
removeFromDomOnClose: false,
122186
})
123187
124188
const buttons = computed<DialogButton[]>(() => {
@@ -129,51 +193,20 @@ const buttons = computed<DialogButton[]>(() => {
129193
{
130194
label: 'Close',
131195
onclick: (dialog: any) => {
132-
if (!props.askForCloseConfirmation) {
133-
dialog.hide();
134-
} else {
135-
showConfirmationOnClose.value = true;
136-
}
196+
tryToHideModal();
137197
},
138198
options: {}
139199
}
140200
];
141201
});
142202
143-
const showConfirmationOnClose = ref(false);
144-
onMounted(async () => {
145-
//await one tick when all is mounted
146-
await nextTick();
147-
modal.value = new Modal(
148-
modalEl.value,
149-
{
150-
closable: props.closable,
151-
backdrop: props.clickToCloseOutside ? 'dynamic' : 'static',
152-
onHide: async () => {
153-
if (props.beforeCloseFunction) {
154-
await props.beforeCloseFunction();
155-
}
156-
},
157-
onShow: async () => {
158-
if (props.beforeOpenFunction) {
159-
await props.beforeOpenFunction();
160-
}
161-
},
162-
}
163-
);
164-
})
165-
166-
onUnmounted(() => {
167-
//destroy tooltip
168-
modal.value?.destroy();
169-
})
170203
171204
function open() {
172-
modal.value?.show();
205+
modalRef.value.open();
173206
}
174207
175208
function close() {
176-
modal.value?.hide();
209+
modalRef.value.hide();
177210
}
178211
179212
defineExpose({
@@ -183,11 +216,7 @@ defineExpose({
183216
})
184217
185218
function tryToHideModal() {
186-
if (!props.askForCloseConfirmation ) {
187-
modal.value?.hide();
188-
} else {
189-
showConfirmationOnClose.value = true;
190-
}
219+
modalRef.value?.tryToHideModal();
191220
}
192221
193222

0 commit comments

Comments
 (0)