-
-
Save antixrist/4458c76b34c183c3fa93975d39bdd12b to your computer and use it in GitHub Desktop.
Модалки могут быть вложены друг в друга, могут открываться одна поверх другой, а закрываться по `Esc` будут в правильном порядке. Модалка не диктует правила для контента по визуальному отображению. Поэтому бэкграунды, крестик для закрытия, ширину/высоту - можно настроить индивидуально для содержимого каждой модалки
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
export default { | |
data () { | |
return { myModalOpened: false }; | |
} | |
}; | |
</script> | |
<template> | |
<p> <button @click="myModalOpened = !myModalOpened">Модалочка</button> <p> | |
<modal v-model="myModalOpened"> | |
<p><button @click="myModalOpened = false;">Закрыть</button></p> | |
<p>Контент модалки. Здесь может быть что угодно, в т.ч. другие компоненты</p> | |
</modal> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
function modalsOnEscListener (e) { | |
if (e.keyCode !== 27) { return; } | |
let topOpenedModal; | |
[...this.$root.createdModals] | |
.reverse() | |
.some(instance => instance.isOpened && instance.closeOnEsc ? (topOpenedModal = instance, true) : false) | |
; | |
topOpenedModal && topOpenedModal.close(); | |
} | |
export default { | |
props: { | |
value: { | |
type: Boolean, | |
default: false | |
}, | |
position: { | |
type: String, | |
default: '' | |
}, | |
overlay: { | |
type: Boolean, | |
default: true | |
}, | |
closeOnOverlayClick: { | |
type: Boolean, | |
default: true | |
}, | |
closeOnEsc: { | |
type: Boolean, | |
default: true | |
}, | |
overlayCSS: Object, | |
bodyCSS: Object, | |
}, | |
watch: { | |
value: { | |
immediate: true, | |
handler (opened, oldVal) { | |
document && document.documentElement.classList[opened ? 'add' : 'remove' ]('modal-opened'); | |
}, | |
}, | |
}, | |
computed: { | |
isOpened () { | |
return this.value; | |
} | |
}, | |
methods: { | |
open () { | |
this.$emit('input', true); | |
}, | |
close () { | |
this.$emit('input', false); | |
}, | |
closeOnOverlay () { | |
this.overlay && this.closeOnOverlayClick && this.close(); | |
}, | |
}, | |
beforeCreate () { | |
// beforeCreate - просто создаётся эксземпляр компонента | |
this.$root.createdModals = this.$root.createdModals || []; | |
this.$root.createdModals.push(this); | |
this.$root.modalsOnEscListenerAdded = this.$root.modalsOnEscListenerAdded || false; | |
}, | |
beforeMount () { | |
// beforeMount - компонент уже монтируется в dom-дерево. поэтому и событие можно вешать | |
if (!this.$root.modalsOnEscListenerAdded) { | |
this.$root.modalsOnEscListenerAdded = true; | |
document && document.addEventListener('keyup', modalsOnEscListener.bind(this)); | |
} | |
}, | |
beforeDestroy () { | |
const { createdModals } = this.$root; | |
createdModals.splice(createdModals.indexOf(this), 1); | |
// перед уничтожением проверим - есть ли ещё инстансы какой-нибудь модалки | |
if (!createdModals.length) { | |
// если нет, то снимаем событие на `esc` | |
document && document.removeEventListener('keyup', modalsOnEscListener); | |
} | |
}, | |
}; | |
</script> | |
<template lang="pug"> | |
.modal(v-if="value") | |
.modal__overlay( | |
v-if="overlay", | |
:style="overlayCSS", | |
@click="closeOnOverlay()", | |
) | |
.modal__outer | |
.modal__outer-scrollable | |
.modal__y-outer(@click.self="closeOnOverlay()") | |
.modal__y( | |
@click.self="closeOnOverlay()", | |
:style="{ 'vertical-align': position == 'center' ? 'middle' : position }", | |
) | |
.modal__x(@click.self="closeOnOverlay()") | |
.modal__body(@click.self="closeOnOverlay()", :style="bodyCSS") | |
.modal__content: slot | |
</template> | |
<style lang="scss"> | |
html.modal-opened { | |
height: 100%; | |
overflow: hidden; | |
} | |
body { | |
height: 100%; | |
overflow: hidden; | |
} | |
.modal { | |
position: fixed; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
overflow: hidden; | |
z-index: 999; | |
&__overlay { | |
position: absolute; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
background: rgba(#000, .7); | |
} | |
&__outer { | |
position: absolute; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
z-index: 2; | |
overflow: hidden; | |
&-scrollable { | |
position: absolute; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
overflow-y: auto; | |
} | |
} | |
&__body { | |
padding: 1em; | |
} | |
&__content { | |
box-shadow: 0 0 10px rgba(#000, .6); | |
position: relative; | |
} | |
&__y-outer { | |
display: table; | |
width: 100%; | |
height: 100%; | |
} | |
&__y { | |
display: table-cell; | |
vertical-align: middle; | |
text-align: center; | |
} | |
&__x { | |
text-align: left; | |
display: inline-block; | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment