Skip to content

@adimis/react-formix is a powerful React library that simplifies form management and validation for modern web applications.

License

Notifications You must be signed in to change notification settings

AdimisDev/adimis-react-formix

Repository files navigation

@adimis/react-formix

Welcome to @adimis/react-formix, the ultimate React library for creating dynamic, schema-based forms. Built with modern web development in mind, this library harnesses the power of React, Zod for robust schema validation, and react-hook-form for seamless form state management. Whether you need to build custom forms from the ground up or use prebuilt components for rapid deployment, @adimis/react-formix is your go-to solution.

Key Features

  • Schema-Based Form Generation: Define your form fields and validation rules using a JSON schema.
  • Zod-Powered Validation: Leverage Zod for strong data validation and meaningful feedback.
  • Flexible Layout Options: Create responsive and dynamic form layouts effortlessly.
  • Conditional Rendering: Dynamically show or hide form fields based on other field values.
  • State Persistence: Save and restore form state using local storage or session storage.
  • Custom Render Functions: Specify custom render functions inside the schema array's object.
  • TypeScript Support: Utilize generic TypeScript typing for form field types.
  • Dynamic Field Visibility: Update field visibility in real-time based on user input.
  • Dynamic Validation: Remove field validations dynamically based on specific conditions.
  • Developer Tools: Integrate developer tools for debugging and inspecting form state.
  • Customizable Themes: Adapt form themes to align with your application's design.
  • Latest React and Next.js Support: Ensure compatibility with modern web development practices.

Installation and Setup

  1. Install the package via npm:

    npm install @adimis/react-formix
  2. Import the style in your main.tsx or index.tsx file:

    import "@adimis/react-formix/dist/style.css";
  3. Install and setup Tailwind CSS: Tailwind CSS Docs

  4. Add the following to your index.css file:

    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    @layer base {
      :root {
        --background: hsl(0, 0%, 100%);
        --foreground: hsl(222.2, 84%, 4.9%);
        --card: hsl(0, 0%, 100%);
        --card-foreground: hsl(222.2, 84%, 4.9%);
        --popover: hsl(0, 0%, 100%);
        --popover-foreground: hsl(222.2, 84%, 4.9%);
        --primary: hsl(221.2, 83.2%, 53.3%);
        --primary-foreground: hsl(210, 40%, 98%);
        --secondary: hsl(210, 40%, 96.1%);
        --secondary-foreground: hsl(222.2, 47.4%, 11.2%);
        --muted: hsl(210, 40%, 96.1%);
        --muted-foreground: hsl(215.4, 16.3%, 46.9%);
        --accent: hsl(210, 40%, 96.1%);
        --accent-foreground: hsl(222.2, 47.4%, 11.2%);
        --destructive: hsl(0, 84.2%, 60.2%);
        --destructive-foreground: hsl(210, 40%, 98%);
        --border: hsl(214.3, 31.8%, 91.4%);
        --input: hsl(214.3, 31.8%, 91.4%);
        --ring: hsl(221.2, 83.2%, 53.3%);
        --radius: 1rem;
      }
    
      .dark {
        --background: hsl(222.2, 84%, 4.9%);
        --foreground: hsl(210, 40%, 98%);
        --card: hsl(222.2, 84%, 4.9%);
        --card-foreground: hsl(210, 40%, 98%);
        --popover: hsl(222.2, 84%, 4.9%);
        --popover-foreground: hsl(210, 40%, 98%);
        --primary: hsl(217.2, 91.2%, 59.8%);
        --primary-foreground: hsl(222.2, 47.4%, 11.2%);
        --secondary: hsl(217.2, 32.6%, 17.5%);
        --secondary-foreground: hsl(210, 40%, 98%);
        --muted: hsl(217.2, 32.6%, 17.5%);
        --muted-foreground: hsl(215, 20.2%, 65.1%);
        --accent: hsl(217.2, 32.6%, 17.5%);
        --accent-foreground: hsl(210, 40%, 98%);
        --destructive: hsl(0, 62.8%, 30.6%);
        --destructive-foreground: hsl(210, 40%, 98%);
        --border: hsl(217.2, 32.6%, 17.5%);
        --input: hsl(217.2, 32.6%, 17.5%);
        --ring: hsl(224.3, 76.3%, 48%);
      }
    }
    
    @layer base {
      * {
        @apply border-border;
      }
    
      body {
        @apply bg-background text-foreground;
      }
    }
  5. Install required dependencies:

    npm i tailwindcss-animate
  6. Setup your tsconfig.config.js file:

    /** @type {import('tailwindcss').Config} */
    export default {
      darkMode: ["class"],
      content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
      theme: {
        container: {
          center: true,
          padding: "2rem",
          screens: {
            "2xl": "1400px",
          },
        },
        extend: {
          colors: {
            border: "var(--border)",
            input: "var(--input)",
            ring: "var(--ring)",
            background: "var(--background)",
            foreground: "var(--foreground)",
            primary: {
              DEFAULT: "var(--primary)",
              foreground: "var(--primary-foreground)",
            },
            secondary: {
              DEFAULT: "var(--secondary)",
              foreground: "var(--secondary-foreground)",
            },
            destructive: {
              DEFAULT: "var(--destructive)",
              foreground: "var(--destructive-foreground)",
            },
            muted: {
              DEFAULT: "var(--muted)",
              foreground: "var(--muted-foreground)",
            },
            accent: {
              DEFAULT: "var(--accent)",
              foreground: "var(--accent-foreground)",
            },
            popover: {
              DEFAULT: "var(--popover)",
              foreground: "var(--popover-foreground)",
            },
            card: {
              DEFAULT: "var(--card)",
              foreground: "var(--card-foreground)",
            },
          },
          borderRadius: {
            lg: "var(--radius)",
            md: "calc(var(--radius) - 2px)",
            sm: "calc(var(--radius) - 4px)",
          },
          keyframes: {
            "accordion-down": {
              from: { height: "0" },
              to: { height: "var(--radix-accordion-content-height)" },
            },
            "accordion-up": {
              from: { height: "var(--radix-accordion-content-height)" },
              to: { height: "0" },
            },
          },
          animation: {
            "accordion-down": "accordion-down 0.2s ease-out",
            "accordion-up": "accordion-up 0.2s ease-out",
          },
        },
      },
      plugins: [require("tailwindcss-animate")],
    };

Usage

Here's an example of how to create a basic signup form using @adimis/react-formix:

"use client";

import { z } from "zod";
import { Input } from "./components/input";
import { Button } from "./components/button";
import {
  FormBody,
  FormContent,
  FormixFormProvider,
  FormDescription,
  FormFlexFields,
  FormFooter,
  FormHeader,
  FormTitle,
  ISchemaFormProps,
  ThemeProvider,
} from "@adimis/react-formix"; // 120.1k (gzipped: 34.2k)
import "@adimis/react-formix/dist/style.css";

interface SignUp {
  username: string;
  email: string;
  address: string;
  phone: string;
  password: string;
  gender: string;
  terms: boolean;
  file: File;
  date: Date;
  year: number;
  expertise: string[];
}

const App = () => {
  const schemaFormProps: ISchemaFormProps<SignUp> = {
    formLabel: "Example Barebone Form",
    formSlug: "example-barebone-form",
    persistFormResponse: "localStorage",
    devTools: true,
    formDisabled: false,
    enableConditionalRendering: true,
    schema: [
      {
        key: "username",
        label: "Username",
        description: "Enter your desired username.",
        autoComplete: "username",
        type: "text",
        placeholder: "Your username",
        defaultValue: "",
        validations: z
          .string()
          .min(1, "Username is required")
          .max(20, "Username must not exceed 20 characters"),
        render: ({
          formDisabled,
          formItem,
          formMethods,
          submitButtonLoading,
        }) => (
          <Input
            type={formItem.type}
            id={formItem.key}
            disabled={formDisabled || submitButtonLoading}
            style={{
              width: "100%",
              height: "10px",
              border: "1px solid #ccc",
              borderRadius: "5px",
              padding: "10px",
              margin: "5px 0",
            }}
            {...formMethods.register(formItem.key)}
          />
        ),
      },
      {
        key: "email",
        label: "Email",
        description: "Enter your email address.",
        autoComplete: "email",
        type: "email",
        placeholder: "Your email",
        defaultValue: "",
        validations: z
          .string()
          .email("Enter a valid email address")
          .min(1, "Email is required"),
        render: ({
          formDisabled,
          formItem,
          formMethods,
          submitButtonLoading,
        }) => (
          <Input
            type={formItem.type}
            id={formItem.key}
            disabled={formDisabled || submitButtonLoading}
            style={{
              width: "100%",
              height: "10px",
              border: "1px solid #ccc",
              borderRadius: "5px",
              padding: "10px",
              margin: "5px 0",
            }}
            {...formMethods.register(formItem.key)}
          />
        ),
      },
      {
        key: "address",
        label: "Address",
        description: "Enter your full address.",
        autoComplete: "address-line1",
        type: "text",
        placeholder: "Your address",
        defaultValue: "",
        validations: z
          .string()
          .min(10, "Address should be at least 10 characters"),
        render: ({
          formDisabled,
          formItem,
          formMethods,
          submitButtonLoading,
        }) => (
          <Input
            type={formItem.type}
            id={formItem.key}
            disabled={formDisabled || submitButtonLoading}
            style={{
              width: "100%",
              height: "10px",
              border: "1px solid #ccc",
              borderRadius: "5px",
              padding: "10px",
              margin: "5px 0",
            }}
            {...formMethods.register(formItem.key)}
          />
        ),
      },
      {
        key: "phone",
        label: "Phone",
        description: "Enter your phone number with country code.",
        autoComplete: "tel",
        type: "tel",
        placeholder: "+1234567890",
        defaultValue: "",
        validations: z
          .string()
          .regex(/^\+?(\d.*){10,}$/, "Enter a valid phone number"),
        render: ({
          formDisabled,
          formItem,
          formMethods,
          submitButtonLoading,
        }) => (
          <Input
            type={formItem.type}
            id={formItem.key}
            disabled={formDisabled || submitButtonLoading}
            style={{
              width: "100%",
              height: "10px",
              border: "1px solid #ccc",
              borderRadius: "5px",
              padding: "10px",
              margin: "5px 0",
            }}
            {...formMethods.register(formItem.key)}
          />
        ),
      },
      {
        key: "password",
        label: "Password",
        description: "Enter a strong password.",
        autoComplete: "new-password",
        type: "password",
        placeholder: "Your password",
        defaultValue: "",
        validations: z
          .string()
          .min(8, "Password should be at least 8 characters")
          .max(20, "Password must not exceed 20 characters"),
        displayConditions: [
          {
            dependentField: "email",
            dependentFieldValue: "admin@adimis.in",
            operator: "===",
          },
        ],
        removeValidationConditions: [
          {
            dependentField: "email",
            dependentFieldValue: "admin@adimis.in",
            operator: "!==",
          },
        ],
        render: ({
          formDisabled,
          formItem,
          formMethods,
          submitButtonLoading,
        }) => (
          <Input
            type={formItem.type}
            id={formItem.key}
            disabled={formDisabled || submitButtonLoading}
            style={{
              width: "100%",
              height: "10px",
              border: "1px solid #ccc",
              borderRadius: "5px",
              padding: "10px",
              margin: "5px 0",
            }}
            {...formMethods.register(formItem.key)}
          />
        ),
      },
    ],
    defaultValues: {
      email: "adimis.ai.001@gmail.com",
      phone: "919625183597",
    },
    onSubmit: (values) =>
      console.log(
        "On Submit Example Form Response: ",
        JSON.stringify(values, null, 4)
      ),
    onInvalidSubmit: (values) =>
      console.log(
        "On Submit Invalid Example Form Response: ",
        JSON.stringify(values, null, 4)
      ),
  };

  return (
    <ThemeProvider defaultTheme="dark">
      <FormixFormProvider {...schemaFormProps}>
        <FormBody>
          <FormHeader>
            <FormTitle />
            <FormDescription />
          </FormHeader>
          <FormContent>
            <FormFlexFields fluid columns={2} />
          </FormContent>
          <FormFooter>
            <Button type="submit" className="mt-5">
              Submit
            </Button>
          </FormFooter>
        </FormBody>
      </FormixFormProvider>
    </ThemeProvider>
  );
};

export default App;

Exports

Core Components

FormixProvider

Wraps the form and provides the Formix context to its children. This component initializes the form schema and context, making it accessible to all nested components.

useFormix

A hook for accessing the Formix context. It provides access to the form's state and methods, enabling high-level interaction with the form.

Styled Components

FormixFormProvider

The primary context component that utilizes the form schema and methods. It offers access to form state and functions for seamless form interaction.

FormBody

The main body of the form. It wraps all form elements and handles form submission, maintaining a consistent structure and layout.

FormHeader

The header section of the form, used to display the form title and description, providing clear context for the form's purpose.

FormFooter

The footer section of the form, typically used for submit buttons or other form actions, ensuring user actions are clearly presented at the end of the form.

FormContent

The content section of the form, wrapping the main form fields and organizing them within the form's structure.

FormTitle

Displays the form title. This component is used within FormHeader to show the main heading of the form.

FormDescription

Displays the form description. This component is used within FormHeader to provide additional context or instructions for the form.

FormFlexFields

Renders form fields in a flexible grid layout, helping in creating responsive forms with customizable grid settings such as column count and gap size.

FormField

Wraps individual form fields and connects them to the form context using react-hook-form's Controller component to manage field state and validation.

FieldItem

A wrapper for individual form items, providing a consistent structure and context for form fields.

FieldLabel

Renders the label for a form field, ensuring that labels are consistently styled and correctly associated with their corresponding input elements.

FieldControl

Renders the control element (input, select, etc.) for a form field, managing the state and attributes necessary for the field's functionality and accessibility.

FieldDescription

Renders the description for a form field, providing additional information or instructions about the field's purpose or expected input.

FieldErrorMessage

Displays error messages for form fields, showing validation errors to help users correct their inputs.

useFormField

A hook for accessing the form field context and state, providing field-level information and methods for interacting with the form.

ThemeProvider

Provides theme context to its children, allowing for dynamic theme changes and customization across the application.

useTheme

A hook for accessing the theme context, providing the current theme and a function to update the theme.

Contributing

We welcome contributions to @adimis/react-formix. If you have suggestions for new features, improvements, or bug fixes, please open an issue or submit a pull request on our GitHub repository.

License

This project is licensed under the GPL-3.0-only License. See the LICENSE file for details.

Additional Information

For detailed documentation, refer to the docs folder. Open the index.html file located at node_modules/@adimis/react-formix/docs/index.html in a live server to access the documentation.

About

@adimis/react-formix is a powerful React library that simplifies form management and validation for modern web applications.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published