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

PropTypeDefault
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));}