Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Docs] ES UI form library #78654

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c116f2d
Setup dev docs website
sebelga Sep 28, 2020
41ec445
Add docs
sebelga Sep 28, 2020
e3100f8
Update src/plugins/es_ui_shared/docs/docs/form_lib/core/about.md
sebelga Oct 5, 2020
76edfc2
Apply suggestions from code review
sebelga Oct 5, 2020
5ac1e87
Update core/about.md
sebelga Oct 5, 2020
48e71f2
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Oct 5, 2020
2ebd95d
Apply suggestions from code review
sebelga Oct 5, 2020
13d58f0
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Oct 5, 2020
22ae174
Apply suggestions from code review
sebelga Oct 5, 2020
00690d6
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Oct 5, 2020
433bedf
Apply suggestions from code review
sebelga Oct 5, 2020
837a18f
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Oct 5, 2020
6eb1431
Update core/form_component.md
sebelga Oct 5, 2020
b49fdd3
Apply suggestions from code review
sebelga Oct 5, 2020
567d71d
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Oct 5, 2020
6a59a2e
Update core/form_hook.md
sebelga Oct 5, 2020
f63c2e7
Apply suggestions from code review
sebelga Oct 5, 2020
4267d00
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Oct 6, 2020
390f798
Update core/use_field.md
sebelga Oct 6, 2020
453996f
Apply suggestions from code review
sebelga Oct 6, 2020
287b5b5
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Oct 6, 2020
17d345b
Update core/use_form_hook.md
sebelga Oct 6, 2020
231c331
Apply suggestions from code review
sebelga Oct 6, 2020
9846787
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Oct 6, 2020
27c31ac
Apply suggestions from code review
sebelga Oct 6, 2020
e3dcf1b
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Oct 6, 2020
869267e
Update examples/dynamic_fields.md
sebelga Oct 6, 2020
43ff400
Merge branch 'master' into docs/es-ui-shared-2
sebelga Feb 17, 2021
a387dd8
Change field composition example to a "car configurator" form
sebelga Feb 17, 2021
8e679e3
Remove unnecessary block in fields_composition example
sebelga Feb 17, 2021
627fb7d
Split example on how to listen to form changes in root or child compo…
sebelga Feb 17, 2021
42a53e9
Update comments as suggested in CR
sebelga Feb 17, 2021
29e59de
Simplify de/serializer example + add description
sebelga Feb 17, 2021
ccaeb55
Simplify "Style fields" examples
sebelga Feb 17, 2021
229947d
Apply suggestions from code review
sebelga Feb 18, 2021
4f56f94
Update validation examples from CR suggestions
sebelga Feb 18, 2021
856640c
Merge branch 'docs/es-ui-shared-2' of github.com:sebelga/kibana into …
sebelga Feb 18, 2021
66a8e0b
Update src/plugins/es_ui_shared/docs/docs/form_lib/examples/react_to_…
sebelga Feb 18, 2021
422479b
Update src/plugins/es_ui_shared/docs/docs/form_lib/examples/react_to_…
sebelga Feb 18, 2021
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
4 changes: 4 additions & 0 deletions .gitignore
Expand Up @@ -71,6 +71,10 @@ report.asciidoc
# TS incremental build cache
*.tsbuildinfo

# Docusaurus
.docusaurus
.cache-loader

# Yarn local mirror content
.yarn-local-mirror

Expand Down
7 changes: 6 additions & 1 deletion package.json
Expand Up @@ -37,6 +37,7 @@
},
"author": "Rashid Khan <rashid.khan@elastic.co>",
"scripts": {
"docs": "docusaurus start ./src/plugins/es_ui_shared/docs",
"preinstall": "node ./preinstall_check",
"kbn": "node scripts/kbn",
"es": "node scripts/es",
Expand Down Expand Up @@ -95,6 +96,8 @@
"dependencies": {
"@babel/core": "^7.12.10",
"@babel/runtime": "^7.12.5",
"@docusaurus/core": "^2.0.0-alpha.62",
"@docusaurus/preset-classic": "^2.0.0-alpha.62",
"@elastic/datemath": "link:packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary",
"@elastic/ems-client": "7.12.0",
Expand Down Expand Up @@ -136,6 +139,7 @@
"@kbn/utils": "link:packages/kbn-utils",
"@loaders.gl/core": "^2.3.1",
"@loaders.gl/json": "^2.3.1",
"@mdx-js/react": "^1.5.8",
"@slack/webhook": "^5.0.4",
"@storybook/addons": "^6.0.16",
"@turf/along": "6.0.1",
Expand Down Expand Up @@ -177,6 +181,7 @@
"chokidar": "^3.4.3",
"chroma-js": "^1.4.1",
"classnames": "2.2.6",
"clsx": "^1.1.1",
"color": "1.0.3",
"commander": "^3.0.2",
"concat-stream": "1.6.2",
Expand Down Expand Up @@ -852,4 +857,4 @@
"yargs": "^15.4.1",
"zlib": "^1.0.5"
}
}
}
22 changes: 22 additions & 0 deletions src/plugins/es_ui_shared/docs/babel.config.js
@@ -0,0 +1,22 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
78 changes: 78 additions & 0 deletions src/plugins/es_ui_shared/docs/docs/form_lib/core/about.md
@@ -0,0 +1,78 @@
---
id: about
title: Core
sidebar_label: Getting started
---

The core exposes the main building blocks (hooks and components) needed to build your form.

It is important to note that the core **is not** responsible for rendering UI. Its responsibility is to return form and fields **state and handlers** that you can connect to React components. The core of the form lib is agnostic of any UI rendering the form.

In Kibana we work with [the EUI component library](https://elastic.github.io/eui) and have created [field components](../helpers/components.md) that wrap EUI components. With these components, connection with the form lib is already done for you.

## Getting started

The three required components to build a form are:

- `useForm()` hook to declare a new form
- `<Form />` component that will wrap your form and create a context for it
- `<UseField />` component to declare a field

Let's see them in action before going into details

```js
import { useForm, Form, UseField } from 'src/plugins/es_ui_shared/public';

export const UserForm = () => {
const { form } = useForm(); // 1

return (
<Form form={form}> // 2
<UseField path="name" /> // 3
<UseField path="lastName" />

<button onClick={form.submit}>Submit</button>
</Form>
);
};
```

1. We use the `useForm` hook to declare a new form.
2. We then wrap our form with the `<Form />` component, providing the `form` that we have just created.
3. Finally, we declared two fields with the `<UseField />` component, providing a unique `path` for each one of them.

If you were to run this code in the browser and click on the "Submit" button nothing would happen as we haven't defined any handler to execute when submitting the form. Let's do that now along with providing a `UserFormData` interface to the form, which we will get back in our `onSubmit` handler.

```js
import { useForm, Form, UseField, FormConfig } from 'src/plugins/es_ui_shared/public';
sebelga marked this conversation as resolved.
Show resolved Hide resolved

interface UserFormData {
name: string;
lastName: string;
}

export const UserForm = () => {
const onFormSubmit: FormConfig<UserFormData>['onSubmit'] = async (data, isValid) => {
console.log("Is form valid:", isValid);
if (!isValid) {
// Maybe show a callout?
return;
}

console.log("Form data:", data);
};

const { form } = useForm({ onSubmit: onFormSubmit });

return (
<Form form={form}>
...
<button onClick={form.submit}>Submit</button>
</Form>
);
};
```

Great! We have our first working form. No state to worry about, just a simple declarative way to build our fields.

Those of you who are attentive might have noticed that the above form _does_ render the fields in the UI although we said earlier that the core of the form lib is not responsible for any UI rendering. This is because the `<UseField />` has a fallback mechanism to render an `<input type="text" />` and hook to the field `value` and `onChange`. Unless you have styled your `input` elements and don't require other field types like `checkbox` or `select`, you will probably want to customize how the the `<UseField />` renders. We will see that in a future section.
93 changes: 93 additions & 0 deletions src/plugins/es_ui_shared/docs/docs/form_lib/core/default_value.md
@@ -0,0 +1,93 @@
---
id: default_value
title: defaultValue
sidebar_label: defaultValue
---

There are multiple places where you can define the default value of a field. Note that by "default value" we are saying "the initial value" of a field. Once the field is initiated it has its own internal state and can't be controlled.

## Order of precedence

1. As a prop on the `<UseField path="name" defaultValue="John" />` component
2. In the **form** `defaultValue` config passed to `useForm({ defaultValue: { ... } })`
3. In the **field** `defaultValue` config parameter (either passed as prop to `<UseField />` prop or declared inside a form schema)
4. If no default value is found above, it defaults to `""` (empty string)

### As a prop on `<UseField />`

This takes over any other `defaultValue` defined elsewhere. What you provide as prop is what you will have as default value for the field. Remember that the `<UseField />` **is not** a controlled component, so changing the `defaultValue` prop to another value does not have any effect.

```js
// Here we manually set the default value
<UseField path="user.firstName" defaultValue="John" />
```

### In the form `defaultValue` config passed to `useForm()`

The above solution works well for very small forms, but with larger form it is not very convenient to manually add the default value of each field.

```js
// Let's imagine some data coming from the server
const fetchedData = {
user: {
firstName: 'John',
lastName: 'Snow',
}
}

// We need to manually write each connection, which is not convenient
<UseField path="user.firstName" defaultValue={fetchedData.user.firstName} />
<UseField path="user.lastName" defaultValue={fetchedData.user.lastName} />
```

It is much easier to provide the `defaultValue` object (probably some data that we have fetched from the server) at the form level

```js
const { form } = useForm({ defaultValue: fetchedData });

// And the defaultValue for each field will be automatically mapped to its paths
<UseField path="user.firstName" />
<UseField path="user.lastName" />
```

### In the field `defaultValue` config parameter of the field config

When you are creating a new resource, the form is empty and there is no data coming from the server to map. You still might want to define a default value for your fields.

```js
interface Props {
fetchedData?: { index: boolean }
}

export const MyForm = ({ fetchedData }: Props) => {
// fetchedData can be "undefined" or an object.
// If it is undefined, then the config.defaultValue will be used
const { form } = useForm({ defaultValue: fetchedData });

return (
<UseField path="index" config={{ defaultValue: true } />
);
}
```

Or the same but using a form schema

```js
const schema = {
// Field config for the path "index" declared below
index: {
defaultValue: true,
},
};

export const MyComponent = ({ fetchedData }: Props) => {
// 1. If defaultValue is not undefined **and** there is a value at the "index" path, use it
// 2. otherwise if there is a schema with a config at the "index" path read its "defaultValue"
// 3. if it's still undefined, use an "" (empty string) - which will throw an error for a checkbox field-.
const { form } = useForm({ schema, defaultValue: fetchedData });

return (
<UseField path="index" />
);
}
```