Skip to content

Commit

Permalink
Merge pull request #709 from vrk-kpa/feature/dropdown-disabled-option
Browse files Browse the repository at this point in the history
[Feature] DropdownItem disabled option
  • Loading branch information
jenkrisu authored Apr 17, 2023
2 parents 305540a + 621322a commit dec2ccc
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 37 deletions.
32 changes: 32 additions & 0 deletions src/core/Dropdown/Dropdown/Dropdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,38 @@ const exampleRef = createRef();
</>;
```

```js
import React from 'react';
import { Dropdown, DropdownItem } from 'suomifi-ui-components';

const [selectedValue, setSelectedValue] = React.useState(null);
const [status, setStatus] = React.useState('default');

<Dropdown
name="dropdown_example_1"
labelText="Dropdown with disabled option"
hintText="Some informative text"
status={status}
visualPlaceholder="Select a value"
>
<DropdownItem value={'dropdown-item-1'}>
Dropdown Item 1
</DropdownItem>
<DropdownItem value={'dropdown-item-2'}>
Dropdown Item 2
</DropdownItem>
<DropdownItem value={'dropdown-item-3'} disabled>
Dropdown Item 3
</DropdownItem>
<DropdownItem value={'dropdown-item-4'}>
Dropdown Item 4
</DropdownItem>
<DropdownItem value={'dropdown-item-5'}>
Dropdown Item 5
</DropdownItem>
</Dropdown>;
```

```js
import { useState } from 'react';
import { Dropdown, DropdownItem } from 'suomifi-ui-components';
Expand Down
37 changes: 37 additions & 0 deletions src/core/Dropdown/Dropdown/Dropdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,43 @@ describe('Dropdown with additional aria-label', () => {
});
});

describe('DropdownOption', () => {
it('should be selected when item is clicked', async () => {
const BasicDropdown = TestDropdown({ labelText: 'Dropdown' });
const { getByRole, getAllByRole } = render(BasicDropdown);
const menuButton = getByRole('button') as HTMLButtonElement;
await act(async () => {
fireEvent.click(menuButton);
});
const option = getAllByRole('option')[0];
await act(async () => {
fireEvent.click(option);
});
expect(menuButton).toHaveTextContent('Item 1');
});

it('should not be selected when disabled', async () => {
const BasicDropdown = (
<Dropdown labelText="Dropdown" visualPlaceholder="Select value">
<DropdownItem value={'item-1'} disabled>
Item 1
</DropdownItem>
<DropdownItem value={'item-2'}>Item 2</DropdownItem>
</Dropdown>
);
const { getByRole, getAllByRole } = render(BasicDropdown);
const menuButton = getByRole('button') as HTMLButtonElement;
await act(async () => {
fireEvent.click(menuButton);
});
const option = getAllByRole('option')[0];
await act(async () => {
fireEvent.click(option);
});
expect(menuButton).toHaveTextContent('Select value');
});
});

describe('statusText', () => {
it('has status text defined by prop', () => {
const statusTextProps: DropdownProps = {
Expand Down
61 changes: 28 additions & 33 deletions src/core/Dropdown/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,36 @@ class BaseDropdown extends Component<DropdownProps> {
this.setState({ showPopover: false });
}

private handleSpaceAndEnter = (
popoverItems: Array<ReactElement<DropdownItemProps>>,
getNextItem: () => ReactElement<DropdownItemProps>,
) => {
const { focusedDescendantId, showPopover } = this.state;
if (!showPopover) {
this.openPopover();
if (!focusedDescendantId) {
const nextItem = getNextItem();
if (nextItem) {
this.setState({ focusedDescendantId: nextItem.props.value });
}
}
} else if (showPopover && focusedDescendantId) {
const focusedItem = popoverItems.find(
(item) => item?.props.value === focusedDescendantId,
);
if (focusedItem && !focusedItem.props.disabled) {
this.handleItemSelection(focusedItem.props.value);
}
}
};

private handleKeyDown = (event: React.KeyboardEvent) => {
const { focusedDescendantId, showPopover } = this.state;
const popoverItems = Array.isArray(this.props.children)
? this.props.children
: [this.props.children];
: this.props.children !== undefined
? [this.props.children]
: undefined;
if (!popoverItems) return;
const index = !!focusedDescendantId
? popoverItems.findIndex(
Expand Down Expand Up @@ -298,43 +323,13 @@ class BaseDropdown extends Component<DropdownProps> {
case ' ': {
event.preventDefault();
this.setState({ showPopover: !showPopover });
if (!showPopover) {
this.openPopover();
if (!focusedDescendantId) {
const nextItem = getNextItem();
if (nextItem) {
this.setState({ focusedDescendantId: nextItem.props.value });
}
}
} else if (showPopover && focusedDescendantId) {
const focusedItem = popoverItems.find(
(item) => item?.props.value === focusedDescendantId,
);
if (focusedItem) {
this.handleItemSelection(focusedItem.props.value);
}
}
this.handleSpaceAndEnter(popoverItems, getNextItem);
break;
}

case 'Enter': {
event.preventDefault();
if (!showPopover) {
this.openPopover();
if (!focusedDescendantId) {
const nextItem = getNextItem();
if (nextItem) {
this.setState({ focusedDescendantId: nextItem.props.value });
}
}
} else if (focusedDescendantId && showPopover) {
const focusedItem = popoverItems.find(
(item) => item?.props.value === focusedDescendantId,
);
if (focusedItem) {
this.handleItemSelection(focusedItem.props.value);
}
}
this.handleSpaceAndEnter(popoverItems, getNextItem);
break;
}

Expand Down
33 changes: 33 additions & 0 deletions src/core/Dropdown/Dropdown/__snapshots__/Dropdown.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,15 @@ exports[`Basic dropdown should match snapshot 1`] = `
color: hsl(0,0%,100%);
}
.c12.fi-dropdown_item--disabled {
color: hsl(202,7%,67%);
cursor: not-allowed;
}
.c12.fi-dropdown_item--disabled:hover {
color: hsl(202,7%,67%);
}
@media (forced-colors:active) {
.c12.fi-dropdown_item--hasKeyboardFocus,
.c12.fi-dropdown_item:hover {
Expand Down Expand Up @@ -652,6 +661,7 @@ exports[`Basic dropdown should match snapshot 1`] = `
class="c2 fi-select-item-list_content_wrapper"
>
<li
aria-disabled="false"
aria-selected="false"
class="c11 c12 fi-dropdown_item fi-dropdown_item--hasKeyboardFocus"
id="test-id-item-1"
Expand All @@ -661,6 +671,7 @@ exports[`Basic dropdown should match snapshot 1`] = `
Item 1
</li>
<li
aria-disabled="false"
aria-selected="false"
class="c11 c12 fi-dropdown_item"
id="test-id-item-2"
Expand Down Expand Up @@ -1269,6 +1280,15 @@ exports[`Controlled Dropdown should match snapshot 1`] = `
color: hsl(0,0%,100%);
}
.c12.fi-dropdown_item--disabled {
color: hsl(202,7%,67%);
cursor: not-allowed;
}
.c12.fi-dropdown_item--disabled:hover {
color: hsl(202,7%,67%);
}
@media (forced-colors:active) {
.c12.fi-dropdown_item--hasKeyboardFocus,
.c12.fi-dropdown_item:hover {
Expand Down Expand Up @@ -1354,6 +1374,7 @@ exports[`Controlled Dropdown should match snapshot 1`] = `
class="c2 fi-select-item-list_content_wrapper"
>
<li
aria-disabled="false"
aria-selected="false"
class="c11 c12 fi-dropdown_item"
id="test-id-item-1"
Expand All @@ -1363,6 +1384,7 @@ exports[`Controlled Dropdown should match snapshot 1`] = `
Item 1
</li>
<li
aria-disabled="false"
aria-selected="true"
class="c11 c12 fi-dropdown_item fi-dropdown_item--hasKeyboardFocus fi-dropdown_item--selected"
id="test-id-item-2"
Expand Down Expand Up @@ -1963,6 +1985,15 @@ exports[`Dropdown with additional aria-label should match snapshot 1`] = `
color: hsl(0,0%,100%);
}
.c12.fi-dropdown_item--disabled {
color: hsl(202,7%,67%);
cursor: not-allowed;
}
.c12.fi-dropdown_item--disabled:hover {
color: hsl(202,7%,67%);
}
@media (forced-colors:active) {
.c12.fi-dropdown_item--hasKeyboardFocus,
.c12.fi-dropdown_item:hover {
Expand Down Expand Up @@ -2046,6 +2077,7 @@ exports[`Dropdown with additional aria-label should match snapshot 1`] = `
class="c2 fi-select-item-list_content_wrapper"
>
<li
aria-disabled="false"
aria-selected="false"
class="c11 c12 fi-dropdown_item fi-dropdown_item--hasKeyboardFocus"
id="test-id-item-1"
Expand All @@ -2055,6 +2087,7 @@ exports[`Dropdown with additional aria-label should match snapshot 1`] = `
Item 1
</li>
<li
aria-disabled="false"
aria-selected="false"
class="c11 c12 fi-dropdown_item"
id="test-id-item-2"
Expand Down
9 changes: 9 additions & 0 deletions src/core/Dropdown/DropdownItem/DropdownItem.basestyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export const baseStyles = (theme: SuomifiTheme) => css`
}
}
&--disabled {
color: ${theme.colors.depthBase};
cursor: not-allowed;
&:hover {
color: ${theme.colors.depthBase};
}
}
@media (forced-colors: active) {
&--hasKeyboardFocus,
&:hover {
Expand Down
27 changes: 23 additions & 4 deletions src/core/Dropdown/DropdownItem/DropdownItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export interface DropdownItemProps {
children: ReactNode;
/** Classname for item */
className?: string;
/** Disables dropdown option
* @default false
*/
disabled?: boolean;
}

interface BaseDropdownItemProps extends DropdownItemProps {
Expand All @@ -27,31 +31,46 @@ interface BaseDropdownItemProps extends DropdownItemProps {
const dropdownItemClassNames = {
hasKeyboardFocus: `${dropdownClassNames.item}--hasKeyboardFocus`,
selected: `${dropdownClassNames.item}--selected`,
disabled: `${dropdownClassNames.item}--disabled`,
noSelectedStyles: `${dropdownClassNames.item}--noSelectedStyles`,
icon: `${dropdownClassNames.item}_icon`,
};

const BaseDropdownItem = (props: BaseDropdownItemProps & SuomifiThemeProp) => {
const { children, className, theme, consumer, value, ...passProps } = props;
const {
children,
className,
theme,
consumer,
value,
disabled = false,
...passProps
} = props;
const selected = consumer.selectedDropdownValue === value;
const hasKeyboardFocus = consumer.focusedItemValue === value;

const listElementId = `${consumer.id}-${value}`;

const handleClick = () => {
if (!disabled) {
consumer.onItemClick(value);
}
};

return (
<HtmlLi
className={classnames(className, dropdownClassNames.item, {
[dropdownItemClassNames.hasKeyboardFocus]: hasKeyboardFocus,
[dropdownItemClassNames.selected]: selected,
[dropdownItemClassNames.disabled]: disabled,
[dropdownItemClassNames.noSelectedStyles]: consumer.noSelectedStyles,
})}
tabIndex={-1}
role="option"
aria-disabled={disabled}
aria-selected={selected}
id={listElementId}
onClick={() => {
consumer.onItemClick(value);
}}
onClick={handleClick}
{...passProps}
>
{children}
Expand Down

0 comments on commit dec2ccc

Please sign in to comment.