391 lines
10 KiB
Vue
391 lines
10 KiB
Vue
<script setup>
|
|
import stepperCheck from '@images/svg/stepper-check.svg'
|
|
|
|
const props = defineProps({
|
|
items: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
currentStep: {
|
|
type: Number,
|
|
required: false,
|
|
default: 0,
|
|
},
|
|
direction: {
|
|
type: String,
|
|
required: false,
|
|
default: 'horizontal',
|
|
},
|
|
iconSize: {
|
|
type: [
|
|
String,
|
|
Number,
|
|
],
|
|
required: false,
|
|
default: 52,
|
|
},
|
|
isActiveStepValid: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: undefined,
|
|
},
|
|
align: {
|
|
type: String,
|
|
required: false,
|
|
default: 'default',
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits(['update:currentStep'])
|
|
|
|
const currentStep = ref(props.currentStep || 0)
|
|
const activeOrCompletedStepsClasses = computed(() => index => index < currentStep.value ? 'stepper-steps-completed' : index === currentStep.value ? 'stepper-steps-active' : '')
|
|
const isHorizontalAndNotLastStep = computed(() => index => props.direction === 'horizontal' && props.items.length - 1 !== index)
|
|
|
|
// check if validation is enabled
|
|
const isValidationEnabled = computed(() => {
|
|
return props.isActiveStepValid !== undefined
|
|
})
|
|
|
|
watchEffect(() => {
|
|
if (props.currentStep !== undefined && props.currentStep < props.items.length && props.currentStep >= 0)
|
|
currentStep.value = props.currentStep
|
|
emit('update:currentStep', currentStep.value)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<VSlideGroup
|
|
v-model="currentStep"
|
|
class="app-stepper"
|
|
show-arrows
|
|
:direction="props.direction"
|
|
:class="`app-stepper-${props.align} ${props.items[0].icon ? 'app-stepper-icons' : ''}`"
|
|
>
|
|
<VSlideGroupItem
|
|
v-for="(item, index) in props.items"
|
|
:key="item.title"
|
|
:value="index"
|
|
>
|
|
<div
|
|
class="cursor-pointer app-stepper-step mx-1"
|
|
:class="[
|
|
(!props.isActiveStepValid && (isValidationEnabled)) && 'stepper-steps-invalid',
|
|
activeOrCompletedStepsClasses(index),
|
|
]"
|
|
@click="!isValidationEnabled && emit('update:currentStep', index)"
|
|
>
|
|
<!-- SECTION stepper step with icon -->
|
|
<template v-if="item.icon">
|
|
<div class="stepper-icon-step text-high-emphasis d-flex align-center gap-2">
|
|
<!-- 👉 icon and title -->
|
|
<div
|
|
class="d-flex align-center gap-3 step-wrapper"
|
|
:class="[props.direction === 'horizontal' && 'flex-column']"
|
|
>
|
|
<div class="stepper-icon">
|
|
<template v-if="typeof item.icon === 'object'">
|
|
<Component :is="item.icon" />
|
|
</template>
|
|
|
|
<VIcon
|
|
v-else
|
|
:icon="item.icon"
|
|
:size="item.size || props.iconSize"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="stepper-title font-weight-medium text-base mb-1">
|
|
{{ item.title }}
|
|
</p>
|
|
<p
|
|
v-if="item.subtitle"
|
|
class="stepper-subtitle text-sm mb-0"
|
|
>
|
|
{{ item.subtitle }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 👉 append chevron -->
|
|
<VIcon
|
|
v-if="isHorizontalAndNotLastStep(index)"
|
|
class="flip-in-rtl stepper-chevron-indicator mx-6"
|
|
size="18"
|
|
icon="ri-arrow-right-s-line"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<!-- !SECTION -->
|
|
|
|
<!-- SECTION stepper step without icon -->
|
|
<template v-else>
|
|
<div class="d-flex align-center gap-x-2">
|
|
<div class="d-flex align-center gap-2">
|
|
<div
|
|
class="d-flex align-center justify-center"
|
|
style="block-size: 24px; inline-size: 24px;"
|
|
>
|
|
<!-- 👉 custom circle icon -->
|
|
<template v-if="index >= currentStep">
|
|
<div
|
|
v-if="(!isValidationEnabled || props.isActiveStepValid || index !== currentStep)"
|
|
class="stepper-step-indicator"
|
|
/>
|
|
|
|
<VIcon
|
|
v-else
|
|
icon="ri-error-warning-line"
|
|
size="24"
|
|
color="error"
|
|
/>
|
|
</template>
|
|
|
|
<!-- 👉 step completed icon -->
|
|
|
|
<component
|
|
:is="stepperCheck"
|
|
v-else
|
|
class="stepper-step-icon"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 👉 Step Number -->
|
|
<h4 :class="`${!item.subtitle ? 'text-h6' : 'text-h4'} step-number`">
|
|
{{ (index + 1).toString().padStart(2, '0') }}
|
|
</h4>
|
|
</div>
|
|
|
|
<!-- 👉 title and subtitle -->
|
|
<div
|
|
class="app-stepper-title-wrapper"
|
|
style="line-height: 0;"
|
|
>
|
|
<h6 class="text-base font-weight-medium step-title">
|
|
{{ item.title }}
|
|
</h6>
|
|
|
|
<p
|
|
v-if="item.subtitle"
|
|
class="text-sm step-subtitle mb-0"
|
|
>
|
|
{{ item.subtitle }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- 👉 stepper step line -->
|
|
<div
|
|
v-if="isHorizontalAndNotLastStep(index)"
|
|
class="stepper-step-line"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
v-if="props.direction === 'vertical' && props.items.length - 1 !== index"
|
|
class="stepper-step-line vertical"
|
|
/>
|
|
</template>
|
|
<!-- !SECTION -->
|
|
</div>
|
|
</VSlideGroupItem>
|
|
</VSlideGroup>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
@use "@core-scss/base/mixins.scss";
|
|
|
|
/* stylelint-disable no-descending-specificity */
|
|
.app-stepper {
|
|
&.app-stepper-default:not(.app-stepper-icons) .app-stepper-step:not(:last-child) {
|
|
inline-size: 100%;
|
|
}
|
|
|
|
// 👉 stepper step with icon and default
|
|
.v-slide-group__content {
|
|
.stepper-step-indicator {
|
|
border: .1875rem solid rgb(var(--v-theme-primary));
|
|
border-radius: 50%;
|
|
background-color: rgb(var(--v-theme-surface));
|
|
block-size: 1.25rem;
|
|
inline-size: 1.25rem;
|
|
opacity: var(--v-activated-opacity);
|
|
}
|
|
|
|
.stepper-step-line {
|
|
border-radius: 0.1875rem;
|
|
background-color: rgb(var(--v-theme-primary));
|
|
block-size: 0.1875rem;
|
|
opacity: var(--v-activated-opacity);
|
|
}
|
|
|
|
.stepper-step-line:not(.vertical) {
|
|
inline-size: 100%;
|
|
min-inline-size: 3rem;
|
|
}
|
|
|
|
.stepper-step-line.vertical {
|
|
border-radius: 1.25rem;
|
|
block-size: 1.25rem;
|
|
inline-size: 0.1875rem;
|
|
margin-inline-start: 0.625rem;
|
|
}
|
|
|
|
.stepper-chevron-indicator {
|
|
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
|
|
}
|
|
|
|
.stepper-steps-completed,
|
|
.stepper-steps-active {
|
|
.stepper-icon-step,
|
|
.stepper-step-icon {
|
|
color: rgb(var(--v-theme-primary)) !important;
|
|
}
|
|
|
|
.stepper-step-indicator {
|
|
border-width: 0.3125rem;
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.stepper-steps-completed {
|
|
.stepper-step-line {
|
|
opacity: 1;
|
|
}
|
|
|
|
.stepper-chevron-indicator {
|
|
color: rgb(var(--v-theme-primary));
|
|
}
|
|
}
|
|
|
|
.stepper-steps-invalid.stepper-steps-active {
|
|
.stepper-icon-step,
|
|
.step-number,
|
|
.step-title,
|
|
.step-subtitle {
|
|
color: rgb(var(--v-theme-error)) !important;
|
|
}
|
|
}
|
|
|
|
.app-stepper-step:not(.stepper-steps-active,.stepper-steps-completed) {
|
|
.step-number{
|
|
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
|
|
}
|
|
}
|
|
}
|
|
|
|
.app-stepper-title-wrapper{
|
|
text-wrap: nowrap;
|
|
}
|
|
|
|
// 👉 stepper step with bg color
|
|
&.stepper-icon-step-bg {
|
|
.v-slide-group__content{
|
|
row-gap: 1.5rem;
|
|
}
|
|
|
|
.stepper-icon-step {
|
|
.step-wrapper {
|
|
flex-direction: row !important;
|
|
}
|
|
|
|
.stepper-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 0.3125rem;
|
|
background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
|
|
block-size: 2.5rem;
|
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
|
inline-size: 2.5rem;
|
|
margin-inline-end: 0.3rem;
|
|
}
|
|
|
|
.stepper-title,
|
|
.stepper-subtitle {
|
|
line-height: normal;
|
|
}
|
|
|
|
.stepper-title {
|
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
|
font-size: 0.9375rem;
|
|
line-height: 1.375rem;
|
|
}
|
|
|
|
.stepper-subtitle {
|
|
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
|
font-size: 0.8125rem;
|
|
font-weight: 400;
|
|
line-height: 1.25rem;
|
|
}
|
|
}
|
|
|
|
.stepper-steps-active {
|
|
.stepper-icon-step {
|
|
.stepper-icon {
|
|
background-color: rgb(var(--v-theme-primary));
|
|
color: rgba(var(--v-theme-on-primary));
|
|
|
|
@include mixins.elevation(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
.stepper-steps-completed {
|
|
.stepper-icon-step {
|
|
.stepper-icon {
|
|
background: rgba(var(--v-theme-primary), 0.16);
|
|
color: rgba(var(--v-theme-primary));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 👉 stepper alignment
|
|
&.app-stepper-default {
|
|
.v-slide-group__content {
|
|
justify-content: space-between;
|
|
}
|
|
}
|
|
|
|
&.app-stepper-center {
|
|
.v-slide-group__content {
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
&.app-stepper- {
|
|
.v-slide-group__content {
|
|
justify-content: start;
|
|
}
|
|
}
|
|
|
|
&.app-stepper-end {
|
|
.v-slide-group__content {
|
|
justify-content: end;
|
|
}
|
|
}
|
|
|
|
&.app-stepper-icons {
|
|
.app-stepper-step:not(.stepper-steps-active,.stepper-steps-completed) {
|
|
.stepper-title {
|
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
|
}
|
|
}
|
|
|
|
&:not(.stepper-icon-step-bg) {
|
|
.step-wrapper {
|
|
padding-block: 1.25rem;
|
|
padding-inline: 1.875rem;
|
|
}
|
|
}
|
|
|
|
&.v-slide-group--vertical{
|
|
.step-wrapper {
|
|
padding-inline: 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|