Skip to content
This repository has been archived by the owner on Aug 21, 2023. It is now read-only.

[HSDS-205] DropList features #976

Merged
merged 6 commits into from Sep 8, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 32 additions & 9 deletions src/components/DropList/DropList.Combobox.jsx
Expand Up @@ -2,10 +2,12 @@ import React, { useState, useRef, useEffect } from 'react'
import { useCombobox } from 'downshift'
import useDeepCompareEffect from 'use-deep-compare-effect'
import { noop } from '../../utilities/other'
import { isFunction } from '../../utilities/is'
import {
itemToString,
isItemSelected,
renderListContents,
isItemHighlightable,
} from './DropList.utils'
import {
getA11ySelectionMessageCommon,
Expand All @@ -25,6 +27,7 @@ function Combobox({
closeOnBlur = true,
closeOnSelection = true,
customEmptyList = null,
customEmptyListItems,
'data-cy': dataCy = `DropList.${VARIANTS.COMBOBOX}`,
handleSelectedItemChange = noop,
inputPlaceholder = 'Search',
Expand All @@ -41,6 +44,11 @@ function Combobox({
withMultipleSelection = false,
}) {
const [inputItems, setInputItems] = useState(items)
const isListEmpty = items.length === 0
const allItems =
isListEmpty && Array.isArray(customEmptyListItems)
? customEmptyListItems
: inputItems
const inputEl = useRef(null)

const {
Expand All @@ -50,11 +58,12 @@ function Combobox({
getMenuProps,
highlightedIndex,
inputValue,
setHighlightedIndex,
} = useCombobox({
initialInputValue: '',
initialIsOpen: isOpen,
isOpen,
items: inputItems,
items: allItems,
itemToString,
selectedItem,

Expand All @@ -67,11 +76,26 @@ function Combobox({
},

onInputValueChange({ inputValue }) {
setInputItems(
items.filter(item =>
itemToString(item).toLowerCase().startsWith(inputValue.toLowerCase())
)
let filtered = items.filter(item =>
itemToString(item).toLowerCase().startsWith(inputValue.toLowerCase())
)
const isListEmpty = filtered.length === 0

if (isListEmpty && Array.isArray(customEmptyListItems)) {
const processed = customEmptyListItems.map(item => {
if (isFunction(item.customizeLabel)) {
item.label = item.customizeLabel(inputValue)
item.inputValue = inputValue
}

return item
})

filtered = processed
}

setHighlightedIndex(filtered.findIndex(isItemHighlightable))
setInputItems(filtered)
},

onIsOpenChange(changes) {
Expand Down Expand Up @@ -103,7 +127,7 @@ function Combobox({
return stateReducerCommon({
changes,
closeOnSelection,
items,
items: allItems,
selectedItems,
state,
type: `${VARIANTS.COMBOBOX}.${type}`,
Expand Down Expand Up @@ -160,7 +184,7 @@ function Combobox({
menuCSS={menuCSS}
{...getComboboxProps()}
>
<InputSearchHolderUI show={items.length > 0}>
<InputSearchHolderUI show={allItems.length > 0}>
<input
data-event-driver
{...getInputProps({
Expand Down Expand Up @@ -201,9 +225,8 @@ function Combobox({
>
{renderListContents({
customEmptyList,
emptyList: items.length === 0,
inputValue,
items: inputItems,
items: allItems,
renderListItem,
})}
</MenuListUI>
Expand Down
7 changes: 3 additions & 4 deletions src/components/DropList/DropList.ListItem.jsx
Expand Up @@ -3,9 +3,9 @@ import classNames from 'classnames'
import { isFunction, isObject, isString } from '../../utilities/is'
import {
getItemContentKeyName,
isItemAction,
isItemADivider,
isItemAGroupLabel,
isItemReset,
objectHasKey,
} from './DropList.utils'
import {
Expand Down Expand Up @@ -37,7 +37,6 @@ const ListItem = forwardRef(
}

const contentKey = getItemContentKeyName(item)
const isReset = isItemReset(item)

if (isItemAGroupLabel(item)) {
return (
Expand All @@ -55,7 +54,7 @@ const ListItem = forwardRef(
'DropListItem',
isSelected && 'is-selected',
isDisabled && 'is-disabled',
isReset && 'is-reset-item',
objectHasKey(item, 'type') && `is-type-${item.type}`,
highlightedIndex === index && 'is-highlighted',
withMultipleSelection && 'with-multiple-selection',
isString(extraClassNames) && extraClassNames,
Expand Down Expand Up @@ -93,7 +92,7 @@ const ListItem = forwardRef(
<ListItemTextUI>
{isObject(item) ? item[contentKey] : item}
</ListItemTextUI>
{withMultipleSelection && !isReset ? (
{withMultipleSelection && !isItemAction(item) ? (
<SelectedBadge isSelected={isSelected} />
) : null}
</ListItemUI>
Expand Down
15 changes: 10 additions & 5 deletions src/components/DropList/DropList.Select.jsx
Expand Up @@ -20,6 +20,7 @@ function Select({
closeOnBlur = true,
closeOnSelection = true,
customEmptyList = null,
customEmptyListItems,
'data-cy': dataCy = `DropList.${VARIANTS.SELECT}`,
enableLeftRightNavigation = false,
handleSelectedItemChange = noop,
Expand All @@ -35,6 +36,11 @@ function Select({
toggleOpenedState = noop,
withMultipleSelection = false,
}) {
const isListEmpty = items.length === 0
const allItems =
isListEmpty && Array.isArray(customEmptyListItems)
? customEmptyListItems
: items
const {
highlightedIndex,
getItemProps,
Expand All @@ -44,7 +50,7 @@ function Select({
} = useSelect({
initialIsOpen: isOpen,
isOpen,
items,
items: allItems,
itemToString,
selectedItem,

Expand Down Expand Up @@ -85,7 +91,7 @@ function Select({
return stateReducerCommon({
changes,
closeOnSelection,
items,
items: allItems,
selectedItems,
state,
type: `${VARIANTS.SELECT}.${type}`,
Expand Down Expand Up @@ -129,7 +135,7 @@ function Select({
function handleMenuKeyDown(event) {
if (enableLeftRightNavigation) {
if (event.key === 'ArrowRight') {
if (highlightedIndex !== items.length - 1) {
if (highlightedIndex !== allItems.length - 1) {
setHighlightedIndex(highlightedIndex + 1)
}
} else if (event.key === 'ArrowLeft') {
Expand Down Expand Up @@ -195,8 +201,7 @@ function Select({
>
{renderListContents({
customEmptyList,
emptyList: items.length === 0,
items,
items: allItems,
renderListItem,
})}
</MenuListUI>
Expand Down
3 changes: 2 additions & 1 deletion src/components/DropList/DropList.constants.js
Expand Up @@ -4,10 +4,11 @@ export const VARIANTS = {
}

export const ITEM_TYPES = {
ACTION: 'action',
DIVIDER: 'divider',
GROUP: 'group',
GROUP_LABEL: 'group_label',
RESET_DROPLIST: 'reset_droplist',
INERT: 'inert',
}

export const DROPLIST_TOGGLER = 'DropListToggler'
Expand Down
19 changes: 5 additions & 14 deletions src/components/DropList/DropList.css.js
Expand Up @@ -129,20 +129,11 @@ export const ListItemUI = styled('li')`
cursor: default;
}

&.is-reset-item {
height: 50px;
margin: 0 5px;
padding: 0 15px;
line-height: 50px;
color: ${getColor('charcoal.300')};

&.is-highlighted,
&:hover {
color: ${getColor('charcoal.300')};
text-decoration: underline;
cursor: pointer;
background-color: transparent;
}
&.is-type-inert,
&.is-highlighted.is-type-inert,
&.with-multiple-selection.is-highlighted.is-type-inert {
background-color: transparent;
cursor: default;
}
`

Expand Down
1 change: 0 additions & 1 deletion src/components/DropList/DropList.downshift.common.js
Expand Up @@ -22,7 +22,6 @@ export function stateReducerCommon({
case `${COMBOBOX}.${useCombobox.stateChangeTypes.InputChange}`:
return {
...changes,
highlightedIndex: 0,
}

case `${COMBOBOX}.${useCombobox.stateChangeTypes.InputBlur}`:
Expand Down
34 changes: 18 additions & 16 deletions src/components/DropList/DropList.jsx
Expand Up @@ -12,14 +12,15 @@ import {
flattenListItems,
getDropListVariant,
getItemContentKeyName,
isItemReset,
isItemAction,
isItemRegular,
isTogglerOfType,
itemToString,
parseSelectionFromProps,
removeItemFromArray,
requiredItemPropsCheck,
useWarnings,
isItemInert,
} from './DropList.utils'
import {
SimpleButton,
Expand All @@ -36,6 +37,7 @@ function DropListManager({
closeOnClickOutside = true,
closeOnSelection = true,
customEmptyList = null,
customEmptyListItems,
'data-cy': dataCy,
enableLeftRightNavigation = false,
focusTogglerOnMenuClose = true,
Expand All @@ -55,7 +57,6 @@ function DropListManager({
toggler = {},
variant = VARIANTS.SELECT,
withMultipleSelection = false,
withResetSelectionItem,
}) {
const [isOpen, setOpenedState] = useState(false)
const tippyInstanceRef = useRef(null)
Expand All @@ -68,7 +69,7 @@ function DropListManager({
withMultipleSelection ? parsedSelection : []
)
const [parsedItems, setParsedItems] = useState(
flattenListItems(items, withMultipleSelection && withResetSelectionItem)
flattenListItems(items, withMultipleSelection)
)

const { getCurrentScope } = useContext(GlobalContext) || {}
Expand All @@ -95,10 +96,8 @@ function DropListManager({
}, [{ state: parsedSelection }, withMultipleSelection])

useDeepCompareEffect(() => {
setParsedItems(
flattenListItems(items, withMultipleSelection && withResetSelectionItem)
)
}, [items, withMultipleSelection, withResetSelectionItem])
setParsedItems(flattenListItems(items, withMultipleSelection))
}, [items, withMultipleSelection])

useEffect(() => {
setOpenedState(isMenuOpen)
Expand Down Expand Up @@ -163,7 +162,12 @@ function DropListManager({
return
}

if (selectedItem.isDisabled) {
if (selectedItem.isDisabled || isItemInert(selectedItem)) {
return
}

if (isItemAction(selectedItem)) {
onSelect(null, selectedItem)
return
}

Expand All @@ -188,8 +192,6 @@ function DropListManager({
item: itemToRemove,
key: contentKey,
})
} else if (isItemReset(selectedItem)) {
updatedSelection = selectedItems
}
}

Expand Down Expand Up @@ -267,6 +269,7 @@ function DropListManager({
closeOnBlur={closeOnBlur}
closeOnSelection={closeOnSelection}
customEmptyList={customEmptyList}
customEmptyListItems={customEmptyListItems}
data-cy={dataCy}
enableLeftRightNavigation={enableLeftRightNavigation}
handleSelectedItemChange={handleSelectedItemChange}
Expand Down Expand Up @@ -328,8 +331,12 @@ DropListManager.propTypes = {
closeOnClickOutside: PropTypes.bool,
/** Whether to close the DropList when an item is selected */
closeOnSelection: PropTypes.bool,
/** Pass a React Element to render a custom message or style when the List is empty */
/** Pass an Element to render a custom message or style when the List is empty */
customEmptyList: PropTypes.any,
/** To render "extra" items when the list is empty, as opposed to just customizind the rendering like `customEmptyList` does */
customEmptyListItems: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, itemShape, dividerShape, groupShape])
),
/** Data attr applied to the DropList for Cypress tests. By default one of 'DropList.Select' or 'DropList.Combobox' depending on the variant used */
'data-cy': PropTypes.string,
/** Enable navigation with Right and Left arrows (useful for horizontally rendered lists) */
Expand Down Expand Up @@ -374,11 +381,6 @@ DropListManager.propTypes = {
variant: PropTypes.oneOf(['select', 'Select', 'combobox', 'Combobox']),
/** Enable multiple selection of items */
withMultipleSelection: PropTypes.bool,
/** Adds an "inert" item at the end of the list with a type of `reset_droplist`, use it to implement a "Clear Selection" or "Reset to defaults" type of option */
withResetSelectionItem: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]),
}

export default DropListManager