Skip to content

Commit

Permalink
feat(Field.ArraySelection): add dataPath prop (#3872)
Browse files Browse the repository at this point in the history
Co-authored-by: Tobias Høegh <[email protected]>
  • Loading branch information
joakbjerk and tujoworker authored Aug 30, 2024
1 parent 7f37704 commit 35427d7
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,42 @@ export const CheckboxNestingWithLogic = () => (
</ComponentBox>
)

export const CheckboxWithDataPath = () => {
return (
<ComponentBox>
<Form.Handler
data={{
myDataPath: [
{ title: 'Foo!', value: 'foo' },
{ title: 'Bar!', value: 'bar' },
{ title: 'Baz!', value: 'baz' },
],
}}
>
<Field.ArraySelection
label="Populated by dataPath"
dataPath="/myDataPath"
/>
</Form.Handler>
</ComponentBox>
)
}

export const CheckboxWithData = () => {
return (
<ComponentBox>
<Field.ArraySelection
label="Populated by data"
data={[
{ title: 'Foo!', value: 'foo' },
{ title: 'Bar!', value: 'bar' },
{ title: 'Baz!', value: 'baz' },
]}
/>
</ComponentBox>
)
}

// Button

export const ButtonEmpty = () => (
Expand Down Expand Up @@ -498,3 +534,41 @@ export const ButtonNestingWithLogic = () => (
</Form.Handler>
</ComponentBox>
)

export const ButtonWithDataPath = () => {
return (
<ComponentBox>
<Form.Handler
data={{
myDataPath: [
{ title: 'Foo!', value: 'foo' },
{ title: 'Bar!', value: 'bar' },
{ title: 'Baz!', value: 'baz' },
],
}}
>
<Field.ArraySelection
variant="button"
label="Populated by dataPath"
dataPath="/myDataPath"
/>
</Form.Handler>
</ComponentBox>
)
}

export const ButtonWithData = () => {
return (
<ComponentBox>
<Field.ArraySelection
variant="button"
label="Populated by data"
data={[
{ title: 'Foo!', value: 'foo' },
{ title: 'Bar!', value: 'bar' },
{ title: 'Baz!', value: 'baz' },
]}
/>
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ You can nest other fields and show them based on your desired logic.

<Examples.CheckboxNestingWithLogic />

#### Checkbox with a path to populate the data

<Examples.CheckboxWithDataPath />

#### Checkbox with the data property

<Examples.CheckboxWithData />

---

### Button variant demos
Expand Down Expand Up @@ -112,6 +120,14 @@ You can nest other fields and show them based on your desired logic.

<Examples.ButtonError />

#### Button with a path to populate the data

<Examples.ButtonWithDataPath />

#### Button with the data property

<Examples.ButtonWithData />

#### Button with nested fields and logic

You can nest other fields and show them based on your desired logic.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import classnames from 'classnames'
import FieldBlock from '../../FieldBlock'
import { useFieldProps } from '../../hooks'
import { ReturnAdditional } from '../../hooks/useFieldProps'
import { FieldHelpProps, FieldProps, FormError } from '../../types'
import { FieldHelpProps, FieldProps, FormError, Path } from '../../types'
import { pickSpacingProps } from '../../../../components/flex/utils'
import { getStatus, mapOptions } from '../Selection'
import { getStatus, mapOptions, Data } from '../Selection'
import { HelpButtonProps } from '../../../../components/HelpButton'
import ToggleButtonGroupContext from '../../../../components/toggle-button/ToggleButtonGroupContext'
import DataContext from '../../DataContext/Context'
import useDataValue from '../../hooks/useDataValue'

type OptionProps = React.ComponentProps<
React.FC<{
Expand All @@ -28,12 +29,25 @@ export type Props = FieldHelpProps &
children?: React.ReactNode
variant?: 'checkbox' | 'button' | 'checkbox-button'
optionsLayout?: 'horizontal' | 'vertical'
/**
* The path to the context data (Form.Handler).
* The context data object needs to have a `value` and a `title` property.
*/
dataPath?: Path

/**
* Data to be used for the component. The object needs to have a `value` and a `title` property.
* The generated options will be placed above given JSX based children.
*/
data?: Data
}

function ArraySelection(props: Props) {
const {
id,
path,
dataPath,
data,
className,
variant = 'checkbox',
layout = 'vertical',
Expand All @@ -53,6 +67,9 @@ function ArraySelection(props: Props) {
children,
} = useFieldProps(props)

const { getValueByPath } = useDataValue()
const dataList = dataPath ? getValueByPath(dataPath) : data

const fieldBlockProps = {
forId: id,
className: classnames(
Expand Down Expand Up @@ -96,6 +113,7 @@ function ArraySelection(props: Props) {
warning,
emptyValue,
htmlAttributes,
dataList,
children,
value,
disabled,
Expand Down Expand Up @@ -132,6 +150,7 @@ export function useCheckboxOrToggleOptions({
warning,
emptyValue,
htmlAttributes,
dataList,
children,
value,
disabled,
Expand All @@ -145,6 +164,7 @@ export function useCheckboxOrToggleOptions({
warning?: Props['warning']
emptyValue?: Props['emptyValue']
htmlAttributes?: Props['htmlAttributes']
dataList?: Props['data']
children?: Props['children']
value?: Props['value']
disabled?: Props['disabled']
Expand All @@ -153,8 +173,8 @@ export function useCheckboxOrToggleOptions({
}) {
const { setFieldProps } = useContext(DataContext)
const optionsCount = useMemo(
() => React.Children.count(children),
[children]
() => React.Children.count(children) + (dataList?.length || 0),
[dataList, children]
)
const collectedData = []

Expand Down Expand Up @@ -234,7 +254,12 @@ export function useCheckboxOrToggleOptions({
]
)

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

if (path) {
setFieldProps?.(path + '/arraySelectionData', collectedData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,14 @@ export const arraySelectionProperties: PropertiesTableProps = {
type: 'React.Node',
status: 'optional',
},
data: {
doc: 'Data to be used for the component. The object needs to have a `value` and a `title` property. Provide the Dropdown or Autocomplete data in the format documented here: [Dropdown](/uilib/components/dropdown) and [Autocomplete](/uilib/components/autocomplete) documentation.',
type: 'array',
status: 'optional',
},
dataPath: {
doc: 'The path to the context data (Form.Handler). The context data object needs to have a `value` and a `title` property. The generated options will be placed above given JSX based children.',
type: 'string',
status: 'optional',
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,59 @@ describe('ArraySelection', () => {
expect(optionC).toHaveClass('dnb-checkbox__status--error')
expect(optionD).toHaveClass('dnb-checkbox__status--error')
})

it('should support "dataPath"', () => {
render(
<Form.Handler
data={{
myList: [
{ value: 'foo', title: 'Foo!' },
{ value: 'bar', title: 'Bar!' },
],
mySelection: 'bar',
}}
>
<Field.ArraySelection
variant="checkbox"
path="/mySelection"
dataPath="/myList"
>
<Field.Option value="baz">Baz!</Field.Option>
</Field.ArraySelection>
</Form.Handler>
)

const options = Array.from(
document.querySelectorAll('.dnb-checkbox')
)
expect(options).toHaveLength(3)

const [option1, option2, option3] = options

expect(option1).toHaveTextContent('Foo!')
expect(option2).toHaveTextContent('Bar!')
expect(option3).toHaveTextContent('Baz!')

expect(option1.querySelector('input').checked).toBe(false)
expect(option2.querySelector('input').checked).toBe(true)
expect(option3.querySelector('input').checked).toBe(false)

expect(option1.querySelector('input').id).toBe(
option1.querySelector('label').getAttribute('for')
)
expect(option2.querySelector('input').id).toBe(
option2.querySelector('label').getAttribute('for')
)
expect(option3.querySelector('input').id).toBe(
option3.querySelector('label').getAttribute('for')
)
expect(option1.querySelector('input').id).not.toBe(
option2.querySelector('label').getAttribute('for')
)
expect(option1.querySelector('input').id).not.toBe(
option3.querySelector('label').getAttribute('for')
)
})
})

describe.each(['button', 'checkbox-button'])(
Expand Down Expand Up @@ -441,6 +494,59 @@ describe('ArraySelection', () => {
expect(optionC).toHaveClass('dnb-toggle-button__status--error')
expect(optionD).toHaveClass('dnb-toggle-button__status--error')
})

it('should support "dataPath"', () => {
render(
<Form.Handler
data={{
myList: [
{ value: 'foo', title: 'Foo!' },
{ value: 'bar', title: 'Bar!' },
],
mySelection: 'bar',
}}
>
<Field.ArraySelection
variant={testVariant}
path="/mySelection"
dataPath="/myList"
>
<Field.Option value="baz">Baz!</Field.Option>
</Field.ArraySelection>
</Form.Handler>
)

const options = Array.from(
document.querySelectorAll(
`.dnb-forms-field-array-selection__button `
)
)
expect(options).toHaveLength(3)

const [option1, option2, option3] = options

expect(option1).toHaveTextContent('Foo!')
expect(option2).toHaveTextContent('Bar!')
expect(option3).toHaveTextContent('Baz!')

expect(option1.querySelector('button')).toHaveAttribute(
'aria-pressed',
'false'
)
expect(option1).not.toHaveClass('dnb-toggle-button--checked')

expect(option2.querySelector('button')).toHaveAttribute(
'aria-pressed',
'true'
)
expect(option2).toHaveClass('dnb-toggle-button--checked')

expect(option3.querySelector('button')).toHaveAttribute(
'aria-pressed',
'false'
)
expect(option3).not.toHaveClass('dnb-toggle-button--checked')
})
}
)
})

0 comments on commit 35427d7

Please sign in to comment.