Modal.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <template>
  2. <portal to="modals">
  3. <div
  4. v-if="showModal"
  5. class="fixed inset-0 flex justify-center"
  6. :class="overflow ? 'overflow-auto' : 'items-center'"
  7. >
  8. <transition
  9. @before-leave="backdropLeaving = true"
  10. @after-leave="backdropLeaving = false"
  11. enter-active-class="transition-all transition-fast ease-out-quad"
  12. leave-active-class="transition-all transition-medium ease-in-quad"
  13. enter-class="opacity-0"
  14. enter-to-class="opacity-100"
  15. leave-class="opacity-100"
  16. leave-to-class="opacity-0"
  17. appear
  18. >
  19. <div v-if="showBackdrop">
  20. <div
  21. class="inset-0 bg-black opacity-25"
  22. :class="overflow ? 'fixed pointer-events-none' : 'absolute'"
  23. @click="close"
  24. ></div>
  25. </div>
  26. </transition>
  27. <transition
  28. @before-leave="cardLeaving = true"
  29. @after-leave="cardLeaving = false"
  30. enter-active-class="transition-all transition-fast ease-out-quad"
  31. leave-active-class="transition-all transition-medium ease-in-quad"
  32. enter-class="opacity-0 scale-70"
  33. enter-to-class="opacity-100 scale-100"
  34. leave-class="opacity-100 scale-100"
  35. leave-to-class="opacity-0 scale-70"
  36. appear
  37. >
  38. <div v-if="showContent" class="relative">
  39. <slot></slot>
  40. </div>
  41. </transition>
  42. </div>
  43. </portal>
  44. </template>
  45. <script>
  46. export default {
  47. props: {
  48. open: {
  49. type: Boolean,
  50. required: true,
  51. },
  52. overflow: {
  53. type: Boolean,
  54. required: false,
  55. default: false,
  56. },
  57. },
  58. data() {
  59. return {
  60. showModal: false,
  61. showBackdrop: false,
  62. showContent: false,
  63. backdropLeaving: false,
  64. cardLeaving: false,
  65. }
  66. },
  67. created() {
  68. const onEscape = e => {
  69. if (this.open && e.keyCode === 27) {
  70. this.close()
  71. }
  72. }
  73. document.addEventListener('keydown', onEscape)
  74. this.$once('hook:destroyed', () => {
  75. document.removeEventListener('keydown', onEscape)
  76. })
  77. },
  78. watch: {
  79. open: {
  80. handler: function(newValue) {
  81. if (newValue) {
  82. this.show()
  83. } else {
  84. this.close()
  85. }
  86. },
  87. immediate: true,
  88. },
  89. leaving(newValue) {
  90. if (newValue === false) {
  91. this.showModal = false
  92. this.$emit('close')
  93. }
  94. },
  95. },
  96. computed: {
  97. leaving() {
  98. return this.backdropLeaving || this.cardLeaving
  99. },
  100. },
  101. methods: {
  102. show() {
  103. this.showModal = true
  104. this.showBackdrop = true
  105. this.showContent = true
  106. },
  107. close() {
  108. this.showBackdrop = false
  109. this.showContent = false
  110. },
  111. },
  112. }
  113. </script>
  114. <style>
  115. .origin-top-right {
  116. transform-origin: top right;
  117. }
  118. .transition-all {
  119. transition-property: all;
  120. }
  121. .transition-fastest {
  122. transition-duration: 50ms;
  123. }
  124. .transition-faster {
  125. transition-duration: 100ms;
  126. }
  127. .transition-fast {
  128. transition-duration: 150ms;
  129. }
  130. .transition-medium {
  131. transition-duration: 200ms;
  132. }
  133. .ease-out-quad {
  134. transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
  135. }
  136. .ease-in-quad {
  137. transition-timing-function: cubic-bezier(0.55, 0.085, 0.68, 0.53);
  138. }
  139. .scale-70 {
  140. transform: scale(0.7);
  141. }
  142. .scale-100 {
  143. transform: scale(1);
  144. }
  145. </style>