Create form component for drag-drop in Atri project

A form component can have different kinds of form controls such as input, radio and checkbox etc. For a more detailed example, you can see the code here. A minimal boilerplate to start can be found here.

Create manifest

In the manifest, notice the custom field, it has a fields key that uses array_typed_map. The array_typed_map will allow your users to pick the type of children component the user want inside the form. In this example, we are adding text, number, password and email.

// file: CustomForm/CustomForm.manifest.tsx
import { createComponentManifest } from "@atrilabs/utils";

export default createComponentManifest({
    name: "CustomForm",
    // add typography related options
    styles: {
        typographyOptions: true,
    },
    custom: {
        fields: {
            type: "array_typed_map",
            attributes: [
                {
                    fieldName: "text",
                    type: "map",
                    attributes: [
                        { fieldName: "id", type: "text" },
                        { fieldName: "label", type: "text" },
                        { fieldName: "placeholder", type: "text" },
                    ],
                },
                {
                    fieldName: "number",
                    type: "map",
                    attributes: [
                        { fieldName: "id", type: "text" },
                        { fieldName: "label", type: "text" },
                        { fieldName: "placeholder", type: "text" },
                    ],
                },
                {
                    fieldName: "password",
                    type: "map",
                    attributes: [
                        { fieldName: "id", type: "text" },
                        { fieldName: "label", type: "text" },
                        { fieldName: "placeholder", type: "text" },
                    ],
                },
                {
                    fieldName: "email",
                    type: "map",
                    attributes: [
                        { fieldName: "id", type: "text" },
                        { fieldName: "label", type: "text" },
                        { fieldName: "placeholder", type: "text" },
                    ],
                },
            ],
        },
    },
});

Create a type file

// file: CustomForm/types.ts

export type CommonFormControlOptions = {
    label?: string;
    id?: string;
    placeholder?: string;
};

Create form React component

// file: CustomForm/CustomForm.tsx

import React from "react";
import { CommonFormControlOptions } from "./types";
import { Text } from "./components/Text";

const Form = React.forwardRef<
    HTMLFormElement,
    {
        className: string;
        id: string;
        styles: React.CSSProperties;
        attrs: { class: string };
        custom: {
            fields: {
                selectedOption:
                    | "none"
                    | "text"
                    | "number"
                    | "password"
                    | "email";
                text: CommonFormControlOptions;
                password: CommonFormControlOptions;
                email: CommonFormControlOptions;
                number: CommonFormControlOptions;
            }[];
        };
    }
>((props, ref) => {
    return (
        <form
            ref={ref}
            className={`${props.className} ${props.attrs?.class || ""}`}
            id={props.id}
            style={props.styles}
        >
        </form>
    );
});

export default Form;

Create form control React components

// file: CustomForm/components/Text.tsx

import { CommonFormControlOptions } from "../types";

export const Text = (props?: CommonFormControlOptions & { key: string }) => {
    return (
        <div>
            <label htmlFor={props?.id}>{props?.label}</label>
            <input {...props} key={props?.key} name={props?.id} type="text" />
        </div>
    );
};

Add form control to form component

// file: CustomForm/CustomForm.tsx

import React from "react";
import { CommonFormControlOptions } from "./types";
import { Text } from "./components/Text";

const Form = React.forwardRef<
    HTMLFormElement,
    {
        className: string;
        id: string;
        styles: React.CSSProperties;
        attrs: { class: string };
        custom: {
            fields: {
                selectedOption:
                    | "none"
                    | "text"
                    | "number"
                    | "password"
                    | "email";
                text: CommonFormControlOptions;
                password: CommonFormControlOptions;
                email: CommonFormControlOptions;
                number: CommonFormControlOptions;
            }[];
        };
    }
>((props, ref) => {
    return (
        <form
            ref={ref}
            className={`${props.className} ${props.attrs?.class || ""}`}
            id={props.id}
            style={props.styles}
        >
            {/** Add different types of input widgets */}
            {props.custom.fields.map((field, index) => {
                if (field.selectedOption === "text") {
                    return <Text {...field.text} key={`${props.id}${index}`} />;
                }
                return <div></div>;
            })}
        </form>
    );
});

export default Form;

Other common form control types

For adding more input types, you have to make changes in CustomForm/CustomForm.manifest.tsx and CustomForm/CustomForm.tsx

// file: CustomForm/CustomForm.manifest.tsx
import { createComponentManifest } from "@atrilabs/utils";

export default createComponentManifest({
  name: "CustomForm",
  // add typography related options
  styles: {
    typographyOptions: true,
  },
  custom: {
    fields: {
      type: "array_typed_map",
      attributes: [
        {
          fieldName: "text",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            { fieldName: "placeholder", type: "text" },
          ],
        },
        {
          fieldName: "number",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            { fieldName: "placeholder", type: "text" },
          ],
        },
        {
          fieldName: "password",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            { fieldName: "placeholder", type: "text" },
          ],
        },
        {
          fieldName: "radio",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            {
              fieldName: "options",
              type: "json",
              schema: Joi.array()
                .unique()
                .items(
                  Joi.object({
                    value: Joi.string().required(),
                    label: Joi.string().required(),
                  })
                )
                .id("radio"),
            },
          ],
        },
        {
          fieldName: "checkbox",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            {
              fieldName: "options",
              type: "json",
              schema: Joi.array()
                .unique()
                .items(
                  Joi.object({
                    value: Joi.string().required(),
                    label: Joi.string().required(),
                  })
                )
                .id("checkbox"),
            },
          ],
        },
        {
          fieldName: "color",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
          ],
        },
        {
          fieldName: "date",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
          ],
        },
        {
          fieldName: "datetimeLocal",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
          ],
        },
        {
          fieldName: "email",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            { fieldName: "placeholder", type: "text" },
          ],
        },
        {
          fieldName: "time",
          type: "map",
          attributes: [
            { fieldName: "label", type: "text" },
            { fieldName: "id", type: "text" },
          ],
        },
        {
          fieldName: "url",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            { fieldName: "placeholder", type: "text" },
          ],
        },
        {
          fieldName: "search",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            { fieldName: "placeholder", type: "text" },
          ],
        },
        {
          fieldName: "file",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            { fieldName: "multiple", type: "boolean" },
          ],
        },
        {
          fieldName: "select",
          type: "map",
          attributes: [
            { fieldName: "id", type: "text" },
            { fieldName: "label", type: "text" },
            { fieldName: "multiple", type: "boolean" },
            {
              fieldName: "options",
              type: "json",
              schema: Joi.array()
                .unique()
                .items(
                  Joi.object({
                    value: Joi.string().required(),
                    label: Joi.string().required(),
                  })
                )
                .id("select"),
            },
          ],
        },
      ],
    },
  },
});