Skip to content

kbgarcia8/react-dynamic-form

Repository files navigation

react-dynamic-form

Reusable React dynamic form showcasing editable, expandable and flexible inputs

npm version downloads

Table of Contents

Features

  • 🖌️ Themeing support via styled components
  • 🆗 Typescript support
  • ↔️ Expandable and editable via nested form structure
  • 🧩 Ease of integration in any React project

Installation

npm install @kbgarcia8/react-dynamic-form
# or
yarn add @kbgarcia8/react-dynamic-form

Usage

Case 1: Using fieldsets prop

Supports multi-section forms that allows multiple groups of related inputs

This is designed for scenarios where your for needs to be split into multiple groups of input each representing different types of information

Example Scenario:

Cart Logic

  • Payment method information
  • Billing address
  • Shipping address
  • Contact details

Each of these becomes its own fieldset inside a single DynamicForm.

import { DynamicForm } from '@kbgarcia8/react-dynamic-form'
import type { FieldsetShape } from '@kbgarcia8/react-dynamic-form'

const addressInputsArray = [
    {
      //Input Props
      type: ... as const, id: ..., isRequired: ...,
      //dataAttributes is obtained through map
      disabled: ..., name: ..., value: ..., placeholderText: ...,
      //Start of Label Props
      textLabel: ..., additionalInfo: ..., $labelFlexDirection: ... as const, svg: <.../>,
      //Start of EditableInputProps
      labelClass: ..., inputClass: ..., isEditable: ...,
      //Additional in inputEntryShape
      editIcon: <.../>, //=>editIcon in EditableInputProps
      deleteIcon: <.../>,
      editing: ...,
      //These are informations editable within radio input acting as selection
      editableInformation: [
          {
              name: ...,
              info: ...,
              type: ... as const
          }
      ],
      //onClick functions obtained through map
    }
];

const fieldsets:FieldsetShape[] = [
	{
        legend: "Address Informations",
        inputs: addressInputsArray.map((address, index) => ({
            ...address,
            id: `${address.id}-${index}`,
            onChange: ...,
            onClickEdit: ...,
            onClickDelete: ...,
            onClickSave: ...,
            onClickCancel: ...,
            dataAttributes: {
                "data-index": index
            }
        })),
        isExpandable: true
    },
];

function App() {
  return (
    <DynamicForm
        className={'with-fieldsets'}
        fieldsets={fieldsets}
        id="address"
        isExpandable={true}
        {...props}
    />
  )
}

Case 2: Using formInputs prop

Supports single-section form that only needs one group of information

A legend text is optional in this part. This is a common/basic form scenario. It only needs an array of inputs which is rendered as a single form

Example Scenario:
  • Profile information
  • Login form
  • Address form
  • Single purpose editable section
import { DynamicForm } from '@kbgarcia8/react-dynamic-form'
import type { FieldsetShape } from '@kbgarcia8/react-dynamic-form'

const addressInputsArray = [
    {
      //Input Props
      type: ... as const, id: ..., isRequired: ...,
      //dataAttributes is obtained through map
      disabled: ..., name: ..., value: ..., placeholderText: ...,
      //Start of Label Props
      textLabel: ..., additionalInfo: ..., $labelFlexDirection: ... as const, svg: <.../>,
      //Start of EditableInputProps
      labelClass: ..., inputClass: ..., isEditable: ...,
      //Additional in inputEntryShape
      editIcon: <.../>, //=>editIcon in EditableInputProps
      deleteIcon: <.../>,
      editing: ...,
      //These are informations editable within radio input acting as selection
      editableInformation: [
          {
              name: ...,
              info: ...,
              type: ... as const
          }
      ],
      //onClick functions obtained through map
    }
];

const addressInputs = addressInputsArray.map((address, index) => ({
    ...address,
    id: `${address.id}-${index}`,
    onChange: ...,
    onClickEdit: ...,
    onClickDelete: ...,
    onClickSave: ...,
    onClickCancel: ...,
    dataAttributes: {
        "data-index": index
    }
})),

function App() {
  return (
    <DynamicForm
        className={'with-fieldsets'}
        legendText={'Address Information'}
        formInputs={addressInputs}
        id="address"
        isExpandable={true}
        {...props}
    />
  )
}

API

Dynamic Form Props

Prop Type Default Description
fieldsets FieldsetShape[] null Used in multi-section form containing inputs divided into information groups

If used, legendText and formInputs is no longer needed
legendText string
formInputs inputEntryShape[] If no fieldsets is provided, this is an object containing information for label and inputs of a single-section form
isExpandable boolean false If inputs are used as options commonly in type: checkbox/radio, this is to enabale a button for entry adding. If fieldset is null this is default to false
id string Form tag id
labelAndInputContainerClass string className for <LabeledInput/> component inside <DynamicForm/>
labelClass string className for <Label/> component inside <LabeledInput/> component inside <DynamicForm/>
inputClass string className for <Input/> component inside <LabeledInput/> component inside <DynamicForm/>
handleEditableInputEntryChange (e:React.ChangeEvent<HTMLInputElement|HTMLTextAreaElement>) => void Function to handle onChange of editable inputs
handleAddingInputEntry (e:React.MouseEvent<HTMLButtonElement>) => void Function to add input entry. If isExpandable is false this is not enabled
hasSubmit boolean false This is to enable submit button for <DynamicForm/>
submitText string Submit Text inside submit button for <DynamicForm/>
handleSubmit (e:React.MouseEvent<HTMLButtonElement>) => void Function to handle submit logic for <DynamicForm/>
hasReset boolean false This is to enable reset button for <DynamicForm/>
resetText string Reset Text inside reset button for <DynamicForm/>
handleReset (e:React.MouseEvent<HTMLButtonElement>) => void Function to handle reset logic for <DynamicForm/>
hasCancel boolean false This is to cancel submit button for <DynamicForm/>
cancelText string Cancel Text inside cancel button for <DynamicForm/>
handleCancel (e:React.MouseEvent<HTMLButtonElement>) => void Function to handle cancel logic for <DynamicForm/>
handleSubmitForm (e:React.FormEvent<HTMLFormElement>) => void Function to handle form submit logic for <DynamicForm/>
className string className for <DynamicForm/>
children React.ReactNode React node/s or component placed after <FormActionButtons/> inside <DynamicForm/>

Additional Props information

For editable input element of formInputs or inputs if fieldsets is used. Declared via inputEntryShape[] as NestedEditableOptionProps

Prop Type Default Description
uniqueClass string className to uniquely select/distinguish a LabeledInput container
isEditable boolean To determine if an input is editable or not
If false, all props below are automatically not needed
editing boolean To identify if an editable input is being modified
Can be used in open/close dialogs
onClickEdit (e:React.MouseEvent<HTMLButtonElement>) => void Function to handle edit logic of editable input
editIcon React.ReactNode TSX/JSX svg component that will serve as an icon for edit button of editable input
onClickDelete (e:React.MouseEvent<HTMLButtonElement>) => void Function to handle delete logic of editable input
deleteIcon React.ReactNode TSX/JSX svg component that will serve as an icon for delete button of editable input
onClickSave (e:React.MouseEvent<HTMLButtonElement>) => void Function to handle save logic of editable input
onClickCancel (e:React.MouseEvent<HTMLButtonElement>) => void Function to handle cancel logic of editable input

For editableInformation object inside editable input. Declared as EditableInformation interface. Below is the properties needed:

Property Type Description
name string Serves as placeholder text for editable input
info string Serves as value for editable input
type string Input type of editable input

For more information on props types see:

propTypes

Note that types/interfaces are also exported via npm package and can be imported as shown below:

import type { FieldsetShape, inputEntryShape } from '@kbgarcia8/react-dynamic-form'

Customization

Theme Support via styled components

Option 1: Users want theming via default Theme provided

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { ThemeContextProvider } from '@kbgarcia8/react-dynamic-form'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ThemeContextProvider>
      <App />
    </ThemeContextProvider>
  </StrictMode>,
)

Option 2: Users want custom theme

You allow overriding currentTheme:

<ThemeContextProvider initialTheme={myCustomLightTheme} secondTheme={myCustomDarkTheme}>
  <DynamicForm />
</ThemeContextProvider>
Reminder for custom theme override:

Below is the supported format for creating a theme object. Usually consisting of light and dark theme

asColor function is used to ensure that color to be supplied in color properties are colors (e.g. hexcode and rgb code). Note that keys of colors are changeable since it has type ``

import { asColor } from '@kbgarcia8/react-dynamic-form'

export const lightTheme:Theme = {
    name: 'light',
    colors: {
        text: asColor('#333446'),
        bg: asColor('#EEEEEE'),
        ...
    }
}

export const darkTheme:Theme = {
    name: 'dark',
    colors: {
        bg: asColor('#333446'),
        text: asColor('#EEEEEE'),
        ...
    }
}

Example usage of theme using styled-components

Inside <Component>.styles.[ts | js]

export const DefaultButton = styled.button`
  color: ${({theme})=> theme.colors.bg || 'white'};
`

Example usage of theme toggle inside component

import useTheme from '@kbgarcia8/react-dynamic-form'

const ThemeToggleButton = () => {
  const { currentTheme, toggleTheme } = useTheme();

  const isDark = currentTheme.name === 'dark' ? true : false;

  return (
    <Styled.Root
      checked={isDark}
      onCheckedChange={toggleTheme}
    >
      <Styled.Thumb $isDark={isDark}>
        {isDark ? <Moon size={20} /> : <Sun size={20} />}
      </Styled.Thumb>
    </Styled.Root>
  );
}

Styling components via styled-components

Available Default Class Names

Every renderable part of the form receives predictable classes or id:

Component Default class name Notes
Form ${id}-form Root form container
Fieldset Wrapper ${id}-fieldset-wrapper One per fieldset
Fieldset ${legend}-fieldset Based on fieldset legend
Legend ${legend}-legend Based on fieldset legend
LabeledInput container ${labelAndInputContainerClass} ${input.uniqueClass} User-controlled
Label ${inputClass} User-controlled (inside LabeledInput)
Input ${labelAndInputContainerClass} ${input.uniqueClass} User-controlled (inside LabeledInput)
Add Entry Button add-input-entry Used when isExpandable
Add Entry Button Wrapper add-input-button-space
No Entry Message default styled component Target using parent wrapper
Children Container children-container
import styled from "styled-components";
import { DynamicForm } from "@kbgarcia8/react-dynamic-form";

const StyledMyForm = styled(DynamicForm)`
  &.address-form {...}

  .address-fieldset-wrapper {...}

  .add-input-entry {...}

  .children-container {...}

  .<labelInputContainerClass> {...}

  .<inputClass> {...}

  .<labelClass> {...}
`;

Local Development

git clone https://github.com/kbgarcia8/react-dynamic-form
cd react-dynamic-form
npm install
npm run dev

License

license

About

NPM Package for a dynamic form showcasing editable, expandable and flexible inputs

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published