All files / src/components DeploymentProgressBar.vue

0% Statements 0/50
100% Branches 1/1
100% Functions 1/1
0% Lines 0/50

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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113                                                                                                                                                                                                                                 
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { Check } from 'lucide-vue-next'
 
const props = defineProps<{
  currentStep: number
}>()
 
const { t } = useI18n()
 
// 4 Schritte: Auswahl -> Verteilung -> Variablen -> Übersicht
const steps = [
  { step: 1, key: 'deployment.steps.config' },
  { step: 2, key: 'deployment.steps.assignment' },
  { step: 3, key: 'deployment.steps.vars' },
  { step: 4, key: 'deployment.steps.summary' }
]
 
// Berechnet die Breite automatisch basierend auf der Länge des Arrays (jetzt 3)
const progressWidth = computed(() => {
  const totalSteps = steps.length
  // Schutz vor Division durch Null, falls nur 1 Schritt da wäre
  if (totalSteps <= 1) return '0%'
  
  const percentage = ((props.currentStep - 1) / (totalSteps - 1)) * 100
  // Begrenzung auf 0-100% zur Sicherheit
  return `${Math.min(Math.max(percentage, 0), 100)}%`
})
 
// Hilfsfunktion für Text-Ausrichtung
const getTextAlignmentClass = (step: number, total: number) => {
  if (step === 1) return 'left-0 origin-left'              // Erster: Linksbuendig
  if (step === total) return 'right-0 origin-right'        // Letzter: Rechtsbuendig
  return 'left-1/2 -translate-x-1/2 origin-center'         // Alle dazwischen: Zentriert
}
</script>
 
<template>
  <div class="w-full mb-8 px-2"> 
    <div class="relative">
      <div class="absolute top-1/2 left-0 w-full h-1 bg-gray-200 -translate-y-1/2 rounded-full"></div>
 
      <div 
        class="absolute top-1/2 left-0 h-1 bg-emerald-500 -translate-y-1/2 rounded-full transition-all duration-500 ease-out"
        :style="{ width: progressWidth }"
      ></div>
 
      <div class="relative flex justify-between w-full">
        
        <div 
          v-for="item in steps" 
          :key="item.step" 
          class="flex flex-col items-center group relative" 
        >
          <div
            class="flex items-center justify-center w-8 h-8 rounded-full border-2 text-sm font-bold z-10 transition-all duration-300 bg-white"
            :class="[
              currentStep >= item.step
                ? 'border-emerald-600 text-emerald-600 shadow-[0_0_10px_rgba(16,185,129,0.4)]'
                : 'border-gray-300 text-gray-400',
              // Füllt den Kreis komplett grün, wenn der Schritt erledigt ist
              currentStep > item.step ? '!bg-emerald-600 !text-white' : '',
              // Aktueller Schritt: dezent größer pulsieren statt
              // ein-/ausblenden (animate-pulse), damit der User immer
              // sieht, wo er ist.
              currentStep === item.step ? 'text-emerald-600 animate-step-pulse' : ''
            ]"
          >
            <Check v-if="currentStep > item.step" :size="16" />
            <span v-else>{{ item.step }}</span>
          </div>
 
          <span 
            class="absolute top-10 text-xs font-bold uppercase tracking-wider transition-colors duration-300 whitespace-nowrap"
            :class="[
              currentStep >= item.step ? 'text-emerald-700' : 'text-gray-400',
              getTextAlignmentClass(item.step, steps.length)
            ]"
          >
            {{ t(item.key) }}
          </span>
        </div>
 
      </div>
    </div>
    
    <div class="h-6"></div>
  </div>
</template>
 
<style scoped>
/* Statt Tailwinds animate-pulse (das die Opacity moduliert und den
   aktuellen Schritt halb-unsichtbar macht) skalieren wir den Kreis
   leicht hin und her. So bleibt er immer voll sichtbar und das Auge
   wird trotzdem dorthin gezogen. transform-origin ist mittig, damit
   die Position auf der Linie nicht „wackelt“. */
@keyframes step-pulse {
  0%, 100% {
    transform: scale(1);
    box-shadow: 0 0 10px rgba(16, 185, 129, 0.4);
  }
  50% {
    transform: scale(1.18);
    box-shadow: 0 0 14px rgba(16, 185, 129, 0.6);
  }
}
 
.animate-step-pulse {
  animation: step-pulse 1.6s ease-in-out infinite;
  transform-origin: center;
}
</style>