Next.js Supabase V3 (#463)
Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
345
docs/components/multi-step-forms.mdoc
Normal file
345
docs/components/multi-step-forms.mdoc
Normal file
@@ -0,0 +1,345 @@
|
||||
---
|
||||
status: "published"
|
||||
label: "Multi Step Forms"
|
||||
title: "Multi Step Forms in the Next.js Supabase SaaS kit"
|
||||
description: "Building multi-step forms in the Next.js Supabase SaaS kit"
|
||||
order: 0
|
||||
---
|
||||
|
||||
{% callout type="warning" title="Deprecated in v3" %}
|
||||
`MultiStepForm` was removed in v3. Use `Form` with conditional step rendering instead.
|
||||
{% /callout %}
|
||||
|
||||
The Multi-Step Form Component is a powerful and flexible wrapper around React Hook Form, Zod, and Shadcn UI. It provides a simple API to create multi-step forms with ease, perfect for complex registration processes, surveys, or any scenario where you need to break down a long form into manageable steps.
|
||||
|
||||
## Features
|
||||
|
||||
- Easy integration with React Hook Form and Zod for form management and validation
|
||||
- Built-in step management
|
||||
- Customizable layout and styling
|
||||
- Progress tracking with optional Stepper component
|
||||
- TypeScript support for type-safe form schemas
|
||||
|
||||
{% component path="multi-step-form" /%}
|
||||
|
||||
## Usage
|
||||
|
||||
Here's a basic example of how to use the Multi-Step Form Component:
|
||||
|
||||
```tsx
|
||||
import { MultiStepForm, MultiStepFormStep } from '@kit/ui/multi-step-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
const FormSchema = createStepSchema({
|
||||
step1: z.object({ /* ... */ }),
|
||||
step2: z.object({ /* ... */ }),
|
||||
});
|
||||
|
||||
export function MyForm() {
|
||||
const form = useForm({
|
||||
resolver: zodResolver(FormSchema),
|
||||
// ...
|
||||
});
|
||||
|
||||
const onSubmit = (data) => {
|
||||
// Handle form submission
|
||||
};
|
||||
|
||||
return (
|
||||
<MultiStepForm schema={FormSchema} form={form} onSubmit={onSubmit}>
|
||||
<MultiStepFormStep name="step1">
|
||||
{/* Step 1 fields */}
|
||||
</MultiStepFormStep>
|
||||
<MultiStepFormStep name="step2">
|
||||
{/* Step 2 fields */}
|
||||
</MultiStepFormStep>
|
||||
</MultiStepForm>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
### MultiStepForm
|
||||
|
||||
The main wrapper component that manages the form state and step progression.
|
||||
|
||||
Props:
|
||||
- `schema`: Zod schema for form validation
|
||||
- `form`: React Hook Form's `useForm` instance
|
||||
- `onSubmit`: Function to handle form submission
|
||||
- `className`: Optional CSS classes
|
||||
|
||||
### MultiStepFormStep
|
||||
|
||||
Represents an individual step in the form.
|
||||
|
||||
Props:
|
||||
- `name`: Unique identifier for the step (should match a key in your schema)
|
||||
- `children`: Step content
|
||||
|
||||
### MultiStepFormHeader
|
||||
|
||||
Optional component for adding a header to your form, often used with the Stepper component.
|
||||
|
||||
### MultiStepFormContextProvider
|
||||
|
||||
Provides access to form context within child components.
|
||||
|
||||
### useMultiStepFormContext
|
||||
|
||||
The hook returns an object with the following properties:
|
||||
|
||||
- `form: UseFormReturn<z.infer<Schema>>` - The original form object.
|
||||
- `currentStep: string` - The name of the current step.
|
||||
- `currentStepIndex: number` - The index of the current step (0-based).
|
||||
- `totalSteps: number` - The total number of steps in the form.
|
||||
- `isFirstStep: boolean` - Whether the current step is the first step.
|
||||
- `isLastStep: boolean` - Whether the current step is the last step.
|
||||
- `nextStep: (e: React.SyntheticEvent) => void` - Function to move to the next step.
|
||||
- `prevStep: (e: React.SyntheticEvent) => void` - Function to move to the previous step.
|
||||
- `goToStep: (index: number) => void` - Function to jump to a specific step by index.
|
||||
- `direction: 'forward' | 'backward' | undefined` - The direction of the last step change.
|
||||
- `isStepValid: () => boolean` - Function to check if the current step is valid.
|
||||
- `isValid: boolean` - Whether the entire form is valid.
|
||||
- `errors: FieldErrors<z.infer<Schema>>` - Form errors from React Hook Form.
|
||||
- `mutation: UseMutationResult` - A mutation object for handling form submission.
|
||||
|
||||
## Example
|
||||
|
||||
Here's a more complete example of a multi-step form with three steps: Account, Profile, and Review. The form uses Zod for schema validation and React Hook Form for form management.
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { Button } from '@kit/ui/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@kit/ui/form';
|
||||
import { Input } from '@kit/ui/input';
|
||||
import {
|
||||
MultiStepForm,
|
||||
MultiStepFormContextProvider,
|
||||
MultiStepFormHeader,
|
||||
MultiStepFormStep,
|
||||
createStepSchema,
|
||||
useMultiStepFormContext,
|
||||
} from '@kit/ui/multi-step-form';
|
||||
import { Stepper } from '@kit/ui/stepper';
|
||||
|
||||
const FormSchema = createStepSchema({
|
||||
account: z.object({
|
||||
username: z.string().min(3),
|
||||
email: z.string().email(),
|
||||
}),
|
||||
profile: z.object({
|
||||
password: z.string().min(8),
|
||||
age: z.coerce.number().min(18),
|
||||
}),
|
||||
});
|
||||
|
||||
type FormValues = z.infer<typeof FormSchema>;
|
||||
|
||||
export function MultiStepFormDemo() {
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
account: {
|
||||
username: '',
|
||||
email: '',
|
||||
},
|
||||
profile: {
|
||||
password: '',
|
||||
},
|
||||
},
|
||||
reValidateMode: 'onBlur',
|
||||
mode: 'onBlur',
|
||||
});
|
||||
|
||||
const onSubmit = (data: FormValues) => {
|
||||
console.log('Form submitted:', data);
|
||||
};
|
||||
|
||||
return (
|
||||
<MultiStepForm
|
||||
className={'space-y-10 p-8 rounded-xl border'}
|
||||
schema={FormSchema}
|
||||
form={form}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<MultiStepFormHeader
|
||||
className={'flex w-full flex-col justify-center space-y-6'}
|
||||
>
|
||||
<h2 className={'text-xl font-bold'}>Create your account</h2>
|
||||
|
||||
<MultiStepFormContextProvider>
|
||||
{({ currentStepIndex }) => (
|
||||
<Stepper
|
||||
variant={'numbers'}
|
||||
steps={['Account', 'Profile', 'Review']}
|
||||
currentStep={currentStepIndex}
|
||||
/>
|
||||
)}
|
||||
</MultiStepFormContextProvider>
|
||||
</MultiStepFormHeader>
|
||||
|
||||
<MultiStepFormStep name="account">
|
||||
<AccountStep />
|
||||
</MultiStepFormStep>
|
||||
|
||||
<MultiStepFormStep name="profile">
|
||||
<ProfileStep />
|
||||
</MultiStepFormStep>
|
||||
|
||||
<MultiStepFormStep name="review">
|
||||
<ReviewStep />
|
||||
</MultiStepFormStep>
|
||||
</MultiStepForm>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountStep() {
|
||||
const { form, nextStep, isStepValid } = useMultiStepFormContext();
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<div className={'flex flex-col gap-4'}>
|
||||
<FormField
|
||||
name="account.username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="account.email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={nextStep} disabled={!isStepValid()}>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileStep() {
|
||||
const { form, nextStep, prevStep } = useMultiStepFormContext();
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<div className={'flex flex-col gap-4'}>
|
||||
<FormField
|
||||
name="profile.password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="password" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="profile.age"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Age</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button type={'button'} variant={'outline'} onClick={prevStep}>
|
||||
Previous
|
||||
</Button>
|
||||
|
||||
<Button onClick={nextStep}>Next</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
function ReviewStep() {
|
||||
const { prevStep, form } = useMultiStepFormContext<typeof FormSchema>();
|
||||
const values = form.getValues();
|
||||
|
||||
return (
|
||||
<div className={'flex flex-col space-y-4'}>
|
||||
<div className={'flex flex-col space-y-4'}>
|
||||
<div>Great! Please review the values.</div>
|
||||
|
||||
<div className={'flex flex-col space-y-2 text-sm'}>
|
||||
<div>
|
||||
<span>Username</span>: <span>{values.account.username}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Email</span>: <span>{values.account.email}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Age</span>: <span>{values.profile.age}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button type={'button'} variant={'outline'} onClick={prevStep}>
|
||||
Back
|
||||
</Button>
|
||||
|
||||
<Button type={'submit'}>Create Account</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The inner components `AccountStep`, `ProfileStep`, and `ReviewStep` represent the individual steps of the form. They use the `useMultiStepFormContext` hook to access form utilities like `nextStep`, `prevStep`, and `isStepValid`.
|
||||
|
||||
These are built using ShadcnUI - so please [do refer to the ShadcnUI documentation](https://ui.shadcn.com/docs/components/form) for more information on how to use the components.
|
||||
|
||||
## Tips
|
||||
|
||||
1. Use the `createStepSchema` helper to easily create Zod schemas for your multi-step form.
|
||||
2. Leverage the `useMultiStepFormContext` hook in your step components to access form utilities.
|
||||
3. Combine with the Stepper component for visual progress indication.
|
||||
4. Customize the look and feel using the provided `className` props and your own CSS.
|
||||
|
||||
The Multi-Step Form Component simplifies the creation of complex, multi-step forms while providing a great user experience. It's flexible enough to handle a wide variety of use cases while keeping your code clean and maintainable.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user