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> |