npm package: @iwanha/form-builder
Description: A flexible React form builder component with support for various input types, date pickers, tables, data fetching, and Elasticsearch logging
Author: Iwan Hadi Setiawan
Latest version: 1.4.4

# @iwanha/form-builder

A flexible and powerful React form builder component with support for various input types, date pickers, tables, and data fetching. Built with TypeScript, React Hook Form, shadcn/ui, and Tailwind CSS.

## Features

- 🎯 **Dynamic Form Generation**: Create forms from JSON configuration
- 📝 **Multiple Input Types**: Text, email, password, number, select, radio, file
- 📅 **Advanced Date Components**: Date picker, date range, date-time picker with various combinations
- 📊 **Data Tables**: Client-side and server-side TanStack tables with sorting, filtering, and pagination
- ✅ **Form Validation**: Built-in validation with custom rules
- 🎨 **Styled Components**: Beautiful UI components based on shadcn/ui and Tailwind CSS
- 🔄 **Data Fetching**: Built-in HTTP client for form submission and data fetching
- ⏳ **Loading States**: Professional loading indicators with disabled submit buttons
- 📡 **Response Handling**: Built-in response management for API integration
- 📱 **Responsive Design**: Mobile-first responsive layouts
- 🎭 **TypeScript Support**: Full TypeScript support with comprehensive type definitions

## Installation

```bash
npm install @iwanha/form-builder
```

### Peer Dependencies

Make sure you have the following peer dependencies installed:

```bash
npm install react react-dom
```

### Styling Requirements

This package comes with pre-compiled CSS styles, so you don't need to install Tailwind CSS. Simply import the CSS file:

```tsx
import '@iwanha/form-builder/dist/styles.css';
```

**Alternative**: If you're already using Tailwind CSS in your project, you can skip importing the CSS file as the components will use your existing Tailwind classes.

**Note**: The package works with or without Tailwind CSS - the pre-compiled CSS ensures consistent styling across all projects.

## Quick Start

```tsx
import React, { useState } from 'react';
import { FormBuilder, FormData } from '@iwanha/form-builder';
// Import the CSS styles
import '@iwanha/form-builder/dist/styles.css';

const formData: FormData = {
  formName: "Contact Form",
  formDesc: "Please fill out your information",
  type: "form",
  action: "post",
  url: "https://api.example.com/contact",
  input: [
    {
      id: "name",
      sequence: "0",
      components: "input",
      type: "text",
      validation: {
        required: true,
        minLength: 2
      },
      attribut: {
        label: "Full Name",
        placeholder: "Enter your full name"
      }
    },
    {
      id: "email",
      sequence: "1",
      components: "input",
      type: "email",
      validation: {
        required: true
      },
      attribut: {
        label: "Email",
        placeholder: "Enter your email"
      }
    },
    {
      id: "country",
      sequence: "2",
      components: "select",
      validation: {
        required: true
      },
      attribut: {
        label: "Country",
        placeholder: "Select your country"
      },
      list: [
        { value: "us", label: "United States" },
        { value: "ca", label: "Canada" },
        { value: "uk", label: "United Kingdom" }
      ]
    }
  ]
};

function App() {
  const [responseData, setResponseData] = useState<unknown>(null);

  const handleResponseData = (data: unknown) => {
    console.log('Form response:', data);
    setResponseData(data);
    // Handle your response here - show success message, redirect, etc.
  };

  return (
    <div className="p-4">
      <FormBuilder 
        data={formData} 
        onResponseData={handleResponseData}
      />
      
      {/* Optional: Display response data */}
      {responseData && (
        <div className="mt-4 p-3 bg-green-50 border border-green-200 rounded">
          <p className="text-green-800 font-semibold">✅ Form submitted successfully!</p>
          <details className="mt-2">
            <summary className="cursor-pointer text-sm text-green-600">View Response</summary>
            <pre className="mt-1 text-xs bg-green-100 p-2 rounded overflow-auto">
              {JSON.stringify(responseData, null, 2)}
            </pre>
          </details>
        </div>
      )}
    </div>
  );
}

export default App;
```

## API Reference

### FormBuilder Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| data | `FormData` | Yes | Configuration object that defines the form structure |
| onResponseData | `(data: unknown) => void` | No | Callback function that receives response data after form submission |

## Response Handling

**🚨 MANDATORY for handling form responses**: Use the `onResponseData` prop to capture API responses from form submissions.

```tsx
import React, { useState } from 'react';
import { FormBuilder, FormData } from '@iwanha/form-builder';
import '@iwanha/form-builder/dist/styles.css';

function App() {
  const [responseData, setResponseData] = useState<unknown>(null);

  const handleResponseData = (data: unknown) => {
    console.log('Response received:', data);
    setResponseData(data);
    // Handle the response data here - update state, show notifications, etc.
  };

  const formData: FormData = {
    formName: "Contact Form",
    formDesc: "Submit your information",
    type: "form",
    action: "post",
    url: "https://api.example.com/contact",
    input: [
      {
        id: "name",
        sequence: "0",
        components: "input",
        type: "text",
        validation: { required: true },
        attribut: {
          label: "Name",
          placeholder: "Enter your name"
        }
      }
    ]
  };

  return (
    <div className="p-4">
      <FormBuilder 
        data={formData} 
        onResponseData={handleResponseData}
      />
      
      {/* Display response data */}
      {responseData && (
        <div className="mt-4 p-4 bg-gray-100 rounded">
          <h3>Response Data:</h3>
          <pre>{JSON.stringify(responseData, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

export default App;
```

### Response Flow

1. **User submits form** → FormBuilder makes HTTP request to your API
2. **API responds** → `onResponseData` callback is triggered with the response
3. **Handle response** → You can update state, show notifications, navigate, etc.

### Response Data Types

The `onResponseData` callback receives different data based on the submission:

- **HTTP submissions**: Receives `response.data` from the API call
- **Non-HTTP submissions**: Receives the form data itself
- **Both scenarios**: You get the actual response/data to work with

### Example with Error Handling

```tsx
const handleResponseData = (data: unknown) => {
  try {
    // Type guard or validate the response structure
    if (data && typeof data === 'object' && 'success' in data) {
      const response = data as { success: boolean; message: string; id?: number };
      
      if (response.success) {
        console.log('Form submitted successfully!', response);
        // Redirect to success page or show success message
      } else {
        console.error('Form submission failed:', response.message);
        // Show error message to user
      }
    } else {
      console.log('Response data:', data);
      // Handle other response formats
    }
  } catch (error) {
    console.error('Error processing response:', error);
  }
};
```

**Note**: The response handling is essential for any form that submits data to an API. Without `onResponseData`, you won't be able to access the server's response or handle success/error scenarios in your application.

### FormData Interface

```typescript
interface FormData {
  formName: string;                    // Form title
  formDesc?: string;                   // Form description
  url?: string;                        // API endpoint for form submission
  header?: Record<string, string>;     // HTTP headers
  action?: "post" | "get" | "put" | "delete"; // HTTP method
  formType?: "multipart/form-data" | "application/x-www-form-urlencoded";
  type: "form" | "display";           // Form type: interactive form or display only
  variables?: Record<string, unknown>; // Variables for dynamic content
  input: FormInputField[];            // Array of form fields
}
```

### FormInputField Interface

```typescript
interface FormInputField {
  id: string;                         // Unique field identifier
  sequence: string;                   // Display order
  position?: "left" | "center" | "right"; // Layout position
  components: "input" | "radio" | "select" | "date-picker" | "date-range-picker" | 
              "date-time-picker" | "date-time-range-picker" | "date-range-time-picker" |
              "tanstack-table" | "tanstack-table-server-side";
  type?: "text" | "email" | "password" | "number" | "file" | "textarea";
  value?: unknown;                    // Default value
  validation?: FormValidation;        // Validation rules
  attribut?: FormAttribute;           // Field attributes
  list?: Array<{value: string, label: string, description?: string}> | string;
  columns?: string;                   // For table components
  serverConfig?: unknown;             // For server-side table configuration
}
```

## Component Types

### Input Components

- **input**: Text, email, password, number, file inputs, textarea
- **select**: Dropdown selection
- **radio**: Radio button groups

### Date Components

- **date-picker**: Single date selection
- **date-range-picker**: Date range selection
- **date-time-picker**: Date and time selection
- **date-time-range-picker**: Date and time range selection
- **date-range-time-picker**: Date range with time selection

### Table Components

- **tanstack-table**: Client-side data table with sorting and filtering
- **tanstack-table-server-side**: Server-side data table with API integration

## Advanced Usage

### Using Variables

You can use variables to make your forms dynamic:

```typescript
const formData: FormData = {
  formName: "User Profile",
  type: "form",
  variables: {
    userEmail: "john@example.com",
    countries: [
      { value: "us", label: "United States" },
      { value: "ca", label: "Canada" }
    ]
  },
  input: [
    {
      id: "email",
      sequence: "0",
      components: "input",
      type: "email",
      value: "variables.userEmail", // References the variable
      attribut: {
        label: "Email",
        placeholder: "Enter email"
      }
    },
    {
      id: "country",
      sequence: "1",
      components: "select",
      list: "variables.countries", // References the variable
      attribut: {
        label: "Country",
        placeholder: "Select country"
      }
    }
  ]
};
```

### Table Configuration

```typescript
const tableData: FormData = {
  formName: "Users Table",
  type: "display",
  url: "https://api.example.com/users",
  action: "get",
  variables: {
    columns: [
      { key: "id", header: "ID", type: "number", sortable: true },
      { key: "name", header: "Name", type: "text", sortable: true },
      { key: "email", header: "Email", type: "text" },
      { key: "actions", header: "Actions", type: "actions", standardActions: {
        view: "/users/{id}",
        edit: "/users/{id}/edit"
      }}
    ]
  },
  input: [
    {
      id: "usersTable",
      sequence: "0",
      components: "tanstack-table",
      columns: "variables.columns"
    }
  ]
};
```

### Nested Data Paths

Both client-side and server-side table components support nested data paths using dot notation. This is particularly useful when working with APIs that return nested response structures.

#### Client-Side Tables with Nested Data

```typescript
const nestedClientTableData: FormData = {
  formName: "Nested Data Example",
  type: "display",
  url: "https://api.example.com/response", // Returns: { data: { users: [...] } }
  action: "get",
  variables: {
    dataPath: "data.users", // ← Nested path to extract array
    columns: [
      {
        key: "id",
        header: "ID",
        type: "number"
      },
      {
        key: "profile.firstName", // ← Nested column access
        header: "First Name",
        type: "text"
      },
      {
        key: "profile.lastName", // ← Nested column access
        header: "Last Name",
        type: "text"
      },
      {
        key: "contact.address.city", // ← Deeply nested access
        header: "City",
        type: "text"
      },
      {
        key: "company.details.name", // ← Complex nested structure
        header: "Company",
        type: "text"
      }
    ]
  },
  input: [
    {
      id: "table",
      components: "tanstack-table",
      sequence: "0",
      list: "variables.dataPath",
      columns: "variables.columns"
    }
  ]
};
```

#### Server-Side Tables with Nested Paths

```typescript
const nestedServerTableData: FormData = {
  formName: "Server-Side Nested Example",
  type: "display",
  url: "https://api.example.com/products",
  action: "get",
  variables: {
    serverColumns: [
      { key: "id", header: "Product ID", type: "number", sortable: true },
      { key: "details.name", header: "Product Name", type: "text", sortable: true },
      { key: "pricing.current.amount", header: "Price", type: "currency" },
      { key: "inventory.stock.available", header: "Stock", type: "number" }
    ],
    serverConfig: {
      pagination: {
        sizeParam: "limit",
        skipParam: "offset"
      },
      response: {
        dataPath: "data.products", // ← Nested path for data array
        totalPath: "meta.pagination.total" // ← Nested path for total count
      },
      search: {
        param: "q",
        debounceMs: 500,
        searchUrl: "https://api.example.com/products/search"
      }
    }
  },
  input: [
    {
      id: "serverTable",
      components: "tanstack-table-server-side",
      sequence: "0",
      columns: "variables.serverColumns",
      serverConfig: "variables.serverConfig"
    }
  ]
};
```

#### Common API Response Patterns

| API Response Structure | Configuration |
|------------------------|---------------|
| `{ users: [...] }` | `dataPath: "users"` |
| `{ data: { users: [...] } }` | `dataPath: "data.users"` |
| `{ response: { body: { items: [...] } } }` | `dataPath: "response.body.items"` |
| `{ data: { users: { edges: [...] } } }` | `dataPath: "data.users.edges"` (GraphQL style) |

#### Nested Column Access Examples

```typescript
// For data structure like:
// {
//   "id": 1,
//   "user": {
//     "profile": {
//       "personal": {
//         "firstName": "John",
//         "lastName": "Doe"
//       },
//       "contact": {
//         "email": "john@example.com",
//         "addresses": {
//           "primary": {
//             "street": "123 Main St",
//             "city": "New York",
//             "coordinates": {
//               "lat": "40.7128",
//               "lng": "-74.0060"
//             }
//           }
//         }
//       }
//     }
//   }
// }

const columns = [
  { key: "id", header: "ID" },
  { key: "user.profile.personal.firstName", header: "First Name" },
  { key: "user.profile.personal.lastName", header: "Last Name" },
  { key: "user.profile.contact.email", header: "Email" },
  { key: "user.profile.contact.addresses.primary.city", header: "City" },
  { key: "user.profile.contact.addresses.primary.coordinates.lat", header: "Latitude" }
];
```

The nested path support is unlimited in depth - you can access data at any level of nesting using dot notation.

## Validation

The package supports comprehensive form validation:

```typescript
{
  validation: {
    required: true,
    minLength: 3,
    maxLength: 50,
    regex: {
      value: "^[a-zA-Z ]+$",
      message: "Only letters and spaces allowed"
    }
  }
}
```

## Styling

This package uses shadcn/ui components built on top of Radix UI primitives with Tailwind CSS for styling. The components are designed to be responsive and work well across different screen sizes.

**Technology Stack:**
- **shadcn/ui**: Modern React components built on Radix UI
- **Radix UI**: Unstyled, accessible UI primitives
- **Tailwind CSS**: Utility-first CSS framework
- **Lucide React**: Beautiful & consistent icon library

The pre-compiled CSS ensures that all components render correctly regardless of your project's setup.

## TypeScript Support

Full TypeScript support is included with comprehensive type definitions. All interfaces and types are exported for use in your applications.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT License - see LICENSE file for details.

## Author

Iwan Hadi Setiawan (@iwanha)