Skip to content
This repository has been archived by the owner on Apr 25, 2024. It is now read-only.

added basic form builder documentation #114

Merged
merged 6 commits into from Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 18 additions & 17 deletions components/DynamicStep/DynamicStep.jsx
Expand Up @@ -20,27 +20,28 @@ const DynamicStep = ({
<>
<form onSubmit={handleSubmit((data) => onStepSubmit(data))}>
<div className="govuk-form-group">
{components?.map(({ conditionalRender, ...componentProps }) =>
componentProps.name ? (
{components?.map(({ conditionalRender, ...componentProps }) => {
if (
conditionalRender &&
!conditionalRender({
...formData,
...stepValues,
}) ? null : (
<DynamicInput
key={componentProps.name}
id={stepId[0]}
register={register}
control={control}
errors={errors}
multiStepIndex={isMulti && (parseInt(stepId[1]) - 1 || 0)}
{...componentProps}
/>
)
) : (
componentProps
)
)}
})
) {
return null;
}
return (
<DynamicInput
key={componentProps.name}
id={stepId[0]}
register={register}
control={control}
errors={errors}
multiStepIndex={isMulti && (parseInt(stepId[1]) - 1 || 0)}
{...componentProps}
/>
);
})}
</div>
{isMulti && (
<Button
Expand Down
2 changes: 1 addition & 1 deletion components/Form/AddressLookup/AddressLookup.stories.jsx
Expand Up @@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form';
import { Button } from 'components/Form';

export default {
title: 'Form/AddressLookup',
title: 'Form Components/AddressLookup',
component: AddressLookup,
};

Expand Down
2 changes: 1 addition & 1 deletion components/Form/Button/Button.stories.jsx
@@ -1,7 +1,7 @@
import Button from './Button';

export default {
title: 'Form/Button',
title: 'Form Components/Button',
component: Button,
};

Expand Down
2 changes: 1 addition & 1 deletion components/Form/Checkbox/Checkbox.stories.jsx
@@ -1,7 +1,7 @@
import Checkbox from './Checkbox';

export default {
title: 'Form/Checkbox',
title: 'Form Components/Checkbox',
component: Checkbox,
};

Expand Down
2 changes: 1 addition & 1 deletion components/Form/EmailInput/EmailInput.stories.jsx
@@ -1,7 +1,7 @@
import EmailInput from './EmailInput';

export default {
title: 'Form/EmailInput',
title: 'Form Components/EmailInput',
component: EmailInput,
};

Expand Down
2 changes: 1 addition & 1 deletion components/Form/NumberInput/NumberInput.stories.jsx
@@ -1,7 +1,7 @@
import NumberInput from './NumberInput';

export default {
title: 'Form/NumberInput',
title: 'Form Components/NumberInput',
component: NumberInput,
};

Expand Down
2 changes: 1 addition & 1 deletion components/Form/PhoneInput/PhoneInput.stories.jsx
@@ -1,7 +1,7 @@
import PhoneInput from './PhoneInput';

export default {
title: 'Form/PhoneInput',
title: 'Form Components/PhoneInput',
component: PhoneInput,
};

Expand Down
2 changes: 1 addition & 1 deletion components/Form/Radios/Radios.stories.jsx
@@ -1,7 +1,7 @@
import Radios from './Radios';

export default {
title: 'Form/Radios',
title: 'Form Components/Radios',
component: Radios,
};

Expand Down
2 changes: 1 addition & 1 deletion components/Form/Select/Select.stories.jsx
Expand Up @@ -2,7 +2,7 @@ import Select from './Select';
import NationalitySelect from './Nationality';

export default {
title: 'Form/Select',
title: 'Form Components/Select',
component: Select,
};

Expand Down
2 changes: 1 addition & 1 deletion components/Form/TextArea/TextArea.stories.jsx
@@ -1,7 +1,7 @@
import TextArea from './TextArea';

export default {
title: 'Form/TextArea',
title: 'Form Components/TextArea',
component: TextArea,
};

Expand Down
2 changes: 1 addition & 1 deletion components/Form/TextInput/TextInput.stories.jsx
@@ -1,7 +1,7 @@
import TextInput from './TextInput';

export default {
title: 'Form/TextInput',
title: 'Form Components/TextInput',
component: TextInput,
};

Expand Down
48 changes: 48 additions & 0 deletions docs/formBuilder/01-FormWizard.stories.mdx
@@ -0,0 +1,48 @@
import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="FormBuilder/Introduction" />

# Form Builder

The Form builder helps you build multi-steps dynamic forms.

## How to create a new form

The `FormWizard` is the core module, it's taking care of handling the logic and the flow of the form.
sirLisko marked this conversation as resolved.
Show resolved Hide resolved

It's composed of 2 other components:

- `DynamicStep` that is orchestrating the single-step logic (i.e. creating the actual HTML form)
sirLisko marked this conversation as resolved.
Show resolved Hide resolved
- `DynamicInput` that is taking care of display the correct component
sirLisko marked this conversation as resolved.
Show resolved Hide resolved

In order to create a new form, it's enough to create a page component with `[...stepId]` as the file name.

`[...stepId]` is needed because it's the way NextJs represent **catch-all routes**, see more information in the [NextJs documentation](https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes).

```js
<FormWizard
formPath="/form/test"
title="test form"
onFormSubmit={(formData) => alert(formData)}
formSteps={[
{
id: 'first-step',
title: 'First Step',
components: [...],
},
]}
/>
```

The `FormWizard` itself needs only 4 props to work:

- `formPath` the prefix of the actual form (i.e. `/form/test/` it will become `/form/test/${stepId}`)
- `title` simply the form title
- `onFormSubmit` the callback called on form submit, with the form data injected as the first argument
- `formSteps` the actual steps that are going to compose the form
- `defaultValues` (optional) - the default values of the form, that are going to be pre-filled
- `successMessage` (optional) - a custom message that is going to appear on the top banner of the confirmation page

## Functionalities

The form builder is dynamic, along with the functionality of multi-step, it can show/hide _steps_ and _inputs_ based on other input value (or even default values). These functionalities are defined in the `formSteps` object. [For more info](/?path=/story/formbuilder-02-formsteps--page).
97 changes: 97 additions & 0 deletions docs/formBuilder/02-FormSteps.stories.mdx
@@ -0,0 +1,97 @@
import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="FormBuilder/FormSteps" />

# FormSteps

`FormSteps` allows you to define a form with multiple steps. Each step is defined by one or more [components](/?path=/story/formbuilder-03-components--page).

## Basic example

```js
[
{
id: 'first-step',
title: 'First Step',
components: [
{
component: 'Text',
name: 'first_name',
label: 'First Name',
},
{
component: 'Text',
name: 'last_name',
label: 'Last Name',
},
],
},
];
```

Each step has the following properties:

- `id` the step id, that is going to be user in the URL (i.e. `/form/test/:stepId`)
- `title` that is going to be shown on top of the form
- `components` an array of objects, each object correspond to an input field
sirLisko marked this conversation as resolved.
Show resolved Hide resolved

## Advanced examples

> See [an example](https://github.com/LBHackney-IT/lbh-social-care/blob/main/data/forms/test.jsx) on Github

### Conditional render

```js
[
{
id: 'first-step',
title: 'First Step',
{
component: 'CheckBox',
name: 'show_next_step',
label: 'Title',
},
}
{
id: 'condition-step',
title: 'Conditional Step',
conditionalRender: ({ show_next_step }) => show_next_step === true,
components: [
{
component: 'CheckBox',
label="Show title"
name="show_title"
},
{
conditionalRender: ({ show_title }) => show_title === true,
component: 'TextInput',
name: 'title',
label: 'Title',
},
],
},
];
```

`conditionalRender` is supported by the steps and the components. It's a function that gets as first argument the `formData` and if it returns true shows the step/input otherwise hide it.
sirLisko marked this conversation as resolved.
Show resolved Hide resolved

### Multiple steps

```js
[
{
isMultiple: true,
id: 'multi-step',
title: 'Multi Step',
{
component: 'TextInput',
name: 'title',
label: 'Title',
},
}
]
```

`isMulti` allows you to create multiple instances of the same step and it's handy if you have to give to the user the possibility to enter multiple times the same form (i.e. if you are adding a list).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'give the user the possibility to enter the same form multiple times'


The form data generated by a multi-step is an array of values, each position corresponds of the data entered for that position.
sirLisko marked this conversation as resolved.
Show resolved Hide resolved
29 changes: 29 additions & 0 deletions docs/formBuilder/03-FormComponents.stories.mdx
@@ -0,0 +1,29 @@
import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="FormBuilder/Components" />

# Components

`components` defined the input fields. They can be simple as a `TextInput` or advanced as the `AddressLookup`.
sirLisko marked this conversation as resolved.
Show resolved Hide resolved

## Basic example

```js
{
component: 'TextInput',
name: 'title',
label: 'Title',
rules: { required: true },
}
```

Each component has the following properties:

- `component` the component type (i.e. `TextInput`, `CheckBox`, etc.)
- `name` the name of the property that is going to contain the value of the input
- `rules` for validation, is an object that is reflecting the [react-hook-form](https://react-hook-form.com/api/) validation APIs
- `label` the input label

There are then other properties that are depending on the input type, for example, `options` for `CheckBox` or `width` for `TextInput`.
sirLisko marked this conversation as resolved.
Show resolved Hide resolved

You can see the full list of components in the sidebar 👈
13 changes: 7 additions & 6 deletions package.json
Expand Up @@ -14,7 +14,8 @@
"prestorybook": "cpx \"node_modules/govuk-frontend/govuk/assets/**/*\" assets",
"e2e": "PORT=3000 start-server-and-test start http://localhost:3000 cy:run",
"cy:run": "cypress run",
"cy:open": "cypress open"
"cy:open": "cypress open",
"build-storybook": "build-storybook"
},
"dependencies": {
"axios": "^0.21.0",
Expand All @@ -36,10 +37,11 @@
"uk-postcode-validator": "^1.1.0"
},
"devDependencies": {
"@storybook/addon-actions": "^6.0.27",
"@storybook/addon-essentials": "^6.0.27",
"@storybook/addon-links": "^6.0.27",
"@storybook/react": "^6.0.27",
"@babel/core": "^7.12.10",
"@storybook/addon-actions": "^6.1.11",
"@storybook/addon-essentials": "^6.1.11",
"@storybook/addon-links": "^6.1.11",
"@storybook/react": "^6.1.11",
"@testing-library/jest-dom": "^5.11.5",
"@testing-library/react": "^11.1.0",
"babel-eslint": "^10.1.0",
Expand Down Expand Up @@ -69,7 +71,6 @@
"style-loader": "^2.0.0"
},
"optionalDependencies": {
"@babel/core": "^7.12.3",
"typescript": "^4.0.3",
"webpack": "^5.0.0"
},
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Expand Up @@ -129,7 +129,7 @@
semver "^5.4.1"
source-map "^0.5.0"

"@babel/core@^7.1.0", "@babel/core@^7.12.1", "@babel/core@^7.12.3", "@babel/core@^7.7.5":
"@babel/core@^7.1.0", "@babel/core@^7.12.1", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.7.5":
version "7.12.10"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.10.tgz#b79a2e1b9f70ed3d84bbfb6d8c4ef825f606bccd"
integrity sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==
Expand Down Expand Up @@ -1884,7 +1884,7 @@
dependencies:
"@sinonjs/commons" "^1.7.0"

"@storybook/addon-actions@6.1.11", "@storybook/addon-actions@^6.0.27":
"@storybook/addon-actions@6.1.11", "@storybook/addon-actions@^6.1.11":
version "6.1.11"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.1.11.tgz#73e91cc95c45ea477cfd4f3603f6b95f5829eab6"
integrity sha512-J44XLx2G732OG7Az79Cpk5UlI5SyXHeQqdykwT/4IEQXSBXAYWSTIJJjpJdcjR/D+zpklab1QDSiWxCrKbe81A==
Expand Down Expand Up @@ -1985,7 +1985,7 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"

"@storybook/addon-essentials@^6.0.27":
"@storybook/addon-essentials@^6.1.11":
version "6.1.11"
resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.1.11.tgz#0147d429b33581cc9d42398d73a8c6a5962d5796"
integrity sha512-p1AfzzR9qt6s0xxg/GclRqClbtV+67lWLUV0d37xmTNDnGbiE5guk7nKnga0yLrzhQawiOBjRUFMfAQj2t7Ycw==
Expand All @@ -2003,7 +2003,7 @@
regenerator-runtime "^0.13.7"
ts-dedent "^2.0.0"

"@storybook/addon-links@^6.0.27":
"@storybook/addon-links@^6.1.11":
version "6.1.11"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-6.1.11.tgz#84b20b0c13bbfc8270cdf52389ce52f98c415b6a"
integrity sha512-OIBeOdTHNr/egIuyOUEtEBtZJXVp9PEPQ4MDxvKsCgpnXruPhjenPNW+mQKxm4SefpePW7zkx6k1ubpYVJUeag==
Expand Down Expand Up @@ -2308,7 +2308,7 @@
dependencies:
core-js "^3.0.1"

"@storybook/react@^6.0.27":
"@storybook/react@^6.1.11":
version "6.1.11"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.1.11.tgz#e94403cd878c66b445df993bad9bec9023db3ebe"
integrity sha512-EmR7yvVW6z6AYhfzAgJMGR/5+igeBGa1EePaEIibn51r5uboSB72N12NaADyF2OaycIdV+0sW6vP9Zvlvexa/w==
Expand Down