Dynamic Form Section
Installation
Add the following Soul components
The dynamic-form-section component uses the dynamic-form and section-layout components. Make sure you have added them to your project.
Install the following dependencies
npm install clsx
Copy and paste the following code into your project
sections/dynamic-form-section/index.tsx
import { clsx } from 'clsx';import { DynamicForm, DynamicFormAction } from '@/vibes/soul/form/dynamic-form';import { Field, FieldGroup } from '@/vibes/soul/form/dynamic-form/schema';import { SectionLayout } from '@/vibes/soul/sections/section-layout';export interface DynamicFormSectionProps<F extends Field> { title?: string; subtitle?: string; action: DynamicFormAction<F>; fields: Array<F | FieldGroup<F>>; submitLabel?: string; className?: string;}/** * This component supports various CSS variables for theming. Here's a comprehensive list, along * with their default values: * * ```css * :root { * --dynamic-form-font-family: var(--font-family-body); * --dynamic-form-title-font-family: var(--font-family-heading); * --dynamic-form-title: hsl(var(--foreground)); * --dynamic-form-subtitle: hsl(var(--contrast-500)); * } * ``` */export function DynamicFormSection<F extends Field>({ className, title, subtitle, fields, submitLabel, action,}: DynamicFormSectionProps<F>) { return ( <SectionLayout className={clsx( 'mx-auto w-full max-w-4xl font-[family-name:var(--dynamic-form-font-family,var(--font-family-body))]', className, )} containerSize="lg" > {Boolean(title) && ( <header className="pb-8 @2xl:pb-12 @4xl:pb-16"> <h1 className="mb-5 font-[family-name:var(--dynamic-form-title-font-family,var(--font-family-heading))] text-4xl font-medium leading-none text-[var(--dynamic-form-title,hsl(var(--foreground)))] @xl:text-5xl"> {title} </h1> {Boolean(subtitle) && ( <p className="mb-10 text-base font-light leading-none text-[var(--dynamic-form-subtitle,hsl(var(--contrast-500)))] @xl:text-lg"> {subtitle} </p> )} </header> )} <DynamicForm action={action} fields={fields} submitLabel={submitLabel} /> </SectionLayout> );}
Usage
import { DynamicFormSection } from '@/vibes/soul/sections/dynamic-form-section';function Usage() { return ( <DynamicFormSection action={action} fields={fields} subtitle="Register an account with us to get updates and track your orders." title="Sign up" /> );}export const fields = [ { type: 'email', label: 'Email', name: 'email', required: true }, { type: 'password', label: 'Password', name: 'password', required: true }, { type: 'confirm-password', label: 'Confirm password', name: 'confirm-password', required: true },]export async function action( prevState: { fields: Array<Field | FieldGroup<Field>>; lastResult: SubmissionResult | null }, payload: FormData,) { 'use server'; const submission = parseWithZod(payload, { schema: schema(prevState.fields) }); if (submission.status !== 'success') { return { fields: prevState.fields, lastResult: submission.reply({ formErrors: ['Boom!'] }), }; } await new Promise((resolve) => setTimeout(resolve, 1000)); return { fields: prevState.fields, lastResult: submission.reply({ resetForm: true }), };}
API Reference
DynamicFormSectionProps
Prop | Type | Default |
---|---|---|
title | string | |
subtitle | string | |
action* | DynamicFormAction<F> | |
fields* | Array<F | FieldGroup<F>> | |
submitLabel | string | |
className | string |
DynamicFormAction
interface State<F extends Field> { fields: Array<F | FieldGroup<F>>; lastResult: SubmissionResult | null;}export type DynamicFormAction<F extends Field> = Action<State<F>, FormData>;
This component uses Confom to handle form submissions. Refer to the Conform docs for more details.
Here's an example of an action function that does validation and simulates signing up a new user:
export async function action( prevState: { fields: Array<Field | FieldGroup<Field>>; lastResult: SubmissionResult | null }, payload: FormData,) { 'use server'; const submission = parseWithZod(payload, { schema: schema(prevState.fields) }); if (submission.status !== 'success') { return { fields: prevState.fields, lastResult: submission.reply({ formErrors: ['Boom!'] }), }; } await new Promise((resolve) => setTimeout(resolve, 1000)); return { fields: prevState.fields, lastResult: submission.reply({ resetForm: true }), };}
CSS Variables
This component supports various CSS variables for theming. Here's a comprehensive list.
:root { --dynamic-form-font-family: var(--font-family-body); --dynamic-form-title-font-family: var(--font-family-heading); --dynamic-form-title: hsl(var(--foreground)); --dynamic-form-subtitle: hsl(var(--contrast-500));}