lib/components/AccordionWrapper.vue
<template>
<div class="accordion-wrapper">
<div class="accordion-wrapper__content ml-5">
<!-- @slot Content with all the steps declarations -->
<slot />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { AccordionKey } from '@/keys'
import type { AccordionProvide, Step } from '@/types'
const STEP_CHANGE_EVENT: string = 'step-change'
export default defineComponent({
name: 'AccordionWrapper',
provide(): AccordionProvide {
return {
[AccordionKey]: {
emitAccordionNextStepEvent: this.emitAccordionNextStepEvent,
emitAccordionPreviousStepEvent: this.emitAccordionPreviousStepEvent,
isActiveStep: this.isActiveStep,
isPreviousStep: this.isPreviousStep,
isFirstStep: this.isFirstStep,
isLastStep: this.isLastStep,
step: this.step,
steps: this.steps
}
}
},
model: {
prop: 'step',
event: STEP_CHANGE_EVENT
},
props: {
/**
* Current active step value. Modified on "step-change" event.
*/
step: {
type: [String, Symbol, Object as () => Step],
required: true
},
/**
* List of the steps. Numbering respects the array positions
*/
steps: {
type: Array as PropType<Step[]>,
required: true
}
},
computed: {
activeStepIndex(): number {
return this.steps.indexOf(this.step)
}
},
methods: {
isFirstStep(step: Step): boolean {
return this.steps.indexOf(step) === 0
},
isLastStep(step: Step): boolean {
return this.steps.indexOf(step) === this.steps.length - 1
},
isActiveStep(step: Step): boolean {
return this.step === step
},
isPreviousStep(step: Step): boolean {
return this.steps.indexOf(step) < this.activeStepIndex
},
emitAccordionNextStepEvent(): void {
/**
* When the step is changed it updates the step v-model value.
* @event step-change
* @param Mixed New step value.
*/
this.$emit(STEP_CHANGE_EVENT, this.steps[this.activeStepIndex + 1] || this.step)
},
emitAccordionPreviousStepEvent(): void {
this.$emit(STEP_CHANGE_EVENT, this.steps[this.activeStepIndex - 1] || this.step)
}
}
})
</script>
<style lang="scss" scoped>
@use 'sass:math';
@import '../styles/variables.scss';
.accordion-wrapper {
$step-bullet-size: 2rem;
$step-bullet-font-size: math.div($step-bullet-size, 2);
&__content {
counter-reset: step 0;
max-width: 550px;
&__step {
margin-bottom: $spacer;
position: relative;
opacity: $btn-disabled-opacity;
transition: $transition-base;
&--active,
&--previous {
opacity: 1;
}
&__heading + .collapse > .card-body {
padding-top: 0;
}
&:before {
counter-increment: step;
content: counter(step);
z-index: 10;
line-height: $step-bullet-size;
height: $step-bullet-size;
width: $step-bullet-size;
text-align: center;
font-size: $step-bullet-font-size;
border-radius: 50%;
display: block;
color: white;
font-weight: bolder;
position: absolute;
right: calc(100% + #{$spacer});
top: $spacer * 1.25;
background: $text-muted;
transition: background $transition-base;
}
&--active:before,
&--previous:before {
background: $info;
}
&:not(:last-of-type):after {
content: '';
z-index: 0;
position: absolute;
top: $spacer * 1.25;
right: calc(100% + #{$spacer} + #{math.div($step-bullet-size, 2)});
transform: translateX(50%);
bottom: -4rem;
width: 3px;
background: transparent;
transition: background 400ms;
}
&--previous:not(:last-of-type):after {
background: $info;
}
}
}
}
</style>