Advanced concepts
Animations
Learn how to add animations to a multi-step form using Motion.
Initial steps
We'll show you how to add animations to a multi-step form using Motion. A pre-built form is available in the GitHub repository below, so go ahead and clone it to follow along.
git clone https://github.com/martiserra99/formity-animations
Make sure you run the following command to install all the dependencies.
npm install
Additionally, you also need to install Motion by doing the following.
npm install motion
Key prop
If you look at schema.tsx
, you'll see that each FormStep
receives a key
prop. To animate form transitions, we need to use the key
inside the MultiStep
component.
We will update the MultiStep
component with the following code.
// multi-step/multi-step.tsx
import type { ReactNode } from "react";
import type { OnNext, OnBack } from "@formity/react";
import { useMemo } from "react";
import type { FormityStatus } from "@/types";
import { MultiStepContext } from "./multi-step-context";
interface MultiStepProps {
id: string;
onNext: OnNext;
onBack: OnBack;
status: FormityStatus;
children: ReactNode;
}
export function MultiStep({
id,
onNext,
onBack,
status,
children,
}: MultiStepProps) {
const values = useMemo(
() => ({ onNext, onBack, status }),
[onNext, onBack, status],
);
return (
<div key={id} className="h-full">
<MultiStepContext.Provider value={values}>
{children}
</MultiStepContext.Provider>
</div>
);
}
We will update the schema.tsx
file with the following code.
// schema.tsx
import type { Schema, Form, Return, Cond } from "@formity/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
FormStep,
FormStepContent,
FormStepHeading,
FormStepInputs,
FormStepRow,
} from "./components/form-step";
import { Select } from "./components/input/select";
import { TextInput } from "./components/input/text-input";
import { NumberInput } from "./components/input/number-input";
import { NextButton } from "./components/buttons/next-button";
import { BackButton } from "./components/buttons/back-button";
import type { FormityStatus } from "./types";
import { MultiStep } from "./multi-step";
export type Values = [
Form<{ name: string; surname: string; age: number }>,
Form<{ softwareDeveloper: string }>,
Cond<{
then: [
Form<{ expertise: string }>,
Return<{
name: string;
surname: string;
age: number;
softwareDeveloper: true;
expertise: string;
}>,
];
else: [
Form<{ interested: string }>,
Return<{
name: string;
surname: string;
age: number;
softwareDeveloper: false;
interested: string;
}>,
];
}>,
];
export type Params = {
status: FormityStatus;
};
export const schema: Schema<Values, object, Params> = [
{
form: {
values: () => ({
name: ["", []],
surname: ["", []],
age: [20, []],
}),
render: ({ values, params, onNext, onBack }) => (
<MultiStep
id="yourself"
onNext={onNext}
onBack={onBack}
status={params.status}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
name: z
.string()
.min(1, { message: "Required" })
.max(20, { message: "Must be at most 20 characters" }),
surname: z
.string()
.min(1, { message: "Required" })
.max(20, { message: "Must be at most 20 characters" }),
age: z
.number()
.min(18, { message: "Minimum of 18 years old" })
.max(99, { message: "Maximum of 99 years old" }),
}),
)}
>
<FormStepContent>
<FormStepHeading>Tell us about yourself</FormStepHeading>
<FormStepInputs>
<FormStepRow>
<TextInput name="name" label="Name" placeholder="Your name" />
<TextInput
name="surname"
label="Surname"
placeholder="Your surname"
/>
</FormStepRow>
<NumberInput name="age" label="Age" placeholder="Your age" />
</FormStepInputs>
<NextButton>Next</NextButton>
</FormStepContent>
</FormStep>
</MultiStep>
),
},
},
{
form: {
values: () => ({
softwareDeveloper: ["yes", []],
}),
render: ({ values, params, onNext, onBack }) => (
<MultiStep
id="softwareDeveloper"
onNext={onNext}
onBack={onBack}
status={params.status}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
softwareDeveloper: z.string(),
}),
)}
>
<FormStepContent>
<FormStepHeading>Are you a software developer?</FormStepHeading>
<FormStepInputs>
<Select
name="softwareDeveloper"
label="Software developer"
options={[
{ value: "yes", label: "Yes" },
{ value: "no", label: "No" },
]}
/>
</FormStepInputs>
<FormStepRow>
<BackButton>Back</BackButton>
<NextButton>Next</NextButton>
</FormStepRow>
</FormStepContent>
</FormStep>
</MultiStep>
),
},
},
{
cond: {
if: ({ softwareDeveloper }) => softwareDeveloper === "yes",
then: [
{
form: {
values: () => ({
expertise: ["frontend", []],
}),
render: ({ values, params, onNext, onBack }) => (
<MultiStep
id="expertise"
onNext={onNext}
onBack={onBack}
status={params.status}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
expertise: z.string(),
}),
)}
>
<FormStepContent>
<FormStepHeading>
What is your area of expertise?
</FormStepHeading>
<FormStepInputs>
<Select
name="expertise"
label="Expertise"
options={[
{ value: "frontend", label: "Frontend development" },
{ value: "backend", label: "Backend development" },
{ value: "mobile", label: "Mobile development" },
]}
/>
</FormStepInputs>
<FormStepRow>
<BackButton>Back</BackButton>
<NextButton>Submit</NextButton>
</FormStepRow>
</FormStepContent>
</FormStep>
</MultiStep>
),
},
},
{
return: ({ name, surname, age, expertise }) => ({
name,
surname,
age,
softwareDeveloper: true,
expertise,
}),
},
],
else: [
{
form: {
values: () => ({
interested: ["yes", []],
}),
render: ({ values, params, onNext, onBack }) => (
<MultiStep
id="interested"
onNext={onNext}
onBack={onBack}
status={params.status}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
interested: z.string(),
}),
)}
>
<FormStepContent>
<FormStepHeading>
Are you interested in learning how to code?
</FormStepHeading>
<FormStepInputs>
<Select
name="interested"
label="Interested"
options={[
{ value: "yes", label: "Yes, I am interested." },
{ value: "no", label: "No, it is not for me." },
{ value: "maybe", label: "Maybe, I am not sure." },
]}
/>
</FormStepInputs>
<FormStepRow>
<BackButton>Back</BackButton>
<NextButton>Submit</NextButton>
</FormStepRow>
</FormStepContent>
</FormStep>
</MultiStep>
),
},
},
{
return: ({ name, surname, age, interested }) => ({
name,
surname,
age,
softwareDeveloper: false,
interested,
}),
},
],
},
},
];
Animation
After that, we need to use motion
and AnimatePresence
to ensure the form transition plays correctly when the key
prop changes.
// multi-step/multi-step.tsx
import type { ReactNode } from "react";
import type { OnNext, OnBack } from "@formity/react";
import { useMemo } from "react";
import { AnimatePresence, motion } from "motion/react";
import type { FormityStatus } from "@/types";
import { MultiStepContext } from "./multi-step-context";
interface MultiStepProps {
id: string;
onNext: OnNext;
onBack: OnBack;
status: FormityStatus;
children: ReactNode;
}
export function MultiStep({
id,
onNext,
onBack,
status,
children,
}: MultiStepProps) {
const values = useMemo(
() => ({ onNext, onBack, status }),
[onNext, onBack, status],
);
return (
<AnimatePresence mode="popLayout" initial={false}>
<motion.div
key={id}
initial={{ opacity: 0 }}
animate={{
opacity: 1,
transition: { delay: 0.25, duration: 0.25 },
}}
exit={{
opacity: 0,
transition: { delay: 0, duration: 0.25 },
}}
className="h-full"
>
<MultiStepContext.Provider value={values}>
{children}
</MultiStepContext.Provider>
</motion.div>
</AnimatePresence>
);
}
Block interaction
While we are transitioning to the next form, we may want to block the interaction with the current form. To do it, we first need to update types.ts
to include the moving
property as shown below.
// types.ts
export type Status = FormityStatus | EndStatus;
export type FormityStatus = {
type: "formity";
moving: boolean;
submitting: boolean;
};
export type EndStatus = {
type: "end";
};
Then, in the MultiStep
component we can update this property when navigating to the next or previous step, and use the inert
prop to disable interaction with the current form while the transition is in progress.
// multi-step/multi-step.tsx
import type { ReactNode } from "react";
import type { OnNext, OnBack } from "@formity/react";
import { useMemo, useCallback } from "react";
import { AnimatePresence, motion } from "motion/react";
import type { FormityStatus } from "@/types";
import { MultiStepContext } from "./multi-step-context";
interface MultiStepProps {
id: string;
onNext: OnNext;
onBack: OnBack;
status: FormityStatus;
onChangeStatus: (status: FormityStatus) => void;
children: ReactNode;
}
export function MultiStep({
id,
onNext,
onBack,
status,
onChangeStatus,
children,
}: MultiStepProps) {
const handleNext = useCallback<OnNext>(
(values) => {
onChangeStatus({ type: "formity", moving: true, submitting: false });
onNext(values);
},
[onNext, onChangeStatus],
);
const handleBack = useCallback<OnBack>(
(values) => {
onChangeStatus({ type: "formity", moving: true, submitting: false });
onBack(values);
},
[onBack, onChangeStatus],
);
const values = useMemo(
() => ({ onNext: handleNext, onBack: handleBack, status }),
[handleNext, handleBack, status],
);
return (
<AnimatePresence
mode="popLayout"
initial={false}
onExitComplete={() => {
onChangeStatus({ type: "formity", moving: false, submitting: false });
}}
>
<motion.div
key={id}
inert={status.moving}
initial={{ opacity: 0 }}
animate={{
opacity: 1,
transition: { delay: 0.25, duration: 0.25 },
}}
exit={{
opacity: 0,
transition: { delay: 0, duration: 0.25 },
}}
className="h-full"
>
<MultiStepContext.Provider value={values}>
{children}
</MultiStepContext.Provider>
</motion.div>
</AnimatePresence>
);
}
We also need to update the schema so that we provide the onChangeStatus
callback to the MultiStep
component.
// schema.tsx
import type { Schema, Form, Return, Cond } from "@formity/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
FormStep,
FormStepContent,
FormStepHeading,
FormStepInputs,
FormStepRow,
} from "./components/form-step";
import { Select } from "./components/input/select";
import { TextInput } from "./components/input/text-input";
import { NumberInput } from "./components/input/number-input";
import { NextButton } from "./components/buttons/next-button";
import { BackButton } from "./components/buttons/back-button";
import type { FormityStatus } from "./types";
import { MultiStep } from "./multi-step";
export type Values = [
Form<{ name: string; surname: string; age: number }>,
Form<{ softwareDeveloper: string }>,
Cond<{
then: [
Form<{ expertise: string }>,
Return<{
name: string;
surname: string;
age: number;
softwareDeveloper: true;
expertise: string;
}>,
];
else: [
Form<{ interested: string }>,
Return<{
name: string;
surname: string;
age: number;
softwareDeveloper: false;
interested: string;
}>,
];
}>,
];
export type Params = {
status: FormityStatus;
onChangeStatus: (status: FormityStatus) => void;
};
export const schema: Schema<Values, object, Params> = [
{
form: {
values: () => ({
name: ["", []],
surname: ["", []],
age: [20, []],
}),
render: ({ values, params, onNext, onBack }) => (
<MultiStep
id="yourself"
onNext={onNext}
onBack={onBack}
status={params.status}
onChangeStatus={params.onChangeStatus}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
name: z
.string()
.min(1, { message: "Required" })
.max(20, { message: "Must be at most 20 characters" }),
surname: z
.string()
.min(1, { message: "Required" })
.max(20, { message: "Must be at most 20 characters" }),
age: z
.number()
.min(18, { message: "Minimum of 18 years old" })
.max(99, { message: "Maximum of 99 years old" }),
}),
)}
>
<FormStepContent>
<FormStepHeading>Tell us about yourself</FormStepHeading>
<FormStepInputs>
<FormStepRow>
<TextInput name="name" label="Name" placeholder="Your name" />
<TextInput
name="surname"
label="Surname"
placeholder="Your surname"
/>
</FormStepRow>
<NumberInput name="age" label="Age" placeholder="Your age" />
</FormStepInputs>
<NextButton>Next</NextButton>
</FormStepContent>
</FormStep>
</MultiStep>
),
},
},
{
form: {
values: () => ({
softwareDeveloper: ["yes", []],
}),
render: ({ values, params, onNext, onBack }) => (
<MultiStep
id="softwareDeveloper"
onNext={onNext}
onBack={onBack}
status={params.status}
onChangeStatus={params.onChangeStatus}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
softwareDeveloper: z.string(),
}),
)}
>
<FormStepContent>
<FormStepHeading>Are you a software developer?</FormStepHeading>
<FormStepInputs>
<Select
name="softwareDeveloper"
label="Software developer"
options={[
{ value: "yes", label: "Yes" },
{ value: "no", label: "No" },
]}
/>
</FormStepInputs>
<FormStepRow>
<BackButton>Back</BackButton>
<NextButton>Next</NextButton>
</FormStepRow>
</FormStepContent>
</FormStep>
</MultiStep>
),
},
},
{
cond: {
if: ({ softwareDeveloper }) => softwareDeveloper === "yes",
then: [
{
form: {
values: () => ({
expertise: ["frontend", []],
}),
render: ({ values, params, onNext, onBack }) => (
<MultiStep
id="expertise"
onNext={onNext}
onBack={onBack}
status={params.status}
onChangeStatus={params.onChangeStatus}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
expertise: z.string(),
}),
)}
>
<FormStepContent>
<FormStepHeading>
What is your area of expertise?
</FormStepHeading>
<FormStepInputs>
<Select
name="expertise"
label="Expertise"
options={[
{ value: "frontend", label: "Frontend development" },
{ value: "backend", label: "Backend development" },
{ value: "mobile", label: "Mobile development" },
]}
/>
</FormStepInputs>
<FormStepRow>
<BackButton>Back</BackButton>
<NextButton>Submit</NextButton>
</FormStepRow>
</FormStepContent>
</FormStep>
</MultiStep>
),
},
},
{
return: ({ name, surname, age, expertise }) => ({
name,
surname,
age,
softwareDeveloper: true,
expertise,
}),
},
],
else: [
{
form: {
values: () => ({
interested: ["yes", []],
}),
render: ({ values, params, onNext, onBack }) => (
<MultiStep
id="interested"
onNext={onNext}
onBack={onBack}
status={params.status}
onChangeStatus={params.onChangeStatus}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
interested: z.string(),
}),
)}
>
<FormStepContent>
<FormStepHeading>
Are you interested in learning how to code?
</FormStepHeading>
<FormStepInputs>
<Select
name="interested"
label="Interested"
options={[
{ value: "yes", label: "Yes, I am interested." },
{ value: "no", label: "No, it is not for me." },
{ value: "maybe", label: "Maybe, I am not sure." },
]}
/>
</FormStepInputs>
<FormStepRow>
<BackButton>Back</BackButton>
<NextButton>Submit</NextButton>
</FormStepRow>
</FormStepContent>
</FormStep>
</MultiStep>
),
},
},
{
return: ({ name, surname, age, interested }) => ({
name,
surname,
age,
softwareDeveloper: false,
interested,
}),
},
],
},
},
];
After that, we need to update app.tsx
with the code shown below.
// app.tsx
import { useCallback, useState } from "react";
import { Formity, type OnReturn } from "@formity/react";
import { End } from "./components/end";
import { schema, type Values, type Params } from "./schema";
import type { Status } from "./types";
export default function App() {
const [status, setStatus] = useState<Status>({
type: "formity",
moving: false,
submitting: false,
});
const onReturn = useCallback<OnReturn<Values>>(async (output) => {
setStatus({ type: "formity", moving: false, submitting: true });
// Show output in the console
console.log(output);
// Simulate a network request
await new Promise((resolve) => setTimeout(resolve, 2000));
setStatus({ type: "end" });
}, []);
if (status.type === "end") {
return (
<End
onStart={() =>
setStatus({ type: "formity", moving: false, submitting: false })
}
/>
);
}
return (
<Formity<Values, object, Params>
schema={schema}
params={{ status, onChangeStatus: (status) => setStatus(status) }}
onReturn={onReturn}
/>
);
}
With a slow animation, you'll notice the current form stays interactive during navigation. This happens because the key
changes with the status, causing inert
to only affect the next form. To fix it, we need to update the status before navigating.
// multi-step/multi-step.tsx
import type { ReactNode } from "react";
import type { OnNext, OnBack } from "@formity/react";
import { useMemo, useCallback } from "react";
import { AnimatePresence, motion } from "motion/react";
import type { FormityStatus } from "@/types";
import { MultiStepContext } from "./multi-step-context";
interface MultiStepProps {
id: string;
onNext: OnNext;
onBack: OnBack;
status: FormityStatus;
onChangeStatus: (status: FormityStatus) => void;
children: ReactNode;
}
export function MultiStep({
id,
onNext,
onBack,
status,
onChangeStatus,
children,
}: MultiStepProps) {
const handleNext = useCallback<OnNext>(
(values) => {
onChangeStatus({ type: "formity", moving: true, submitting: false });
setTimeout(() => onNext(values), 0);
},
[onNext, onChangeStatus],
);
const handleBack = useCallback<OnBack>(
(values) => {
onChangeStatus({ type: "formity", moving: true, submitting: false });
setTimeout(() => onBack(values), 0);
},
[onBack, onChangeStatus],
);
const values = useMemo(
() => ({ onNext: handleNext, onBack: handleBack, status }),
[handleNext, handleBack, status],
);
return (
<AnimatePresence
mode="popLayout"
initial={false}
onExitComplete={() => {
onChangeStatus({ type: "formity", moving: false, submitting: false });
}}
>
<motion.div
key={id}
inert={status.moving}
initial={{ opacity: 0 }}
animate={{
opacity: 1,
transition: { delay: 0.25, duration: 0.25 },
}}
exit={{
opacity: 0,
transition: { delay: 0, duration: 0.25 },
}}
className="h-full"
>
<MultiStepContext.Provider value={values}>
{children}
</MultiStepContext.Provider>
</motion.div>
</AnimatePresence>
);
}
Animation direction
To play different animations when navigating forward or backward, we need to track the animation direction. For this reason, we need to update types.ts
as shown below.
// types.ts
export type Status = FormityStatus | EndStatus;
export type FormityStatus = {
type: "formity";
moving: "next" | "back" | false;
submitting: boolean;
};
export type EndStatus = {
type: "end";
};
Then, we need to update the MultiStep
component as shown below.
// multi-step/multi-step.tsx
import type { ReactNode } from "react";
import type { MotionProps } from "motion/react";
import type { OnNext, OnBack } from "@formity/react";
import { useMemo, useCallback } from "react";
import { AnimatePresence, motion } from "motion/react";
import type { FormityStatus } from "@/types";
import { MultiStepContext } from "./multi-step-context";
interface MultiStepProps {
id: string;
onNext: OnNext;
onBack: OnBack;
status: FormityStatus;
onChangeStatus: (status: FormityStatus) => void;
children: ReactNode;
}
export function MultiStep({
id,
onNext,
onBack,
status,
onChangeStatus,
children,
}: MultiStepProps) {
const handleNext = useCallback<OnNext>(
(values) => {
onChangeStatus({ type: "formity", moving: "next", submitting: false });
setTimeout(() => onNext(values), 0);
},
[onNext, onChangeStatus],
);
const handleBack = useCallback<OnBack>(
(values) => {
onChangeStatus({ type: "formity", moving: "back", submitting: false });
setTimeout(() => onBack(values), 0);
},
[onBack, onChangeStatus],
);
const values = useMemo(
() => ({ onNext: handleNext, onBack: handleBack, status }),
[handleNext, handleBack, status],
);
return (
<AnimatePresence
mode="popLayout"
initial={false}
onExitComplete={() => {
onChangeStatus({ type: "formity", moving: false, submitting: false });
}}
>
<motion.div
key={id}
inert={Boolean(status.moving)}
animate={{
x: 0,
opacity: 1,
transition: { delay: 0.25, duration: 0.25 },
}}
{...motionProps(status.moving)}
className="h-full"
>
<MultiStepContext.Provider value={values}>
{children}
</MultiStepContext.Provider>
</motion.div>
</AnimatePresence>
);
}
function motionProps(animate: "next" | "back" | false): MotionProps {
switch (animate) {
case "next":
return {
initial: { x: 50, opacity: 0 },
exit: {
x: -50,
opacity: 0,
transition: { delay: 0, duration: 0.25 },
},
};
case "back":
return {
initial: { x: -50, opacity: 0 },
exit: {
x: 50,
opacity: 0,
transition: { delay: 0, duration: 0.25 },
},
};
default:
return {};
}
}
Progress bar
We could also add a progress bar that is animated every time we go to a different step. To do it, we will create a FormStepContainer
component as shown below.
// components/form-step-container.tsx
import type { ReactNode } from "react";
import { motion } from "motion/react";
interface FormStepContainerProps {
children: ReactNode;
progress: {
numberOfSteps: number;
currentStep: number;
};
}
export function FormStepContainer({
children,
progress,
}: FormStepContainerProps) {
return (
<div className="relative h-full">
<div className="absolute inset-x-0 top-0 z-10 h-1 bg-blue-500/50">
<motion.div
initial={false}
animate={{
width: `${(progress.currentStep / progress.numberOfSteps) * 100}%`,
}}
className="h-full bg-blue-500"
/>
</div>
<>{children}</>
</div>
);
}
Now, we will be able to use this component in the schema.
// schema.tsx
import type { Schema, Form, Return, Cond } from "@formity/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormStepContainer } from "./components/form-step-container";
import {
FormStep,
FormStepContent,
FormStepHeading,
FormStepInputs,
FormStepRow,
} from "./components/form-step";
import { Select } from "./components/input/select";
import { TextInput } from "./components/input/text-input";
import { NumberInput } from "./components/input/number-input";
import { NextButton } from "./components/buttons/next-button";
import { BackButton } from "./components/buttons/back-button";
import type { FormityStatus } from "./types";
import { MultiStep } from "./multi-step";
export type Values = [
Form<{ name: string; surname: string; age: number }>,
Form<{ softwareDeveloper: string }>,
Cond<{
then: [
Form<{ expertise: string }>,
Return<{
name: string;
surname: string;
age: number;
softwareDeveloper: true;
expertise: string;
}>,
];
else: [
Form<{ interested: string }>,
Return<{
name: string;
surname: string;
age: number;
softwareDeveloper: false;
interested: string;
}>,
];
}>,
];
export type Params = {
status: FormityStatus;
onChangeStatus: (status: FormityStatus) => void;
};
export const schema: Schema<Values, object, Params> = [
{
form: {
values: () => ({
name: ["", []],
surname: ["", []],
age: [20, []],
}),
render: ({ values, params, onNext, onBack }) => (
<FormStepContainer progress={{ numberOfSteps: 3, currentStep: 1 }}>
<MultiStep
id="yourself"
onNext={onNext}
onBack={onBack}
status={params.status}
onChangeStatus={params.onChangeStatus}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
name: z
.string()
.min(1, { message: "Required" })
.max(20, { message: "Must be at most 20 characters" }),
surname: z
.string()
.min(1, { message: "Required" })
.max(20, { message: "Must be at most 20 characters" }),
age: z
.number()
.min(18, { message: "Minimum of 18 years old" })
.max(99, { message: "Maximum of 99 years old" }),
}),
)}
>
<FormStepContent>
<FormStepHeading>Tell us about yourself</FormStepHeading>
<FormStepInputs>
<FormStepRow>
<TextInput
name="name"
label="Name"
placeholder="Your name"
/>
<TextInput
name="surname"
label="Surname"
placeholder="Your surname"
/>
</FormStepRow>
<NumberInput name="age" label="Age" placeholder="Your age" />
</FormStepInputs>
<NextButton>Next</NextButton>
</FormStepContent>
</FormStep>
</MultiStep>
</FormStepContainer>
),
},
},
{
form: {
values: () => ({
softwareDeveloper: ["yes", []],
}),
render: ({ values, params, onNext, onBack }) => (
<FormStepContainer progress={{ numberOfSteps: 3, currentStep: 2 }}>
<MultiStep
id="softwareDeveloper"
onNext={onNext}
onBack={onBack}
status={params.status}
onChangeStatus={params.onChangeStatus}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
softwareDeveloper: z.string(),
}),
)}
>
<FormStepContent>
<FormStepHeading>Are you a software developer?</FormStepHeading>
<FormStepInputs>
<Select
name="softwareDeveloper"
label="Software developer"
options={[
{ value: "yes", label: "Yes" },
{ value: "no", label: "No" },
]}
/>
</FormStepInputs>
<FormStepRow>
<BackButton>Back</BackButton>
<NextButton>Next</NextButton>
</FormStepRow>
</FormStepContent>
</FormStep>
</MultiStep>
</FormStepContainer>
),
},
},
{
cond: {
if: ({ softwareDeveloper }) => softwareDeveloper === "yes",
then: [
{
form: {
values: () => ({
expertise: ["frontend", []],
}),
render: ({ values, params, onNext, onBack }) => (
<FormStepContainer
progress={{ numberOfSteps: 3, currentStep: 3 }}
>
<MultiStep
id="expertise"
onNext={onNext}
onBack={onBack}
status={params.status}
onChangeStatus={params.onChangeStatus}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
expertise: z.string(),
}),
)}
>
<FormStepContent>
<FormStepHeading>
What is your area of expertise?
</FormStepHeading>
<FormStepInputs>
<Select
name="expertise"
label="Expertise"
options={[
{
value: "frontend",
label: "Frontend development",
},
{ value: "backend", label: "Backend development" },
{ value: "mobile", label: "Mobile development" },
]}
/>
</FormStepInputs>
<FormStepRow>
<BackButton>Back</BackButton>
<NextButton>Submit</NextButton>
</FormStepRow>
</FormStepContent>
</FormStep>
</MultiStep>
</FormStepContainer>
),
},
},
{
return: ({ name, surname, age, expertise }) => ({
name,
surname,
age,
softwareDeveloper: true,
expertise,
}),
},
],
else: [
{
form: {
values: () => ({
interested: ["yes", []],
}),
render: ({ values, params, onNext, onBack }) => (
<FormStepContainer
progress={{ numberOfSteps: 3, currentStep: 3 }}
>
<MultiStep
id="interested"
onNext={onNext}
onBack={onBack}
status={params.status}
onChangeStatus={params.onChangeStatus}
>
<FormStep
defaultValues={values}
resolver={zodResolver(
z.object({
interested: z.string(),
}),
)}
>
<FormStepContent>
<FormStepHeading>
Are you interested in learning how to code?
</FormStepHeading>
<FormStepInputs>
<Select
name="interested"
label="Interested"
options={[
{ value: "yes", label: "Yes, I am interested." },
{ value: "no", label: "No, it is not for me." },
{ value: "maybe", label: "Maybe, I am not sure." },
]}
/>
</FormStepInputs>
<FormStepRow>
<BackButton>Back</BackButton>
<NextButton>Submit</NextButton>
</FormStepRow>
</FormStepContent>
</FormStep>
</MultiStep>
</FormStepContainer>
),
},
},
{
return: ({ name, surname, age, interested }) => ({
name,
surname,
age,
softwareDeveloper: false,
interested,
}),
},
],
},
},
];