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(Dropdown): add support for numeric values by using selectedKey or object based data entries #2666

Merged
merged 3 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ You may check out the [DrawerList Properties](#drawerlist-properties) down below
| Properties | Description |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | _(optional)_ give a title to let the users know what they have to do. Defaults to `Valgmeny`. |
| `value` | _(optional)_ define a preselected data entry (index). More info down below. |
| `variant` | _(optional)_ defines the kind of dropdown. Possible values are `primary`, `secondary`, `tertiary` and `signal`. Defaults to `secondary`. |
| `icon` | _(optional)_ icon to be included in the dropdown. |
| `icon_size` | _(optional)_ change the size of the icon pragmatically. |
Expand Down Expand Up @@ -43,6 +44,12 @@ You may check out the [DrawerList Properties](#drawerlist-properties) down below

<DrawerListProperties />

## Value

Should either be an index (integer) of the data array or a key – defined by `selectedKey` (the deprecated `selected_key` should not start with a number) inside an array item.

If `data` is an object, use the object key as the `value` to define the selected item. Can be a string or integer.

## Data structure

```js
Expand All @@ -51,7 +58,7 @@ const data = [
// Every data item can, beside "content" - contain what ever
{
// (optional) can be what ever
selected_key: 'key_0',
selectedKey: 'key_0',

// (optional) is show instead of "content", once selected
selected_value: 'Item 1 Value',
Expand All @@ -63,17 +70,17 @@ const data = [

// more items ...
{
selected_key: 'key_1',
selectedKey: 'key_1',
content: ['Item 2 Value', 'Item 2 Content'],
},
{
selected_key: 'key_2',
selectedKey: 'key_2',
selected_value: 'Item 3 Value',
suffix_value: 'Addition 3',
content: ['Item 3 Content A', 'Item 3 Content B'],
},
{
selected_key: 'key_3',
selectedKey: 'key_3',
selected_value: 'Item 4 Value',
suffix_value: 'Addition 4',
content: ['Item 4 Content A', <>Custom Component</>],
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ export default class Autocomplete extends React.PureComponent {
PropTypes.oneOfType([
PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
PropTypes.shape({
selectedKey: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selected_key: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selected_value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node,
Expand Down
8 changes: 8 additions & 0 deletions packages/dnb-eufemia/src/components/dropdown/Dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ export default class Dropdown extends React.PureComponent {
PropTypes.oneOfType([
PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
PropTypes.shape({
selectedKey: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selected_key: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selected_value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,74 @@ describe('Dropdown component', () => {
expect(on_select.mock.calls[1][0].data).toStrictEqual(selectedItem) // second call!
})

it('will select correct item on given numeric selectedKey', () => {
const { rerender } = render(
<Dropdown
{...props}
value={20}
data={[
{ selectedKey: 10, content: 'Ten' },
{ selectedKey: 20, content: 'Twenty' },
{ selectedKey: 30, content: 'Thirty' },
]}
/>
)

open()

{
const selectedElement = document.querySelector(
'.dnb-drawer-list__option--selected'
)
expect(selectedElement).toBeInTheDocument()
expect(selectedElement.textContent).toBe('Twenty')
}

keydown(38) // up
keydown(32) // space
open()

{
const selectedElement = document.querySelector(
'.dnb-drawer-list__option--selected'
)
expect(selectedElement).toBeInTheDocument()
expect(selectedElement.textContent).toBe('Ten')
}

rerender(
<Dropdown
{...props}
value={30}
data={{
10: 'Ten',
20: 'Twenty',
30: 'Thirty',
}}
/>
)

{
const selectedElement = document.querySelector(
'.dnb-drawer-list__option--selected'
)
expect(selectedElement).toBeInTheDocument()
expect(selectedElement.textContent).toBe('Thirty')
}

keydown(38) // up
keydown(32) // space
open()

{
const selectedElement = document.querySelector(
'.dnb-drawer-list__option--selected'
)
expect(selectedElement).toBeInTheDocument()
expect(selectedElement.textContent).toBe('Twenty')
}
})

it('has no selected items on using prevent_selection', async () => {
const on_change = jest.fn()
const title = 'custom title'
Expand Down
10 changes: 5 additions & 5 deletions packages/dnb-eufemia/src/extensions/forms/Field/CountryCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,27 @@ function CountryCode(props: Props) {
const autocompleteData = useMemo(
() =>
countries.map((country) => ({
selected_key: `+${country.code}`,
selectedKey: `+${country.code}`,
selected_value: `${country.iso} (+${country.code})`,
content: `+${country.code} ${country.name}`,
})),
[]
)

const forwardHandleChange = useCallback(
({ data: changedData }: { data: { selected_key: string } }) => {
if (!changedData || !changedData.selected_key.trim()) {
({ data: changedData }: { data: { selectedKey: string } }) => {
if (!changedData || !changedData.selectedKey.trim()) {
handleChange?.(emptyValue)
return
}

handleChange?.(changedData?.selected_key)
handleChange?.(changedData?.selectedKey)
},
[emptyValue, handleChange]
)

const valueIndex = autocompleteData.findIndex(
(item) => item.selected_key === value
(item) => item.selectedKey === value
)

return (
Expand Down
16 changes: 8 additions & 8 deletions packages/dnb-eufemia/src/extensions/forms/Field/Selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ function Selection(props: Props) {
} = useDataValue(props)

const handleDropdownChange = useCallback(
({ data: { selected_key } }) => {
({ data: { selectedKey } }) => {
handleChange?.(
!selected_key || selected_key === clearValue
!selectedKey || selectedKey === clearValue
? emptyValue
: selected_key
: selectedKey
)
},
[handleChange, emptyValue, clearValue]
Expand All @@ -80,14 +80,14 @@ function Selection(props: Props) {
// copies of value as arguments.
const handleShow = useCallback(
({ data }) => {
setHasFocus(true, data?.selected_key)
setHasFocus(true, data?.selectedKey)
},
[setHasFocus]
)

const handleHide = useCallback(
({ data }) => {
setHasFocus(false, data?.selected_key)
setHasFocus(false, data?.selectedKey)
},
[setHasFocus]
)
Expand Down Expand Up @@ -172,7 +172,7 @@ function Selection(props: Props) {
// Option components
return child.props.text
? {
selected_key: String(child.props.value ?? ''),
selectedKey: String(child.props.value ?? ''),
content: [
child.props.children ?? child.props.title ?? (
<em>Untitled</em>
Expand All @@ -181,7 +181,7 @@ function Selection(props: Props) {
],
}
: {
selected_key: child.props.value,
selectedKey: child.props.value,
content: child.props.children ?? child.props.title,
}
}
Expand All @@ -194,7 +194,7 @@ function Selection(props: Props) {
const data = [
clear
? {
selected_key: clearValue,
selectedKey: clearValue,
content: (
<em>
{sharedContext?.translation.Forms.selectionClearSelected}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ describe('Selection', () => {
expect(screen.queryByText('Fooo')).not.toBeInTheDocument()
})

it('renders selected option', () => {
render(
<Field.Selection value="20">
<Field.Option value="10" title="Ten" />
<Field.Option value="20" title="Twenty" />
<Field.Option value="30" title="Thirty" />
</Field.Selection>
)
expect(screen.getByText('Twenty')).toBeInTheDocument()
expect(screen.queryByText('Ten')).not.toBeInTheDocument()
expect(screen.queryByText('Thirty')).not.toBeInTheDocument()
})

it('should change option based on external value change', async () => {
const { rerender } = render(
<Field.Selection value="bar">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export type DrawerListDefaultValue = string | number;
export type DrawerListValue = string | number;
export type DrawerListDataObject = {
selected_value?: string | React.ReactNode;
selected_key?: string | number | React.ReactNode;
selectedKey?: string | number;
/** @deprecated Use "selectedKey" instead */
selected_key?: string | number;
suffix_value?: string | React.ReactNode;
content?: string | React.ReactNode | string[];
search_content?: string | React.ReactNode | string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ export const drawerListPropTypes = {
PropTypes.oneOfType([
PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
PropTypes.shape({
selectedKey: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selected_key: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
selected_value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node,
Expand Down Expand Up @@ -274,11 +282,11 @@ export const normalizeData = (props) => {

if (data && typeof data === 'object' && !Array.isArray(data)) {
const list = []
for (let i in data) {
for (const key in data) {
list.push({
selected_key: i,
value: i,
content: data[i],
selected_key: key,
value: key,
content: data[key],
type: 'object',
})
}
Expand Down Expand Up @@ -308,16 +316,36 @@ export const getData = (props) => {
}

export const getCurrentIndex = (value, data) => {
// if a key is given as a not numeric value
// 1. if a non-numeric value is given
if (/[^0-9]/.test(String(value))) {
return data?.findIndex((cur) => parseCurrentValue(cur) === value)
}
// is numeric
else if (parseFloat(value) > -1) {
// 2. if "selectedKey" is given in data, we now handle it as a value, and not an index.
else if (selectedKeyExists()) {
return data?.findIndex(
(cur) => String(parseCurrentValue(cur)) === String(value)
)
}
// 3. if is numeric, handle it as a index.
else if (!isNaN(parseFloat(value))) {
return value
}

return null

function selectedKeyExists() {
for (let i = 0, l = data?.length; i < l; i++) {
if (i > 10) {
return false
}
if (
typeof data[i]?.selectedKey !== 'undefined' ||
data[i]?.type === 'object'
) {
return true
}
}
}
}

export const getSelectedItemValue = (value, state) => {
Expand All @@ -331,6 +359,9 @@ export const getSelectedItemValue = (value, state) => {
}

export const parseCurrentValue = (current) => {
if (typeof current?.selectedKey !== 'undefined') {
return current?.selectedKey
}
if (typeof current?.selected_key !== 'undefined') {
return current?.selected_key
}
Expand Down