Modal.vue 2.9 KB

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