Advanced concepts
Progress bar
Learn how to add a progress bar in these forms.
First steps
There is a really simple way to add a progress bar in these forms. To learn how to do it clone the following Github repository so that you don't need to start from scratch.
git clone https://github.com/martiserra99/formity-docs-progress-bar
Make sure you run the following command to install all the dependencies:
npm install
Create progress bar
To add the progress bar we will create the components/screen.tsx
file with the following component:
// src/components/screen.tsx
import { motion } from "framer-motion";
interface ScreenProps {
progress: { total: number; current: number };
children: React.ReactNode;
}
export default function Screen({ progress, children }: ScreenProps) {
return (
<div className="relative h-full w-full">
<Progress total={progress.total} current={progress.current} />
{children}
</div>
);
}
interface ProgressProps {
total: number;
current: number;
}
function Progress({ total, current }: ProgressProps) {
return (
<div className="absolute left-0 right-0 top-0 h-1 bg-indigo-500/50">
<motion.div
className="h-full bg-indigo-500"
animate={{ width: `${(current / total) * 100}%` }}
/>
</div>
);
}
The Screen
component is a wrapper for the form and it contains the progress bar.
// ...
interface ScreenProps {
progress: { total: number; current: number };
children: React.ReactNode;
}
export default function Screen({ progress, children }: ScreenProps) {
return (
<div className="relative h-full w-full">
<Progress total={progress.total} current={progress.current} />
{children}
</div>
);
}
// ...
The Progress
component is the progress bar, and it receives two props: the total number of steps in the form and the step that we are currently on.
// ...
interface ProgressProps {
total: number;
current: number;
}
function Progress({ total, current }: ProgressProps) {
return (
<div className="absolute left-0 right-0 top-0 h-1 bg-indigo-500/50">
<motion.div
className="h-full bg-indigo-500"
animate={{ width: `${(current / total) * 100}%` }}
/>
</div>
);
}
As we can see, we are using Framer Motion to animate the progress bar when we go to the next or previous step.
After creating the component, we need to update the components.tsx
file to add this new component:
// src/components.tsx
// ...
import Screen from "@/components/screen";
// ...
type Parameters = {
screen: {
progress: { total: number; current: number };
children: Value;
};
// ...
};
const components: Components<Parameters> = {
screen: ({ progress, children }, render) => (
<Screen progress={progress}>{render(children)}</Screen>
),
// ...
};
export default components;
Create schema
Now that we have the component created, we can use it in the schema. We can update the schema.ts
file and use the screen component to show the progress bar:
// src/schema.ts
import { Schema } from "formity";
const schema: Schema = [
{
form: {
defaultValues: {
name: ["", []],
},
resolver: {
name: [[{ "#$ne": ["#$name", ""] }, "Required"]],
},
render: {
screen: {
progress: { total: 3, current: 1 },
children: {
form: {
step: "$step",
defaultValues: "$defaultValues",
resolver: "$resolver",
onNext: "$onNext",
children: {
formLayout: {
heading: "What is your name?",
description: "We would want to know your name",
fields: [
{
textField: {
name: "name",
label: "Name",
},
},
],
button: {
next: { text: "Next" },
},
},
},
},
},
},
},
},
},
{
form: {
defaultValues: {
surname: ["", []],
},
resolver: {
surname: [[{ "#$ne": ["#$surname", ""] }, "Required"]],
},
render: {
screen: {
progress: { total: 3, current: 2 },
children: {
form: {
step: "$step",
defaultValues: "$defaultValues",
resolver: "$resolver",
onNext: "$onNext",
children: {
formLayout: {
heading: "What is your surname?",
description: "We would want to know your surname",
fields: [
{
textField: {
name: "surname",
label: "Surname",
},
},
],
button: {
next: { text: "Next" },
},
back: {
back: { onBack: "$onBack" },
},
},
},
},
},
},
},
},
},
{
form: {
defaultValues: {
age: [18, []],
},
resolver: {
age: [[{ "#$ne": ["#$age", ""] }, "Required"]],
},
render: {
screen: {
progress: { total: 3, current: 3 },
children: {
form: {
step: "$step",
defaultValues: "$defaultValues",
resolver: "$resolver",
onNext: "$onNext",
children: {
formLayout: {
heading: "What is your age?",
description: "We would want to know your age",
fields: [
{
textField: {
name: "age",
label: "Age",
},
},
],
button: {
next: { text: "Next" },
},
back: {
back: { onBack: "$onBack" },
},
},
},
},
},
},
},
},
},
{
return: {
name: "$name",
surname: "$surname",
age: "$age",
},
},
];
export default schema;
If the form has conditions or loops, we can't know the number of steps the user will go through to complete the form. In that case, we can pass the minimum number of steps that we know for sure the user will go through to the total
prop.