@fivexlabs/conform-react v1.1.0
@fivexlabs/conform-react
โจ Features
๐ฏ Core Features
- ๐ Declarative Schema Definition: Define entire forms using JSON schemas
- ๐ Dynamic Conditional Logic: Show/hide fields based on other field values
- โ Comprehensive Validation: Built-in and custom validation with internationalization support
- ๐จ Customizable Components: Provide your own React components for any field type
- โฟ Accessibility First: WCAG compliant forms with proper ARIA attributes
- ๐ Performance Optimized: Built on React Hook Form for optimal performance
- ๐ฆ TypeScript Ready: Full TypeScript support with comprehensive type definitions
๐ฅ Advanced Features (New!)
- ๐ Smart File Upload: Drag & drop interface with progress tracking, image preview, and validation
- ๐ Async Autocomplete: Search-as-you-type with debouncing, templates, and API integration
- ๐ Business Date/Time Picker: Business rules, timezone support, and restriction handling
- ๐ Cross-field Validation: Dependencies between fields with real-time validation
- ๐งฎ Computed Fields: Auto-calculated values based on other field data
- โก Real-time Updates: Live form state management and analytics
๐ฆ Installation
npm install @fivexlabs/conform-react react-hook-form yup date-fns
# or
yarn add @fivexlabs/conform-react react-hook-form yup date-fnsNote:
date-fnsis required for the advanced date/time picker functionality.
๐ Quick Start
import React from 'react';
import { FormRenderer, FormSchema } from '@fivexlabs/conform-react';
const schema: FormSchema = {
title: "User Registration",
fields: [
{
name: "fullName",
label: "Full Name",
type: "text",
validation: { required: true, minLength: 2 }
},
{
name: "email",
label: "Email Address",
type: "email",
validation: { required: true, email: true }
},
{
name: "subscribe",
label: "Subscribe to newsletter?",
type: "checkbox",
defaultValue: false
},
{
name: "reason",
label: "Why do you want to subscribe?",
type: "textarea",
visibleWhen: { subscribe: true },
validation: { required: true, maxLength: 200 }
}
]
};
function App() {
const handleSubmit = (data: Record<string, any>) => {
console.log('Form submitted:', data);
};
return (
<FormRenderer
schema={schema}
onSubmit={handleSubmit}
onFormChange={(data) => console.log('Form changed:', data)}
/>
);
}๐ฅ Advanced Features Showcase
๐ Smart File Upload
Create powerful file upload experiences with drag & drop, progress tracking, and validation:
{
name: "documents",
type: "fileUpload",
label: "Project Documents",
props: {
multiple: true,
accept: ['.pdf', '.docx', '.jpg', '.png'],
maxSize: 10 * 1024 * 1024, // 10MB
maxFiles: 5,
preview: true,
upload: {
endpoint: '/api/upload',
method: 'POST',
headers: { 'Authorization': 'Bearer token123' }
}
},
onUploadProgress: (progress, file) => console.log(`${file.name}: ${progress}%`),
onUploadComplete: (response, file) => console.log('Upload completed:', response)
}๐ Async Autocomplete
Build smart search interfaces with real-time suggestions:
{
name: "assignee",
type: "autocomplete",
label: "Project Assignee",
props: {
searchEndpoint: '/api/users/search',
displayKey: 'name',
valueKey: 'id',
debounce: 300,
minChars: 2,
template: '{{name}} - {{department}} ({{email}})',
multiple: false
}
}๐ Business Date/Time Picker
Handle complex business rules with smart date/time selection:
{
name: "deadline",
type: "dateTime",
label: "Project Deadline",
props: {
includeTime: true,
format: 'yyyy-MM-dd HH:mm',
restrictions: {
minDate: 'today',
businessDaysOnly: true,
disabledDays: ['saturday', 'sunday'],
disabledDates: ['2024-12-25', '2024-01-01']
}
}
}๐ Cross-field Validation
Create intelligent forms with field dependencies:
{
name: "endDate",
type: "dateTime",
label: "Project End Date",
validation: {
custom: async (value, formData) => {
if (value <= formData.startDate) {
return 'End date must be after start date';
}
return true;
},
dependencies: ['startDate'] // Re-validate when startDate changes
}
}๐งฎ Computed Fields
Auto-calculate values based on other field data:
{
name: "totalCost",
type: "text",
label: "Total Cost",
props: { disabled: true }, // Read-only
computed: {
formula: (formData) => {
const hours = formData.estimatedHours || 0;
const rate = formData.hourlyRate || 0;
return `$${(hours * rate).toFixed(2)}`;
},
dependencies: ['estimatedHours', 'hourlyRate']
}
}๐ Documentation
Basic Form Schema
A form schema defines the structure, validation, and behavior of your form:
interface FormSchema {
title?: string;
description?: string;
fields: (FormField | FieldGroup)[];
layout?: LayoutConfig;
submitButtonText?: string;
resetButtonText?: string;
showResetButton?: boolean;
}Field Types
Text Input Fields
{
name: "username",
label: "Username",
type: "text", // "text" | "email" | "password" | "url" | "tel"
placeholder: "Enter your username",
validation: {
required: true,
minLength: 3,
pattern: "^[a-zA-Z0-9_]+$"
},
validationMessages: {
pattern: "Username can only contain letters, numbers, and underscores"
}
}Select Fields
{
name: "country",
label: "Country",
type: "select",
options: [
{ label: "United States", value: "us" },
{ label: "Canada", value: "ca" },
{ label: "United Kingdom", value: "uk" }
],
validation: { required: true }
}Array Fields (Repeatable Sections)
{
name: "addresses",
label: "Addresses",
type: "array",
minItems: 1,
maxItems: 3,
itemSchema: {
fields: [
{
name: "street",
label: "Street Address",
type: "text",
validation: { required: true }
},
{
name: "city",
label: "City",
type: "text",
validation: { required: true }
}
]
}
}Conditional Logic
Simple Conditional Syntax
{
name: "otherReason",
label: "Please specify",
type: "text",
visibleWhen: { reason: "other" }, // Show when reason equals "other"
disabledWhen: { status: "readonly" } // Disable when status equals "readonly"
}Advanced Conditional Logic
{
name: "discount",
label: "Discount Code",
type: "text",
conditional: {
and: [
{ field: "membershipType", operator: "equals", value: "premium" },
{ field: "orderAmount", operator: "greaterThan", value: 100 }
]
}
}Complex Conditions with MongoDB-style Operators
{
name: "specialOffer",
label: "Special Offer",
type: "checkbox",
visibleWhen: {
age: { $gte: 18 },
country: { $in: ["us", "ca", "uk"] },
email: { $contains: "@company.com" }
}
}Validation
Built-in Validation Rules
{
validation: {
required: true,
minLength: 5,
maxLength: 50,
min: 0,
max: 100,
pattern: "^[A-Z][a-z]+$",
email: true // For email fields
}
}Custom Validation
{
validation: {
custom: async (value) => {
// Async validation example
const isUnique = await checkUsernameUniqueness(value);
return isUnique || "Username is already taken";
}
}
}Field Groups
{
type: "group",
title: "Personal Information",
description: "Please provide your personal details",
collapsible: true,
fields: [
{
name: "firstName",
label: "First Name",
type: "text",
validation: { required: true }
},
{
name: "lastName",
label: "Last Name",
type: "text",
validation: { required: true }
}
]
}Custom Components
You can provide your own React components for any field type:
import { TextField } from '@mui/material';
const customComponents = {
text: ({ field, value, onChange, error, ...props }) => (
<TextField
label={field.label}
value={value || ''}
onChange={(e) => onChange(e.target.value)}
error={!!error}
helperText={error}
fullWidth
{...props}
/>
),
select: CustomSelectComponent,
// ... other custom components
};
<FormRenderer
schema={schema}
customComponents={customComponents}
onSubmit={handleSubmit}
/>Advanced Usage
Custom Rendering
<FormRenderer
schema={schema}
onSubmit={handleSubmit}
renderSubmitButton={(props) => (
<CustomButton loading={props.loading} onClick={props.onClick}>
{props.text}
</CustomButton>
)}
renderErrorSummary={(errors) => (
<CustomErrorDisplay errors={errors} />
)}
renderFieldWrapper={({ field, children, error }) => (
<div className="custom-field-wrapper">
{children}
{error && <span className="error-icon">โ ๏ธ</span>}
</div>
)}
/>Form State Management
const FormWithState = () => {
const [formData, setFormData] = useState({});
return (
<FormRenderer
schema={schema}
initialValues={{ username: 'defaultUser' }}
onSubmit={handleSubmit}
onFormChange={setFormData}
onFieldChange={(fieldName, value, allValues) => {
console.log(`Field ${fieldName} changed to:`, value);
}}
/>
);
};๐จ Styling
The library provides unstyled components by default with semantic CSS classes for easy styling:
.conform-form {
/* Main form container */
}
.form-field {
/* Individual field wrapper */
}
.form-group {
/* Field group container */
}
.form-error {
/* Error message styling */
}For Tailwind CSS users, the default components include Tailwind classes that you can customize.
๐งช Testing
npm test๐ API Reference
FormRenderer Props
| Prop | Type | Required | Description |
|---|---|---|---|
schema | FormSchema | โ | The JSON schema defining the form structure |
onSubmit | (data: Record<string, any>) => void \| Promise<void> | โ | Called when form is submitted |
initialValues | Record<string, any> | โ | Initial form values |
onFormChange | (data: Record<string, any>) => void | โ | Called when any field changes |
onFieldChange | (fieldName: string, value: any, allValues: Record<string, any>) => void | โ | Called when a specific field changes |
customComponents | Record<string, React.ComponentType<any>> | โ | Custom components for field types |
loading | boolean | โ | Shows loading state |
disabled | boolean | โ | Disables entire form |
๐ Changelog
See CHANGELOG.md for a detailed history of all changes and new features.
๐ค Contributing
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ข About Fivex Labs
Fivex Labs is a technology company focused on building innovative tools and libraries for modern web development. We believe in creating solutions that are both powerful and developer-friendly.
Visit us at fivexlabs.com to learn more about our work and other open-source projects.