All files / src/components/ui Modal.vue

100% Statements 38/38
100% Branches 0/0
100% Functions 0/0
100% Lines 38/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79              1x 1x 1x 1x   1x 1x 1x   1x 1x 1x 1x                   1x 1x 1x 1x 1x 1x 1x 1x   1x 1x 1x 1x 1x   1x 1x 1x 1x 1x 1x   1x 1x 1x 1x   1x 1x 1x 1x                                      
<!-- src/components/ui/Modal.vue -->
<script setup lang="ts">
defineProps<{ show: boolean }>()
defineEmits(['close'])
</script>
 
<template>
  <div
    v-if="show"
    class="fixed inset-0 bg-black/50 backdrop-blur-[2px] flex items-center justify-center z-50 p-4"
    @click.self="$emit('close')"
  >
    <div
      class="bg-white rounded-2xl shadow-2xl w-full max-w-[520px] animate-fade-in flex flex-col max-h-[90vh]"
      @click.stop
    >
      <!-- Header -->
      <div class="flex justify-between items-center px-6 py-5 border-b border-gray-100">
        <div class="text-xl font-semibold text-gray-900 leading-tight pr-4">
          <!--
            Two slot names accepted:
              * ``#header`` — the historical name (CoursesView,
                CourseDetailView already use this).
              * ``#title``  — the more intuitive alias (DeploymentDetailView
                and AppsDetailView use this; without the alias the title
                silently fell through to the default "Modal" text, which
                shipped to users as a visible bug).
            Either slot wins; both empty falls back to "Modal".
          -->
          <slot name="header">
            <slot name="title">Modal</slot>
          </slot>
        </div>
        <button
          @click="$emit('close')"
          class="p-2 -mr-1 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors flex-shrink-0"
          aria-label="Close"
        >
          <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
          </svg>
        </button>
      </div>
 
      <!-- Body -->
      <div class="px-6 py-5 overflow-y-auto flex-grow">
        <slot name="body">
          <slot></slot>
        </slot>
      </div>
 
      <!-- Footer -->
      <div
        v-if="$slots.footer"
        class="px-6 py-4 border-t border-gray-100 bg-gray-50 rounded-b-2xl"
      >
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</template>
 
<style scoped>
@keyframes fade-in {
  from { 
    opacity: 0; 
    transform: scale(0.95); 
  }
  to { 
    opacity: 1; 
    transform: scale(1); 
  }
}
 
.animate-fade-in {
  animation: fade-in 0.2s ease-out;
}
</style>