npm package: @siphesihle_thomo/form-generator
Description: A schema driven form generator for React
Author: Siphesihle Thomo
Latest version: 1.0.0

# @form-generator/core

A schema-driven form generator for React applications that combines the power of [Zod](https://zod.dev/) for validation, [React Hook Form](https://react-hook-form.com/) for form state management, and customizable field renderers.

## Features

- 🎯 **Schema-driven**: Define your form structure using Zod schemas
- 🔧 **Type-safe**: Full TypeScript support with automatic type inference
- 🎨 **Customizable**: Extensible field renderers for any UI component
- ⚡ **Performance**: Built on React Hook Form for optimal performance
- 🎨 **Styling**: Built-in Tailwind CSS classes with custom styling support
- 🔌 **Flexible**: Support for custom field types and renderers

## Installation

```bash
npm install @form-generator/core
# or
yarn add @form-generator/core
# or
pnpm add @form-generator/core
```

### Peer Dependencies

This package requires the following peer dependencies:

```bash
npm install react react-dom zod react-hook-form @hookform/resolvers
```

## Quick Start

```tsx
import React from 'react';
import { z } from 'zod';
import { FormGenerator } from '@form-generator/core';

// Define your form schema
const userSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

// Define your field configurations
const fieldConfigs = [
  {
    name: 'name',
    label: 'Full Name',
    type: 'text',
    placeholder: 'Enter your full name',
    required: true,
  },
  {
    name: 'email',
    label: 'Email Address',
    type: 'email',
    placeholder: 'Enter your email',
    required: true,
  },
  {
    name: 'password',
    label: 'Password',
    type: 'password',
    placeholder: 'Enter your password',
    required: true,
  },
];

function App() {
  const handleSubmit = (data: z.infer<typeof userSchema>) => {
    console.log('Form data:', data);
    // Handle form submission
  };

  return (
    <div className="max-w-md mx-auto mt-8 p-6">
      <h1 className="text-2xl font-bold mb-6">User Registration</h1>
      <FormGenerator
        schema={userSchema}
        fieldConfigs={fieldConfigs}
        onSubmit={handleSubmit}
        submitText="Create Account"
      />
    </div>
  );
}

export default App;
```

## API Reference

### FormGenerator

The main component that renders the form based on your schema and field configurations.

#### Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `schema` | `z.ZodType<any, any>` | ✅ | Zod schema for form validation |
| `fieldConfigs` | `FieldProps[]` | ✅ | Array of field configurations |
| `onSubmit` | `(data: any) => void` | ✅ | Callback function called on form submission |
| `className` | `string` | ❌ | CSS classes for the form container |
| `submitText` | `string` | ❌ | Text for the submit button (default: "Submit") |
| `submitVariant` | `"default" \| "destructive" \| "outline"` | ❌ | Button styling variant |
| `submitClassName` | `string` | ❌ | Custom CSS classes for submit button |
| `customRenderers` | `Partial<Record<FieldType, FieldRenderer>>` | ❌ | Custom field renderers |

### FieldProps

Configuration object for individual form fields.

```tsx
interface FieldProps {
  name: string;                    // Field name (must match schema)
  label?: string;                  // Field label
  type?: FieldType;               // Field type
  placeholder?: string;            // Input placeholder
  options?: Option[];             // Options for select/radio fields
  required?: boolean;             // Whether field is required
  disabled?: boolean;             // Whether field is disabled
  styles?: {                      // Custom CSS classes
    container?: string;
    label?: string;
    input?: string;
    error?: string;
  };
  render?: FieldRenderer;         // Custom renderer function
}
```

### Field Types

Currently supported field types:

- `text` - Text input
- `email` - Email input
- `password` - Password input
- `number` - Number input
- `checkbox` - Checkbox input
- `radio` - Radio button group
- `select` - Select dropdown
- `textarea` - Textarea

### useForm Hook

A custom hook that provides form methods with Zod validation.

```tsx
import { useForm } from '@form-generator/core';

const formMethods = useForm(schema);
```

## Advanced Usage

### Custom Field Renderers

You can create custom renderers for specific field types:

```tsx
import React from 'react';
import { FormGenerator, FieldProps } from '@form-generator/core';

const customRenderers = {
  textarea: ({ name, label, placeholder, required }: FieldProps) => (
    <div className="mb-4">
      {label && (
        <label htmlFor={name} className="block mb-1 font-medium text-sm text-gray-700">
          {label}
        </label>
      )}
      <textarea
        id={name}
        name={name}
        placeholder={placeholder}
        required={required}
        className="w-full border rounded px-3 py-2 focus:outline-none focus:ring focus:ring-black min-h-[100px]"
      />
    </div>
  ),
  select: ({ name, label, options, required }: FieldProps) => (
    <div className="mb-4">
      {label && (
        <label htmlFor={name} className="block mb-1 font-medium text-sm text-gray-700">
          {label}
        </label>
      )}
      <select
        id={name}
        name={name}
        required={required}
        className="w-full border rounded px-3 py-2 focus:outline-none focus:ring focus:ring-black"
      >
        <option value="">Select an option</option>
        {options?.map((option) => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
      </select>
    </div>
  ),
};

function App() {
  return (
    <FormGenerator
      schema={schema}
      fieldConfigs={fieldConfigs}
      onSubmit={handleSubmit}
      customRenderers={customRenderers}
    />
  );
}
```

### Custom Styling

You can customize the styling of individual fields:

```tsx
const fieldConfigs = [
  {
    name: 'email',
    label: 'Email',
    type: 'email',
    styles: {
      container: 'mb-6',
      label: 'block mb-2 text-sm font-semibold text-gray-800',
      input: 'w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent',
      error: 'mt-1 text-sm text-red-600',
    },
  },
];
```

### Complex Forms

For complex forms with nested objects or arrays:

```tsx
const complexSchema = z.object({
  personalInfo: z.object({
    firstName: z.string().min(2),
    lastName: z.string().min(2),
    email: z.string().email(),
  }),
  preferences: z.object({
    newsletter: z.boolean(),
    notifications: z.boolean(),
  }),
});

const complexFieldConfigs = [
  {
    name: 'personalInfo.firstName',
    label: 'First Name',
    type: 'text',
  },
  {
    name: 'personalInfo.lastName',
    label: 'Last Name',
    type: 'text',
  },
  {
    name: 'personalInfo.email',
    label: 'Email',
    type: 'email',
  },
  {
    name: 'preferences.newsletter',
    label: 'Subscribe to newsletter',
    type: 'checkbox',
  },
  {
    name: 'preferences.notifications',
    label: 'Enable notifications',
    type: 'checkbox',
  },
];
```

### Form Validation

The form automatically validates based on your Zod schema:

```tsx
const validationSchema = z.object({
  username: z.string()
    .min(3, 'Username must be at least 3 characters')
    .max(20, 'Username must be less than 20 characters')
    .regex(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores'),
  email: z.string()
    .email('Please enter a valid email address'),
  password: z.string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, 'Password must contain at least one uppercase letter, one lowercase letter, and one number'),
  confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
  message: "Passwords don't match",
  path: ["confirmPassword"],
});
```

## Examples

### Login Form

```tsx
const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(1, 'Password is required'),
});

const loginFields = [
  {
    name: 'email',
    label: 'Email',
    type: 'email',
    placeholder: 'Enter your email',
  },
  {
    name: 'password',
    label: 'Password',
    type: 'password',
    placeholder: 'Enter your password',
  },
];

<FormGenerator
  schema={loginSchema}
  fieldConfigs={loginFields}
  onSubmit={handleLogin}
  submitText="Sign In"
  submitVariant="default"
/>
```

### Registration Form with Custom Styling

```tsx
const registrationFields = [
  {
    name: 'firstName',
    label: 'First Name',
    type: 'text',
    required: true,
    styles: {
      container: 'mb-4',
      label: 'block text-sm font-medium text-gray-700 mb-1',
      input: 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500',
    },
  },
  {
    name: 'lastName',
    label: 'Last Name',
    type: 'text',
    required: true,
  },
  {
    name: 'email',
    label: 'Email Address',
    type: 'email',
    required: true,
  },
  {
    name: 'role',
    label: 'Role',
    type: 'select',
    options: [
      { value: 'user', label: 'User' },
      { value: 'admin', label: 'Administrator' },
      { value: 'moderator', label: 'Moderator' },
    ],
  },
];

<FormGenerator
  schema={registrationSchema}
  fieldConfigs={registrationFields}
  onSubmit={handleRegistration}
  submitText="Create Account"
  submitVariant="default"
  className="max-w-lg mx-auto p-6 bg-white rounded-lg shadow-md"
/>
```

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Support

If you have any questions or need help, please open an issue on GitHub or contact the maintainers.