UNPKG

finform-react-builder

Version:

A powerful, flexible React form builder with dynamic field rendering, custom validation, multi-step forms, Material-UI integration, image component support, toggle/radio buttons, switches, autocomplete, and advanced button positioning

904 lines (790 loc) 21.4 kB
# finform-react-builder A powerful, flexible React form builder with dynamic field rendering, custom validation, and Material-UI integration. [![npm version](https://badge.fury.io/js/finform-react-builder.svg)](https://www.npmjs.com/package/finform-react-builder) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ## 🚨 Migration Notice **If you're currently using this package from GitHub Packages**, you'll need to update your installation: ```bash # Remove the old package npm uninstall @finflow-analytics/finform-react-builder # Install from public npm registry npm install finform-react-builder ``` If you have a `.npmrc` file with GitHub Packages configuration, remove the line: ``` @finflow-analytics:registry=https://npm.pkg.github.com ``` ## ✨ Features - 🎨 **Material-UI Integration** - Beautiful, responsive form components - 🔧 **Dynamic Field Rendering** - text, email, password (with eye toggle), number, select, checkbox, radio, switch, autocomplete (single/multiple with checkboxes and +N more), date, textarea, image - ✅ **Advanced Validation** - Built-in validation with custom regex patterns and validation functions - 📱 **Responsive Grid System** - Flexible column-based layout system - 🎯 **TypeScript Support** - Full type safety and IntelliSense support - 🚀 **Easy Integration** - Simple API with sensible defaults - 🔄 **Real-time Validation** - Instant feedback with react-hook-form - 🎨 **Customizable** - Highly configurable with custom validation rules - 🔄 **Multi-Step Forms** - Support for step-by-step form completion with smart navigation - 🧩 **Custom Components** - Inject any React node via `type: 'component'` - 💾 **Stateless Design** - Works with data from backend/config without local storage dependencies - 📎 **Document Upload** - Drag & drop uploader with validation and preview ## 📦 Installation ```bash npm install finform-react-builder ``` ### Peer Dependencies Make sure you have the required peer dependencies installed: ```bash npm install react react-dom @mui/material @emotion/react @emotion/styled ``` ## 🚀 Quick Start ```tsx import React from 'react'; import { FinForm, FieldConfig } from 'finform-react-builder'; const fields: FieldConfig[] = [ { name: 'firstName', label: 'First Name', type: 'text', placeholder: 'Enter your first name', required: true, col: 6 }, { name: 'lastName', label: 'Last Name', type: 'text', placeholder: 'Enter your last name', required: true, col: 6 }, { name: 'email', label: 'Email', type: 'email', placeholder: 'Enter your email', required: true, col: 12 } ]; function App() { const handleSubmit = (data: any) => { console.log('Form submitted:', data); }; return ( <FinForm fields={fields} onSubmit={handleSubmit} submitButtonText="Submit Form" /> ); } export default App; ``` ## 📋 Field Types ### Password (with visibility toggle) ```tsx { name: 'password', label: 'Password', type: 'password', placeholder: 'Enter password', required: true, helperText: 'Use at least 8 characters.' } ``` Behavior: An eye icon toggles visibility between •••• and plain text. ### Text Fields ```tsx { name: 'username', label: 'Username', type: 'text', placeholder: 'Enter username', validation: { pattern: /^[a-zA-Z0-9_]+$/, message: 'Username can only contain letters, numbers, and underscores' } } ``` ### Email Fields ```tsx { name: 'email', label: 'Email Address', type: 'email', placeholder: 'Enter your email', required: true } ``` ### Number Fields ```tsx { name: 'age', label: 'Age', type: 'number', placeholder: 'Enter your age', validation: { min: 18, max: 120, message: 'Age must be between 18 and 120' } } ``` ### Select Fields ```tsx { name: 'country', label: 'Country', type: 'select', options: [ { label: 'United States', value: 'us' }, { label: 'Canada', value: 'ca' }, { label: 'United Kingdom', value: 'uk' } ], required: true } ``` ### Date Fields ```tsx { name: 'birthDate', label: 'Birth Date', type: 'date', validation: { custom: (value) => { const today = new Date(); const birthDate = new Date(value); return birthDate < today; }, message: 'Birth date must be in the past' } } ``` ### Image Fields ```tsx { name: 'profileImage', label: 'Profile Image', type: 'image', src: 'https://example.com/image.jpg', alt: 'User profile image', width: 300, height: 200, style: { border: '2px solid #e0e0e0', borderRadius: '8px' }, onClick: () => { console.log('Image clicked!'); } } ``` ### Textarea Fields ```tsx { name: 'bio', label: 'Biography', type: 'textarea', placeholder: 'Tell us about yourself...', validation: { maxLength: 500, message: 'Biography must be less than 500 characters' } } ``` ### Checkbox Fields ```tsx { name: 'agree', label: 'Agree to terms', type: 'checkbox', required: true } ``` ### Autocomplete (Single) ```tsx { name: 'country', label: 'Country', type: 'autocomplete', placeholder: 'Search country', options: [ { label: 'United States', value: 'us' }, { label: 'Canada', value: 'ca' }, ], // For API-driven: // api_endpoint: '/common/countries?format=dropdown', // api_method: 'GET', // value_field: 'id', // label_field: 'label', } ``` ### Autocomplete (Multiple) with Checkboxes and +N More Chips ```tsx { name: 'skills', label: 'Skills', type: 'autocomplete', multiple: true, options: [ { label: 'JavaScript', value: 'javascript' }, { label: 'React', value: 'react' }, { label: 'TypeScript', value: 'typescript' }, ], // Stored value is an array of primitive values: ['javascript', 'react'] } ``` Behavior: - Renders checkboxes in the dropdown when `multiple: true`. - Only the first 2 selections are rendered as chips; if more, shows a `+N more` chip. - Works with API-driven data using `api_endpoint`, `value_field`, `label_field`. ### Copy icon for disabled fields For any disabled field, a small copy icon appears to the right, allowing users to copy the field value to clipboard. ```tsx { name: 'readonly_id', label: 'User ID', type: 'text', value: 'USR-7842-AB', disabled: true } ``` ### Toggle (Segmented Radios) ```tsx { name: 'gender', label: 'Gender', type: 'toggle', options: [ { label: 'Male', value: 'male' }, { label: 'Female', value: 'female' }, { label: 'Other', value: 'other' }, ], required: true, } ``` ### Radio (Vertical) ```tsx { name: 'contactPreference', label: 'Contact Preference', type: 'radio', options: [ { label: 'Email', value: 'email' }, { label: 'Phone', value: 'phone' }, ], } ``` ### Switch ```tsx { name: 'notifications', label: 'Enable Notifications', type: 'switch', } ``` ### Title and Section Headings ### Custom Component Field Render arbitrary React content inside the form layout. ```tsx { name: 'custom_block', type: 'component', col: 12, step: 2, content: <div>Any JSX here</div> } ``` ```tsx { name: 'formTitle', type: 'title', label: 'Create User', variant: 'h4', col: 12 } { name: 'detailsSection', type: 'section', label: 'Details', variant: 'h6', col: 12 } ``` ## 🔗 API-Driven Fields and Dependencies ### Basic API-Driven Select ```tsx { name: 'country_id', label: 'Country', type: 'select', api_endpoint: '/common/countries?format=dropdown', api_method: 'GET', value_field: 'id', label_field: 'label', } ``` ### Dependent Field (Conditional + depends_on) ```tsx { name: 'org_type_id', label: 'Entity Type', type: 'autocomplete', api_endpoint: '/company/org-types/active/list', api_method: 'GET', value_field: 'id', label_field: 'name', }, { name: 'parent_id', label: 'Parent Entity', type: 'autocomplete', api_endpoint: '/company/offices/potential-parents/{org_type_id}?format=dropdown', api_method: 'GET', depends_on: 'org_type_id', conditional: true, value_field: 'id', label_field: 'label', } ``` Behavior: - The dependent field (`parent_id`) is disabled until `org_type_id` has a value. - When the dependency value changes, the dependent field is reset (clears previous selection). ## ✅ Validation and Submit Button Behavior - Required fields must be filled; for arrays (e.g., multi-select), value must be non-empty. - Required checkboxes must be true. - Any validation error (including on non-required fields) disables submit. - Single-step forms: built-in Submit button is disabled until valid. - Multi-step forms: Next/Submit are disabled until the current step is valid. - Custom buttons passed via `buttons` or `buttonGroup` are also auto-disabled when they are flow actions (Next/Submit/Save/Finish/Complete) and the step/form is invalid. Number validation example (number type): ```tsx { name: 'longitude', label: 'Longitude', type: 'number', validation: { min: -180, max: 180, message: 'Longitude must be between -180 and 180' }, } ``` For `type: 'text'` number-like validation, use `pattern` or `custom` in `validation`. ## 🧭 Buttons and Button Groups ### Custom Buttons ```tsx <FinForm fields={fields} buttons={[ { text: 'Cancel', type: 'button', onClick: () => navigate(-1) }, { text: 'Submit', type: 'submit', color: 'primary' }, ]} /> ``` - Submit (and Next) buttons are auto-disabled when the form (or current step) is invalid. - Non-submit buttons (e.g., Cancel) remain clickable. ### Button Group ```tsx <FinForm fields={fields} buttonGroup={{ position: 'right', buttons: [ { text: 'Previous', type: 'button' }, { text: 'Next', type: 'button' }, { text: 'Submit', type: 'submit' }, ], }} /> ``` - Buttons with text containing "Next", "Submit", "Save", "Finish", "Complete" are treated as flow actions and auto-disabled appropriately. ## 🎨 Theming ```tsx <FinForm theme={{ primaryColor: '#2196f3', secondaryColor: '#f50057', backgroundColor: '#fafafa', textColor: '#333', borderRadius: 8, spacing: 16, typography: { fontFamily: 'Roboto, Arial, sans-serif', fontSize: 16 }, }} fields={fields} onSubmit={...} /> ``` ## 🖼️ Image Field ```tsx { name: 'profileImage', label: 'Profile Image', type: 'image', defaultValue: 'https://via.placeholder.com/300x200', alt: 'User profile image', width: 300, height: 200, onClick: () => console.log('Image clicked') } ``` ## 🔁 onChange `onChange` is fired with current form values on every change, without creating render loops, and accounts for dependent resets. ```tsx { name: 'newsletter', label: 'Subscribe to Newsletter', type: 'checkbox', required: true } ``` ## 🔧 Advanced Validation ### Custom Regex Patterns ```tsx { name: 'phoneNumber', label: 'Phone Number', type: 'text', placeholder: '+1 (555) 123-4567', validation: { pattern: /^\+?[1-9]\d{1,14}$/, message: 'Please enter a valid phone number' } } ``` ### Custom Validation Functions ```tsx { name: 'password', label: 'Password', type: 'password', placeholder: 'Enter password', validation: { minLength: 8, custom: (value) => { const hasUpperCase = /[A-Z]/.test(value); const hasLowerCase = /[a-z]/.test(value); const hasNumbers = /\d/.test(value); const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(value); return hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar; }, message: 'Password must contain uppercase, lowercase, number and special character' } } ``` ## 📐 Grid System Use the responsive grid system to control field layout: ```tsx // Full width field { name: 'description', type: 'textarea', col: 12 } // Half width fields { name: 'firstName', type: 'text', col: 6 } { name: 'lastName', type: 'text', col: 6 } // Responsive breakpoints { name: 'address', type: 'text', xs: 12, // Full width on extra small screens md: 8, // 8/12 width on medium screens lg: 6 // Half width on large screens } ``` ## 🔄 Multi-Step Forms FinForm supports multi-step forms with smart navigation and completion tracking. Perfect for complex forms that need to be broken down into manageable steps. ### Basic Multi-Step Form ```tsx const multiStepFields: FieldConfig[] = [ // Step 1: Personal Information { name: 'firstName', label: 'First Name', type: 'text', required: true, step: 1, col: 6, }, { name: 'lastName', label: 'Last Name', type: 'text', required: true, step: 1, col: 6, }, { name: 'email', label: 'Email Address', type: 'email', required: true, step: 1, col: 12, }, // Step 2: Address Information { name: 'street', label: 'Street Address', type: 'text', required: true, step: 2, col: 12, }, { name: 'city', label: 'City', type: 'text', required: true, step: 2, col: 6, }, { name: 'state', label: 'State', type: 'select', required: true, step: 2, col: 3, options: [ { label: 'California', value: 'CA' }, { label: 'New York', value: 'NY' }, { label: 'Texas', value: 'TX' }, ], }, { name: 'zipCode', label: 'ZIP Code', type: 'text', required: true, step: 2, col: 3, validation: { pattern: /^\d{5}(-\d{4})?$/, message: 'Please enter a valid ZIP code', }, }, // Step 3: Preferences { name: 'newsletter', label: 'Subscribe to Newsletter', type: 'checkbox', step: 3, col: 12, }, { name: 'preferences', label: 'Communication Preferences', type: 'select', step: 3, col: 6, options: [ { label: 'Email', value: 'email' }, { label: 'Phone', value: 'phone' }, { label: 'Both', value: 'both' }, ], }, ]; function MultiStepForm() { const [currentStep, setCurrentStep] = useState(1); const handleStepChange = (step: number, totalSteps: number) => { console.log(`Moving to step ${step} of ${totalSteps}`); setCurrentStep(step); }; return ( <FinForm fields={multiStepFields} onSubmit={(data) => console.log('Form submitted:', data)} isMultiStep={true} currentStep={currentStep} onStepChange={handleStepChange} submitButtonText="Complete Registration" stepNavigationProps={{ showStepNumbers: true, showStepTitles: true, stepTitles: ['Personal Info', 'Address', 'Preferences'], }} /> ); } ``` ### Smart Step Navigation with Backend Data The form automatically detects completed steps and starts from the first incomplete step when data is provided: ```tsx // Data from backend showing user has completed step 1 const backendData = { firstName: 'John', lastName: 'Doe', email: 'john.doe@example.com', // Step 2 and 3 are empty }; function SmartMultiStepForm() { return ( <FinForm fields={multiStepFields} onSubmit={(data) => console.log('Form submitted:', data)} defaultValues={backendData} isMultiStep={true} // Form will automatically start at step 2 since step 1 is complete submitButtonText="Complete Registration" /> ); } ``` ### Multi-Step Form Props | Prop | Type | Description | Default | |------|------|-------------|---------| | `isMultiStep` | `boolean` | Enable multi-step functionality | `false` | | `currentStep` | `number` | Current step (controlled by parent) | `1` | | `onStepChange` | `(step: number, totalSteps: number) => void` | Step change callback | - | | `showStepNavigation` | `boolean` | Show step navigation | `true` | | `stepNavigationProps` | `object` | Step navigation configuration | `{}` | ### Step Navigation Configuration ```tsx stepNavigationProps={{ showStepNumbers: true, // Show step numbers showStepTitles: true, // Show step titles stepTitles: ['Step 1', 'Step 2', 'Step 3'], // Custom step titles }} ``` ### Field Step Assignment Add a `step` property to any field to assign it to a specific step: ```tsx { name: 'fieldName', label: 'Field Label', type: 'text', step: 1, // This field will appear in step 1 required: true, } ``` ## 🎛️ API Reference ### FinForm Props | Prop | Type | Description | Required | |------|------|-------------|----------| | `fields` | `FieldConfig[]` | Array of field configurations | ✅ | | `onSubmit` | `(data: any) => void` | Form submission handler | ✅ | | `onChange` | `(data: any) => void` | Form change handler | ❌ | | `submitButtonText` | `string` | Submit button text | ❌ | | `defaultValues` | `Record<string, any>` | Default form values | ❌ | | `isMultiStep` | `boolean` | Enable multi-step functionality | ❌ | | `currentStep` | `number` | Current step (controlled by parent) | ❌ | | `onStepChange` | `(step: number, totalSteps: number) => void` | Step change callback | ❌ | | `showStepNavigation` | `boolean` | Show step navigation | ❌ | | `stepNavigationProps` | `object` | Step navigation configuration | ❌ | ### Field Configuration ```tsx interface BaseField { name: string; label: string; type: 'text' | 'email' | 'password' | 'number' | 'select' | 'checkbox' | 'date' | 'textarea' | 'image'; placeholder?: string; required?: boolean; disabled?: boolean; validation?: ValidationRule; step?: number; // Step number for multi-step forms (1-based) col?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; xs?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; sm?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; md?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; lg?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; xl?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; } interface ImageField extends BaseField { type: 'image'; src?: string; // Image source URL alt?: string; // Alt text for accessibility width?: number | string; // Image width height?: number | string; // Image height style?: React.CSSProperties; // Custom styles className?: string; // Custom CSS class onClick?: () => void; // Click handler } ``` ### Validation Rules ```tsx interface ValidationRule { pattern?: string | RegExp; message?: string; required?: boolean; minLength?: number; maxLength?: number; min?: number; max?: number; custom?: (value: any) => boolean | string; } ``` ## 💡 Examples ### Complete Registration Form ```tsx const registrationFields: FieldConfig[] = [ { name: 'firstName', label: 'First Name', type: 'text', placeholder: 'Enter first name', required: true, col: 6 }, { name: 'lastName', label: 'Last Name', type: 'text', placeholder: 'Enter last name', required: true, col: 6 }, { name: 'email', label: 'Email', type: 'email', placeholder: 'Enter email address', required: true, col: 12 }, { name: 'password', label: 'Password', type: 'password', placeholder: 'Enter password', required: true, col: 6, validation: { minLength: 8, pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/, message: 'Password must be at least 8 characters with uppercase, lowercase, number and special character' } }, { name: 'confirmPassword', label: 'Confirm Password', type: 'password', placeholder: 'Confirm password', required: true, col: 6 }, { name: 'agreeToTerms', label: 'I agree to the Terms and Conditions', type: 'checkbox', required: true, col: 12 } ]; ``` ## 🛠️ Development ### Building the Library ```bash # Build for production npm run build:lib # Build types only npm run build:types ``` ### Running Development Server ```bash npm run dev ``` ## 📄 License MIT © [Ritik](https://github.com/finflow-analytics/finform) ## 🤝 Contributing Contributions, issues and feature requests are welcome! Feel free to check [issues page](https://github.com/finflow-analytics/finform/issues). ## 📞 Support If you like this project, please ⭐ star it on [GitHub](https://github.com/finflow-analytics/finform)! ## 📦 Document Upload Component Drag & drop upload with validation, preview, and optional submit button. ```tsx import { DocumentUpload } from 'finform-react-builder'; <DocumentUpload title="Drag & Drop Files" subtitle="Upload images or PDFs" buttonText="Select Files" fileSupportedText=".jpg, .jpeg, .png, .pdf up to 10MB" config={{ id: 'kyc-docs', fileTypes: ['image/*', 'application/pdf'], maxSize: 10 * 1024 * 1024, multiple: true, required: true, submitButton: { text: 'Upload', position: 'right' }, }} value={files} onChange={setFiles} onSubmit={(files) => console.log('Submitting', files)} /> ``` ## 🧭 Custom Stepper Card-style stepper with titles, connectors, and progress bar. ```tsx <FinForm fields={fields} stepNavigationProps={{ stepTitles: ['Basic Information', 'Permissions Matrix', 'Review'], }} onSubmit={...} /> ``` ## 📝 Helper Text Provide guidance beneath inputs via `helperText` on any field. ```tsx { name: 'first_name', label: 'First Name', type: 'text', helperText: 'Only alphabets, min 2 characters.' } ``` ## 📏 Input Size All inputs default to MUI size "small" for compact UI.