feat: Some improvements for modal component

Added click outside, persistent and close on Esc
This commit is contained in:
Ilya Artamonov
2023-07-15 13:19:22 +03:00
parent 10be366d17
commit 591789b10f
4 changed files with 100 additions and 72 deletions

View File

@@ -2,10 +2,23 @@
import ModalExample from './modal/examples/ModalExample.vue'; import ModalExample from './modal/examples/ModalExample.vue';
import ModalSizeExample from './modal/examples/ModalSizeExample.vue'; import ModalSizeExample from './modal/examples/ModalSizeExample.vue';
import ModalEscapableExample from './modal/examples/ModalEscapableExample.vue'; import ModalEscapableExample from './modal/examples/ModalEscapableExample.vue';
import ModalPersistentExample from './modal/examples/ModalPersistentExample.vue';
</script> </script>
# Vue Modal - Flowbite # Vue Modal - Flowbite
## Demo #### Use the modal component to show interactive dialogs and notifications to your website users available in multiple sizes, colors, and styles
---
:::tip
Original reference: [https://flowbite.com/docs/components/modal/](https://flowbite.com/docs/components/modal/)
:::
The modal component can be used as an interactive dialog on top of the main content area of the website to show notifications and gather information using form elements from your website users.
Get started with multiple sizes, colors, and styles built with the utility classes from Tailwind CSS and the components from Flowbite.
## Default modal
<ModalExample /> <ModalExample />
@@ -101,3 +114,35 @@ import { Modal } from 'flowbite-vue'
<Modal :escapable="false" /> <Modal :escapable="false" />
</template> </template>
``` ```
## Persistent
Clicking outside of the element or pressing esc key will not send `close` event.
Demo:
<ModalPersistentExample />
```vue
<script setup>
import { Modal } from 'flowbite-vue'
</script>
<template>
<Modal persistent />
</template>
```
## API
### Props:
| Name | Values | Default |
|------------|-----------------------------------------------------------|---------|
| size | `md`,`lg`, `xl`, `2xl`, `3xl`, `4xl`, `5xl`, `6xl`, `7xl` | 2xl |
| escapable | `true`, `false` | `true` |
| persistent | `true`, `false` | `true` |
### Events:
| Name | Type |
|---------------------|----------------------------------------------------------------------------------|
| `close` | Clicked on the close button, pressed `Esc`, or clicked outside the modal content |
| `click:outside` | Clicked outside the modal content |

View File

@@ -3,7 +3,7 @@
<button @click="showModal" type="button" class="mt-5 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"> <button @click="showModal" type="button" class="mt-5 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
{{ triggerText }} {{ triggerText }}
</button> </button>
<Modal :escapable="escapable" :size="size" v-if="isShowModal" @close="closeModal"> <Modal :escapable="escapable" :size="size" v-if="isShowModal" @close="closeModal" :persistent="persistent">
<template #header> <template #header>
<div class="flex items-center text-lg"> <div class="flex items-center text-lg">
Terms of Service Terms of Service
@@ -32,42 +32,22 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Modal } from '../../../../src/index' import { Modal } from '../../../../src/index'
import type { PropType } from 'vue' import type { ModalSize } from '../../../../src/components/Modal/types'
import type { ModalSize, ModalPosition } from '../../../../src/components/Modal/types'
import { ref } from 'vue' import { ref } from 'vue'
defineProps({ interface ModalProps {
children: { size?: ModalSize,
type: Array, escapable?: boolean,
default() { persistent?: boolean,
return [] triggerText?: string
}, }
}, withDefaults(defineProps<ModalProps>(), {
popup: { size: '2xl',
type: Boolean, escapable: true,
default: false, persistent: false,
}, triggerText: 'Demo Modal',
position: {
type: String as PropType<ModalPosition>,
default: 'center',
},
show: {
type: Boolean,
default: false,
},
size: {
type: String as PropType<ModalSize>,
default: '2xl',
},
escapable: {
type: Boolean,
default: true,
},
triggerText: {
type: String,
default: 'Demo Modal',
},
}) })
const isShowModal = ref(false) const isShowModal = ref(false)
function closeModal() { function closeModal() {
isShowModal.value = false isShowModal.value = false

View File

@@ -0,0 +1,8 @@
<template>
<div class="vp-raw">
<ModalExample trigger-text="Persistent" :persistent="true" />
</div>
</template>
<script setup>
import ModalExample from './ModalExample.vue'
</script>

View File

@@ -2,12 +2,13 @@
<div> <div>
<div class="bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40" /> <div class="bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40" />
<div <div
tabindex="-1" tabindex="0"
ref="modalRef"
class="overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 w-full md:inset-0 h-modal md:h-full justify-center items-center flex" class="overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 w-full md:inset-0 h-modal md:h-full justify-center items-center flex"
v-on="escapable ? { click: closeModal } : {}" @keyup.esc="closeWithEsc"
@click.self="clickOutside"
> >
<div <div
@click.stop
class="relative p-4 w-full h-full md:h-auto" class="relative p-4 w-full h-full md:h-auto"
:class="`${modalSizeClasses[size]}`" :class="`${modalSizeClasses[size]}`"
> >
@@ -16,7 +17,7 @@
<!-- Modal header --> <!-- Modal header -->
<div class="p-4 rounded-t flex justify-between items-center" :class="$slots.header ? 'border-b border-gray-200 dark:border-gray-600' : ''"> <div class="p-4 rounded-t flex justify-between items-center" :class="$slots.header ? 'border-b border-gray-200 dark:border-gray-600' : ''">
<slot name="header" /> <slot name="header" />
<button v-if="escapable" @click="closeModal" aria-label="close" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <button v-if="!persistent" @click="closeModal" aria-label="close" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white">
<slot name="close-icon"> <slot name="close-icon">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</slot> </slot>
@@ -36,34 +37,20 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue' import type { ModalSize } from './types'
import type { ModalSize, ModalPosition } from './types' import { onMounted, ref, type Ref } from 'vue'
const props = defineProps({ interface ModalProps {
children: { size?: ModalSize,
type: Array, escapable?: boolean,
default() { persistent?: boolean
return [] }
}, const props = withDefaults(defineProps<ModalProps>(), {
}, size: '2xl',
popup: { escapable: true,
type: Boolean, persistent: false,
default: false,
},
position: {
type: String as PropType<ModalPosition>,
default: 'center',
},
size: {
type: String as PropType<ModalSize>,
default: '2xl',
},
escapable: {
type: Boolean,
default: true
},
}) })
const emit = defineEmits(['close']) const emit = defineEmits(['close', 'click:outside'])
const modalSizeClasses = { const modalSizeClasses = {
xs: 'max-w-xs', xs: 'max-w-xs',
sm: 'max-w-sm', sm: 'max-w-sm',
@@ -81,12 +68,20 @@ const modalSizeClasses = {
function closeModal() { function closeModal() {
emit('close') emit('close')
} }
function clickOutside() {
addEventListener("keyup", function(e) { if(!props.persistent) {
if (props.escapable) { emit('click:outside')
if (e.key === "Escape") { closeModal()
closeModal();
} }
} }
function closeWithEsc() {
if(props.escapable && !props.persistent) closeModal()
}
const modalRef: Ref<HTMLElement | null> = ref(null)
onMounted(() => {
if(modalRef.value) {
modalRef.value.focus()
}
}) })
</script> </script>