# 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.