Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ exports[`data toolbar DataToolbarFilter 1`] = `
isChecked={false}
isDisabled={false}
isFocused={false}
isNoResultsOption={false}
isPlaceholder={false}
isSelected={false}
keyHandler={[Function]}
Expand All @@ -185,6 +186,7 @@ exports[`data toolbar DataToolbarFilter 1`] = `
isChecked={false}
isDisabled={false}
isFocused={false}
isNoResultsOption={false}
isPlaceholder={false}
isSelected={false}
keyHandler={[Function]}
Expand Down Expand Up @@ -213,6 +215,7 @@ exports[`data toolbar DataToolbarFilter 1`] = `
createText="Create"
customContent={null}
direction="down"
hasInlineFilter={false}
isCreatable={false}
isDisabled={false}
isExpanded={false}
Expand Down Expand Up @@ -250,6 +253,7 @@ exports[`data toolbar DataToolbarFilter 1`] = `
ariaLabelledBy=" pf-toggle-id-4"
className=""
handleTypeaheadKeys={[Function]}
hasClearButton={false}
id="pf-toggle-id-4"
isActive={false}
isDisabled={false}
Expand Down Expand Up @@ -884,6 +888,7 @@ exports[`data toolbar DataToolbarFilter 1`] = `
isChecked={false}
isDisabled={false}
isFocused={false}
isNoResultsOption={false}
isPlaceholder={false}
isSelected={false}
keyHandler={[Function]}
Expand All @@ -898,6 +903,7 @@ exports[`data toolbar DataToolbarFilter 1`] = `
isChecked={false}
isDisabled={false}
isFocused={false}
isNoResultsOption={false}
isPlaceholder={false}
isSelected={false}
keyHandler={[Function]}
Expand Down Expand Up @@ -926,6 +932,7 @@ exports[`data toolbar DataToolbarFilter 1`] = `
createText="Create"
customContent={null}
direction="down"
hasInlineFilter={false}
isCreatable={false}
isDisabled={false}
isExpanded={false}
Expand Down Expand Up @@ -963,6 +970,7 @@ exports[`data toolbar DataToolbarFilter 1`] = `
ariaLabelledBy=" pf-toggle-id-5"
className=""
handleTypeaheadKeys={[Function]}
hasClearButton={false}
id="pf-toggle-id-5"
isActive={false}
isDisabled={false}
Expand Down Expand Up @@ -2345,6 +2353,7 @@ exports[`data toolbar DataToolbarToggleGroup 1`] = `
isChecked={false}
isDisabled={false}
isFocused={false}
isNoResultsOption={false}
isPlaceholder={false}
isSelected={false}
keyHandler={[Function]}
Expand All @@ -2359,6 +2368,7 @@ exports[`data toolbar DataToolbarToggleGroup 1`] = `
isChecked={false}
isDisabled={false}
isFocused={false}
isNoResultsOption={false}
isPlaceholder={false}
isSelected={false}
keyHandler={[Function]}
Expand Down Expand Up @@ -2387,6 +2397,7 @@ exports[`data toolbar DataToolbarToggleGroup 1`] = `
createText="Create"
customContent={null}
direction="down"
hasInlineFilter={false}
isCreatable={false}
isDisabled={false}
isExpanded={false}
Expand Down Expand Up @@ -2424,6 +2435,7 @@ exports[`data toolbar DataToolbarToggleGroup 1`] = `
ariaLabelledBy=" pf-toggle-id-0"
className=""
handleTypeaheadKeys={[Function]}
hasClearButton={false}
id="pf-toggle-id-0"
isActive={false}
isDisabled={false}
Expand Down Expand Up @@ -2559,6 +2571,7 @@ exports[`data toolbar DataToolbarToggleGroup 1`] = `
isChecked={false}
isDisabled={false}
isFocused={false}
isNoResultsOption={false}
isPlaceholder={false}
isSelected={false}
keyHandler={[Function]}
Expand All @@ -2573,6 +2586,7 @@ exports[`data toolbar DataToolbarToggleGroup 1`] = `
isChecked={false}
isDisabled={false}
isFocused={false}
isNoResultsOption={false}
isPlaceholder={false}
isSelected={false}
keyHandler={[Function]}
Expand Down Expand Up @@ -2601,6 +2615,7 @@ exports[`data toolbar DataToolbarToggleGroup 1`] = `
createText="Create"
customContent={null}
direction="down"
hasInlineFilter={false}
isCreatable={false}
isDisabled={false}
isExpanded={false}
Expand Down Expand Up @@ -2638,6 +2653,7 @@ exports[`data toolbar DataToolbarToggleGroup 1`] = `
ariaLabelledBy=" pf-toggle-id-1"
className=""
handleTypeaheadKeys={[Function]}
hasClearButton={false}
id="pf-toggle-id-1"
isActive={false}
isDisabled={false}
Expand Down
107 changes: 74 additions & 33 deletions packages/react-core/src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import TimesCircleIcon from '@patternfly/react-icons/dist/js/icons/times-circle-
import { SelectMenu } from './SelectMenu';
import { SelectOption, SelectOptionObject } from './SelectOption';
import { SelectToggle } from './SelectToggle';
import { SelectContext, SelectVariant, SelectDirection } from './selectConstants';
import { SelectContext, SelectVariant, SelectDirection, KeyTypes } from './selectConstants';
import { Chip, ChipGroup } from '../ChipGroup';
import { keyHandler, getNextIndex } from '../../helpers/util';
import { Omit, PickOptional } from '../../helpers/typeUtils';
import { InjectedOuiaProps, withOuiaContext } from '../withOuia';
import { Divider } from '../Divider';

// seed for the aria-labelledby ID
let currentId = 0;
Expand Down Expand Up @@ -81,6 +82,8 @@ export interface SelectProps
toggleIcon?: React.ReactElement;
/** Custom content to render in the select menu. If this prop is defined, the variant prop will be ignored and the select will render with a single select toggle */
customContent?: React.ReactNode;
/** Flag indicating if select should have an inline text input for filtering */
hasInlineFilter?: boolean;
}

export interface SelectState {
Expand All @@ -94,6 +97,7 @@ export interface SelectState {

class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectState> {
private parentRef = React.createRef<HTMLDivElement>();
private filterRef = React.createRef<HTMLInputElement>();
private refCollection: HTMLElement[] = [];

static defaultProps: PickOptional<SelectProps> = {
Expand Down Expand Up @@ -122,7 +126,8 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
onCreateOption: () => undefined as void,
toggleIcon: null as React.ReactElement,
onFilter: null,
customContent: null
customContent: null,
hasInlineFilter: false
};

state: SelectState = {
Expand All @@ -135,6 +140,10 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
};

componentDidUpdate = (prevProps: SelectProps, prevState: SelectState) => {
if (this.props.hasInlineFilter) {
this.refCollection[0] = this.filterRef.current;
}

if (!prevState.openedOnEnter && this.state.openedOnEnter) {
this.refCollection[0].focus();
}
Expand Down Expand Up @@ -190,7 +199,10 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
typeaheadFilteredChildren = [];
}
if (typeaheadFilteredChildren.length === 0) {
!isCreatable && typeaheadFilteredChildren.push(<SelectOption isDisabled key={0} value={noResultsFoundText} />);
!isCreatable &&
typeaheadFilteredChildren.push(
<SelectOption isDisabled key={0} value={noResultsFoundText} isNoResultsOption />
);
}
if (isCreatable && e.target.value !== '') {
const newValue = e.target.value;
Expand Down Expand Up @@ -357,10 +369,11 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
ouiaId,
createText,
noResultsFoundText,
hasInlineFilter,
...props
} = this.props;
/* eslint-enable @typescript-eslint/no-unused-vars */
const { openedOnEnter, typeaheadInputValue, typeaheadActiveChild } = this.state;
const { openedOnEnter, typeaheadInputValue, typeaheadActiveChild, typeaheadFilteredChildren } = this.state;
const selectToggleId = toggleId || `pf-toggle-id-${currentId++}`;
let childPlaceholderText = null;
if (!customContent) {
Expand All @@ -371,6 +384,25 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
(children[0] && this.getDisplay(children[0].props.value, 'node'));
}
}

const hasOnClear = onClear !== Select.defaultProps.onClear;
const hasAnySelections =
selections && (Array.isArray(selections) ? (selections.length > 0 ? true : false) : selections !== '');
const clearBtn = (
<button
className={css(buttonStyles.button, buttonStyles.modifiers.plain, styles.selectToggleClear)}
onClick={e => {
this.clearSelection(e);
onClear(e);
}}
aria-label={ariaLabelClear}
type="button"
disabled={isDisabled}
>
<TimesCircleIcon aria-hidden />
</button>
);

let selectedChips = null;
if (variant === SelectVariant.typeaheadMulti) {
selectedChips = (
Expand All @@ -385,6 +417,36 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
);
}

let filterWithChildren = children;
if (hasInlineFilter) {
const filterBox = (
<React.Fragment>
<div key="inline-filter" className={css(styles.selectMenuInput)}>
<input
key="inline-filter-input"
type="search"
className={css(formStyles.formControl, formStyles.modifiers.search)}
onChange={this.onChange}
onKeyDown={event => {
if (event.key === KeyTypes.ArrowUp) {
this.handleArrowKeys(0, 'up');
} else if (event.key === KeyTypes.ArrowDown) {
this.handleArrowKeys(0, 'down');
}
}}
ref={this.filterRef}
autoComplete="off"
></input>
</div>
<Divider key="inline-filter-divider" />
</React.Fragment>
);
this.refCollection[0] = this.filterRef.current;
filterWithChildren = [filterBox, ...(typeaheadFilteredChildren as React.ReactElement[])].map((option, index) =>
React.cloneElement(option, { key: index })
);
}

return (
<div
className={css(
Expand Down Expand Up @@ -414,6 +476,7 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
ariaLabelToggle={ariaLabelToggle}
handleTypeaheadKeys={this.handleTypeaheadKeys}
isDisabled={isDisabled}
hasClearButton={hasOnClear}
>
{customContent && (
<div className={css(styles.selectToggleWrapper)}>
Expand All @@ -427,6 +490,7 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
<span className={css(styles.selectToggleText)}>
{this.getDisplay(selections as string, 'node') || placeholderText || childPlaceholderText}
</span>
{hasOnClear && hasAnySelections && clearBtn}
</div>
)}
{variant === SelectVariant.checkbox && !customContent && (
Expand All @@ -440,6 +504,7 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
</div>
)}
</div>
{hasOnClear && hasAnySelections && clearBtn}
</React.Fragment>
)}
{variant === SelectVariant.typeahead && !customContent && (
Expand All @@ -465,20 +530,7 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
disabled={isDisabled}
/>
</div>
{(selections || typeaheadInputValue) && (
<button
className={css(buttonStyles.button, buttonStyles.modifiers.plain, styles.selectToggleClear)}
onClick={e => {
this.clearSelection(e);
onClear(e);
}}
aria-label={ariaLabelClear}
type="button"
disabled={isDisabled}
>
<TimesCircleIcon aria-hidden />
</button>
)}
{(selections || typeaheadInputValue) && clearBtn}
</React.Fragment>
)}
{variant === SelectVariant.typeaheadMulti && !customContent && (
Expand All @@ -501,20 +553,8 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
disabled={isDisabled}
/>
</div>
{((selections && (Array.isArray(selections) && selections.length > 0)) || typeaheadInputValue) && (
<button
className={css(buttonStyles.button, buttonStyles.modifiers.plain, styles.selectToggleClear)}
onClick={e => {
this.clearSelection(e);
onClear(e);
}}
aria-label={ariaLabelClear}
type="button"
disabled={isDisabled}
>
<TimesCircleIcon aria-hidden />
</button>
)}
{((selections && (Array.isArray(selections) && selections.length > 0)) || typeaheadInputValue) &&
clearBtn}
</React.Fragment>
)}
</SelectToggle>
Expand Down Expand Up @@ -557,8 +597,9 @@ class Select extends React.Component<SelectProps & InjectedOuiaProps, SelectStat
sendRef={this.sendRef}
keyHandler={this.handleArrowKeys}
maxHeight={maxHeight}
hasInlineFilter={hasInlineFilter}
>
{children}
{filterWithChildren}
</SelectMenu>
)}
{(variant === SelectVariant.typeahead || variant === SelectVariant.typeaheadMulti) &&
Expand Down
Loading