Skip to content

Commit

Permalink
feat(forms): add Field.Indeterminate
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed May 14, 2024
1 parent 4967cf0 commit 9f89c82
Show file tree
Hide file tree
Showing 32 changed files with 1,329 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ import { Checkbox, HelpButton } from '@dnb/eufemia/src'

export const CheckboxUnchecked = () => (
<ComponentBox data-visual-test="checkbox-default">
<Checkbox label="Checkbox" on_change={(e) => console.log(e)} />
<Checkbox label="Checkbox" onChange={(e) => console.log(e)} />
</ComponentBox>
)

export const CheckboxChecked = () => (
<ComponentBox data-visual-test="checkbox-checked">
<Checkbox
label="Label"
label_position="left"
labelPosition="left"
checked
on_change={({ checked }) => console.log(checked)}
onChange={({ checked }) => console.log(checked)}
/>
</ComponentBox>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ As for now, there are two sizes. `medium` is the default size.

The checkbox offers a fully controlled indeterminate state (partialy checked).

Here is a indeterminate state (partially checked) [working example](/uilib/extensions/forms/base-fields/Indeterminate).

<CheckboxIndeterminate />

<CheckboxIndeterminateLarge />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ There is a corresponding [Value.Boolean](/uilib/extensions/forms/Value/Boolean)
import { Field } from '@dnb/eufemia/extensions/forms'
render(<Field.Boolean path="/myState" />)
```

### Indeterminate checkbox

Here is a indeterminate state (partially checked) [working example](/uilib/extensions/forms/base-fields/Indeterminate/).
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ showTabs: true

import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable'
import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs'
import { BooleanProperties } from '@dnb/eufemia/src/extensions/forms/Field/Boolean/BooleanDocs'

## Properties

### Field-specific props

| Property | Type | Description |
| ----------- | -------- | -------------------------------------------------------------------------------------- |
| `trueText` | `string` | Text to show in the UI when value is `true`. |
| `falseText` | `string` | Text to show in the UI when value is `false`. |
| `variant` | `string` | Choice of input feature. Can be: `checkbox`, `button`, `checkbox-button` or `buttons`. |
<PropertiesTable props={BooleanProperties} />

### General props

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: 'Indeterminate'
description: 'The `Field.Indeterminate` component is used to display and handle the indeterminate state of a checkbox.'
componentType: 'primitive'
hideInMenu: true
showTabs: true
theme: 'sbanken'
tabs:
- title: Info
key: '/info'
- title: Demos
key: '/demos'
- title: Properties
key: '/properties'
breadcrumb:
- text: Forms
href: /uilib/extensions/forms/
- text: Base fields
href: /uilib/extensions/forms/base-fields/
- text: Indeterminate
href: /uilib/extensions/forms/base-fields/Indeterminate/
---

import Info from 'Docs/uilib/extensions/forms/base-fields/Indeterminate/info'
import Demos from 'Docs/uilib/extensions/forms/base-fields/Indeterminate/demos'

<Info />
<Demos />
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Card, Flex } from '@dnb/eufemia/src'
import ComponentBox from '../../../../../../shared/tags/ComponentBox'
import { Field, Form } from '@dnb/eufemia/src/extensions/forms'

export const MixedIndeterminateDependence = () => {
return (
<ComponentBox>
<Form.Handler onChange={console.log}>
<Card stack>
<Field.Indeterminate
dependencePaths={['/child1', '/child2', '/child3']}
label="Indeterminate"
/>

<Field.Toggle
label="Checkbox 1"
path="/child1"
valueOn="what-ever"
valueOff="you-name-it"
required
/>

<Field.Boolean label="Checkbox 2" path="/child2" required />

<Field.Toggle
label="Checkbox 3"
path="/child3"
valueOn="on"
valueOff="off"
/>
</Card>

<Form.SubmitButton />
</Form.Handler>
</ComponentBox>
)
}

export const NestedIndeterminateDependence = () => {
return (
<ComponentBox>
<Form.Handler onChange={console.log}>
<Card stack>
<Field.Indeterminate
label="1"
path="/p1"
dependencePaths={['/c2.1', '/p2.2', '/c3.1', '/c3.2']}
/>

<Flex.Stack left="large">
<Field.Boolean label="2.1" path="/c2.1" />
<Field.Indeterminate
label="2.2"
valueOn="what-ever"
valueOff="you-name-it"
path="/p2.2"
dependencePaths={['/c3.1', '/c3.2']}
/>

<Flex.Stack left="large">
<Field.Boolean label="3.1" path="/c3.1" />
<Field.Toggle
label="3.2"
path="/c3.2"
valueOn="what-ever"
valueOff="you-name-it"
/>
</Flex.Stack>
</Flex.Stack>
</Card>
</Form.Handler>
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
showTabs: true
---

import * as Examples from './Examples'

## Demos

### Indeterminate state (partially checked)

<Examples.MixedIndeterminateDependence />

### Nested indeterminate state

<Examples.NestedIndeterminateDependence />
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
showTabs: false
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
showTabs: true
---

## Description

The `Field.Indeterminate` component is used to display and handle the indeterminate state of a checkbox. It is an uncontrolled component, meaning that the state is managed automatically.

```tsx
import { Field } from '@dnb/eufemia/extensions/forms'
render(
<Field.Indeterminate
dependencePaths={['/checkbox1', '/checkbox2', '/checkbox3']}
path="/checkboxParent"
/>,
)
```

It should only be used in combination with checkbox looking variants.

Under the hood the [Toggle](/uilib/extensions/forms/base-fields/Toggle/) base field is used. That means you can use all the props from the `Toggle` component.

## Details about the state handling

The indeterminate state of a parent checkbox should be shown when some children checkboxes are checked, but not all. In detail:

- When all children are checked, the the parent should get checked.
- When the parent gets checked (clicked), all children should get checked.
- When all children are unchecked, the parent should get unchecked.
- When the parent gets unchecked (clicked), all children should get unchecked.
- When some children are checked, the parent should be set in an indeterminate state.
- When the parent gets clicked, all children should get checked. This behavior can be changed to the opposite or `auto` by using the `propagateIndeterminateState` prop. Auto means that the parent will switch from its current state to be inverted.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
showTabs: true
---

import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable'
import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs'
import { IndeterminateProperties } from '@dnb/eufemia/src/extensions/forms/Field/Indeterminate/IndeterminateDocs'

## Properties

### Field-specific props

<PropertiesTable props={IndeterminateProperties} />

### General props

<PropertiesTable props={fieldProperties} valueType="any" />
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ There is a corresponding [Value.String](/uilib/extensions/forms/Value/String) co

```tsx
import { Field } from '@dnb/eufemia/extensions/forms'
render(<Field.String path="/myState" />)
render(<Field.String path="/myValue" />)
```

## Browser autofill
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ showTabs: true
import { Field } from '@dnb/eufemia/extensions/forms'
render(<Field.Toggle path="/myState" />)
```

### Indeterminate checkbox

Here is a indeterminate state (partially checked) [working example](/uilib/extensions/forms/base-fields/Indeterminate/).
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@ showTabs: true

import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable'
import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs'
import { ToggleProperties } from '@dnb/eufemia/src/extensions/forms/Field/Toggle/ToggleDocs'

## Properties

### Field-specific props

| Property | Type | Description |
| ---------- | ----------------------------- | --------------------------------------------------------------------------------------------------- |
| `valueOn` | `string`, `number`, `boolean` | _(required)_ Source data value when the toggle is in the "on-state" (varies based on UI variant). |
| `valueOff` | `string`, `number`, `boolean` | _(required)_ Source data value when the toggle is in the "off-state". |
| `textOn` | `string` | _(optional)_ Text to show in the UI when in the "on-state". |
| `textOff` | `string` | _(optional)_ Text to show in the UI when in the "off-state". |
| `variant` | `string` | _(optional)_ Choice of input feature. Can be: `checkbox`, `button`, `checkbox-button` or `buttons`. |
<PropertiesTable props={ToggleProperties} />

### General props

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ breadcrumb:

Change log for the Eufemia Forms extension.

## v10.31

- Added [Field.Indeterminate](/uilib/extensions/forms/base-fields/Indeterminate) component to handle checkbox indeterminate (partial) states.

## v10.30

- Added [Form.FieldProps](/uilib/extensions/forms/Form/FieldProps/) component to forward field properties, such as `required` or `disabled` to all nested field components.
Expand Down
2 changes: 1 addition & 1 deletion packages/dnb-eufemia/src/components/checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ function Checkbox(localProps: CheckboxProps) {

useEffect(() => {
ref.current.indeterminate = indeterminate
}, [indeterminate])
}, [indeterminate, ref])

const callOnChange: CheckboxProps['onChange'] = useCallback(
(args) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export interface ContextState {
submitState: Partial<EventStateObject>
_isInsideFormElement?: boolean
props: ProviderProps<unknown>
fieldProps?: Record<Path, unknown>
}

export const defaultContextState: ContextState = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,7 @@ export default function Provider<Data extends JsonObject>(
showAllErrors: showAllErrorsRef.current,
mountedFieldPaths: mountedFieldPathsRef.current,
ajvInstance: ajvRef.current,
fieldProps: fieldPropsRef.current,

/** Additional */
id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import React from 'react'
import ToggleField, { Props as ToggleFieldProps } from '../Toggle'
import useTranslation from '../../hooks/useTranslation'
import { FieldProps, Path } from '../../types'

export type Props = Omit<
ToggleFieldProps,
'valueOn' | 'valueOff' | 'textOn' | 'textOff'
> & {
type BooleanProps = {
trueText?: string
falseText?: string
variant?: ToggleFieldProps['variant']
dependencePaths?: never
}
type NeverBooleanProps = {
// eslint-disable-next-line no-unused-vars
[K in keyof Partial<Omit<BooleanProps, 'dependencePaths'>>]: never
}
export type IndeterminateProps = FieldProps<unknown> & {
dependencePaths: Array<Path>
} & NeverBooleanProps
export type Props = FieldProps<unknown> & BooleanProps

function BooleanComponent(props: Props) {
function BooleanComponent(props: Props | IndeterminateProps) {
const { trueText, falseText, ...restProps } = props
const translations = useTranslation().BooleanField

return (
<ToggleField
{...restProps}
{...(restProps as ToggleFieldProps)}
valueOn={true}
valueOff={false}
textOn={trueText ?? translations.yes}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { PropertiesTableProps } from '../../../../shared/types'

export const BooleanProperties: PropertiesTableProps = {
trueText: {
doc: 'Text to show in the UI when value is `true`.',
type: 'string',
status: 'optional',
},
falseText: {
doc: 'Text to show in the UI when value is `false`.',
type: 'string',
status: 'optional',
},
variant: {
doc: 'Choice of input feature. Can be: `checkbox`, `button`, `checkbox-button` or `buttons`.',
type: 'string',
status: 'optional',
},
}
4 changes: 2 additions & 2 deletions packages/dnb-eufemia/src/extensions/forms/Field/FieldDocs.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { PropertiesTableProps } from '../../../shared/types'
import { fieldBlockProperties } from '../FieldBlock/FieldBlockDocs'
import { fieldBlockSharedProperties } from '../FieldBlock/FieldBlockDocs'
import {
dataValueEvents,
dataValueProperties,
} from '../hooks/DataValueDocs'

export const fieldProperties: PropertiesTableProps = {
...dataValueProperties,
...fieldBlockProperties,
...fieldBlockSharedProperties,
}

export const fieldEvents: PropertiesTableProps = {
Expand Down

0 comments on commit 9f89c82

Please sign in to comment.