From b7562ef2ba47886c427331f92d88b8ed28c8ceca Mon Sep 17 00:00:00 2001 From: Alfarish Fizikri Date: Wed, 23 Jul 2025 15:09:43 +0700 Subject: [PATCH 1/2] Create component --- src/constants/Categories.ts | 3 +- src/constants/Components.ts | 1 + src/constants/code/Components/stepperCode.ts | 59 ++++ src/content/Components/Stepper/Stepper.vue | 275 +++++++++++++++++++ src/demo/Components/StepperDemo.vue | 192 +++++++++++++ 5 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 src/constants/code/Components/stepperCode.ts create mode 100644 src/content/Components/Stepper/Stepper.vue create mode 100644 src/demo/Components/StepperDemo.vue diff --git a/src/constants/Categories.ts b/src/constants/Categories.ts index ac41eb1..1a5163f 100644 --- a/src/constants/Categories.ts +++ b/src/constants/Categories.ts @@ -80,7 +80,8 @@ export const CATEGORIES = [ 'Flowing Menu', 'Elastic Slider', 'Stack', - 'Chroma Grid' + 'Chroma Grid', + 'Stepper' ] }, { diff --git a/src/constants/Components.ts b/src/constants/Components.ts index d543dc2..596c6c4 100644 --- a/src/constants/Components.ts +++ b/src/constants/Components.ts @@ -69,6 +69,7 @@ const components = { 'tilted-card': () => import('../demo/Components/TiltedCardDemo.vue'), 'stack': () => import('../demo/Components/StackDemo.vue'), 'chroma-grid': () => import('../demo/Components/ChromaGridDemo.vue'), + 'stepper': () => import('../demo/Components/StepperDemo.vue'), }; const backgrounds = { diff --git a/src/constants/code/Components/stepperCode.ts b/src/constants/code/Components/stepperCode.ts new file mode 100644 index 0000000..218495e --- /dev/null +++ b/src/constants/code/Components/stepperCode.ts @@ -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: ` + +` +}); diff --git a/src/content/Components/Stepper/Stepper.vue b/src/content/Components/Stepper/Stepper.vue new file mode 100644 index 0000000..4d7f02d --- /dev/null +++ b/src/content/Components/Stepper/Stepper.vue @@ -0,0 +1,275 @@ + + + diff --git a/src/demo/Components/StepperDemo.vue b/src/demo/Components/StepperDemo.vue new file mode 100644 index 0000000..358fa12 --- /dev/null +++ b/src/demo/Components/StepperDemo.vue @@ -0,0 +1,192 @@ + + + From bafee9ccbdbec027c066c0ea48441641b5433f53 Mon Sep 17 00:00:00 2001 From: Alfarish Fizikri Date: Sun, 27 Jul 2025 07:22:43 +0700 Subject: [PATCH 2/2] feat(Stepper): add lockOnComplete prop --- src/content/Components/Stepper/Stepper.vue | 26 ++++++++++++++-------- src/demo/Components/StepperDemo.vue | 7 ++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/content/Components/Stepper/Stepper.vue b/src/content/Components/Stepper/Stepper.vue index 4d7f02d..4d0f1f4 100644 --- a/src/content/Components/Stepper/Stepper.vue +++ b/src/content/Components/Stepper/Stepper.vue @@ -14,7 +14,7 @@ @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' + isCompleted && lockOnComplete ? 'cursor-default' : 'cursor-pointer' ]" :style="getStepIndicatorStyle(index + 1)" > @@ -154,6 +154,7 @@ interface StepperProps { nextButtonText?: string; disableStepIndicators?: boolean; renderStepIndicator?: Component; + lockOnComplete?: boolean; } const props = withDefaults(defineProps(), { @@ -169,7 +170,8 @@ const props = withDefaults(defineProps(), { backButtonText: 'Back', nextButtonText: 'Continue', disableStepIndicators: false, - renderStepIndicator: undefined + renderStepIndicator: undefined, + lockOnComplete: true }); const slots = useSlots(); @@ -212,7 +214,7 @@ const getStepContentExit = () => ({ }); const handleStepClick = (step: number) => { - if (isCompleted.value) return; + if (isCompleted.value && props.lockOnComplete) return; if (!props.disableStepIndicators) { direction.value = step > currentStep.value ? 1 : -1; updateStep(step); @@ -220,7 +222,7 @@ const handleStepClick = (step: number) => { }; const handleCustomStepClick = (clicked: number) => { - if (isCompleted.value) return; + if (isCompleted.value && props.lockOnComplete) return; if (clicked !== currentStep.value && !props.disableStepIndicators) { direction.value = clicked > currentStep.value ? 1 : -1; updateStep(clicked); @@ -259,12 +261,18 @@ const handleComplete = () => { props.onFinalStepCompleted?.(); }; -watch(currentStep, newStep => { - props.onStepChange?.(newStep); - if (!isCompleted.value) { - measureHeight(); +watch( + currentStep, + (newStep, oldStep) => { + props.onStepChange?.(newStep); + if (newStep !== oldStep && !isCompleted.value) { + nextTick(measureHeight); + } else if (!props.lockOnComplete && isCompleted.value) { + isCompleted.value = false; + nextTick(measureHeight); + } } -}); +); onMounted(() => { if (props.initialStep !== 1) { diff --git a/src/demo/Components/StepperDemo.vue b/src/demo/Components/StepperDemo.vue index 358fa12..6b8667d 100644 --- a/src/demo/Components/StepperDemo.vue +++ b/src/demo/Components/StepperDemo.vue @@ -21,6 +21,7 @@ :on-final-step-completed="handleFinalStepCompleted" :next-button-props="{ disabled: step === 3 && !name }" :disable-step-indicators="step === 3 && !name" + :lock-on-complete="false" back-button-text="Previous" next-button-text="Next" > @@ -170,6 +171,12 @@ const propData = [ type: '(props: RenderStepIndicatorProps) => VNode', default: 'undefined', description: 'Renders a custom step indicator component.' + }, + { + name: 'lockOnComplete', + type: 'boolean', + default: 'false', + description: 'Prevents returning to previous steps after completing the stepper.' } ];