- Features
- Installation
- Usage
- API
- Dynamic Form Props
- Additional Props information
- For editable input element of formInputs or inputs if fieldsets is used. Declared via inputEntryShape[] as NestedEditableOptionProps
- For editableInformation object inside editable input. Declared as EditableInformation interface. Below is the properties needed:
- For more information on props types see:
- Customization
- Local Development
- 🖌️ Themeing support via styled components
- 🆗 Typescript support
↔️ Expandable and editable via nested form structure- 🧩 Ease of integration in any React project
npm install @kbgarcia8/react-dynamic-form
# or
yarn add @kbgarcia8/react-dynamic-formThis is designed for scenarios where your for needs to be split into multiple groups of input each representing different types of information
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}
/>
)
}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
- 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}
/>
)
}| 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/> |
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 |
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'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>,
)You allow overriding currentTheme:
<ThemeContextProvider initialTheme={myCustomLightTheme} secondTheme={myCustomDarkTheme}>
<DynamicForm />
</ThemeContextProvider>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'),
...
}
}Inside <Component>.styles.[ts | js]
export const DefaultButton = styled.button`
color: ${({theme})=> theme.colors.bg || 'white'};
`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>
);
}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> {...}
`;git clone https://github.com/kbgarcia8/react-dynamic-form
cd react-dynamic-form
npm install
npm run dev