ra-form-layout

New form layouts for complex data entry tasks (accordion, wizard, etc.).

ra-accordion-form

Installation

npm install --save @react-admin/ra-form-layout
# or
yarn add @react-admin/ra-form-layout

The package contains new translation messages (in English and French). You should add them to your i18nProvider:

    import { Admin } from 'react-admin';
    import polyglotI18nProvider from 'ra-i18n-polyglot';
    import englishMessages from 'ra-language-english';
    import frenchMessages from 'ra-language-french';

    import {
        raFormLayoutLanguageEnglish,
        raFormLayoutLanguageFrench,
    } from '@react-admin/ra-form-layout';

    const messages = {
        en: { ...englishMessages, ...raFormLayoutLanguageEnglish },
        fr: { ...frenchMessages, ...raFormLayoutLanguageFrench },
    };

    const i18nProvider = polyglotI18nProvider(locale => messages[locale], 'en');

    const App = () => (
        <Admin i18nProvider={is18nProvider}>
            {/* ... */}
        </Admin>
    );

<AccordionForm>

Alternative to <SimpleForm>, to be used as child of <Create> or <Edit>. Expects <AccordionFormPanel> elements as children.

AccordionForm

By default, each child accordion element handles its expanded state independently.

import {
    Edit,
    TextField,
    TextInput,
    DateInput,
    SelectInput,
    ArrayInput,
    SimpleFormIterator,
    BooleanInput,
} from 'react-admin';

import { AccordionForm, AccordionFormPanel } from '@react-admin/ra-form-layout';

// don't forget the component="div" prop on the main component to disable the main Card
const CustomerEdit: FC = props => (
    <Edit {...props} component="div">
        <AccordionForm autoClose>
            <AccordionFormPanel label="Identity">
                <TextField source="id" />
                <TextInput source="first_name" validate={required()} />
                <TextInput source="last_name" validate={required()} />
                <DateInput source="dob" label="born" validate={required()} />
                <SelectInput source="sex" choices={sexChoices} />
            </AccordionFormPanel>
            <AccordionFormPanel label="Occupations">
                <ArrayInput source="occupations" label="">
                    <SimpleFormIterator>
                        <TextInput source="name" validate={required()} />
                        <DateInput source="from" validate={required()} />
                        <DateInput source="to" />
                    </SimpleFormIterator>
                </ArrayInput>
            </AccordionFormPanel>
            <AccordionFormPanel label="Preferences">
                <SelectInput
                    source="language"
                    choices={languageChoices}
                    defaultValue="en"
                />
                <BooleanInput source="dark_theme" />
                <BooleanInput source="accepts_emails_from_partners" />
            </AccordionFormPanel>
        </AccordionForm>
    </Edit>
);

autoClose

When setting autoClose in the <AccordionForm>, only one accordion remains open at a time. The first accordion is open by default, and when a user opens another one, the current open accordion closes.

import { Edit, TextField, TextInput, DateInput, SelectInput, ArrayInput, SimpleFormIterator, BooleanInput } from 'react-admin';
import { AccordionForm, AccordionFormPanel } from '@react-admin/ra-form-layout';

// don't forget the component="div" prop on the main component to disable the main Card
const CustomerEdit: FC = props => (
    <Edit {...props} component="div">
-       <AccordionForm>
+       <AccordionForm autoClose>
            <AccordionFormPanel label="Identity" defaultExpanded>
                <TextField source="id" />
                ...

toolbar

You can customize the form Toolbar by passing a custom element in the toolbar prop. The form expects the same type of element as <SimpleForm>, see the <SimpleForm toolbar> prop documentation in the react-admin docs.

<AccordionFormPanel>

Renders a material-ui <Accordion> component. In the <AccordionDetails>, renders each child inside a <FormInput> (the same layout as in <SimpleForm>).

Prop Required Type Default Description
label Required string - The main label used as the accordion summary. Appears in red when the accordion has errors
children Required ReactNode - A list of <Input> elements
secondary Optional string - The secondary label used as the accordion summary
defaultExpanded Optional boolean false Set to true to have the accordion expanded by default (except if autoClose = true on the parent)
disabled Optional boolean false If true, the accordion will be displayed in a disabled state.
square Optional boolean false If true, rounded corners are disabled.
import {
    Edit,
    TextField,
    TextInput,
    DateInput,
    SelectInput,
    ArrayInput,
    SimpleFormIterator,
    BooleanInput,
} from 'react-admin';

import { AccordionForm, AccordionFormPanel } from '@react-admin/ra-form-layout';

const CustomerEdit: FC = props => (
    <Edit {...props} component="div">
        <AccordionForm>
            <AccordionFormPanel label="Identity" defaultExpanded>
                <TextField source="id" />
                <TextInput source="first_name" validate={required()} />
                <TextInput source="last_name" validate={required()} />
                <DateInput source="dob" label="born" validate={required()} />
                <SelectInput source="sex" choices={sexChoices} />
            </AccordionFormPanel>
        </AccordionFormPanel>
    </Edit>
);

<AccordionSection>

Renders children (Inputs) inside a material-ui <Accordion> element without a Card style. To be used as child of a <SimpleForm> or a <TabbedForm> element.

AccordionSection

Prefer <AccordionSection> to <AccordionForm> to always display a list of important inputs, then offer accordions for secondary inputs.

Prop Required Type Default Description
label Required string - The main label used as the accordion summary.
children Required ReactNode - A list of <Input> elements
fullWidth Optional boolean false If true, the Accordion take sthe entire form width.
className Optional string - A class name to style the underlying <Accordion>
secondary Optional string - The secondary label used as the accordion summary
defaultExpanded Optional boolean false Set to true to have the accordion expanded by default
disabled Optional boolean false If true, the accordion will be displayed in a disabled state.
square Optional boolean false If true, rounded corners are disabled.
import {
    Edit,
    TextField,
    TextInput,
    DateInput,
    SelectInput,
    ArrayInput,
    SimpleForm,
    SimpleFormIterator,
    BooleanInput,
} from 'react-admin';

import { AccordionForm, AccordionFormPanel } from '@react-admin/ra-form-layout';

const CustomerEdit: FC = props => (
    <Edit {...props} component="div">
        <SimpleForm>
            <TextField source="id" />
            <TextInput source="first_name" validate={required()} />
            <TextInput source="last_name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput source="sex" choices={sexChoices} />
            <AccordionSection label="Occupations" fullWidth>
                <ArrayInput source="occupations" label="">
                    <SimpleFormIterator>
                        <TextInput source="name" validate={required()} />
                        <DateInput source="from" validate={required()} />
                        <DateInput source="to" />
                    </SimpleFormIterator>
                </ArrayInput>
            </AccordionSection>
            <AccordionSection label="Preferences" fullWidth>
                <SelectInput
                    source="language"
                    choices={languageChoices}
                    defaultValue="en"
                />
                <BooleanInput source="dark_theme" />
                <BooleanInput source="accepts_emails_from_partners" />
            </AccordionSection>
        </SimpleFormForm>
    </Edit>
);

<WizardForm>

Alternative to <SimpleForm>, to be used as child of <Create>. Expects <WizardFormStep> elements as children.

Note: You can also use the <WizardForm> as child of <Edit> but it's considered as a bad practice to provide a wizard form for existing resources.

WizardForm

import React, { FC } from 'react';
import { Create, TextInput, required } from 'react-admin';
import { WizardForm, WizardFormStep } from '@react-admin/ra-form-layout';

const PostCreate: FC = props => (
    <Create {...props}>
        <WizardForm>
            <WizardFormStep label="First step">
                <TextInput source="title" validate={required()} />
            </WizardFormStep>
            <WizardFormStep label="Second step">
                <TextInput source="description" />
            </WizardFormStep>
            <WizardFormStep label="Third step">
                <TextInput source="fullDescription" validate={required()} />
            </WizardFormStep>
        </WizardForm>
    </Create>
);

toolbar

You can customize the form toolbar by passing a custom component in the toolbar prop.

import React, { FC } from 'react';
import { Create, TextInput, required } from 'react-admin';
import { WizardForm, WizardFormStep } from '@react-admin/ra-form-layout';

const MyToolbar: FC = ({
    hasPreviousStep,
    hasNextStep,
    onPreviousClick,
    onNextClick,
    handleSubmit,
    handleSubmitWithRedirect,
    invalid,
    redirect,
    saving,
    submitOnEnter,
}) => {
    const save = handleSubmitWithRedirect || handleSubmit;

    return (
        <ul>
            {hasPreviousStep ? (
                <li>
                    <Button onClick={onPreviousClick}>
                        PREVIOUS
                    </Button>
                </li>
            ) : null}
            {hasNextStep ? (
                <li>
                    <Button disabled={invalid} onClick={onNextClick}>
                        NEXT
                    </Button>
                </li>
            ) : (
                <li>
                    <Button disabled={invalid} onClick={save}>
                        SAVE
                    </Button>
                </li>
            )}
        </ul>
    );
};

const PostCreate: FC = props => (
    <Create {...props}>
        <WizardForm toolbar={MyToolbar}>
            <WizardFormStep label="First step">
                <TextInput source="title" validate={required()} />
            </WizardFormStep>
            <WizardFormStep label="Second step">
                <TextInput source="description" />
            </WizardFormStep>
            <WizardFormStep label="Third step">
                <TextInput source="fullDescription" validate={required()} />
            </WizardFormStep>
        </WizardForm>
    </Create>
);

progress

You can also customize the progress stepper by passing a custom component in the progress prop.

const MyProgress: FC = ({
    currentStep,
    onStepClick,
    steps,
}) => (
    <ul>
        {steps.map((step, index) => {
            const label = React.cloneElement(step, { intent: 'label' });

            return (
                <li key={`step_${index}`}>
                    {!onStepClick ? (
                        <span className={currentStep === index ? 'active' : undefined}>
                        {label}
                        </span>
                    ) : (
                        <button onClick={() => onStepClick(index)}>
                            {label}
                        </button>
                    )}
                </li>
            );
        })}
    </ul>
);

const PostCreate: FC = props => (
    <Create {...props}>
        <WizardForm progress={MyProgress}>
            <WizardFormStep label="First step">
                <TextInput source="title" validate={required()} />
            </WizardFormStep>
            <WizardFormStep label="Second step">
                <TextInput source="description" />
            </WizardFormStep>
            <WizardFormStep label="Third step">
                <TextInput source="fullDescription" validate={required()} />
            </WizardFormStep>
        </WizardForm>
    </Create>
);

<CreateDialog> & <EditDialog>

Sometimes it makes sense to edit or create a resource without leaving the context of the list page. For those cases, you can use the <CreateDialog> and <EditDialog> components.

EditDialog

They accept a single child which is the form, either a <SimpleForm>, a <TabbedForm> or a custom one, just like the <Create> and <Edit> components.

import React from 'react';
import { List, Datagrid, SimpleForm, TextField, TextInput, DateInput, required } from 'react-admin';
import { EditDialog, CreateDialog } from '@react-admin/ra-form-layout';

const CustomerList = props => (
    <>
        <List {...props}>
            <Datagrid>
                ...
            </Datagrid>
        </List>
        <EditDialog {...props}>
            <SimpleForm>
                <TextField source="id" />
                <TextInput source="first_name" validate={required()} />
                <TextInput source="last_name" validate={required()} />
                <DateInput source="date_of_birth" label="born" validate={required()} />
            </SimpleForm>
        </EditDialog>
        <CreateDialog {...props}>
            <SimpleForm>
                <TextField source="id" />
                <TextInput source="first_name" validate={required()} />
                <TextInput source="last_name" validate={required()} />
                <DateInput source="date_of_birth" label="born" validate={required()} />
            </SimpleForm>
        </CreateDialog>
    </>
);

Unlike the <Create> and <Edit> components, their title will be displayed in the <Dialog>, not in the <AppBar>.

import React from 'react';
import { List, Datagrid, SimpleForm, TextField, TextInput, DateInput, required } from 'react-admin';
import { EditDialog, CreateDialog } from '@react-admin/ra-form-layout';

const CustomerEditTitle = ({ record }) =>
    record
        ? <span>{record.last_name} {record.first_name}</span>
        : null;

const CustomerList = props => (
    <>
        <List {...props}>
            <Datagrid>
                ...
            </Datagrid>
        </List>
        <EditDialog {...props} title={<CustomerEditTitle />}>
            <SimpleForm>
                <TextField source="id" />
                <TextInput source="first_name" validate={required()} />
                <TextInput source="last_name" validate={required()} />
                <DateInput source="date_of_birth" label="born" validate={required()} />
            </SimpleForm>
        </EditDialog>
        <CreateDialog {...props} title="Create a new customer">
            <SimpleForm>
                <TextField source="id" />
                <TextInput source="first_name" validate={required()} />
                <TextInput source="last_name" validate={required()} />
                <DateInput source="date_of_birth" label="born" validate={required()} />
            </SimpleForm>
        </CreateDialog>
    </>
);

Besides, you can also pass the props accepted by the material-ui <Dialog> like fullWidth or maxWidth.

import React from 'react';
import { List, Datagrid, SimpleForm, TextField, TextInput, DateInput, required } from 'react-admin';
import { EditDialog, CreateDialog } from '@react-admin/ra-form-layout';

const CustomerList = props => (
    <>
        <List {...props}>
            <Datagrid>
                ...
            </Datagrid>
        </List>
        <EditDialog {...props} fullWidth maxWidth="md">
            <SimpleForm>
                <TextField source="id" />
                <TextInput source="first_name" validate={required()} />
                <TextInput source="last_name" validate={required()} />
                <DateInput source="date_of_birth" label="born" validate={required()} />
            </SimpleForm>
        </EditDialog>
        <CreateDialog {...props} fullWidth maxWidth="md">
            <SimpleForm>
                <TextField source="id" />
                <TextInput source="first_name" validate={required()} />
                <TextInput source="last_name" validate={required()} />
                <DateInput source="date_of_birth" label="born" validate={required()} />
            </SimpleForm>
        </CreateDialog>
    </>
);

CHANGELOG

v1.3.0

2020-10-05

  • Upgrade to react-admin 3.9

v1.2.0

2020-10-01

  • (feat) Dialog Form (CreateDialog & EditDialog)

v1.1.0

2020-09-28

  • (feat) Wizard Form

v1.0.1

2020-09-22

  • (fix) Fix Storybook error on history.replace

v1.0.0

2020-09-22

  • First release