CommonDialog.vue 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. <!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { EventHandlers } from '@shared/types/utils'
  4. import { usePointerSwipe } from '@vueuse/core'
  5. import { Events, ref } from 'vue'
  6. import { useDialogState } from './composable'
  7. const props = defineProps<{
  8. name: string
  9. label?: string
  10. listeners?: {
  11. done?: EventHandlers<Events>
  12. }
  13. }>()
  14. defineEmits<{
  15. (e: 'close'): void
  16. }>()
  17. const PX_SWIPE_CLOSE = -150
  18. const top = ref('0')
  19. const dialogElement = ref<HTMLElement>()
  20. const { close } = useDialogState(props)
  21. const { distanceY, isSwiping } = usePointerSwipe(dialogElement, {
  22. onSwipe() {
  23. if (distanceY.value < 0) {
  24. const distance = Math.abs(distanceY.value)
  25. top.value = `${distance}px`
  26. } else {
  27. top.value = '0'
  28. }
  29. },
  30. onSwipeEnd() {
  31. if (distanceY.value <= PX_SWIPE_CLOSE) {
  32. close()
  33. } else {
  34. top.value = '0'
  35. }
  36. },
  37. })
  38. </script>
  39. <script lang="ts">
  40. export default {
  41. inheritAttrs: false,
  42. }
  43. </script>
  44. <template>
  45. <div class="fixed inset-0 z-10 flex overflow-y-auto" role="dialog">
  46. <div
  47. ref="dialogElement"
  48. class="flex h-full grow flex-col bg-black"
  49. :class="{ 'transition-all duration-200 ease-linear': !isSwiping }"
  50. :style="{ transform: `translateY(${top})` }"
  51. >
  52. <div class="mx-4 h-2.5 shrink-0 rounded-t-xl bg-gray-150/40" />
  53. <div
  54. class="relative flex h-16 shrink-0 select-none items-center justify-center rounded-t-xl bg-gray-600/80"
  55. >
  56. <slot name="before-label" />
  57. <div
  58. class="grow text-center text-base font-semibold leading-[19px] text-white"
  59. >
  60. <slot name="label">
  61. {{ i18n.t(label) }}
  62. </slot>
  63. </div>
  64. <slot name="after-label">
  65. <div class="absolute top-0 right-0 bottom-0 flex items-center pr-4">
  66. <div
  67. class="grow cursor-pointer text-blue"
  68. tabindex="0"
  69. role="button"
  70. v-bind="listeners?.done"
  71. @pointerdown.stop
  72. @click="close()"
  73. @keypress.space="close()"
  74. >
  75. {{ i18n.t('Done') }}
  76. </div>
  77. </div>
  78. </slot>
  79. </div>
  80. <div
  81. class="flex grow flex-col items-start overflow-y-auto bg-black text-white"
  82. v-bind="$attrs"
  83. >
  84. <slot />
  85. </div>
  86. </div>
  87. </div>
  88. </template>