Skip to content
Snippets Groups Projects
ADialog.vue 2.68 KiB
Newer Older
<template>
  <dialog
    v-if="modelValue"
    ref="dialogEl"
    class="tw-shadow-xl tw-border tw-border-gray-200 tw-rounded tw-max-w-[95%] tw-p-0 tw-bg-white tw-z-30 tw-flex tw-flex-col"
    @click="maybeClose"
  >
    <header class="tw-flex tw-justify-between tw-p-4 flex-none">
      <div>
        <slot name="header" :title-class="titleClass">
          <p :class="titleClass">{{ title }}</p>
        </slot>
      </div>
      <button
        type="button"
        class="tw-w-8 tw-h-8 tw-ml-auto tw-p-0 tw-flex tw-items-center tw-justify-center tw-bg-gray-100 tw-rounded-full tw-border-none"
        tabindex="-1"
        @click="close"
      >
        <icon-system-uicons-close class="tw-w-6 tw-h-6" />
      </button>
    </header>
    <div class="tw-flex-1 tw-p-4 tw-overflow-y-auto tw-shadow-inner">
      <slot />
    </div>
    <footer
      v-if="slots.footer"
      class="tw-flex-none tw-p-4 tw-border-0 tw-border-t tw-border-solid tw-border-gray-200"
    >
      <slot name="footer" />
    </footer>
  </dialog>
</template>

<script lang="ts" setup>
import { onClickOutside } from '@vueuse/core'
import { nextTick, ref, useSlots, watch } from 'vue'

const titleClass = 'tw-text-lg tw-font-semibold tw-m-0'

const props = withDefaults(
  defineProps<{
    modelValue: boolean
    isModal?: boolean
    isModal: false,
  },
)
const emit = defineEmits<{
  (e: 'update:modelValue', value: boolean): void
}>()
const slots = useSlots()

const dialogEl = ref<HTMLDialogElement>()

function close() {
  emit('update:modelValue', false)
}

function maybeClose(event: MouseEvent) {
  // when dialog is a modal the backdrop of it is still considered the dialog element
  // so the onClickOutside handler below won’t be triggered
  if (dialogEl.value) {
    const rect = dialogEl.value.getBoundingClientRect()
    const isInDialog =
      rect.top <= event.clientY &&
      event.clientY <= rect.top + rect.height &&
      rect.left <= event.clientX &&
      event.clientX <= rect.left + rect.width
    if (!isInDialog) {
      close()
    }
  }
}

watch(
  () => props.modelValue,
  async (isOpen) => {
    if (isOpen && !dialogEl.value) {
      await nextTick()
    }

    if (dialogEl.value) {
      if (isOpen) {
        if (props.isModal) {
          dialogEl.value.showModal()
        } else {
          dialogEl.value.show()
        }
      } else if (dialogEl.value.open) {
        dialogEl.value.close()
      }
    }
  },
  { immediate: true },
)
</script>

<script lang="ts">
export default {
  compatConfig: {
    MODE: 3,
  },
}
</script>

<style lang="postcss" scoped>
dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(3px);
}
</style>