# Schema Form

## Introduction

A React component capable of building HTML forms out of a [JSON schema](https://json-schema.org/understanding-json-schema/index.html).

## Usage

```tsx
import React, { useState } from 'react';

import { SchemaForm, initFormData, JSONSchema, UISchema, FormKit } from '@/components';

const schema: JSONSchema = {
  title: 'General',
  properties: {
    name: {
      type: 'string',
      title: 'Name',
    },
    age: {
      type: 'number',
      title: 'Age',
    },
    sex: {
      type: 'string',
      title: 'sex',
      enum: [1, 2],
      enumNames: ['male', 'female'],
    },
  },
};

const uiSchema: UISchema = {
  name: {
    'ui:widget': 'input',
  },
  age: {
    'ui:widget': 'input',
    'ui:options': {
      type: 'number',
    },
  },
  sex: {
    'ui:widget': 'radio',
  },
};

const Form = () => {
  const [formData, setFormData] = useState(initFormData(schema));
  
  const formRef = useRef<{
    validator: () => Promise<boolean>;
  }>(null);
  
  const refreshConfig: FormKit['refreshConfig'] = async () => {
    // refreshFormConfig();
  };

  const handleChange = (data) => {
    setFormData(data);
  };

  return (
    <SchemaForm
      ref={formRef}
      schema={schema}
      uiSchema={uiSchema}
      formData={formData}
      onChange={handleChange}
      refreshConfig={refreshConfig}
    />
  );
};

export default Form;
```

---

## Form Props

```ts
interface FormProps {
  // Describe the form structure with schema
  schema: JSONSchema | null;
  // Describe the properties of the field
  uiSchema?: UISchema;
  // Describe form data
  formData: Type.FormDataType | null;
  // Callback function when form data changes
  onChange?: (data: Type.FormDataType) => void;
  // Handler for when a form fires a `submit` event
  onSubmit?: (e: React.FormEvent) => void;
  /**
   * Callback method for updating form configuration
   * information (schema/uiSchema) in UIAction
   */
  refreshConfig?: FormKit['refreshConfig'];
}
```

## Form Ref

```ts
  export interface FormRef {
    validator: () => Promise<boolean>;
  }
```

When you need to validate a form and get the result outside the form, you can create a `FormRef` with `useRef` and pass it to the form using the `ref` property.

This allows you to validate the form and get the result outside the form using `formRef.current.validator()`.

---

## Types Definition
### JSONSchema

```ts
export interface JSONSchema {
  title: string;
  description?: string;
  required?: string[];
  properties: {
    [key: string]: {
      type: 'string' | 'boolean' | 'number';
      title: string;
      label?: string;
      description?: string;
      enum?: Array<string | boolean | number>;
      enumNames?: string[];
      default?: string | boolean | number;
    };
  };
}
```

### UISchema
```ts
export interface UISchema {
  [key: string]: {
    'ui:widget'?: UIWidget;
    'ui:options'?: UIOptions;
  };
}
```

### UIWidget
```ts
export type UIWidget =
  | 'textarea'
  | 'input'
  | 'checkbox'
  | 'radio'
  | 'select'
  | 'upload'
  | 'timezone'
  | 'switch'
  | 'legend'
  | 'button';
```

---

### UIOptions
```ts
export type UIOptions =
  | InputOptions
  | SelectOptions
  | UploadOptions
  | SwitchOptions
  | TimezoneOptions
  | CheckboxOptions
  | RadioOptions
  | TextareaOptions
  | ButtonOptions;
```

#### BaseUIOptions
```ts
export interface BaseUIOptions {
  empty?: string;
  // Will be appended to the className of the form component itself
  className?: classnames.Argument;
  class_name?: classnames.Argument;
  // The className that will be attached to a form field container
  field_class_name?: classnames.Argument;
  // Make a form component render into simplified mode
  readOnly?: boolean;
  simplify?: boolean;
  validator?: (
    value,
    formData?,
  ) => Promise<string | true | void> | true | string;
}
```

#### InputOptions
```ts
export interface InputOptions extends BaseUIOptions {
  placeholder?: string;
  inputType?:
    | 'color'
    | 'date'
    | 'datetime-local'
    | 'email'
    | 'month'
    | 'number'
    | 'password'
    | 'range'
    | 'search'
    | 'tel'
    | 'text'
    | 'time'
    | 'url'
    | 'week';
}
```

#### SelectOptions
```ts
export interface SelectOptions extends UIOptions {}
```

#### UploadOptions
```ts
export interface UploadOptions extends BaseUIOptions {
  acceptType?: string;
  imageType?: Type.UploadType;
}
```

#### SwitchOptions
```ts
export interface SwitchOptions extends BaseUIOptions {
  label?: string;
}
```

#### TimezoneOptions
```ts
export interface TimezoneOptions extends UIOptions {
  placeholder?: string;
}
```

#### CheckboxOptions
```ts
export interface CheckboxOptions extends UIOptions {}
```

#### RadioOptions
```ts
export interface RadioOptions extends UIOptions {}
```

#### TextareaOptions
```ts
export interface TextareaOptions extends UIOptions {
  placeholder?: string;
  rows?: number;
}
```

#### ButtonOptions
```ts
export interface ButtonOptions extends BaseUIOptions {
  text: string;
  icon?: string;
  action?: UIAction;
  variant?: ButtonProps['variant'];
  size?: ButtonProps['size'];
}
```

#### UIAction
```ts
export interface UIAction {
  url: string;
  method?: 'get' | 'post' | 'put' | 'delete';
  loading?: {
    text: string;
    state?: 'none' | 'pending' | 'completed';
  };
  on_complete?: {
    toast_return_message?: boolean;
    refresh_form_config?: boolean;
  };
}
```

#### FormKit
```ts
export interface FormKit {
  refreshConfig(): void;
}
```

---

### FormData
```ts
export interface FormValue<T = any> {
  value: T;
  isInvalid: boolean;
  errorMsg: string;
  [prop: string]: any;
}

export interface FormDataType {
  [prop: string]: FormValue;
}
```

---

## Backend API

For backend generating modal form you can return json like this.

### Response

```json
{
  "name": "string",
  "slug_name": "string",
  "description": "string",
  "version": "string",
  "config_fields": [
    {
      "name": "string",
      "type": "textarea",
      "title": "string",
      "description": "string",
      "required": true,
      "value": "string",
      "ui_options": {
        "placeholder": "placeholder",
        "rows": 4
      },
      "options": [
        {
          "value": "string",
          "label": "string"
        }
      ]
    }
  ]
}
```


## reference

- [json schema](https://json-schema.org/understanding-json-schema/index.html)
- [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form)
- [vue-json-schema-form](https://github.com/lljj-x/vue-json-schema-form/)
