diff --git a/packages/patternfly-4/react-core/src/components/Select/CheckboxSelectInput.js b/packages/patternfly-4/react-core/src/components/Select/CheckboxSelectInput.js
deleted file mode 100644
index 09febe47119..00000000000
--- a/packages/patternfly-4/react-core/src/components/Select/CheckboxSelectInput.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react';
-import { Select, SelectVariant, CheckboxSelectOption } from '@patternfly/react-core';
-
-class CheckboxSelectInput extends React.Component {
- state = {
- isExpanded: false,
- selected: []
- };
-
- onToggle = isExpanded => {
- this.setState({
- isExpanded
- });
- };
-
- onSelect = (event, selection) => {
- const { selected } = this.state;
- if (selected.includes(selection)) {
- this.setState(
- prevState => ({ selected: prevState.selected.filter(item => item !== selection) }),
- () => console.log('selections: ', this.state.selected)
- );
- } else {
- this.setState(
- prevState => ({ selected: [...prevState.selected, selection] }),
- () => console.log('selections: ', this.state.selected)
- );
- }
- };
-
- clearSelection = () => {
- this.setState({
- selected: []
- });
- };
-
- options = [
- ,
- ,
- ,
- ,
-
- ];
-
- render() {
- const { isExpanded, selected } = this.state;
- const titleId = 'checkbox-select-id';
- return (
-
-
- Checkbox Title
-
-
-
- );
- }
-}
-
-export default CheckboxSelectInput;
diff --git a/packages/patternfly-4/react-core/src/components/Select/GroupedCheckboxSelectInput.js b/packages/patternfly-4/react-core/src/components/Select/GroupedCheckboxSelectInput.js
deleted file mode 100644
index 2da1cc9e3b5..00000000000
--- a/packages/patternfly-4/react-core/src/components/Select/GroupedCheckboxSelectInput.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import React from 'react';
-import { Select, SelectVariant, CheckboxSelectGroup, CheckboxSelectOption } from '@patternfly/react-core';
-
-class GroupedCheckboxSelectInput extends React.Component {
- state = {
- isExpanded: false,
- selected: []
- };
-
- onToggle = isExpanded => {
- this.setState({
- isExpanded
- });
- };
-
- onSelect = (event, selection) => {
- const { selected } = this.state;
- if (selected.includes(selection)) {
- this.setState(
- prevState => ({ selected: prevState.selected.filter(item => item !== selection) }),
- () => console.log('selections: ', this.state.selected)
- );
- } else {
- this.setState(
- prevState => ({ selected: [...prevState.selected, selection] }),
- () => console.log('selections: ', this.state.selected)
- );
- }
- };
-
- clearSelection = () => {
- this.setState({
- selected: []
- });
- };
-
- options = [
-
-
-
-
-
-
- ,
-
-
-
-
-
- ];
-
- render() {
- const { isExpanded, selected } = this.state;
- const titleId = 'grouped-checkbox-select-id';
- return (
-
-
- Grouped Checkbox Title
-
-
-
- );
- }
-}
-
-export default GroupedCheckboxSelectInput;
diff --git a/packages/patternfly-4/react-core/src/components/Select/Select.d.ts b/packages/patternfly-4/react-core/src/components/Select/Select.d.ts
index 29e4409cd48..e7e0a6278d8 100644
--- a/packages/patternfly-4/react-core/src/components/Select/Select.d.ts
+++ b/packages/patternfly-4/react-core/src/components/Select/Select.d.ts
@@ -3,17 +3,24 @@ import { HTMLProps, FormEvent, ReactNode } from 'react';
export const SelectVariant: {
single: 'single';
checkbox: 'checkbox';
+ typeahead: 'typeahead';
+ typeaheadMulti: 'typeaheadmulti';
};
export interface SelectProps extends HTMLProps {
isExpanded?: boolean;
isGrouped?: boolean;
onToggle(value: boolean): void;
+ onClear?() : void;
placeholderText?: string | ReactNode;
selections?: string | Array;
variant?: string;
width?: string | number;
ariaLabelledBy?: string;
+ ariaLabelTypeAhead?: string;
+ ariaLabelClear?: string;
+ ariaLabelToggle?: string;
+ ariaLabelRemove?: string;
}
declare const Select: React.FunctionComponent;
diff --git a/packages/patternfly-4/react-core/src/components/Select/Select.js b/packages/patternfly-4/react-core/src/components/Select/Select.js
index 30b6773dfd8..efc6b68b699 100644
--- a/packages/patternfly-4/react-core/src/components/Select/Select.js
+++ b/packages/patternfly-4/react-core/src/components/Select/Select.js
@@ -1,12 +1,16 @@
import React from 'react';
import styles from '@patternfly/patternfly/components/Select/select.css';
import badgeStyles from '@patternfly/patternfly/components/Badge/badge.css';
+import formStyles from '@patternfly/patternfly/components/FormControl/form-control.css';
+import buttonStyles from '@patternfly/patternfly/components/Button/button.css';
import { css } from '@patternfly/react-styles';
+import { TimesCircleIcon } from '@patternfly/react-icons';
import PropTypes from 'prop-types';
import SingleSelect from './SingleSelect';
import CheckboxSelect from './CheckboxSelect';
import SelectToggle from './SelectToggle';
import { SelectContext, SelectVariant } from './selectConstants';
+import { Chip, ChipGroup } from '../ChipGroup';
// seed for the aria-labelledby ID
let currentId = 0;
@@ -28,12 +32,22 @@ const propTypes = {
'aria-label': PropTypes.string,
/** Id of label for the Select aria-labelledby */
ariaLabelledBy: PropTypes.string,
+ /** Label for input field of type ahead select variants */
+ ariaLabelTypeAhead: PropTypes.string,
+ /** Label for clear selection button of type ahead select variants */
+ ariaLabelClear: PropTypes.string,
+ /** Label for toggle of type ahead select variants */
+ ariaLabelToggle: PropTypes.string,
+ /** Label for remove chip button of multiple type ahead select variant */
+ ariaLabelRemove: PropTypes.string,
/** Callback for selection behavior */
onSelect: PropTypes.func.isRequired,
/** Callback for toggle button behavior */
onToggle: PropTypes.func.isRequired,
+ /** Callback for typeahead clear button */
+ onClear: PropTypes.func,
/** Variant of rendered Select */
- variant: PropTypes.oneOf(['single', 'checkbox']),
+ variant: PropTypes.oneOf(['single', 'checkbox', 'typeahead', 'typeaheadmulti']),
/** Width of the select container as a number of px or string percentage */
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Additional props are spread to the container */
@@ -47,22 +61,67 @@ const defaultProps = {
isGrouped: false,
'aria-label': null,
ariaLabelledBy: null,
+ ariaLabelTypeAhead: null,
+ ariaLabelClear: 'Clear all',
+ ariaLabelToggle: 'Options menu',
+ ariaLabelRemove: 'Remove',
selections: null,
placeholderText: null,
variant: SelectVariant.single,
width: null,
+ onClear: Function.prototype
};
class Select extends React.Component {
parentRef = React.createRef();
- state = { openedOnEnter: false };
+ state = {
+ openedOnEnter: false,
+ typeaheadValue: null,
+ filteredChildren: this.props.children
+ };
onEnter = () => {
this.setState({ openedOnEnter: true });
};
onClose = () => {
- this.setState({ openedOnEnter: false });
+ this.setState({
+ openedOnEnter: false,
+ typeaheadValue: null,
+ filteredChildren: this.props.children
+ });
+ };
+
+ onChange = e => {
+ let input;
+ try {
+ input = new RegExp(e.target.value, 'i');
+ } catch (err) {
+ input = new RegExp(e.target.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
+ }
+ const filteredChildren =
+ e.target.value !== ''
+ ? this.props.children.filter(child => child.props.value.search(input) === 0)
+ : this.props.children;
+ if (filteredChildren.length === 0) {
+ filteredChildren.push(No results found
);
+ }
+ this.setState({
+ typeaheadValue: e.target.value,
+ filteredChildren
+ });
+ };
+
+ onClick = e => {
+ e.stopPropagation();
+ };
+
+ clearSelection = e => {
+ e.stopPropagation();
+ this.setState({
+ typeaheadValue: '',
+ filteredChildren: this.props.children
+ });
};
render() {
@@ -72,16 +131,21 @@ class Select extends React.Component {
variant,
onToggle,
onSelect,
+ onClear,
isExpanded,
isGrouped,
selections,
ariaLabelledBy,
+ ariaLabelTypeAhead,
+ ariaLabelClear,
+ ariaLabelToggle,
+ ariaLabelRemove,
'aria-label': ariaLabel,
placeholderText,
width,
...props
} = this.props;
- const { openedOnEnter } = this.state;
+ const { openedOnEnter, typeaheadValue, filteredChildren } = this.state;
const selectToggleId = `pf-toggle-id-${currentId++}`;
let childPlaceholderText = null;
if (!selections && !placeholderText) {
@@ -89,6 +153,19 @@ class Select extends React.Component {
childPlaceholderText =
(childPlaceholder[0] && childPlaceholder[0].props.value) || (children[0] && children[0].props.value);
}
+ let selectedChips = null;
+ if (variant === SelectVariant.typeaheadMulti) {
+ selectedChips = (
+
+ {selections &&
+ selections.map(item => (
+ onSelect(e, item)} closeBtnAriaLabel={ariaLabelRemove}>
+ {item}
+
+ ))}
+
+ );
+ }
return (
{variant === SelectVariant.single && (
@@ -126,6 +204,61 @@ class Select extends React.Component {
)}
+ {variant === SelectVariant.typeahead && (
+
+
+
+
+ {selections && (
+
+ )}
+
+ )}
+ {variant === SelectVariant.typeaheadMulti && (
+
+
+ {selections && selections.length > 0 && selectedChips}
+
+
+ {selections && selections.length > 0 && (
+
+ )}
+
+ )}
{variant === SelectVariant.single && isExpanded && (
)}
+ {(variant === SelectVariant.typeahead || variant === SelectVariant.typeaheadMulti) && isExpanded && (
+
+ {filteredChildren}
+
+ )}
);
diff --git a/packages/patternfly-4/react-core/src/components/Select/Select.md b/packages/patternfly-4/react-core/src/components/Select/Select.md
index 0356599b91c..1d8e7388986 100644
--- a/packages/patternfly-4/react-core/src/components/Select/Select.md
+++ b/packages/patternfly-4/react-core/src/components/Select/Select.md
@@ -243,3 +243,168 @@ class GroupedCheckboxSelectInput extends React.Component {
}
}
```
+
+## Typeahead select input
+```js
+import React from 'react';
+import { Select, SelectOption, SelectVariant, CheckboxSelectGroup, CheckboxSelectOption } from '@patternfly/react-core';
+
+class TypeaheadSelectInput extends React.Component {
+ constructor(props) {
+ super(props);
+ this.options = [
+ { value: 'Alabama', disabled: false },
+ { value: 'Florida', disabled: false },
+ { value: 'New Jersey', disabled: false },
+ { value: 'New Mexico', disabled: false },
+ { value: 'New York', disabled: false },
+ { value: 'North Carolina', disabled: false }
+ ];
+
+ this.state = {
+ isExpanded: false,
+ selected: null
+ };
+
+ this.onToggle = isExpanded => {
+ this.setState({
+ isExpanded
+ });
+ };
+
+ this.onSelect = (event, selection, isPlaceholder) => {
+ if (isPlaceholder) this.clearSelection();
+ else {
+ this.setState({
+ selected: selection,
+ isExpanded: false
+ });
+ console.log('selected:', selection);
+ }
+ };
+
+ this.clearSelection = () => {
+ this.setState({
+ selected: null,
+ isExpanded: false
+ });
+ };
+ }
+
+ render() {
+ const { isExpanded, selected } = this.state;
+ const titleId = 'typeahead-select-id';
+ return (
+
+
+ Select a state
+
+
+
+ );
+ }
+}
+```
+
+## Multiple typeahead select input
+```js
+import React from 'react';
+import { Select, SelectOption, SelectVariant, CheckboxSelectGroup, CheckboxSelectOption } from '@patternfly/react-core';
+
+class MultiTypeaheadSelectInput extends React.Component {
+ constructor(props) {
+ super(props);
+ this.options = [
+ { value: 'Alabama', disabled: false },
+ { value: 'Florida', disabled: false },
+ { value: 'New Jersey', disabled: false },
+ { value: 'New Mexico', disabled: false },
+ { value: 'New York', disabled: false },
+ { value: 'North Carolina', disabled: false }
+ ];
+
+ this.state = {
+ isExpanded: false,
+ selected: []
+ };
+
+ this.onToggle = isExpanded => {
+ this.setState({
+ isExpanded
+ });
+ };
+
+ this.onSelect = (event, selection) => {
+ const { selected } = this.state;
+ if (selected.includes(selection)) {
+ this.setState(
+ prevState => ({ selected: prevState.selected.filter(item => item !== selection) }),
+ () => console.log('selections: ', this.state.selected)
+ );
+ } else {
+ this.setState(
+ prevState => ({ selected: [...prevState.selected, selection] }),
+ () => console.log('selections: ', this.state.selected)
+ );
+ }
+ };
+
+ this.clearSelection = () => {
+ this.setState({
+ selected: [],
+ isExpanded: false,
+ });
+ };
+ }
+
+ render() {
+ const { isExpanded, selected } = this.state;
+ const titleId = 'multi-typeahead-select-id';
+
+ return (
+
+
+ Select a state
+
+
+
+ );
+ }
+}
+```
\ No newline at end of file
diff --git a/packages/patternfly-4/react-core/src/components/Select/Select.test.js b/packages/patternfly-4/react-core/src/components/Select/Select.test.js
index cdd86e06a25..10c34b960f8 100644
--- a/packages/patternfly-4/react-core/src/components/Select/Select.test.js
+++ b/packages/patternfly-4/react-core/src/components/Select/Select.test.js
@@ -72,6 +72,108 @@ describe('checkbox select', () => {
});
});
+describe('typeahead select', () => {
+ test('renders closed successfully', () => {
+ const view = mount(
+
+ );
+ expect(view).toMatchSnapshot();
+ });
+
+ test('renders expanded successfully', () => {
+ const view = mount(
+
+ );
+ expect(view).toMatchSnapshot();
+ });
+
+ test('renders selected successfully', () => {
+ const view = mount(
+
+ );
+ expect(view).toMatchSnapshot();
+ });
+
+ test('test onChange', () => {
+ const mockEvent = { target: { value: 'test' } };
+ const view = mount(
+
+ );
+ const inst = view.instance();
+ inst.onChange(mockEvent);
+ view.update();
+ expect(view).toMatchSnapshot();
+ });
+});
+
+describe('typeahead multi select', () => {
+ test('renders closed successfully', () => {
+ const view = mount(
+
+ );
+ expect(view).toMatchSnapshot();
+ });
+
+ test('renders expanded successfully', () => {
+ const view = mount(
+
+ );
+ expect(view).toMatchSnapshot();
+ });
+
+ test('renders selected successfully', () => {
+ const view = mount(
+
+ );
+ expect(view).toMatchSnapshot();
+ });
+
+ test('test onChange', () => {
+ const mockEvent = { target: { value: 'test' } };
+ const view = mount(
+
+ );
+ const inst = view.instance();
+ inst.onChange(mockEvent);
+ view.update();
+ expect(view).toMatchSnapshot();
+ });
+});
+
describe('API', () => {
test('click on item', () => {
const mockToggle = jest.fn();
diff --git a/packages/patternfly-4/react-core/src/components/Select/SelectOption.js b/packages/patternfly-4/react-core/src/components/Select/SelectOption.js
index 60726180efd..fb7a050bd3d 100644
--- a/packages/patternfly-4/react-core/src/components/Select/SelectOption.js
+++ b/packages/patternfly-4/react-core/src/components/Select/SelectOption.js
@@ -1,9 +1,9 @@
import React from 'react';
import styles from '@patternfly/patternfly/components/Select/select.css';
import { css } from '@patternfly/react-styles';
+import { CheckIcon } from '@patternfly/react-icons';
import PropTypes from 'prop-types';
import { SelectContext, KeyTypes } from './selectConstants';
-import { CheckIcon } from '@patternfly/react-icons';
const propTypes = {
/** additional classes added to the Select Option */
diff --git a/packages/patternfly-4/react-core/src/components/Select/SelectToggle.js b/packages/patternfly-4/react-core/src/components/Select/SelectToggle.js
index d77e882ef31..84f4fb635ff 100644
--- a/packages/patternfly-4/react-core/src/components/Select/SelectToggle.js
+++ b/packages/patternfly-4/react-core/src/components/Select/SelectToggle.js
@@ -1,9 +1,10 @@
import React, { Component } from 'react';
import styles from '@patternfly/patternfly/components/Select/select.css';
+import buttonStyles from '@patternfly/patternfly/components/Button/button.css';
import { css } from '@patternfly/react-styles';
import PropTypes from 'prop-types';
import { CaretDownIcon } from '@patternfly/react-icons';
-import { KeyTypes } from './selectConstants';
+import { KeyTypes, SelectVariant } from './selectConstants';
const propTypes = {
/** HTML ID of dropdown toggle */
@@ -32,8 +33,12 @@ const propTypes = {
isPlain: PropTypes.bool,
/** Type of the toggle button, defaults to 'button' */
type: PropTypes.string,
- /** Flag for checkbox variant keyboard interaction */
- isCheckbox: PropTypes.bool,
+ /** Id of label for the Select aria-labelledby */
+ ariaLabelledBy: PropTypes.string,
+ /** Label for toggle of select variants */
+ ariaLabelToggle: PropTypes.string,
+ /** Flag for variant, determines toggle rules and interaction */
+ variant: PropTypes.oneOf(['single', 'checkbox', 'typeahead', 'typeaheadmulti']),
/** Additional props are spread to the container