Files
calendar/packages/ui/components/form/wizard/WizardForm.tsx
T
0fadbe1ce9 fix: clicking on previous step navigates to correct step instead of next (#22146)
* fix: clicking on previous step navigates to correct step instead of next

* fix: implement step navigation in WizardForm and disable navigation in WizardLayout

* fix: adjust step navigation logic in WizardForm and useWizardState

* fix: add clarification comment in goToStep function

---------

Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2025-09-04 04:32:17 +00:00

120 lines
3.6 KiB
TypeScript

"use client";
import type { Dispatch, SetStateAction } from "react";
import { useEffect, useState } from "react";
import classNames from "@calcom/ui/classNames";
import { Button } from "../../button";
import { Steps } from "../../form/step";
import { useWizardState } from "./useWizardState";
export type WizardStep = {
title: string;
containerClassname?: string;
contentClassname?: string;
description: string;
content?:
| ((
setIsPending: Dispatch<SetStateAction<boolean>>,
nav: { onNext: () => void; onPrev: () => void; step: number; maxSteps: number }
) => JSX.Element)
| JSX.Element;
isEnabled?: boolean;
isPending?: boolean;
customActions?: boolean;
};
export interface WizardFormProps {
steps: WizardStep[];
containerClassname?: string;
prevLabel?: string;
nextLabel?: string;
finishLabel?: string;
stepLabel?: React.ComponentProps<typeof Steps>["stepLabel"];
defaultStep?: number;
disableNavigation?: boolean;
}
export function WizardForm({
steps,
containerClassname,
prevLabel = "Back",
nextLabel = "Next",
finishLabel = "Finish",
stepLabel,
defaultStep = 1,
disableNavigation = false,
}: WizardFormProps) {
const { currentStep, maxSteps, nextStep, goToStep, prevStep, isFirstStep, isLastStep } = useWizardState(
defaultStep,
steps.length
);
const [currentStepisPending, setCurrentStepisPending] = useState(false);
const currentStepData = steps[currentStep - 1];
useEffect(() => {
setCurrentStepisPending(false);
}, [currentStep]);
return (
<div className="mx-auto mt-4 print:w-full" data-testid="wizard-form">
<div className={classNames("overflow-hidden md:mb-2 md:w-[700px]", containerClassname)}>
<div className="px-6 py-5">
<h1 className="font-cal text-emphasis text-2xl" data-testid="step-title">
{currentStepData.title}
</h1>
<p className="text-subtle text-sm" data-testid="step-description">
{currentStepData.description}
</p>
{!disableNavigation && (
<Steps
maxSteps={maxSteps}
currentStep={currentStep}
navigateToStep={goToStep}
stepLabel={stepLabel}
data-testid="wizard-step-component"
/>
)}
</div>
</div>
<div className={classNames("mb-8 overflow-hidden md:w-[700px]", containerClassname)}>
<div
className={classNames(
"bg-default border-subtle max-w-3xl rounded-2xl border px-4 py-3 sm:p-4 ",
currentStepData.contentClassname
)}>
{typeof currentStepData.content === "function"
? currentStepData.content(setCurrentStepisPending, {
onNext: nextStep,
onPrev: prevStep,
step: currentStep,
maxSteps,
})
: currentStepData.content}
</div>
{!disableNavigation && !currentStepData.customActions && (
<div className="flex justify-end px-4 py-4 print:hidden sm:px-6">
{!isFirstStep && (
<Button color="secondary" onClick={prevStep}>
{prevLabel}
</Button>
)}
<Button
tabIndex={0}
loading={currentStepisPending}
type="submit"
color="primary"
form={`wizard-step-${currentStep}`}
disabled={currentStepData.isEnabled === false}
className="relative ml-2">
{isLastStep ? finishLabel : nextLabel}
</Button>
</div>
)}
</div>
</div>
);
}