diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 3f242a7b..378a14d2 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -2,6 +2,8 @@ import cx from 'classnames'; import PropTypes from 'prop-types'; import React, { Children, HTMLProps, ReactNode, Ref } from 'react'; +import { useTypeaheadContext } from '../../core/Context'; + import { BaseMenuItem } from '../MenuItem'; import { preventInputBlur } from '../../utils'; @@ -37,6 +39,7 @@ export interface MenuProps extends HTMLProps { emptyLabel?: ReactNode; innerRef?: Ref; maxHeight?: string; + multiple?: boolean; } /** @@ -57,6 +60,8 @@ const Menu = ({ ) : ( props.children ); + + const { onSelectAllClick } = useTypeaheadContext(); return ( /* eslint-disable jsx-a11y/interactive-supports-focus */ @@ -76,6 +81,7 @@ const Menu = ({ maxHeight, overflow: 'auto', }}> + {props.multiple && } {children} /* eslint-enable jsx-a11y/interactive-supports-focus */ diff --git a/src/components/Typeahead/Typeahead.test.tsx b/src/components/Typeahead/Typeahead.test.tsx index b133bcc2..79760bc0 100644 --- a/src/components/Typeahead/Typeahead.test.tsx +++ b/src/components/Typeahead/Typeahead.test.tsx @@ -1636,4 +1636,23 @@ describe(' with custom menu', () => { expect(items[1]).toHaveClass('active'); expect(items[1]).toHaveTextContent('Wisconsin'); }); + + describe(' with multiselect', () => { + it('Should select all options when select all button is pressed', async () => { + const user = userEvent.setup(); + + const { container } = render(); + const input = getInput() + await user.type(input, 'Te'); + + await user.click(screen.getByRole('button')); + + const tokens = getTokens(container); + + expect(tokens).toHaveLength(2); + + expect(screen.getByText('Tennessee')).toBeInTheDocument(); + expect(screen.getByText('Texas')).toBeInTheDocument(); + }); + }); }); diff --git a/src/components/Typeahead/Typeahead.tsx b/src/components/Typeahead/Typeahead.tsx index b7cd2317..86ec514e 100644 --- a/src/components/Typeahead/Typeahead.tsx +++ b/src/components/Typeahead/Typeahead.tsx @@ -279,6 +279,7 @@ class TypeaheadComponent extends React.Component { paginationText, renderMenu, renderMenuItemChildren, + multiple, } = this.props; return (renderMenu || defaultRenderMenu)( @@ -288,6 +289,7 @@ class TypeaheadComponent extends React.Component { emptyLabel, id, maxHeight, + multiple, newSelectionPrefix, paginationText, renderMenuItemChildren, diff --git a/src/core/Context.tsx b/src/core/Context.tsx index b975e652..31d95e73 100644 --- a/src/core/Context.tsx +++ b/src/core/Context.tsx @@ -14,6 +14,7 @@ export interface TypeaheadContextType { onAdd: OptionHandler; onInitialItemChange: (option?: Option) => void; onMenuItemClick: (option: Option, event: SelectEvent) => void; + onSelectAllClick: () => void; setItem: (option: Option, position: number) => void; } @@ -28,6 +29,7 @@ export const defaultContext = { onAdd: noop, onInitialItemChange: noop, onMenuItemClick: noop, + onSelectAllClick: noop, setItem: noop, }; diff --git a/src/core/Typeahead.tsx b/src/core/Typeahead.tsx index 899e0a79..5221167f 100644 --- a/src/core/Typeahead.tsx +++ b/src/core/Typeahead.tsx @@ -230,6 +230,8 @@ function triggerInputChange(input: HTMLInputElement, value: string) { input.dispatchEvent(e); } +let selectedOptions: Option[]; + class Typeahead extends React.Component { static propTypes = propTypes; static defaultProps = defaultProps; @@ -325,6 +327,7 @@ class Typeahead extends React.Component { onInitialItemChange={this._handleInitialItemChange} onKeyDown={this._handleKeyDown} onMenuItemClick={this._handleMenuItemSelect} + onSelectAllClick={this._handleAddAll} onRemove={this._handleSelectionRemove} results={results} setItem={this.setItem} @@ -510,7 +513,6 @@ class Typeahead extends React.Component { _handleSelectionAdd = (option: Option) => { const { multiple, labelKey } = this.props; - let selected: Option[]; let selection = option; let text: string; @@ -523,12 +525,12 @@ class Typeahead extends React.Component { if (multiple) { // If multiple selections are allowed, add the new selection to the // existing selections. - selected = this.state.selected.concat(selection); + selectedOptions = this.state.selected.concat(selection); text = ''; } else { // If only a single selection is allowed, replace the existing selection // with the new one. - selected = [selection]; + selectedOptions = [selection]; text = getOptionLabel(selection, labelKey); } @@ -536,13 +538,42 @@ class Typeahead extends React.Component { (state, props) => ({ ...hideMenu(state, props), initialItem: selection, - selected, + selected: selectedOptions, text, }), - () => this._handleChange(selected) + () => this._handleChange(selectedOptions) ); }; + _handleAddAll = (results: Option[]) => { + const { multiple } = this.props + + const updatedResults = results.map((result) => { + + if (!isString(result) && result.customOption) { + return { ...result, id: uniqueId('new-id-') }; + } + return result + }) + + if (multiple) { + // If multiple selections are allowed, add the all result to the + // existing selections. + selectedOptions = this.state.selected.concat(updatedResults); + } + + this.setState( + (state, props) => ({ + ...hideMenu(state, props), + // initialItem: selection, + selected: selectedOptions, + text: '', + }), + () => this._handleChange(selectedOptions) + ); + } + + _handleSelectionRemove = (selection: Option) => { const selected = this.state.selected.filter( (option) => !isEqual(option, selection) diff --git a/src/core/TypeaheadManager.tsx b/src/core/TypeaheadManager.tsx index f6544363..7467d478 100644 --- a/src/core/TypeaheadManager.tsx +++ b/src/core/TypeaheadManager.tsx @@ -51,6 +51,7 @@ const contextKeys = [ 'onAdd', 'onInitialItemChange', 'onMenuItemClick', + 'onSelectAllClick', 'setItem', ] as (keyof TypeaheadManagerProps)[]; @@ -66,6 +67,7 @@ const TypeaheadManager = (props: TypeaheadManagerProps) => { onMenuToggle, results, selectHint, + onSelectAllClick } = props; const hintText = getHintText(props); @@ -101,6 +103,10 @@ const TypeaheadManager = (props: TypeaheadManagerProps) => { } }; + const handleSelectAll = () => { + onSelectAllClick(results) + } + const childProps = { ...pick(props, propKeys), getInputProps: getInputProps({ @@ -114,6 +120,7 @@ const TypeaheadManager = (props: TypeaheadManagerProps) => { ...pick(props, contextKeys), hintText, isOnlyResult: getIsOnlyResult(props), + onSelectAllClick: handleSelectAll }; return ( diff --git a/src/types.ts b/src/types.ts index 47839204..047e6599 100644 --- a/src/types.ts +++ b/src/types.ts @@ -142,6 +142,7 @@ export interface TypeaheadManagerProps extends TypeaheadPropsAndState { onHide: () => void; onInitialItemChange: (option?: Option) => void; onMenuItemClick: (option: Option, event: SelectEvent) => void; + onSelectAllClick: (result: Option[]) => void, onRemove: OptionHandler; placeholder?: string; results: Option[];