Form Components
The CRUD bundle provides powerful form components that dynamically render form fields based on configuration, handle validation, and support multi-language translations. These components integrate seamlessly with React Hook Form and Zod validation.
Core Architecture
Field Configuration System
All form components use a unified field configuration system:
interface FilterField {
key: string; // Field identifier/name
title?: string; // Display label
type: ColumnTypeEnum; // Field type
placeholder?: string; // Input placeholder
targetResourceAsync?: string; // For async selects
isMulti?: boolean; // Multiple selections
optionsArrayFinite?: {
// Static options
label: string;
value: string | boolean;
}[];
required?: boolean; // Is required field
resourceAsyncLabelParam?: string; // API label parameter
prefixOnSubmit?: string; // Prefix for submission
default?: boolean; // Default value
range?: boolean; // Range input
rangeKeys?: {
// Range field keys
min: string;
max: string;
};
translatable?: boolean; // Multi-language support
itemSpecificPath?: string; // Item-specific path
helperText?: string; // Help text
}Supported Field Types
enum ColumnTypeEnum {
TEXT = "text",
EMAIL = "email",
NUMBER = "number",
TEL = "tel",
SELECT = "select",
DATE = "date",
PASSWORD = "password",
SELECT_ASYNC = "select_async",
URL = "url",
CHECKBOX = "checkbox",
TEXTAREA = "textarea",
BOOLEAN = "boolean",
ICON = "icon",
COLOR = "color",
CURRENCY = "currency",
ENTITY_URL = "entityUrl",
DECIMAL = "DECIMAL",
FLOAT = "FLOAT",
DATETIME = "DATETIME",
TIME = "TIME",
UNORDERED_LIST = "ul",
ORDERED_LIST = "ol",
}Component Overview
1. RenderFormFields
Dynamic form field renderer that supports all field types with validation and internationalization.
Props
interface RenderFormFieldsProps {
input: FilterField; // Field configuration
fqcn_bui: IFQCN_BUI; // Component configuration
form: any; // React Hook Form instance (TODO: INFER TYPE FOR FORM LATER)
formValues?: Record<string, any>; // Pre-populated values
isSubmitting?: boolean; // Form submission state
showLabel?: boolean; // Show field labels
locale?: string; // Locale for translations
tenant?: string; // Tenant identifier
csrfJwt?: string; // CSRF token for API calls
}Features
- 30+ Field Types: Comprehensive field type support
- Real-time Validation: Live validation with error display
- Conditional Fields: Show/hide based on other field values
- Dependent Fields: Fields that change options based on other selections
- Accessibility: Full ARIA support and keyboard navigation
- Internationalization: Multi-language support with translations
- Custom Styling: Tailwind CSS with theme support
Usage Example
import { RenderFormFields } from "@phpcreation/frontend-crud-react-nextjs-bundle/components";
import { useForm } from "react-hook-form";
const formFields: FilterField[] = [
{
key: "name",
title: "Full Name",
type: ColumnTypeEnum.TEXT,
required: true,
placeholder: "Enter your full name",
validation: {
minLength: 2,
maxLength: 100,
},
grid: { xs: 12, md: 6 },
},
{
key: "email",
title: "Email Address",
type: ColumnTypeEnum.EMAIL,
required: true,
placeholder: "user@example.com",
grid: { xs: 12, md: 6 },
},
{
key: "age",
title: "Age",
type: ColumnTypeEnum.NUMBER,
validation: {
min: 18,
max: 120,
},
grid: { xs: 12, md: 4 },
},
{
key: "country",
title: "Country",
type: ColumnTypeEnum.COUNTRY,
required: true,
grid: { xs: 12, md: 4 },
},
{
key: "state",
title: "State/Province",
type: ColumnTypeEnum.STATE,
dependsOn: "country",
grid: { xs: 12, md: 4 },
},
{
key: "bio",
title: "Biography",
type: ColumnTypeEnum.TEXTAREA,
placeholder: "Tell us about yourself...",
helperText: "Maximum 500 characters",
validation: {
maxLength: 500,
},
grid: { xs: 12 },
},
{
key: "newsletter",
title: "Subscribe to Newsletter",
type: ColumnTypeEnum.BOOLEAN,
grid: { xs: 12 },
},
{
key: "interests",
title: "Interests",
type: ColumnTypeEnum.SELECT_ASYNC,
apiEndpoint: "/api/interests/search",
isMulti: true,
conditional: {
field: "newsletter",
value: true,
},
grid: { xs: 12 },
},
];
function UserRegistrationForm() {
const form = useForm({
defaultValues: {
name: "",
email: "",
age: null,
country: "",
state: "",
bio: "",
newsletter: false,
interests: [],
},
});
const onSubmit = (data: any) => {
console.log("Form submitted:", data);
};
return (
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<div className="grid grid-cols-12 gap-4">
{formFields.map((field) => (
<div
key={field.key}
className={`col-span-12 ${
field.grid?.md ? `md:col-span-${field.grid.md}` : ""
} ${field.grid?.lg ? `lg:col-span-${field.grid.lg}` : ""}`}
>
<RenderFormFields
input={field}
fqcn_bui={{
Bundle: "user",
Unit: "registration",
Interface: "form",
}}
form={form}
showLabel={true}
locale="en"
tenant="company"
csrfJwt={csrfToken}
/>
</div>
))}
</div>
<div className="flex justify-end gap-4">
<Button type="button" variant="outline">
Cancel
</Button>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? "Submitting..." : "Submit"}
</Button>
</div>
</form>
);
}2. RenderFilterFields
Specialized component for rendering filter fields with range support and advanced filtering options.
Props
type RenderFilterFieldsProps = {
filter: FilterField; // Filter configuration
fqcn_bui: IFQCN_BUI; // Component configuration
filters: Record<string, any>; // Current filter values
handleFilterChange: (
// Filter change handler
key: string,
items: string | number | boolean | (string | number | boolean)[]
) => void;
isSubmitting?: boolean; // Form submission state
locale?: string; // Locale for translations
tenant?: string; // Tenant identifier
csrfJwt?: string; // CSRF token
};Features
- Range Filters: Min/max value filtering for numbers and dates
- Multi-select Filters: Multiple value selection
- Async Search: Server-side filtering with search
- Date Ranges: Start and end date filtering
- Boolean Filters: True/false/all options
Usage Example
import { RenderFilterFields } from "@phpcreation/frontend-crud-react-nextjs-bundle/components";
const filterFields: FilterField[] = [
{
key: "search",
title: "Search",
type: ColumnTypeEnum.TEXT,
placeholder: "Search users...",
},
{
key: "status",
title: "Status",
type: ColumnTypeEnum.SELECT,
isMulti: true,
options: [
{ label: "Active", value: "active" },
{ label: "Inactive", value: "inactive" },
{ label: "Pending", value: "pending" },
],
},
{
key: "age_range",
title: "Age Range",
type: ColumnTypeEnum.NUMBER,
range: true,
},
{
key: "created_date",
title: "Registration Date",
type: ColumnTypeEnum.DATE,
range: true,
},
{
key: "department_id",
title: "Department",
type: ColumnTypeEnum.SELECT_ASYNC,
apiEndpoint: "/api/departments/search",
searchEndpoint: "/api/departments/search",
},
];
function UserFilters() {
const [filters, setFilters] = useState({});
const handleFilterChange = (key: string, value: any) => {
setFilters((prev) => ({
...prev,
[key]: value,
}));
};
return (
<div className="bg-white p-4 rounded-lg shadow">
<h3 className="text-lg font-semibold mb-4">Filters</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filterFields.map((filter) => (
<RenderFilterFields
key={filter.key}
filter={filter}
fqcn_bui={{ Bundle: "user", Unit: "listing", Interface: "filters" }}
filters={filters}
handleFilterChange={handleFilterChange}
locale="en"
tenant="company"
csrfJwt={csrfToken}
/>
))}
</div>
<div className="flex justify-end gap-2 mt-4">
<Button variant="outline" onClick={() => setFilters({})}>
Clear Filters
</Button>
<Button onClick={() => applyFilters(filters)}>Apply Filters</Button>
</div>
</div>
);
}3. TranslatableFields
Component for managing multi-language form fields with tabbed interface.
Props
interface TranslatableFieldsProps {
languages?: string[]; // Supported languages
initialValues?: Record<string, Record<string, any>>; // Initial translations
fqcn_bui?: IFQCN_BUI; // Component configuration
defaultLanguage?: string; // Default language
locale?: string; // Current locale
}Features
- Multi-language Tabs: Separate tabs for each language
- Field Validation: Per-language validation
- Default Language: Fallback language support
- Translation Status: Visual indicators for translated/untranslated fields
- Synchronized Updates: Changes propagate across language tabs
Usage Example
import { TranslatableFields } from "@phpcreation/frontend-crud-react-nextjs-bundle/components";
import { FormProvider, useForm } from "react-hook-form";
function ProductForm() {
const form = useForm({
defaultValues: {
// Regular fields
sku: "",
price: 0,
// Translatable fields
apiTranslations: {
en_CA: {
title: "",
description: "",
},
fr_CA: {
title: "",
description: "",
},
es: {
title: "",
description: "",
},
},
},
});
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{/* Regular fields */}
<div className="grid grid-cols-2 gap-4">
<RenderFormFields
input={{
key: "sku",
title: "SKU",
type: ColumnTypeEnum.TEXT,
required: true,
}}
fqcn_bui={fqcn_bui}
form={form}
/>
<RenderFormFields
input={{
key: "price",
title: "Price",
type: ColumnTypeEnum.CURRENCY,
required: true,
}}
fqcn_bui={fqcn_bui}
form={form}
/>
</div>
{/* Translatable fields */}
<div className="border rounded-lg p-4">
<h3 className="text-lg font-semibold mb-4">Translations</h3>
<TranslatableFields
languages={["en", "fr", "es"]}
fqcn_bui={{
Bundle: "product",
Unit: "form",
Interface: "translations",
}}
defaultLanguage="en"
locale="en"
/>
</div>
<Button type="submit">Save Product</Button>
</form>
</FormProvider>
);
}Advanced Field Types
Rich Text Editor
const richTextField: FilterField = {
title: "Notes",
key: "notes",
type: ColumnTypeEnum.TEXTAREA,
placeholder: "Input the Notes",
required: false,
};Dynamic Select with Search
const asyncSelectField: FilterField = {
title: "Id",
key: "id",
placeholder: "Select Id",
type: ColumnTypeEnum.SELECT_ASYNC,
targetResourceAsync: "users",
isMulti: true,
default: true,
};Conditional Fields
const conditionalFields: FilterField[] = [
{
key: "user_type",
title: "User Type",
type: ColumnTypeEnum.SELECT,
options: [
{ label: "Individual", value: "individual" },
{ label: "Business", value: "business" },
],
},
{
key: "company_name",
title: "Company Name",
type: ColumnTypeEnum.TEXT,
required: true,
conditional: {
field: "user_type",
value: "business",
},
},
{
key: "tax_id",
title: "Tax ID",
type: ColumnTypeEnum.TEXT,
conditional: {
field: "user_type",
value: "business",
},
},
];Validation Integration
Zod Schema Generation
The form components automatically generate Zod schemas from field configurations:
import { validationSchema } from "@phpcreation/frontend-crud-react-nextjs-bundle/utils/functions";
const fields: FilterField[] = [
{
key: "email",
title: "Email",
type: ColumnTypeEnum.EMAIL,
required: true,
},
{
key: "age",
title: "Age",
type: ColumnTypeEnum.NUMBER,
},
];
// Auto-generated schema
const schema = validationSchema(fields);
// Equivalent to:
// z.object({
// email: z.string().email("Invalid email"),
// age: z.number().min(18).max(120)
// })Best Practices
Complete Form Example
User Registration
import React from 'react';
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import {
RenderFormFields,
TranslatableFields
} from "@phpcreation/frontend-crud-react-nextjs-bundle/components";
const userFields: FilterField[] = [
{
key: "personal_info",
title: "Personal Information",
type: "section" // Section divider
},
{
key: "first_name",
title: "First Name",
type: ColumnTypeEnum.TEXT,
required: true,
grid: { xs: 12, md: 6 }
},
{
key: "last_name",
title: "Last Name",
type: ColumnTypeEnum.TEXT,
required: true,
grid: { xs: 12, md: 6 }
},
{
key: "email",
title: "Email Address",
type: ColumnTypeEnum.EMAIL,
required: true,
grid: { xs: 12, md: 6 }
},
{
key: "phone",
title: "Phone Number",
type: ColumnTypeEnum.TEL,
grid: { xs: 12, md: 6 }
},
{
key: "address_info",
title: "Address Information",
type: "section"
},
{
key: "country",
title: "Country",
type: ColumnTypeEnum.COUNTRY,
required: true,
grid: { xs: 12, md: 4 }
},
{
key: "state",
title: "State/Province",
type: ColumnTypeEnum.STATE,
dependsOn: "country",
grid: { xs: 12, md: 4 }
},
{
key: "city",
title: "City",
type: ColumnTypeEnum.CITY,
dependsOn: "state",
grid: { xs: 12, md: 4 }
},
{
key: "preferences",
title: "Preferences",
type: "section"
},
{
key: "newsletter",
title: "Subscribe to Newsletter",
type: ColumnTypeEnum.BOOLEAN,
grid: { xs: 12 }
},
{
key: "interests",
title: "Areas of Interest",
type: ColumnTypeEnum.SELECT_ASYNC,
apiEndpoint: "/api/interests/search",
isMulti: true,
conditional: {
field: "newsletter",
value: true
},
grid: { xs: 12 }
}
];
function UserRegistrationForm() {
const form = useForm({
resolver: zodResolver(validationSchema(userFields)),
defaultValues: {
first_name: "",
last_name: "",
email: "",
phone: "",
country: "",
state: "",
city: "",
newsletter: false,
interests: []
}
});
const onSubmit = async (data: any) => {
try {
await createUser(data);
toast.success("User registered successfully!");
} catch (error) {
toast.error("Registration failed");
}
};
return (
<FormProvider {...form}>
<div className="max-w-4xl mx-auto p-6">
<div className="bg-white rounded-lg shadow-md p-6">
<h1 className="text-2xl font-bold mb-6">User Registration</h1>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<div className="grid grid-cols-12 gap-4">
{userFields.map((field) => {
if (field.type === "section") {
return (
<div key={field.key} className="col-span-12">
<h3 className="text-lg font-semibold border-b pb-2 mb-4">
{field.title}
</h3>
</div>
);
}
return (
<div
key={field.key}
className={`col-span-12 ${
field.grid?.md ? `md:col-span-${field.grid.md}` : ''
}`}
>
<RenderFormFields
input={field}
fqcn_bui={{Bundle: "user", Unit: "registration", Interface: "form"}}
form={form}
showLabel={true}
locale="en"
tenant="company"
csrfJwt={csrfToken}
/>
</div>
);
})}
</div>
<div className="flex justify-end gap-4 pt-4 border-t">
<Button type="button" variant="outline">
Cancel
</Button>
<Button
type="submit"
disabled={form.formState.isSubmitting}
>
{form.formState.isSubmitting ? "Registering..." : "Register"}
</Button>
</div>
</form>
</div>
</div>
</FormProvider>
);
}
Related Documentation
- Listing Components - RenderFilterFields in listing context
- Entity Management - AddEntityForm, EditEntityForm
- Modal Components - QuickAddModal, QuickEdit
- Utility Functions - validationSchema, useFormValues hooks
This comprehensive documentation covers all form components with detailed configuration options, usage examples, and best practices for building dynamic, validated forms with multi-language support.