mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
Create <Counter /> component
This commit is contained in:
@@ -80,7 +80,8 @@ export const CATEGORIES = [
|
|||||||
'Flowing Menu',
|
'Flowing Menu',
|
||||||
'Elastic Slider',
|
'Elastic Slider',
|
||||||
'Stack',
|
'Stack',
|
||||||
'Chroma Grid'
|
'Chroma Grid',
|
||||||
|
'Stepper'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ const components = {
|
|||||||
'tilted-card': () => import('../demo/Components/TiltedCardDemo.vue'),
|
'tilted-card': () => import('../demo/Components/TiltedCardDemo.vue'),
|
||||||
'stack': () => import('../demo/Components/StackDemo.vue'),
|
'stack': () => import('../demo/Components/StackDemo.vue'),
|
||||||
'chroma-grid': () => import('../demo/Components/ChromaGridDemo.vue'),
|
'chroma-grid': () => import('../demo/Components/ChromaGridDemo.vue'),
|
||||||
|
'stepper': () => import('../demo/Components/StepperDemo.vue'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const backgrounds = {
|
const backgrounds = {
|
||||||
|
|||||||
59
src/constants/code/Components/stepperCode.ts
Normal file
59
src/constants/code/Components/stepperCode.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import code from '@content/Components/Stepper/Stepper.vue?raw';
|
||||||
|
import { createCodeObject } from '@/types/code';
|
||||||
|
|
||||||
|
export const stepper = createCodeObject(code, 'Components/Stepper', {
|
||||||
|
installation: `npm install motion-v`,
|
||||||
|
usage: `<template>
|
||||||
|
<Stepper
|
||||||
|
:initial-step="1"
|
||||||
|
:on-step-change="handleStepChange"
|
||||||
|
:on-final-step-completed="handleFinalStepCompleted"
|
||||||
|
back-button-text="Previous"
|
||||||
|
next-button-text="Next"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h2>Welcome to the Vue Bits stepper!</h2>
|
||||||
|
<p>Check out the next step!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Step 2</h2>
|
||||||
|
<img
|
||||||
|
style="height: 100px; width: 100%; object-fit: cover; border-radius: 15px; margin-top: 1em;"
|
||||||
|
src="https://example.com/image.jpg"
|
||||||
|
alt="Example"
|
||||||
|
/>
|
||||||
|
<p>Custom step content!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>How about an input?</h2>
|
||||||
|
<input
|
||||||
|
v-model="name"
|
||||||
|
class="mt-2 px-3 py-2 border border-gray-300 rounded-md w-full"
|
||||||
|
placeholder="Your name?"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Final Step</h2>
|
||||||
|
<p>You made it!</p>
|
||||||
|
</div>
|
||||||
|
</Stepper>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import Stepper from "./Stepper.vue"
|
||||||
|
|
||||||
|
const name = ref('')
|
||||||
|
|
||||||
|
const handleStepChange = (step) => {
|
||||||
|
console.log('Step changed to:', step)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFinalStepCompleted = () => {
|
||||||
|
console.log('Stepper completed!')
|
||||||
|
}
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
275
src/content/Components/Stepper/Stepper.vue
Normal file
275
src/content/Components/Stepper/Stepper.vue
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex justify-center items-center w-full h-full" v-bind="$attrs">
|
||||||
|
<div
|
||||||
|
:class="`w-full max-w-md p-8 rounded-[2rem] shadow-[0_20px_25px_-5px_rgba(0,0,0,0.1),0_10px_10px_-5px_rgba(0,0,0,0.04)] ${stepCircleContainerClassName}`"
|
||||||
|
style="border: 1px solid #222"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="`flex items-center justify-center w-full ${stepContainerClassName}`"
|
||||||
|
:style="{ marginBottom: isCompleted ? '0' : '2rem' }"
|
||||||
|
>
|
||||||
|
<template v-for="(_, index) in stepsArray" :key="index + 1">
|
||||||
|
<div
|
||||||
|
v-if="!renderStepIndicator"
|
||||||
|
@click="() => handleStepClick(index + 1)"
|
||||||
|
:class="[
|
||||||
|
'relative outline-none flex h-8 w-8 items-center justify-center rounded-full font-semibold',
|
||||||
|
isCompleted ? 'cursor-default' : 'cursor-pointer'
|
||||||
|
]"
|
||||||
|
:style="getStepIndicatorStyle(index + 1)"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
v-if="getStepStatus(index + 1) === 'complete'"
|
||||||
|
class="h-4 w-4 text-white stroke-white"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<Motion
|
||||||
|
as="path"
|
||||||
|
d="M5 13l4 4L19 7"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
:initial="{ pathLength: 0, opacity: 0 }"
|
||||||
|
:animate="
|
||||||
|
getStepStatus(index + 1) === 'complete'
|
||||||
|
? { pathLength: 1, opacity: 1 }
|
||||||
|
: { pathLength: 0, opacity: 0 }
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div v-else-if="getStepStatus(index + 1) === 'active'" class="h-3 w-3 rounded-full bg-white" />
|
||||||
|
<span v-else class="text-sm">{{ index + 1 }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<component
|
||||||
|
v-else
|
||||||
|
:is="renderStepIndicator"
|
||||||
|
:step="index + 1"
|
||||||
|
:current-step="currentStep"
|
||||||
|
:on-step-click="handleCustomStepClick"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="index < totalSteps - 1"
|
||||||
|
class="relative ml-2 mr-2 h-0.5 flex-1 overflow-hidden rounded bg-zinc-600"
|
||||||
|
>
|
||||||
|
<Motion
|
||||||
|
as="div"
|
||||||
|
class="absolute left-0 top-0 h-full"
|
||||||
|
:initial="{ width: 0, backgroundColor: '#52525b' }"
|
||||||
|
:animate="
|
||||||
|
currentStep > index + 1
|
||||||
|
? { width: '100%', backgroundColor: '#27ff64' }
|
||||||
|
: { width: 0, backgroundColor: '#52525b' }
|
||||||
|
"
|
||||||
|
:transition="{ type: 'spring', stiffness: 100, damping: 15, duration: 0.4 }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Motion
|
||||||
|
as="div"
|
||||||
|
:class="`w-full ${contentClassName}`"
|
||||||
|
:style="{
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
marginBottom: isCompleted ? '0' : '2rem'
|
||||||
|
}"
|
||||||
|
:animate="{ height: isCompleted ? 0 : `${parentHeight + 1}px` }"
|
||||||
|
:transition="{ type: 'spring', stiffness: 200, damping: 25, duration: 0.4 }"
|
||||||
|
>
|
||||||
|
<AnimatePresence :initial="false" mode="sync" :custom="direction">
|
||||||
|
<Motion
|
||||||
|
v-if="!isCompleted"
|
||||||
|
ref="containerRef"
|
||||||
|
as="div"
|
||||||
|
:key="currentStep"
|
||||||
|
:initial="getStepContentInitial()"
|
||||||
|
:animate="{ x: '0%', opacity: 1 }"
|
||||||
|
:exit="getStepContentExit()"
|
||||||
|
:transition="{ type: 'tween', stiffness: 300, damping: 30, duration: 0.4 }"
|
||||||
|
:style="{ position: 'absolute', left: 0, right: 0, top: 0 }"
|
||||||
|
>
|
||||||
|
<div ref="contentRef" v-if="slots.default && slots.default()[currentStep - 1]">
|
||||||
|
<component :is="slots.default()[currentStep - 1]" />
|
||||||
|
</div>
|
||||||
|
</Motion>
|
||||||
|
</AnimatePresence>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<div v-if="!isCompleted" :class="`w-full ${footerClassName}`">
|
||||||
|
<div :class="`flex w-full ${currentStep !== 1 ? 'justify-between' : 'justify-end'}`">
|
||||||
|
<button
|
||||||
|
v-if="currentStep !== 1"
|
||||||
|
@click="handleBack"
|
||||||
|
:disabled="backButtonProps?.disabled"
|
||||||
|
:class="`text-zinc-400 bg-transparent cursor-pointer transition-all duration-[350ms] rounded px-2 py-1 border-none hover:text-white ${currentStep === 1 ? 'opacity-50 cursor-not-allowed' : ''}`"
|
||||||
|
v-bind="backButtonProps"
|
||||||
|
>
|
||||||
|
{{ backButtonText }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="isLastStep ? handleComplete() : handleNext()"
|
||||||
|
:disabled="nextButtonProps?.disabled"
|
||||||
|
:class="`border-none bg-[#27ff64] transition-all duration-[350ms] flex items-center justify-center rounded-full text-white font-medium tracking-tight px-3.5 py-1.5 cursor-pointer hover:bg-[#22e55c] disabled:opacity-50 disabled:cursor-not-allowed`"
|
||||||
|
>
|
||||||
|
{{ isLastStep ? 'Complete' : nextButtonText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
computed,
|
||||||
|
useSlots,
|
||||||
|
watch,
|
||||||
|
onMounted,
|
||||||
|
nextTick,
|
||||||
|
useTemplateRef,
|
||||||
|
type VNode,
|
||||||
|
type ButtonHTMLAttributes,
|
||||||
|
type Component
|
||||||
|
} from 'vue';
|
||||||
|
import { Motion, AnimatePresence } from 'motion-v';
|
||||||
|
|
||||||
|
interface StepperProps {
|
||||||
|
children?: VNode[];
|
||||||
|
initialStep?: number;
|
||||||
|
onStepChange?: (step: number) => void;
|
||||||
|
onFinalStepCompleted?: () => void;
|
||||||
|
stepCircleContainerClassName?: string;
|
||||||
|
stepContainerClassName?: string;
|
||||||
|
contentClassName?: string;
|
||||||
|
footerClassName?: string;
|
||||||
|
backButtonProps?: ButtonHTMLAttributes;
|
||||||
|
nextButtonProps?: ButtonHTMLAttributes;
|
||||||
|
backButtonText?: string;
|
||||||
|
nextButtonText?: string;
|
||||||
|
disableStepIndicators?: boolean;
|
||||||
|
renderStepIndicator?: Component;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<StepperProps>(), {
|
||||||
|
initialStep: 1,
|
||||||
|
onStepChange: () => {},
|
||||||
|
onFinalStepCompleted: () => {},
|
||||||
|
stepCircleContainerClassName: '',
|
||||||
|
stepContainerClassName: '',
|
||||||
|
contentClassName: '',
|
||||||
|
footerClassName: '',
|
||||||
|
backButtonProps: () => ({}),
|
||||||
|
nextButtonProps: () => ({}),
|
||||||
|
backButtonText: 'Back',
|
||||||
|
nextButtonText: 'Continue',
|
||||||
|
disableStepIndicators: false,
|
||||||
|
renderStepIndicator: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
const slots = useSlots();
|
||||||
|
const currentStep = ref(props.initialStep);
|
||||||
|
const direction = ref(1);
|
||||||
|
const isCompleted = ref(false);
|
||||||
|
const parentHeight = ref(0);
|
||||||
|
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
|
||||||
|
const contentRef = useTemplateRef<HTMLDivElement>('contentRef');
|
||||||
|
|
||||||
|
const stepsArray = computed(() => slots.default?.() || []);
|
||||||
|
const totalSteps = computed(() => stepsArray.value.length);
|
||||||
|
const isLastStep = computed(() => currentStep.value === totalSteps.value);
|
||||||
|
|
||||||
|
const getStepStatus = (step: number) => {
|
||||||
|
if (isCompleted.value || currentStep.value > step) return 'complete';
|
||||||
|
if (currentStep.value === step) return 'active';
|
||||||
|
return 'inactive';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStepIndicatorStyle = (step: number) => {
|
||||||
|
const status = getStepStatus(step);
|
||||||
|
switch (status) {
|
||||||
|
case 'active':
|
||||||
|
case 'complete':
|
||||||
|
return { backgroundColor: '#27FF64', color: '#fff' };
|
||||||
|
default:
|
||||||
|
return { backgroundColor: '#222', color: '#a3a3a3' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStepContentInitial = () => ({
|
||||||
|
x: direction.value >= 0 ? '-100%' : '100%',
|
||||||
|
opacity: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const getStepContentExit = () => ({
|
||||||
|
x: direction.value >= 0 ? '50%' : '-50%',
|
||||||
|
opacity: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleStepClick = (step: number) => {
|
||||||
|
if (isCompleted.value) return;
|
||||||
|
if (!props.disableStepIndicators) {
|
||||||
|
direction.value = step > currentStep.value ? 1 : -1;
|
||||||
|
updateStep(step);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCustomStepClick = (clicked: number) => {
|
||||||
|
if (isCompleted.value) return;
|
||||||
|
if (clicked !== currentStep.value && !props.disableStepIndicators) {
|
||||||
|
direction.value = clicked > currentStep.value ? 1 : -1;
|
||||||
|
updateStep(clicked);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const measureHeight = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (contentRef.value) {
|
||||||
|
const height = contentRef.value.offsetHeight;
|
||||||
|
if (height > 0 && height !== parentHeight.value) {
|
||||||
|
parentHeight.value = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStep = (newStep: number) => {
|
||||||
|
if (newStep >= 1 && newStep <= totalSteps.value) {
|
||||||
|
currentStep.value = newStep;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
direction.value = -1;
|
||||||
|
updateStep(currentStep.value - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
direction.value = 1;
|
||||||
|
updateStep(currentStep.value + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleComplete = () => {
|
||||||
|
isCompleted.value = true;
|
||||||
|
props.onFinalStepCompleted?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(currentStep, newStep => {
|
||||||
|
props.onStepChange?.(newStep);
|
||||||
|
if (!isCompleted.value) {
|
||||||
|
measureHeight();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.initialStep !== 1) {
|
||||||
|
currentStep.value = props.initialStep;
|
||||||
|
}
|
||||||
|
measureHeight();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
192
src/demo/Components/StepperDemo.vue
Normal file
192
src/demo/Components/StepperDemo.vue
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<template>
|
||||||
|
<Toaster
|
||||||
|
position="bottom-right"
|
||||||
|
:visibleToasts="1"
|
||||||
|
:toastOptions="{
|
||||||
|
style: {
|
||||||
|
fontSize: '12px',
|
||||||
|
borderRadius: '0.75rem',
|
||||||
|
border: '1px solid #333',
|
||||||
|
color: '#fff',
|
||||||
|
backgroundColor: '#0b0b0b'
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative demo-container h-[500px] overflow-hidden">
|
||||||
|
<Stepper
|
||||||
|
:initial-step="step"
|
||||||
|
:on-step-change="handleStepChange"
|
||||||
|
:on-final-step-completed="handleFinalStepCompleted"
|
||||||
|
:next-button-props="{ disabled: step === 3 && !name }"
|
||||||
|
:disable-step-indicators="step === 3 && !name"
|
||||||
|
back-button-text="Previous"
|
||||||
|
next-button-text="Next"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-[#27FF64] text-xl font-semibold">Welcome to the Vue Bits stepper!</h2>
|
||||||
|
<p>Check out the next step!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="mb-4">Step 2</h2>
|
||||||
|
<img
|
||||||
|
class="h-[100px] w-full object-cover object-[center_-70px] rounded-[15px] mt-4"
|
||||||
|
src="https://www.purrfectcatgifts.co.uk/cdn/shop/collections/Funny_Cat_Cards_640x640.png?v=1663150894"
|
||||||
|
alt="Cat cards"
|
||||||
|
/>
|
||||||
|
<p class="mt-4">Custom step content!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="mb-4">How about an input?</h2>
|
||||||
|
<input
|
||||||
|
v-model="name"
|
||||||
|
class="py-3 px-4 border border-[#333] rounded-xl w-full bg-[#0b0b0b] text-white text-sm transition-all duration-200 ease-in-out placeholder-[#888] focus:outline-none focus:border-[#27FF64] focus:shadow-[0_0_0_2px_rgba(39,255,100,0.1)]"
|
||||||
|
placeholder="Your name?"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="text-[#27FF64] text-xl font-semibold">Final Step</h2>
|
||||||
|
<p>You made it!</p>
|
||||||
|
</div>
|
||||||
|
</Stepper>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['motion-v']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="stepper" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="stepper.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { toast, Toaster } from 'vue-sonner';
|
||||||
|
import 'vue-sonner/style.css'
|
||||||
|
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import CodeExample from '@/components/code/CodeExample.vue';
|
||||||
|
import CliInstallation from '@/components/code/CliInstallation.vue';
|
||||||
|
import Dependencies from '@/components/code/Dependencies.vue';
|
||||||
|
|
||||||
|
import Stepper from '@/content/Components/Stepper/Stepper.vue';
|
||||||
|
import { stepper } from '@/constants/code/Components/stepperCode.ts';
|
||||||
|
|
||||||
|
const name = ref('');
|
||||||
|
const step = ref(1);
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'children',
|
||||||
|
type: 'VNode[]',
|
||||||
|
default: '-',
|
||||||
|
description: 'The Step components (or any custom content) rendered inside the stepper.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'initialStep',
|
||||||
|
type: 'number',
|
||||||
|
default: '1',
|
||||||
|
description: 'The first step to display when the stepper is initialized.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'onStepChange',
|
||||||
|
type: '(step: number) => void',
|
||||||
|
default: '() => {}',
|
||||||
|
description: 'Callback fired whenever the step changes.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'onFinalStepCompleted',
|
||||||
|
type: '() => void',
|
||||||
|
default: '() => {}',
|
||||||
|
description: 'Callback fired when the stepper completes its final step.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stepCircleContainerClassName',
|
||||||
|
type: 'string',
|
||||||
|
default: "-",
|
||||||
|
description: 'Custom class name for the container holding the step indicators.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stepContainerClassName',
|
||||||
|
type: 'string',
|
||||||
|
default: "-",
|
||||||
|
description: 'Custom class name for the row holding the step circles/connectors.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'contentClassName',
|
||||||
|
type: 'string',
|
||||||
|
default: "-",
|
||||||
|
description: "Custom class name for the step's main content container."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'footerClassName',
|
||||||
|
type: 'string',
|
||||||
|
default: "-",
|
||||||
|
description: 'Custom class name for the footer area containing navigation buttons.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backButtonProps',
|
||||||
|
type: 'ButtonHTMLAttributes',
|
||||||
|
default: '{}',
|
||||||
|
description: 'Extra props passed to the Back button.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nextButtonProps',
|
||||||
|
type: 'ButtonHTMLAttributes',
|
||||||
|
default: '{}',
|
||||||
|
description: 'Extra props passed to the Next/Complete button.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backButtonText',
|
||||||
|
type: 'string',
|
||||||
|
default: "'Back'",
|
||||||
|
description: 'Text for the Back button.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nextButtonText',
|
||||||
|
type: 'string',
|
||||||
|
default: "'Continue'",
|
||||||
|
description: 'Text for the Next button when not on the last step.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disableStepIndicators',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
description: 'Disables click interaction on step indicators.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'renderStepIndicator',
|
||||||
|
type: '(props: RenderStepIndicatorProps) => VNode',
|
||||||
|
default: 'undefined',
|
||||||
|
description: 'Renders a custom step indicator component.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleFinalStepCompleted = () => {
|
||||||
|
toast('✅ All steps completed!');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStepChange = (newStep: number) => {
|
||||||
|
step.value = newStep;
|
||||||
|
if (newStep === 4) {
|
||||||
|
if (name.value) {
|
||||||
|
toast(`👋🏻 Hello ${name.value}!`)
|
||||||
|
} else {
|
||||||
|
toast(`You didn't provide your name :(`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast(`✅ Step ${newStep}!`)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user