diff --git a/CHANGELOG.md b/CHANGELOG.md index 388abb5d3f..51230c4289 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Edit `buttonBehavior` Adding aria-disabled when button has loading state @kolaps33 ([#1789](https://github.com/stardust-ui/react/pull/1789)) - Added `audio-off` and `clipboard-copied-to` icon to Teams theme @bcalvery ([#1792](https://github.com/stardust-ui/react/pull/1792)) - Export `FocusTrapZoneProps` and `AutoFocusZoneProps` from the main package @sophieH29 ([#1795](https://github.com/stardust-ui/react/pull/1795)) +- Add `checkable` and `checkableIndicator` to the `Dropdown` and `DropdownItem` components @mnajdova ([#1738](https://github.com/stardust-ui/react/pull/1738)) ### Documentation - Restore docs for `Ref` component @layershifter ([#1777](https://github.com/stardust-ui/react/pull/1777)) diff --git a/docs/src/examples/components/Dropdown/Types/DropdownExample.shorthand.tsx b/docs/src/examples/components/Dropdown/Types/DropdownExample.shorthand.tsx index 294f37f537..6fd54cbc62 100644 --- a/docs/src/examples/components/Dropdown/Types/DropdownExample.shorthand.tsx +++ b/docs/src/examples/components/Dropdown/Types/DropdownExample.shorthand.tsx @@ -17,6 +17,7 @@ const DropdownExample = () => ( `${item} has been selected.` }} /> ) diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx index deb393db05..d72bd189ae 100644 --- a/packages/react/src/components/Dropdown/Dropdown.tsx +++ b/packages/react/src/components/Dropdown/Dropdown.tsx @@ -61,6 +61,12 @@ export interface DropdownProps /** The index of the currently active selected item, if dropdown has a multiple selection. */ activeSelectedIndex?: number + /** Item can show check indicator if selected. */ + checkable?: boolean + + /** A slot for a selected indicator in the dropdown list. */ + checkableIndicator?: ShorthandValue + /** A dropdown can be clearable and let users remove their selection. */ clearable?: boolean @@ -250,6 +256,8 @@ class Dropdown extends AutoControlledComponent, Dropdo }), activeSelectedIndex: PropTypes.number, align: PropTypes.oneOf(ALIGNMENTS), + checkable: PropTypes.bool, + checkableIndicator: customPropTypes.itemShorthandWithoutJSX, clearable: PropTypes.bool, clearIndicator: customPropTypes.itemShorthand, defaultActiveSelectedIndex: PropTypes.number, @@ -285,7 +293,7 @@ class Dropdown extends AutoControlledComponent, Dropdo search: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), searchQuery: PropTypes.string, searchInput: customPropTypes.itemShorthand, - toggleIndicator: customPropTypes.itemShorthand, + toggleIndicator: customPropTypes.itemShorthandWithoutJSX, triggerButton: customPropTypes.itemShorthand, unstable_pinned: PropTypes.bool, value: PropTypes.oneOfType([ @@ -297,6 +305,7 @@ class Dropdown extends AutoControlledComponent, Dropdo static defaultProps = { align: 'start', as: 'div', + checkableIndicator: 'stardust-checkmark', clearIndicator: 'stardust-close', itemToString: item => { if (!item || React.isValidElement(item)) { @@ -694,16 +703,26 @@ class Dropdown extends AutoControlledComponent, Dropdo highlightedIndex: number, value: ShorthandValue | ShorthandCollection, ) { - const { loading, loadingMessage, noResultsMessage, renderItem } = this.props + const { + loading, + loadingMessage, + noResultsMessage, + renderItem, + checkable, + checkableIndicator, + } = this.props const { filteredItems } = this.state const items = _.map(filteredItems, (item, index) => render => render(item, () => { + const selected = !this.props.multiple && value === item return DropdownItem.create(item, { defaultProps: { className: Dropdown.slotClassNames.item, active: highlightedIndex === index, - selected: !this.props.multiple && value === item, + selected, + checkable, + checkableIndicator, isFromKeyboard: this.state.itemIsFromKeyboard, variables, ...(typeof item === 'object' && @@ -711,7 +730,7 @@ class Dropdown extends AutoControlledComponent, Dropdo key: (item as any).header, }), }, - overrideProps: this.handleItemOverrides(item, index, getItemProps), + overrideProps: this.handleItemOverrides(item, index, getItemProps, selected), render: renderItem, }) }), @@ -819,16 +838,23 @@ class Dropdown extends AutoControlledComponent, Dropdo item: ShorthandValue, index: number, getItemProps: (options: GetItemPropsOptions>) => any, + selected: boolean, ) => (predefinedProps: DropdownItemProps) => ({ - accessibilityItemProps: getItemProps({ - item, - index, - onClick: e => { - e.stopPropagation() - e.nativeEvent.stopImmediatePropagation() - _.invoke(predefinedProps, 'onClick', e, predefinedProps) - }, - }), + accessibilityItemProps: { + ...getItemProps({ + item, + index, + onClick: e => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + _.invoke(predefinedProps, 'onClick', e, predefinedProps) + }, + }), + // for single selection the selected item should have aria-selected, instead of the highlighted + ...(!this.props.multiple && { + 'aria-selected': selected, + }), + }, }) handleSelectedItemOverrides = (item: ShorthandValue, rtl: boolean) => ( diff --git a/packages/react/src/components/Dropdown/DropdownItem.tsx b/packages/react/src/components/Dropdown/DropdownItem.tsx index 50203c2daf..fbe9518d45 100644 --- a/packages/react/src/components/Dropdown/DropdownItem.tsx +++ b/packages/react/src/components/Dropdown/DropdownItem.tsx @@ -7,6 +7,7 @@ import { UIComponent, RenderResultConfig, createShorthandFactory, commonPropType import { ShorthandValue, ComponentEventHandler, WithAsProp, withSafeTypeForAs } from '../../types' import { UIComponentProps } from '../../lib/commonPropInterfaces' import ListItem from '../List/ListItem' +import Icon, { IconProps } from '../Icon/Icon' import Image, { ImageProps } from '../Image/Image' import Box, { BoxProps } from '../Box/Box' @@ -14,6 +15,7 @@ export interface DropdownItemSlotClassNames { content: string header: string image: string + checkableIndicator: string } export interface DropdownItemProps extends UIComponentProps { @@ -26,6 +28,12 @@ export interface DropdownItemProps extends UIComponentProps { /** Item's content. */ content?: ShorthandValue + /** Item can show check indicator if selected. */ + checkable?: boolean + + /** A slot for a checkable indicator. */ + checkableIndicator?: ShorthandValue + /** Item's header. */ header?: ShorthandValue @@ -65,6 +73,8 @@ class DropdownItem extends UIComponent> { accessibilityItemProps: PropTypes.object, active: PropTypes.bool, content: customPropTypes.itemShorthand, + checkable: PropTypes.bool, + checkableIndicator: customPropTypes.itemShorthandWithoutJSX, header: customPropTypes.itemShorthand, image: customPropTypes.itemShorthandWithoutJSX, onClick: PropTypes.func, @@ -77,7 +87,15 @@ class DropdownItem extends UIComponent> { } renderComponent({ classes, styles, unhandledProps }: RenderResultConfig) { - const { content, header, image, accessibilityItemProps } = this.props + const { + content, + header, + image, + accessibilityItemProps, + selected, + checkable, + checkableIndicator, + } = this.props return ( > { styles: styles.content, }, })} + endMedia={ + selected && + checkable && { + content: Icon.create(checkableIndicator, { + defaultProps: { + className: DropdownItem.slotClassNames.checkableIndicator, + styles: styles.checkableIndicator, + }, + }), + styles: styles.endMedia, + } + } truncateContent truncateHeader {...accessibilityItemProps} @@ -115,6 +145,7 @@ DropdownItem.slotClassNames = { content: `${DropdownItem.className}__content`, header: `${DropdownItem.className}__header`, image: `${DropdownItem.className}__image`, + checkableIndicator: `${DropdownItem.className}__checkable-indicator`, } DropdownItem.create = createShorthandFactory({ Component: DropdownItem, mappedProp: 'header' }) diff --git a/packages/react/src/themes/teams/components/Dropdown/dropdownItemStyles.ts b/packages/react/src/themes/teams/components/Dropdown/dropdownItemStyles.ts index 643a262699..0b9fa4542c 100644 --- a/packages/react/src/themes/teams/components/Dropdown/dropdownItemStyles.ts +++ b/packages/react/src/themes/teams/components/Dropdown/dropdownItemStyles.ts @@ -59,6 +59,13 @@ const dropdownItemStyles: ComponentSlotStylesInput ({ + position: 'relative', + left: pxToRem(3), + }), + endMedia: () => ({ + lineHeight: pxToRem(16), + }), } export default dropdownItemStyles