Refine modal system

This commit is contained in:
Kacper Donat 2020-10-01 21:41:39 +02:00
parent e0574615a7
commit 3bc176936c
7 changed files with 127 additions and 19 deletions

View File

@ -21,7 +21,7 @@
<slot name="actions"> <slot name="actions">
<button class="btn btn-action" ref="action-info" @click="details = !details"> <button class="btn btn-action" ref="action-info" @click="details = !details">
<tooltip>dodatkowe informacje</tooltip> <tooltip>dodatkowe informacje</tooltip>
<ui-icon :icon="details ? 'info-hide' : 'info'"/> <ui-icon icon="info"/>
</button> </button>
<button class="btn btn-action" ref="action-map" v-hover:map> <button class="btn btn-action" ref="action-map" v-hover:map>
@ -30,11 +30,16 @@
</slot> </slot>
</div> </div>
</div> </div>
<fold :visible="details" class="stop__details-fold" lazy>
<stop-details :stop="stop"/>
</fold>
<keep-alive> <keep-alive>
<portal to="popups">
<ui-dialog v-if="details" @leave="details = false" behaviour="modal" class="ui-modal--medium" title="Szczegóły przystanku">
<stop-details :stop="stop"/>
</ui-dialog>
</portal>
</keep-alive>
<keep-alive>
<!-- FIXME: This should be in portal but it's not possible due to information loss, maybe in vue3 it will be better?-->
<ui-dialog reference="action-map" v-if="showMap" arrow class="ui-popup--no-padding" style="width: 500px;" placement="right-start" v-hover:inMap> <ui-dialog reference="action-map" v-if="showMap" arrow class="ui-popup--no-padding" style="width: 500px;" placement="right-start" v-hover:inMap>
<stop-map :stop="stop" style="height: 300px"/> <stop-map :stop="stop" style="height: 300px"/>
</ui-dialog> </ui-dialog>

View File

@ -16,7 +16,9 @@
<div class="track__description"> <div class="track__description">
{{ track.description }} {{ track.description }}
</div> </div>
<span class="badge badge-pill badge-light track__order">#{{ order }}</span> <span class="badge badge-pill badge-light track__order">
#{{ order }}
</span>
</li> </li>
</ul> </ul>
</section> </section>

View File

@ -1,9 +1,9 @@
<div class="ui-backdrop" @click="handleBackdropClick" v-if="currentBehaviour === 'modal'"> <div class="ui-backdrop" @click="handleBackdropClick" v-if="currentBehaviour === 'modal'">
<div class="ui-modal" v-bind="$attrs"> <div class="ui-modal" v-bind="attrs" v-on="$listeners">
<div class="ui-modal__top-bar"> <div class="ui-modal__top-bar">
<div class="ui-modal__header"> <div class="ui-modal__header">
<slot name="header"> <slot name="header">
<div class="ui-modal__title">{{ title }}</div> <div class="ui-modal__title"><slot name="title">{{ title }}</slot></div>
</slot> </slot>
</div> </div>
<button class="btn btn-action ui-modal__close" @click.prevent="handleCloseClick"> <button class="btn btn-action ui-modal__close" @click.prevent="handleCloseClick">
@ -16,7 +16,7 @@
</div> </div>
</div> </div>
</div> </div>
<div :class="[ 'ui-popup', arrow && 'ui-popup--arrow' ]" v-bind="$attrs" v-on="$listeners" v-else> <div :class="[ 'ui-popup', arrow && 'ui-popup--arrow' ]" v-bind="attrs" :style="{ zIndex: zIndex }" v-on="$listeners" v-else>
<div class="ui-popup__arrow" ref="arrow" v-if="arrow"></div> <div class="ui-popup__arrow" ref="arrow" v-if="arrow"></div>
<div class="ui-popup__header" v-if="hasHeader"> <div class="ui-popup__header" v-if="hasHeader">
<slot name="header" /> <slot name="header" />

View File

@ -116,6 +116,10 @@ body {
flex-direction: column; flex-direction: column;
background: url("../images/background.png") repeat-x center bottom 63px; background: url("../images/background.png") repeat-x center bottom 63px;
&.contains-modal {
overflow-y: hidden;
}
main { main {
flex: 1 1 auto; flex: 1 1 auto;
position: relative; position: relative;

View File

@ -7,11 +7,14 @@
align-items: center; align-items: center;
overflow-y: auto; overflow-y: auto;
overscroll-behavior-y: contain; overscroll-behavior-y: contain;
z-index: 10000;
&::after { &::after {
height: 1rem; height: $spacer;
display: block; display: block;
content: ""; content: "";
width: 1px;
flex: 0 0 auto;
} }
} }
@ -31,7 +34,7 @@ $dialog-sizes: (
border-radius: 1px; border-radius: 1px;
@each $size, $width in $dialog-sizes { @each $size, $width in $dialog-sizes {
.ui-modal--#{$size} { &.ui-modal--#{$size} {
width: $width; width: $width;
} }
} }
@ -56,3 +59,11 @@ $dialog-sizes: (
display: flex; display: flex;
margin-bottom: $dialog-margin * 0.75; margin-bottom: $dialog-margin * 0.75;
} }
@include media-breakpoint-down('sm') {
@each $size, $width in $dialog-sizes {
.ui-modal.ui-modal--#{$size} {
width: 100%;
}
}
}

View File

@ -10,9 +10,31 @@ import { defaultBreakpoints } from "../../filters";
*/ */
export type DialogBehaviour = "modal" | "popup"; export type DialogBehaviour = "modal" | "popup";
let openModalCounter: number = 0;
function computeZIndexOfElement(element: HTMLElement): number {
let current = element;
while (true) {
const zIndex = window.getComputedStyle(current).zIndex;
if (zIndex !== "auto") {
return parseInt(zIndex);
}
if (!current.parentElement) {
break;
}
current = current.parentElement;
}
return 0;
}
@Component({ @Component({
template: require('../../../components/ui/dialog.html'),
inheritAttrs: false, inheritAttrs: false,
template: require('../../../components/ui/dialog.html'),
}) })
export default class UiDialog extends Vue { export default class UiDialog extends Vue {
@Prop({ type: String, default: "popup" }) @Prop({ type: String, default: "popup" })
@ -41,11 +63,23 @@ export default class UiDialog extends Vue {
private isMobile: boolean = false; private isMobile: boolean = false;
/** Inherited class hack */
private staticClass: string[] = [];
private zIndex: number = 1000;
private _focusOutEvent; private _focusOutEvent;
private _resizeEvent; private _resizeEvent;
private _popper; private _popper;
get attrs() {
return {
...this.$attrs,
"class": this.staticClass
}
}
get currentBehaviour(): DialogBehaviour { get currentBehaviour(): DialogBehaviour {
if (!this.mobileBehaviour) { if (!this.mobileBehaviour) {
return this.behaviour; return this.behaviour;
@ -93,16 +127,60 @@ export default class UiDialog extends Vue {
} }
mounted() { mounted() {
this.zIndex = computeZIndexOfElement(this.getReferenceElement()) + 100;
this.handleWindowResize(); this.handleWindowResize();
if (this.behaviour === 'popup') { if (this.behaviour === 'popup') {
this.initPopper(); this.mountPopper();
} }
this.staticClass = Array.from(this.$el.classList).filter(cls => ["ui-backdrop", "ui-popup", "ui-popup--arrow"].indexOf(cls) === -1);
window.addEventListener('resize', this._resizeEvent = this.handleWindowResize.bind(this)); window.addEventListener('resize', this._resizeEvent = this.handleWindowResize.bind(this));
this._activated();
} }
private initPopper() { private _activated() {
if (this.behaviour === 'modal') {
this.mountModal();
}
}
private _deactivated() {
if (this.behaviour === 'modal') {
this.dismountModal();
}
}
private mountModal() {
if (openModalCounter === 0) {
document.body.style.paddingRight = `${window.screen.width - document.body.clientWidth}px`
document.body.classList.add('contains-modal');
}
openModalCounter++;
}
private dismountModal() {
openModalCounter--;
if (openModalCounter === 0) {
document.body.style.paddingRight = "";
document.body.classList.remove('contains-modal');
}
}
activated() {
this._activated();
}
deactivated() {
this._deactivated();
}
private mountPopper() {
const reference = this.getReferenceElement(); const reference = this.getReferenceElement();
this._popper = new Popper(reference, this.$el, { this._popper = new Popper(reference, this.$el, {
@ -147,6 +225,8 @@ export default class UiDialog extends Vue {
beforeDestroy() { beforeDestroy() {
this._focusOutEvent && document.removeEventListener('click', this._focusOutEvent, { capture: true }); this._focusOutEvent && document.removeEventListener('click', this._focusOutEvent, { capture: true });
this._deactivated()
} }
removed() { removed() {
@ -179,7 +259,15 @@ export default class UiDialog extends Vue {
} }
if (newBehaviour === 'popup') { if (newBehaviour === 'popup') {
this.$nextTick(() => this.initPopper()); this.$nextTick(() => this.mountPopper());
}
if (newBehaviour === 'modal') {
this.mountModal();
}
if (oldBehaviour === 'modal') {
this.dismountModal();
} }
} }
} }

View File

@ -105,11 +105,9 @@
</button> </button>
</div> </div>
<portal to="popups"> <ui-dialog reference="save" v-if="visibility.save" arrow placement="bottom-end" @leave="visibility.save = false">
<ui-dialog reference="settings-messages" v-if="visibility.messages" arrow placement="left-start" @leave="visibility.messages = false"> <favourites-adder @saved="visibility.save = false"/>
<favourites-adder @saved="visibility.save = false"/> </ui-dialog>
</ui-dialog>
</portal>
</section> </section>
<section class="section picker"> <section class="section picker">
<header class="section__title flex"> <header class="section__title flex">