3737import { computed } from ' vue'
3838import { cva , type VariantProps } from ' class-variance-authority'
3939import { cn } from ' @/lib/utils'
40+ import Card from ' @/components/ui/card/Card.vue'
4041
4142export interface ProgressStep {
4243 id: string
@@ -117,7 +118,6 @@ interface Props {
117118 showSteps? : boolean
118119 hideTitle? : boolean
119120 interactive? : boolean
120- styled? : boolean // New prop for styled container
121121}
122122
123123const props = withDefaults (defineProps <Props >(), {
@@ -126,8 +126,7 @@ const props = withDefaults(defineProps<Props>(), {
126126 size: ' md' ,
127127 showSteps: true ,
128128 hideTitle: false ,
129- interactive: false ,
130- styled: false
129+ interactive: false
131130})
132131
133132const emit = defineEmits <{
@@ -158,13 +157,16 @@ const stepVariants = cva(
158157
159158const clampedProgress = computed (() => Math .max (0 , Math .min (100 , props .progress )))
160159
161- // Calculate the exact position percentage for each step
160+ // Calculate the exact position percentage for each step with padding
162161function getStepPosition(index : number ) {
163162 const totalSteps = props .steps .length
164- if (totalSteps <= 1 ) return 50 // Center if only one step
165-
166- // Calculate position as percentage (0% to 100%)
167- return (index / (totalSteps - 1 )) * 100
163+ if (totalSteps <= 1 ) return 50
164+
165+ const padding = 3 // 3% padding on each side
166+ const availableWidth = 100 - (padding * 2 )
167+ const position = padding + (index / (totalSteps - 1 )) * availableWidth
168+
169+ return position
168170}
169171
170172function handleStepClick(step : ProgressStep , index : number ) {
@@ -176,7 +178,7 @@ function handleStepClick(step: ProgressStep, index: number) {
176178// Get transform style to center text on the exact position
177179function getStepTransform(index : number ) {
178180 const position = getStepPosition (index )
179-
181+
180182 // Adjust transform to center the text on the position
181183 if (position === 0 ) return ' translateX(0%)' // First step: no adjustment needed
182184 if (position === 100 ) return ' translateX(-100%)' // Last step: move completely left
@@ -185,85 +187,111 @@ function getStepTransform(index: number) {
185187 </script >
186188
187189<template >
188- <div
189- :class =" [
190- cn(progressBarsVariants({ variant, size })),
191- styled ? 'rounded-lg bg-muted/50 px-4 py-6 sm:px-6' : ''
192- ]"
193- >
194- <!-- Title -->
195- <div v-if =" title" :class =" styled ? 'mb-6' : 'mb-4'" >
196- <h4 v-if =" hideTitle" class =" sr-only" >{{ title }}</h4 >
197- <p v-else class =" text-sm font-medium text-foreground" >{{ title }}</p >
198- </div >
190+ <Card variant =" white" >
191+ <div class =" px-6" >
192+ <div :class =" cn(progressBarsVariants({ variant, size }))" >
193+ <!-- Title -->
194+ <div v-if =" title" class =" mb-4" >
195+ <h4 v-if =" hideTitle" class =" sr-only" >{{ title }}</h4 >
196+ <p v-else class =" text-sm font-medium text-foreground" >{{ title }}</p >
197+ </div >
199198
200- <!-- Progress Bar -->
201- <div class =" space-y-4" >
202- <div
203- role =" progressbar"
204- :aria-valuenow =" clampedProgress"
205- aria-valuemin =" 0"
206- aria-valuemax =" 100"
207- :aria-label =" title || 'Progress'"
208- :class =" cn(progressBarVariants({ variant, size }))"
209- >
210- <div
211- :class =" cn(progressFillVariants({ variant }))"
212- :style =" { width: `${clampedProgress}%` }"
213- />
214- </div >
199+ <!-- Progress Bar -->
200+ <div class =" mt-6" >
201+ <div class =" relative" >
202+ <div
203+ role =" progressbar"
204+ :aria-valuenow =" clampedProgress"
205+ aria-valuemin =" 0"
206+ aria-valuemax =" 100"
207+ :aria-label =" title || 'Progress'"
208+ :class =" cn(progressBarVariants({ variant, size }))"
209+ >
210+ <div
211+ :class =" cn(progressFillVariants({ variant }))"
212+ :style =" { width: `${clampedProgress}%` }"
213+ />
214+ </div >
215215
216- <!-- Steps -->
217- <div
218- v-if =" showSteps && steps.length > 0"
219- class =" hidden sm:block relative w-full pt-2"
220- >
221- <button
222- v-for =" (step, index) in steps"
223- :key =" step.id"
224- :type =" interactive && step.clickable ? 'button' : undefined"
225- :disabled =" !interactive || !step.clickable"
226- :class =" [
227- cn(stepVariants({
228- status: step.status,
229- clickable: interactive && step.clickable
230- })),
231- 'absolute text-sm font-medium whitespace-nowrap'
232- ]"
233- :style =" {
234- left: `${getStepPosition(index)}%`,
235- transform: getStepTransform(index)
236- }"
237- @click =" handleStepClick(step, index)"
238- >
239- {{ step.label }}
240- </button >
241- </div >
216+ <!-- Step boxes overlaid on progress bar -->
217+ <div
218+ v-if =" showSteps && steps.length > 0"
219+ class =" hidden sm:block absolute inset-0 pointer-events-none"
220+ >
221+ <div
222+ v-for =" (step, index) in steps"
223+ :key =" step.id"
224+ class =" absolute top-1/2"
225+ :style =" {
226+ left: `${getStepPosition(index)}%`,
227+ transform: `translate(-50%, calc(-50% + 1px))`
228+ }"
229+ >
230+ <button
231+ :type =" interactive && step.clickable ? 'button' : undefined"
232+ :disabled =" !interactive || !step.clickable"
233+ :class =" [
234+ 'bg-white border border-gray-300 rounded px-2 py-1 text-sm font-medium pointer-events-auto',
235+ interactive && step.clickable ? 'cursor-pointer hover:bg-gray-50' : ''
236+ ]"
237+ @click =" handleStepClick(step, index)"
238+ >
239+ {{ index + 1 }}
240+ </button >
241+ </div >
242+ </div >
243+ </div >
242244
243- <!-- Mobile Steps (Vertical List) -->
244- <div v-if =" showSteps && steps.length > 0" class =" sm:hidden space-y-2" >
245- <div
246- v-for =" (step, index) in steps"
247- :key =" `mobile-${step.id}`"
248- class =" flex items-center justify-between"
249- >
250- <span :class =" cn(stepVariants({ status: step.status }))" >
251- {{ step.label }}
252- </span >
253- <div class =" flex items-center gap-2" >
254- <!-- Status Icon -->
245+ <!-- Step labels below -->
246+ <div
247+ v-if =" showSteps && steps.length > 0"
248+ class =" hidden sm:block relative w-full mt-2 py-5"
249+ >
255250 <div
251+ v-for =" (step, index) in steps"
252+ :key =" `label-${step.id}`"
256253 :class =" [
257- 'w-2 h-2 rounded-full',
258- step.status === 'completed' ? 'bg-primary' : '' ,
259- step.status === 'current' ? 'bg-primary animate-pulse' : '',
260- step.status === 'pending' ? 'bg-red-500' : '' ,
261- step.status === 'error' ? 'bg-destructive' : ' '
254+ cn(stepVariants({
255+ status: step.status ,
256+ clickable: false
257+ })) ,
258+ 'absolute text-sm font-medium whitespace-nowrap '
262259 ]"
263- />
260+ :style =" {
261+ left: `${getStepPosition(index)}%`,
262+ transform: getStepTransform(index)
263+ }"
264+ >
265+ {{ step.label }}
266+ </div >
267+ </div >
268+
269+ <!-- Mobile Steps (Vertical List) -->
270+ <div v-if =" showSteps && steps.length > 0" class =" sm:hidden space-y-2" >
271+ <div
272+ v-for =" (step, index) in steps"
273+ :key =" `mobile-${step.id}`"
274+ class =" flex items-center justify-between"
275+ >
276+ <span :class =" cn(stepVariants({ status: step.status }))" >
277+ {{ step.label }}
278+ </span >
279+ <div class =" flex items-center gap-2" >
280+ <!-- Status Icon -->
281+ <div
282+ :class =" [
283+ 'w-2 h-2 rounded-full',
284+ step.status === 'completed' ? 'bg-primary' : '',
285+ step.status === 'current' ? 'bg-primary animate-pulse' : '',
286+ step.status === 'pending' ? 'bg-red-500' : '',
287+ step.status === 'error' ? 'bg-destructive' : ''
288+ ]"
289+ />
290+ </div >
291+ </div >
264292 </div >
265293 </div >
266294 </div >
267295 </div >
268- </div >
296+ </Card >
269297</template >
0 commit comments