123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
- <script setup lang="ts">
- import { onKeyUp, usePointerSwipe } from '@vueuse/core'
- import { nextTick, onMounted, ref, type Events } from 'vue'
- import { useTrapTab } from '#shared/composables/useTrapTab.ts'
- import type { EventHandlers } from '#shared/types/utils.ts'
- import stopEvent from '#shared/utils/events.ts'
- import { getFirstFocusableElement } from '#shared/utils/getFocusableElements.ts'
- import CommonButton from '#mobile/components/CommonButton/CommonButton.vue'
- import { closeDialog } from '#mobile/composables/useDialog.ts'
- const props = defineProps<{
- name: string
- label?: string
- content?: string
- // don't focus the first element inside a Dialog after being mounted
- // if nothing is focusable, will focus "Done" button
- noAutofocus?: boolean
- listeners?: {
- done?: EventHandlers<Events>
- }
- }>()
- const emit = defineEmits<{
- close: []
- }>()
- const PX_SWIPE_CLOSE = -150
- const top = ref('0')
- const dialogElement = ref<HTMLElement>()
- const contentElement = ref<HTMLElement>()
- const close = async () => {
- emit('close')
- await closeDialog(props.name)
- }
- const canCloseDialog = () => {
- const currentDialog = dialogElement.value
- if (!currentDialog) {
- return false
- }
- // close dialog only if this is the last one opened
- const dialogs = document.querySelectorAll('[data-common-dialog]')
- return dialogs[dialogs.length - 1] === currentDialog
- }
- onKeyUp('Escape', (e) => {
- if (canCloseDialog()) {
- stopEvent(e)
- close()
- }
- })
- const { distanceY, isSwiping } = usePointerSwipe(dialogElement, {
- onSwipe() {
- if (distanceY.value < 0) {
- const distance = Math.abs(distanceY.value)
- top.value = `${distance}px`
- } else {
- top.value = '0'
- }
- },
- onSwipeEnd() {
- if (distanceY.value <= PX_SWIPE_CLOSE) {
- close()
- } else {
- top.value = '0'
- }
- },
- pointerTypes: ['touch', 'pen'],
- })
- useTrapTab(dialogElement)
- onMounted(() => {
- if (props.noAutofocus) return
- // will try to find focusable element inside dialog
- // if it won't find it, will try to find inside the header
- // most likely will find "Done" button
- const firstFocusable =
- getFirstFocusableElement(contentElement.value) ||
- getFirstFocusableElement(dialogElement.value)
- nextTick(() => {
- firstFocusable?.focus()
- firstFocusable?.scrollIntoView({ block: 'nearest' })
- })
- })
- </script>
- <script lang="ts">
- export default {
- inheritAttrs: false,
- }
- </script>
- <template>
- <div
- :id="`dialog-${name}`"
- class="fixed inset-0 z-10 flex overflow-y-auto"
- :aria-label="$t(label || name)"
- role="dialog"
- >
- <div
- ref="dialogElement"
- data-common-dialog
- class="flex h-full grow flex-col overflow-x-hidden bg-black"
- :class="{ 'transition-all duration-200 ease-linear': !isSwiping }"
- :style="{ transform: `translateY(${top})` }"
- >
- <div class="bg-gray-150/40 mx-4 h-2.5 shrink-0 rounded-t-xl" />
- <div
- class="relative flex h-16 shrink-0 select-none items-center justify-center rounded-t-xl bg-gray-600/80"
- >
- <div
- class="absolute bottom-0 top-0 flex items-center ltr:left-0 ltr:pl-4 rtl:right-0 rtl:pr-4"
- >
- <slot name="before-label" />
- </div>
- <div
- class="line-clamp-2 max-w-[65%] text-center text-base font-semibold leading-[19px] text-white"
- >
- <slot name="label">
- {{ $t(label) }}
- </slot>
- </div>
- <div
- class="absolute bottom-0 top-0 flex items-center ltr:right-0 ltr:pr-4 rtl:left-0 rtl:pl-4"
- >
- <slot name="after-label">
- <CommonButton
- class="grow"
- variant="primary"
- transparent-background
- v-bind="listeners?.done"
- @pointerdown.stop
- @click="close()"
- @keypress.space.prevent="close()"
- >
- {{ $t('Done') }}
- </CommonButton>
- </slot>
- </div>
- </div>
- <div
- ref="contentElement"
- v-bind="$attrs"
- class="flex grow flex-col items-start overflow-y-auto bg-black text-white"
- >
- <slot>{{ content }}</slot>
- </div>
- </div>
- </div>
- </template>
|