Skip to content

Commit

Permalink
chore(forms): refactor and add "Getting started" to its own page
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed Jan 17, 2024
1 parent ee5014f commit 8c311d8
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 143 deletions.
119 changes: 8 additions & 111 deletions packages/dnb-design-system-portal/src/docs/uilib/extensions/forms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ breadcrumb:
href: /uilib/extensions/forms/
---

import * as Examples from './forms/Examples'
import QuickStart from './forms/quick-start'
import InlineImg from 'dnb-design-system-portal/src/shared/tags/Img'
import FormDiagram1 from 'Docs/uilib/extensions/forms/form-diagram-1.png'
import FormDiagram2 from 'Docs/uilib/extensions/forms/form-diagram-2.png'
Expand All @@ -21,69 +21,10 @@ import FormDiagram2 from 'Docs/uilib/extensions/forms/form-diagram-2.png'
- [Philosophy](#philosophy)
- [Features](#features)
- [Examples](#examples)
- [Getting started](#getting-started)
- [First steps](#first-steps)
- [Create your own components](#create-your-own-components)

## Quick start

Field components can be used directly as they are, for example `Field.Email`:

```jsx
import { Field } from '@dnb/eufemia/extensions/forms'
render(<Field.Email />)
```

By building an entire form with components from Eufemia and Eufemia Forms, you save time and code:

```jsx
import { Card } from '@dnb/eufemia'
import { Form, Field } from '@dnb/eufemia/extensions/forms'
render(
<Form.Handler
data={existingData}
onChange={...}
onSubmit={...}
>
<Card spacing="medium">
<Field.String
path="/companyName"
label="Bedriftens navn"
required
/>
<Field.OrganizationNumber
path="/companyOrganizationNumber"
required
/>
<Field.Selection
path="/postalAddressSelect"
label="Ønsket sted for tilsendt post"
variant="radio"
>
<Field.Option value="companyAddress" title="Samme som forretningsadresse" />
<Field.Option value="other" title="Annet" />
</Field.Selection>
</Card>
</Form.Handler>
)
```

Use the [useData](/uilib/extensions/forms/extended-features/Form/useData/) hook to access or modify your form data outside of the form context within your application:

```jsx
import { Form } from '@dnb/eufemia/extensions/forms'
function Component() {
const { data, update } = Form.useData('unique')

return (
<Form.Handler
id="unique"
...
>
...
</Form.Handler>
)
}
```
<QuickStart />

## Philosophy

Expand All @@ -107,7 +48,7 @@ In summary:

- Ready to use data driven form components.
- All functionality in all components can be controlled and overridden via props.
- State management using the declarative [JSON Pointer](/uilib/extensions/forms/#what-is-a-json-pointer) directive (i.e `path="/firstName"`).
- State management using the declarative [JSON Pointer](/uilib/extensions/forms/getting-started/#what-is-a-json-pointer) directive (i.e `path="/firstName"`).
- State can be handled outside of the Form.Handler (Provider Context) with the [useData](/uilib/extensions/forms/extended-features/Form/useData) hook.
- Simple validation (like `minLength` on text fields) as well as advanced and complex [Ajv](https://ajv.js.org/) JSON schema validator (Ajv is like Joi or Yup – check out [some examples](/uilib/extensions/forms/extended-features/#schema-validation)) support on both single fields and the whole data set.
- Building blocks for [creating custom field components](/uilib/extensions/forms/create-component).
Expand Down Expand Up @@ -146,7 +87,7 @@ In this example, all state data, validation process and error handling are done
- [General Demos](/uilib/extensions/forms/general-demos/)
- [Case Demo 1 (fullscreen)](/uilib/extensions/forms/demo-cases/casedemo1/)

## Getting started
## First steps

You import the components from with scopes, such as `Form` and `Field`:

Expand All @@ -163,55 +104,11 @@ render(
)
```

The needed styles are included in the Eufemia core package via `dnb-ui-components`.

### Field components

The data-driven [base field components](/uilib/extensions/forms/base-fields) are named and structured according to the type of data they can display and produce based on the user's input and action in the interface.

On top of these, a number of [feature fields](/uilib/extensions/forms/feature-fields) have been built that have special functionality based on given types of data, such as bank account numbers, e-mails and social security numbers.

### Value components

Beside the interactive [Field](/uilib/extensions/forms/fields/) components, there are also the static [Value](/uilib/extensions/forms/extended-features/Value/) components. Use these to show summaries or read-only parts of your application with benefits such as linking to source data and standardized formatting based on the type of data to be displayed.

### Layout

When building your application forms, preferably use the following layout components. They seamlessly places all the fields and components of Eufemia Forms correctly into place.

- [Flex](/uilib/layout/flex) layout component for easy and consistent application forms.
- [Card](/uilib/components/card) for the default card outline of forms.

### Creating forms

To build an entire form, there are surrounding components such as [Form.Handler](/uilib/extensions/forms/extended-features/Form/Handler) and [StepsLayout](/uilib/extensions/forms/extended-features/StepsLayout) that make data flow and layout easier and save you a lot of extra code, without compromising flexibility.

### State management

The state management is done via the [JSON Pointer](/uilib/extensions/forms/#what-is-a-json-pointer) directive (i.e `path="/firstName"`). This is a standardized way of pointing to a specific part of a JavaScript/JSON object. The JSON Pointer is used to both read and write data, and is also used to validate the data.

<Examples.GettingStarted />

### What is a JSON Pointer?

A [JSON Pointer](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03) is a string of tokens separated by `/` characters, these tokens either specify keys in objects or indexes into arrays.

```ts
const data = {
foo: {
bar: [
{
baz: 'value',
},
],
},
}
const pointer = '/foo/bar/0/baz' // points to 'value'
```
More details in the [Getting started](/uilib/extensions/forms/getting-started/) section.

### Best Practices
### Best practices

- [Best Practices on Forms](/uilib/extensions/forms/best-practices-on-forms/).
- [Best practices on Forms](/uilib/extensions/forms/best-practices-on-forms/).

## Create your own components

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
CreateBasicFieldComponent,
CreateComposedFieldComponent,
} from './Examples'
import UseDataValue from './create-component/useDataValue/info'

# Create your own component

Expand All @@ -24,12 +23,13 @@ By using the building blocks for field components, you save development time, an
```jsx
import {
FieldBlock,
ValueBlock,
useDataValue,
Iterate,
DataContext,
} from '@dnb/eufemia/extensions/forms'
```

<ListBasisAPIs />

## FieldBlock and useDataValue

The `FieldBlock` component and the `useDataValue` hook are the basis for all field components in Eufemia Forms.
Expand All @@ -46,9 +46,9 @@ This example shows a custom component. The `useDataValue` hook receives the prop

### The example explained

Using these two form helpers in your field component triggers several automatic processes. These include timely validation checks, syncing value changes with the `DataContext`, coordinating error messages across multiple fields, and preventing premature error displays while the user is editing the field.
Using these two form helpers in your field component triggers several automatic processes. These include timely validation checks, syncing value changes with the [DataContext](/uilib/extensions/forms/extended-features/DataContext/), coordinating error messages across multiple fields, and preventing premature error displays while the user is editing the field.

Keep in mind, you can customize the behavior of `useDataValue` and other helper functions to make the component work exactly as you want.
Keep in mind, you can customize the behavior of [useDataValue](/) and other helper functions to make the component work exactly as you want.

### Your own validation

Expand Down Expand Up @@ -79,13 +79,3 @@ These are the official sizes you can use when [creating your own form fields](/u
```

You can also use a [FieldBlock](/uilib/extensions/forms/create-component/FieldBlock/) and provide a `width` prop with a value of either `small`, `medium` or `large` and use it as a sized wrapper.

## Components

<ListBasisAPIs />

---

## Hooks

<UseDataValue />
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: 'FieldBlock'
description: '`FieldBlock` is a reusable wrapper for building Field-components. It shows surrounding elements through properties from `FieldProps` like `label` and `error`, and ensure that spacing between different fields work as required when put into surrounding components like `Flex.Container` or `Card`. It can also be used to group multiple inner FieldBlock component, composing error messages together as one component.'
description: '`FieldBlock` is a reusable wrapper for building Field-components. It shows surrounding elements through properties from `FieldProps` like `label` and `error`.'
componentType: 'basis-api'
hideInMenu: true
showTabs: true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: 'useDataValue'
description: 'The `useDataValue` hook standardize handling of the value flow for a single consumer component representing one data point.'
componentType: 'basis-api'
hideInMenu: true
showTabs: true
tabs:
- title: Info
key: '/info'
- title: Demos
key: '/demos'
breadcrumb:
- text: Forms
href: /uilib/extensions/forms/
- text: Create your component
href: /uilib/extensions/forms/create-component/
- text: useDataValue
href: /uilib/extensions/forms/create-component/useDataValue/
---

import Info from 'Docs/uilib/extensions/forms/create-component/useDataValue/info'
import Demos from 'Docs/uilib/extensions/forms/create-component/useDataValue/demos'

<Info />
<Demos />
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react'
import ComponentBox from '../../../../../../shared/tags/ComponentBox'
import {
Field,
FieldBlock,
Form,
JSONSchema,
useDataValue,
} from '@dnb/eufemia/src/extensions/forms'
import { Flex, Slider } from '@dnb/eufemia/src'

export const CustomComponentExample = () => {
return (
<ComponentBox scope={{ useDataValue, FieldBlock }}>
{() => {
const MySliderComponent = (props) => {
const fromInput = React.useCallback(
(event) =>
typeof event === 'number' ? event : event?.value || 0,
[],
)

const errorMessages = React.useMemo(
() => ({
required: 'This field is required',
...props.errorMessages,
}),
[props.errorMessages],
)
const schema = React.useMemo<JSONSchema>(
() =>
props.schema ?? {
type: 'number',
minimum: props.minimum,
maximum: props.maximum,
},
[props.schema, props.minimum, props.maximum],
)

const preparedProps = {
fromInput,
schema,
...errorMessages,
label: 'Label',
...props,
}

const {
id,
label,
info,
warning,
error,
value,
width = 'medium',
minimum = 0,
maximum = 100,
step = 1,
handleChange,
handleFocus,
handleBlur,
} = useDataValue(preparedProps)

const steps = { minimum, maximum, step }

return (
<FieldBlock
forId={id}
label={label}
info={info}
warning={warning}
error={error}
width={width}
>
<Flex.Stack>
<Field.Number
value={value}
showStepControls
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
{...steps}
/>
<Slider
id={id}
value={value}
onChange={handleChange}
onDragStart={handleFocus}
onDragEnd={handleBlur}
{...steps}
/>
</Flex.Stack>
</FieldBlock>
)
}

return (
<Form.Handler data={{ sliderValue: 50 }}>
<MySliderComponent
path="/sliderValue"
minimum={50}
maximum={80}
required
info="Info"
/>
</Form.Handler>
)
}}
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
showTabs: true
---

import * as Examples from './Examples'

## Demos

On the consumer side, we can use this custom component like so:

```jsx
<Form.Handler data={{ sliderValue: 50 }}>
<MySliderComponent
path="/sliderValue"
minimum={50}
maximum={80}
required
info="Info"
/>
</Form.Handler>
```

<Examples.CustomComponentExample />
Loading

0 comments on commit 8c311d8

Please sign in to comment.