123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- <template>
- <transition name="fade" appear @leave="onTransitionLeaveStart">
- <div
- ref="modal"
- class="fixed inset-0 z-10 z-50 overflow-y-auto transition hide-scrollbar"
- role="dialog"
- aria-modal="true"
- >
- <div
- class="flex items-end justify-center min-h-screen text-center sm:block"
- >
- <transition name="fade" appear>
- <div
- class="fixed inset-0 transition bg-primaryLight opacity-90"
- @touchstart="!dialog ? close() : null"
- @touchend="!dialog ? close() : null"
- @mouseup="!dialog ? close() : null"
- @mousedown="!dialog ? close() : null"
- ></div>
- </transition>
- <span
- v-if="placement === 'center'"
- class="sm:h-screen <sm:hidden sm:align-middle"
- aria-hidden="true"
- >​</span
- >
- <transition
- appear
- enter-active-class="transition"
- enter-class="scale-95 translate-y-4"
- enter-to-class="scale-100 translate-y-0"
- leave-active-class="transition"
- leave-class="scale-100 translate-y-0"
- leave-to-class="scale-95 translate-y-4"
- >
- <div
- class="inline-block w-full overflow-hidden text-left align-bottom shadow-lg transition-all transform bg-primary sm:rounded-xl sm:align-middle"
- :class="[
- { 'mt-24 md:mb-8': placement === 'top' },
- { 'p-4': !fullWidth },
- maxWidth,
- ]"
- >
- <div
- v-if="title"
- class="flex items-center justify-between pl-2 mb-4"
- >
- <h3 class="heading">{{ title }}</h3>
- <span class="flex">
- <slot name="actions"></slot>
- <ButtonSecondary
- v-if="dimissible"
- svg="x"
- @click.native="close"
- />
- </span>
- </div>
- <div
- class="flex flex-col overflow-y-auto max-h-md hide-scrollbar"
- :class="{ 'py-2': !fullWidth }"
- >
- <slot name="body"></slot>
- </div>
- <div
- v-if="hasFooterSlot"
- class="flex items-center justify-between flex-1 p-2 mt-4"
- >
- <slot name="footer"></slot>
- </div>
- </div>
- </transition>
- </div>
- </div>
- </transition>
- </template>
- <script lang="ts">
- import { defineComponent, onBeforeUnmount } from "@nuxtjs/composition-api"
- import { useKeybindingDisabler } from "~/helpers/keybindings"
- const PORTAL_DOM_ID = "hoppscotch-modal-portal"
- // Why ?
- const stack = (() => {
- const stack: number[] = []
- return {
- push: stack.push.bind(stack),
- pop: stack.pop.bind(stack),
- peek: () => (stack.length === 0 ? undefined : stack[stack.length - 1]),
- }
- })()
- export default defineComponent({
- props: {
- dialog: {
- type: Boolean,
- default: false,
- },
- title: {
- type: String,
- default: "",
- },
- dimissible: {
- type: Boolean,
- default: true,
- },
- placement: {
- type: String,
- default: "top",
- },
- fullWidth: {
- type: Boolean,
- default: false,
- },
- maxWidth: {
- type: String,
- default: "sm:max-w-lg",
- },
- },
- setup() {
- const { disableKeybindings, enableKeybindings } = useKeybindingDisabler()
- onBeforeUnmount(() => {
- enableKeybindings()
- })
- return {
- disableKeybindings,
- }
- },
- data() {
- return {
- stackId: Math.random(),
- // when doesn't fire on unmount, we should manually remove the modal from DOM
- // (for example, when the parent component of this modal gets destroyed)
- shouldCleanupDomOnUnmount: true,
- }
- },
- computed: {
- hasFooterSlot(): boolean {
- return !!this.$slots.footer
- },
- },
- mounted() {
- const $portal = this.$getPortal()
- $portal.appendChild(this.$refs.modal as any)
- stack.push(this.stackId)
- document.addEventListener("keydown", this.onKeyDown)
- this.disableKeybindings()
- },
- beforeDestroy() {
- const $modal = this.$refs.modal
- if (this.shouldCleanupDomOnUnmount && $modal) {
- this.$getPortal().removeChild($modal as any)
- }
- stack.pop()
- document.removeEventListener("keydown", this.onKeyDown)
- },
- methods: {
- close() {
- this.$emit("close")
- },
- onKeyDown(e: KeyboardEvent) {
- if (e.key === "Escape" && this.stackId === stack.peek()) {
- e.preventDefault()
- this.close()
- }
- },
- onTransitionLeaveStart() {
- this.close()
- this.shouldCleanupDomOnUnmount = false
- },
- $getPortal() {
- let $el = document.querySelector("#" + PORTAL_DOM_ID)
- if ($el) {
- return $el
- }
- $el = document.createElement("DIV")
- $el.id = PORTAL_DOM_ID
- document.body.appendChild($el)
- return $el
- },
- },
- })
- </script>
|