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

feat(forms): add Value.Selection component #3857

Merged
merged 14 commits into from
Aug 26, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const FieldArraySelectionAndOption = () => {
<Field.Option value="baz" title="Baz" />
</Field.ArraySelection>

<Value.ArraySelection label="My selections" path="/myPath" />
<Value.ArraySelection inheritLabel path="/myPath" />
</Flex.Stack>
</Form.Handler>
</ComponentBox>
Expand All @@ -67,7 +67,7 @@ export const FieldArraySelectionPath = () => {
>
<Flex.Stack>
<Field.ArraySelection label="My selections" path="/myPath" />
<Value.ArraySelection label="My selections" path="/myPath" />
<Value.ArraySelection inheritLabel path="/myPath" />
</Flex.Stack>
</Form.Handler>
</ComponentBox>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: 'Selection'
description: '`Value.Selection` is a component for displaying a string value based on a user selection.'
componentType: 'base-value'
langz marked this conversation as resolved.
Show resolved Hide resolved
hideInMenu: true
showTabs: true
tabs:
- title: Info
key: '/info'
- title: Demos
key: '/demos'
- title: Properties
key: '/properties'
breadcrumb:
- text: Forms
href: /uilib/extensions/forms/
- text: Value
href: /uilib/extensions/forms/Value/
- text: Selection
href: /uilib/extensions/forms/Value/Selection/
---

import Info from 'Docs/uilib/extensions/forms/Value/Selection/info'
import Demos from 'Docs/uilib/extensions/forms/Value/Selection/demos'

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

export const Placeholder = () => {
return (
<ComponentBox scope={{ Value }}>
<Value.Selection placeholder="No values selected" />
</ComponentBox>
)
}

export const WithValue = () => {
return (
<ComponentBox scope={{ Value }}>
<Value.Selection value="Bar" />
</ComponentBox>
)
}

export const Label = () => {
return (
<ComponentBox scope={{ Value }}>
<Value.Selection label="Label text" showEmpty />
</ComponentBox>
)
}

export const LabelAndValue = () => {
return (
<ComponentBox scope={{ Value }}>
<Value.Selection label="Label text" value="Foo" />
</ComponentBox>
)
}

export const Inline = () => {
return (
<ComponentBox scope={{ Value }}>
<P>
This is before the component
<Value.Selection value="Baz" inline />
This is after the component
</P>
</ComponentBox>
)
}

export const FieldSelectionPath = () => {
return (
<ComponentBox>
<Form.Handler
data={{
selection: 'bar',
myList: [
{ value: 'foo', title: 'Foo' },
{ value: 'bar', title: 'Bar' },
{ value: 'baz', title: 'Baz' },
],
}}
>
<Flex.Stack>
<Field.Selection
path="/selection"
dataPath="/myList"
variant="radio"
label="My selection"
/>
<Value.Selection
path="/selection"
dataPath="/myList"
inheritLabel
/>
</Flex.Stack>
</Form.Handler>
</ComponentBox>
)
}

export const FieldSelectionAndOption = () => {
return (
<ComponentBox>
<Form.Handler>
<Flex.Stack>
<Field.Selection
label="My selection"
path="/myPath"
variant="radio"
value="bar"
>
<Field.Option value="foo" title="Foo" />
<Field.Option value="bar" title="Bar" />
<Field.Option value="baz" title="Baz" />
</Field.Selection>

<Value.Selection label="My selection" path="/myPath" />
</Flex.Stack>
</Form.Handler>
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
showTabs: true
---

import * as Examples from './Examples'

## Demos

### Placeholder

<Examples.Placeholder />

### Value

<Examples.WithValue />

### Label

<Examples.Label />

### Label and value

<Examples.LabelAndValue />

### Inline

<Examples.Inline />

### Field.Selection with path

When using the same `path` as on a `Field.Selection`, the title will be used as the displayed value.

<Examples.FieldSelectionPath />

### Field.Option and Field.Selection

When using the same `path` as on a `Field.Selection`, the `Field.Option` title will be used as the displayed value.

<Examples.FieldSelectionAndOption />
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
showTabs: true
---

## Description

`Value.Selection` is a component for displaying a string value based on a user selection.

There is a corresponding [Field.Selection](/uilib/extensions/forms/base-fields/Selection) component.

```jsx
import { Value } from '@dnb/eufemia/extensions/forms'
render(<Value.Selection />)
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
showTabs: true
---

import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable'
import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable'
import { SelectionProperties } from '@dnb/eufemia/src/extensions/forms/Value/Selection/SelectionDocs'
import { ValueProperties } from '@dnb/eufemia/src/extensions/forms/Value/ValueDocs'

## Properties

### Field-specific props

<PropertiesTable props={SelectionProperties} />

### General props

<PropertiesTable props={ValueProperties} />
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ showTabs: true

[Field.Option](/uilib/extensions/forms/base-fields/Option/) is a related component.

There is a corresponding [Value.Selection](/uilib/extensions/forms/Value/Selection) component.

```tsx
import { Field } from '@dnb/eufemia/extensions/forms'
render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Change log for the Eufemia Forms extension.

- Added [Iterate.PushContainer](/uilib/extensions/forms/Iterate/PushContainer/) to create new items in an array.
- Added [Value.ArraySelection](/uilib/extensions/forms/Value/ArraySelection/) component to render an array of values.
- Added [Value.Selection](/uilib/extensions/forms/Value/Selection/) component to render a selection value.

## v10.43

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { useCallback, useContext, useMemo } from 'react'
import { Checkbox, HelpButton, ToggleButton } from '../../../../components'
import classnames from 'classnames'
import OptionField from '../Option'
import FieldBlock from '../../FieldBlock'
import { useFieldProps } from '../../hooks'
import { ReturnAdditional } from '../../hooks/useFieldProps'
import { FieldHelpProps, FieldProps, FormError } from '../../types'
import { pickSpacingProps } from '../../../../components/flex/utils'
import { getStatus } from '../Selection'
import { getStatus, mapOptions } from '../Selection'
import { HelpButtonProps } from '../../../../components/HelpButton'
import ToggleButtonGroupContext from '../../../../components/toggle-button/ToggleButtonGroupContext'
import DataContext from '../../DataContext/Context'
Expand Down Expand Up @@ -219,6 +218,7 @@ export function useCheckboxOrToggleOptions({
/>
)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
disabled,
emptyValue,
Expand All @@ -234,29 +234,7 @@ export function useCheckboxOrToggleOptions({
]
)

const mapOptions = useCallback(
(children: React.ReactNode) => {
return React.Children.toArray(children).map(
(child: React.ReactElement<OptionProps>, i) => {
if (React.isValidElement(child)) {
if (child.type === OptionField) {
return createOption(child.props, i)
}

if (child.props.children) {
const nestedChildren = mapOptions(child.props.children)
return React.cloneElement(child, child.props, nestedChildren)
}
}

return child
}
)
},
[createOption]
)

const result = mapOptions(children)
const result = mapOptions(children, { createOption })

if (path) {
setFieldProps?.(path + '/arraySelectionData', collectedData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,6 @@ function renderRadioItems({
const optionsCount =
React.Children.count(children) + (dataList?.length || 0)

const Component = (
variant === 'radio' ? Radio : ToggleButton
) as typeof Radio & typeof ToggleButton

const createOption = (props: OptionProps, i: number) => {
const { error, title, help, children, ...rest } = props

Expand All @@ -339,6 +335,10 @@ function renderRadioItems({
</HelpButton>
) : undefined

const Component = (
variant === 'radio' ? Radio : ToggleButton
) as typeof Radio & typeof ToggleButton

return (
<Component
id={optionsCount === 1 ? id : undefined}
Expand All @@ -354,34 +354,36 @@ function renderRadioItems({
)
}

const mapOptions = (children: React.ReactNode) => {
return React.Children.map(
children,
(child: React.ReactElement<OptionProps>, i) => {
if (React.isValidElement(child)) {
if (child.type === OptionField) {
return createOption(child.props, i)
}

if (child.props.children) {
const nestedChildren = mapOptions(child.props.children)
return React.cloneElement(child, child.props, nestedChildren)
}
}

return child
}
)
}

return [
...(dataList || []).map((props, i) => {
return createOption(props as OptionProps, i)
}),
...(mapOptions(children) || []),
...(mapOptions(children, { createOption }) || []),
].filter(Boolean)
}

export function mapOptions(children: React.ReactNode, { createOption }) {
return React.Children.map(
children,
(child: React.ReactElement<OptionProps>, i) => {
if (React.isValidElement(child)) {
if (child.type === OptionField) {
return createOption(child.props, i)
}

if (child.props.children) {
const nestedChildren = mapOptions(child.props.children, {
createOption,
})
return React.cloneElement(child, child.props, nestedChildren)
}
}

return child
}
)
}

export function makeOptions<T = DrawerListProps['data']>(
children: React.ReactNode
): T {
Expand Down
Loading
Loading