Skip to content
Snippets Groups Projects
ADialog.vue 2.78 KiB
Newer Older
<template>
  <dialog
    ref="dialogEl"
    class="tw-shadow-xl tw-border tw-border-gray-200 tw-rounded tw-p-0 tw-bg-white tw-z-30 tw-flex tw-flex-col"
    style="max-width: 95%"
    @click="maybeCloseFromClickOutside"
    <header class="tw-flex tw-justify-between tw-p-4 flex-none tw-items-center">
        <slot name="header" :title-class="titleClass">
          <p :class="titleClass">{{ title }}</p>
        </slot>
      <slot name="header-extra" />
      <button
        type="button"
        class="btn btn-default tw-w-8 tw-h-8 tw-ml-auto tw-p-0 tw-justify-center tw-rounded-full"
        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 { computed, nextTick, ref, useSlots, watch } from 'vue'

defineOptions({ compatConfig: { MODE: 3 } })
const titleClass = 'tw-text-lg tw-font-semibold tw-select-none tw-m-0'
const props = withDefaults(
  defineProps<{
    modelValue?: boolean | undefined
    isModal?: boolean
    isModal: false,
  },
)
const emit = defineEmits<{
  (e: 'update:modelValue', value: boolean): void
}>()
const slots = useSlots()
const localIsOpen = ref(false)
const dialogEl = ref<HTMLDialogElement>()
const isOpen = computed({
  get() {
    return props.modelValue !== undefined ? props.modelValue : localIsOpen.value
  },
  set(value: boolean) {
    if (props.modelValue !== undefined) {
      emit('update:modelValue', value)
    } else {
      localIsOpen.value = value
    }
  },
})

function close() {
  isOpen.value = false
}

function open() {
  isOpen.value = true
function maybeCloseFromClickOutside(event: MouseEvent | PointerEvent) {
  // @ts-expect-error TS EventTarget does not contain nodeName
  if (event.target?.nodeName === 'DIALOG') close()
}

defineExpose({
  close,
  open,
  hide: close,
  show: open,
})

  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>

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