diff --git a/packages/react-console/src/components/AccessConsoles/__tests__/__snapshots__/AccessConsole.test.tsx.snap b/packages/react-console/src/components/AccessConsoles/__tests__/__snapshots__/AccessConsole.test.tsx.snap index 32918ea58e3..6f90e8217c3 100644 --- a/packages/react-console/src/components/AccessConsoles/__tests__/__snapshots__/AccessConsole.test.tsx.snap +++ b/packages/react-console/src/components/AccessConsoles/__tests__/__snapshots__/AccessConsole.test.tsx.snap @@ -87,6 +87,7 @@ exports[`AccessConsoles switching SerialConsole and VncConsole 1`] = ` "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index c51743628ab..31261990ce5 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -12,7 +12,14 @@ import { SelectMenu } from './SelectMenu'; import { SelectOption, SelectOptionObject } from './SelectOption'; import { SelectGroup, SelectGroupProps } from './SelectGroup'; import { SelectToggle } from './SelectToggle'; -import { SelectContext, SelectVariant, SelectPosition, SelectDirection, KeyTypes } from './selectConstants'; +import { + SelectContext, + SelectVariant, + SelectPosition, + SelectDirection, + KeyTypes, + SelectFooterTabbableItems +} from './selectConstants'; import { Chip, ChipGroup, ChipGroupProps } from '../ChipGroup'; import { Spinner } from '../Spinner'; import { @@ -28,6 +35,7 @@ import { Divider } from '../Divider'; import { ToggleMenuBaseProps, Popper } from '../../helpers/Popper/Popper'; import { createRenderableFavorites, extendItemsWithFavorite } from '../../helpers/favorites'; import { ValidatedOptions } from '../../helpers/constants'; +import { findTabbableElements } from '../../helpers/util'; // seed for the aria-labelledby ID let currentId = 0; @@ -613,9 +621,42 @@ export class Select extends React.Component { + switchFocusToFavoriteMenu = () => { + const { typeaheadCurrIndex, typeaheadStoredIndex } = this.state; + let indexForFocus = 0; + + if (typeaheadCurrIndex !== -1) { + indexForFocus = typeaheadCurrIndex; + } else if (typeaheadStoredIndex !== -1) { + indexForFocus = typeaheadStoredIndex; + } + + if (this.refCollection[indexForFocus] !== null && this.refCollection[indexForFocus][0] !== null) { + this.refCollection[indexForFocus][0].focus(); + } else { + this.clearRef.current.focus(); + } + + this.setState({ + tabbedIntoFavoritesMenu: true, + typeaheadCurrIndex: -1 + }); + }; + + moveFocusToLastMenuItem = () => { + const refCollectionLen = this.refCollection.length; + if ( + refCollectionLen > 0 && + this.refCollection[refCollectionLen - 1] !== null && + this.refCollection[refCollectionLen - 1][0] !== null + ) { + this.refCollection[refCollectionLen - 1][0].focus(); + } + }; + + handleTypeaheadKeys = (position: string, shiftKey: boolean = false) => { const { isOpen, onFavorite } = this.props; - const { typeaheadCurrIndex, tabbedIntoFavoritesMenu, typeaheadStoredIndex } = this.state; + const { typeaheadCurrIndex, tabbedIntoFavoritesMenu } = this.state; const typeaheadActiveChild = this.getTypeaheadActiveChild(typeaheadCurrIndex); if (isOpen) { @@ -633,30 +674,110 @@ export class Select extends React.Component 0) { + if (tabbableItems[tabbableItems.length - 1]) { + tabbableItems[tabbableItems.length - 1].focus(); + } + } } else { - this.clearRef.current.focus(); + this.switchFocusToFavoriteMenu(); } - - this.setState({ - tabbedIntoFavoritesMenu: true, - typeaheadCurrIndex: -1 - }); } else { - this.inputRef.current.focus(); - this.setState({ tabbedIntoFavoritesMenu: false }); + // focus is on menu or footer + if (this.props.footer) { + let tabbedIntoMenu = false; + const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems); + if (tabbableItems.length > 0) { + // if current element is not in footer, tab to first tabbable element in footer, + // if shift was clicked, tab to input since focus is on menu + const currentElementIndex = tabbableItems.findIndex((item: any) => item === document.activeElement); + if (currentElementIndex === -1) { + if (shiftKey) { + // currently in menu, shift back to input + this.inputRef.current.focus(); + } else { + // currently in menu, tab to first tabbable item in footer + tabbableItems[0].focus(); + } + } else { + // already in footer + if (shiftKey) { + // shift to previous item + if (currentElementIndex === 0) { + // on first footer item, shift back to menu + this.switchFocusToFavoriteMenu(); + tabbedIntoMenu = true; + } else { + // shift to previous footer item + tabbableItems[currentElementIndex - 1].focus(); + } + } else { + // tab to next tabbable item in footer or to input. + if (tabbableItems[currentElementIndex + 1]) { + tabbableItems[currentElementIndex + 1].focus(); + } else { + this.inputRef.current.focus(); + } + } + } + } else { + // no tabbable items in footer, tab to input + this.inputRef.current.focus(); + tabbedIntoMenu = false; + } + this.setState({ tabbedIntoFavoritesMenu: tabbedIntoMenu }); + } else { + this.inputRef.current.focus(); + this.setState({ tabbedIntoFavoritesMenu: false }); + } } } else { - this.onToggle(false); + // Close if there is no footer + if (!this.props.footer) { + this.onToggle(false); + } else { + // has footer + const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems); + const currentElementIndex = tabbableItems.findIndex((item: any) => item === document.activeElement); + if (this.inputRef.current === document.activeElement) { + if (shiftKey) { + // close toggle if shift key and tab on input + this.onToggle(false); + } else { + // tab to first tabbable item in footer + if (tabbableItems[0]) { + tabbableItems[0].focus(); + } else { + this.onToggle(false); + } + } + } else { + // focus is in footer + if (shiftKey) { + if (currentElementIndex === 0) { + // shift tab back to input + this.inputRef.current.focus(); + } else { + // shift to previous footer item + tabbableItems[currentElementIndex - 1].focus(); + } + } else { + // tab to next footer item or close tab if last item + if (tabbableItems[currentElementIndex + 1]) { + tabbableItems[currentElementIndex + 1].focus(); + } else { + // no next item, close toggle + this.onToggle(false); + this.inputRef.current.focus(); + } + } + } + } } } else if (!tabbedIntoFavoritesMenu) { if (this.refCollection[0][0] === null) { @@ -856,7 +977,7 @@ export class Select extends React.Component (item as any)?.key === 'loading') === undefined) { if (loadingVariant === 'spinner') { renderableItems.push( - + ); @@ -866,7 +987,6 @@ export class Select extends React.Component @@ -940,6 +1060,20 @@ export class Select extends React.Component 0) { + tabbableItems[0].focus(); + event.stopPropagation(); + event.preventDefault(); + } else { + this.onToggle(false); + } + } } else if (event.key === KeyTypes.Tab && variant === SelectVariant.checkbox) { // More modal-like experience for checkboxes // Let SelectOption handle this @@ -1091,6 +1225,7 @@ export class Select extends React.Component { isChecked: this.checkForValue(option.props.value, checked), sendRef, keyHandler, - index: index++ + index: index++, + isLastOptionBeforeFooter }) )} diff --git a/packages/react-core/src/components/Select/SelectOption.tsx b/packages/react-core/src/components/Select/SelectOption.tsx index 3a225f2fd5c..77e62a76ae3 100644 --- a/packages/react-core/src/components/Select/SelectOption.tsx +++ b/packages/react-core/src/components/Select/SelectOption.tsx @@ -134,7 +134,11 @@ export class SelectOption extends React.Component { } event.stopPropagation(); } else { - keyHandler(index, innerIndex, 'tab'); + if (event.shiftKey) { + keyHandler(index, innerIndex, 'up'); + } else { + keyHandler(index, innerIndex, 'tab'); + } } } event.preventDefault(); diff --git a/packages/react-core/src/components/Select/SelectToggle.tsx b/packages/react-core/src/components/Select/SelectToggle.tsx index 530af9705c4..43202d90347 100644 --- a/packages/react-core/src/components/Select/SelectToggle.tsx +++ b/packages/react-core/src/components/Select/SelectToggle.tsx @@ -3,8 +3,9 @@ import styles from '@patternfly/react-styles/css/components/Select/select'; import buttonStyles from '@patternfly/react-styles/css/components/Button/button'; import { css } from '@patternfly/react-styles'; import CaretDownIcon from '@patternfly/react-icons/dist/esm/icons/caret-down-icon'; -import { KeyTypes, SelectVariant } from './selectConstants'; +import { KeyTypes, SelectVariant, SelectFooterTabbableItems } from './selectConstants'; import { PickOptional } from '../../helpers/typeUtils'; +import { findTabbableElements } from '../../helpers/util'; export interface SelectToggleProps extends React.HTMLProps { /** HTML ID of dropdown toggle */ @@ -22,7 +23,9 @@ export interface SelectToggleProps extends React.HTMLProps { /** Callback for toggle close */ onClose?: () => void; /** @hide Internal callback for toggle keyboard navigation */ - handleTypeaheadKeys?: (position: string) => void; + handleTypeaheadKeys?: (position: string, shiftKey?: boolean) => void; + /** @hide Internal callback to move focus to last menu item */ + moveFocusToLastMenuItem?: () => void; /** Element which wraps toggle */ parentRef: React.RefObject; /** The menu element */ @@ -106,16 +109,18 @@ export class SelectToggle extends React.Component { } }; - findTabbableFooterElements = () => { - const tabbable = this.props.footerRef.current.querySelectorAll('input, button, select, textarea, a[href]'); - const list = Array.prototype.filter.call(tabbable, function(item) { - return item.tabIndex >= '0'; - }); - return list; - }; - handleGlobalKeys = (event: KeyboardEvent) => { - const { parentRef, menuRef, hasFooter, isOpen, variant, onToggle, onClose } = this.props; + const { + parentRef, + menuRef, + hasFooter, + footerRef, + isOpen, + variant, + onToggle, + onClose, + moveFocusToLastMenuItem + } = this.props; const escFromToggle = parentRef && parentRef.current && parentRef.current.contains(event.target as Node); const escFromWithinMenu = menuRef && menuRef.current && menuRef.current.contains && menuRef.current.contains(event.target as Node); @@ -124,13 +129,13 @@ export class SelectToggle extends React.Component { event.key === KeyTypes.Tab && (variant === SelectVariant.typeahead || variant === SelectVariant.typeaheadMulti) ) { - this.props.handleTypeaheadKeys('tab'); + this.props.handleTypeaheadKeys('tab', event.shiftKey); event.preventDefault(); return; } if (isOpen && event.key === KeyTypes.Tab && hasFooter) { - const tabbableItems = this.findTabbableFooterElements(); + const tabbableItems = findTabbableElements(footerRef, SelectFooterTabbableItems); // If no tabbable item in footer close select if (tabbableItems.length <= 0) { @@ -139,14 +144,29 @@ export class SelectToggle extends React.Component { this.toggle.current.focus(); return; } else { - // if current element is not in footer, tab to first tabbable element in footer - const currentElementIndex = tabbableItems.findIndex(item => item === document.activeElement); + // if current element is not in footer, tab to first tabbable element in footer, or close if shift clicked + const currentElementIndex = tabbableItems.findIndex((item: any) => item === document.activeElement); if (currentElementIndex === -1) { - tabbableItems[0].focus(); - return; + if (event.shiftKey) { + if (variant !== 'checkbox') { + // only close non checkbox variation on shift clicked + onToggle(false); + onClose(); + this.toggle.current.focus(); + } + } else { + // tab to footer + tabbableItems[0].focus(); + return; + } } // Current element is in footer. if (event.shiftKey) { + // Move focus back to menuif current tab index is 0 + if (currentElementIndex === 0) { + moveFocusToLastMenuItem(); + event.preventDefault(); + } return; } // Tab to next element in footer or close if there are none @@ -225,6 +245,7 @@ export class SelectToggle extends React.Component { onClose, onClickTypeaheadToggleButton, handleTypeaheadKeys, + moveFocusToLastMenuItem, parentRef, menuRef, id, diff --git a/packages/react-core/src/components/Select/__tests__/__snapshots__/Select.test.tsx.snap b/packages/react-core/src/components/Select/__tests__/__snapshots__/Select.test.tsx.snap index e1f195eaeed..14e6f317884 100644 --- a/packages/react-core/src/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +++ b/packages/react-core/src/components/Select/__tests__/__snapshots__/Select.test.tsx.snap @@ -222,6 +222,7 @@ exports[`checkbox select renders checkbox select groups successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -1143,6 +1144,7 @@ exports[`checkbox select renders checkbox select selections properly 1`] = ` "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -1365,6 +1367,7 @@ exports[`checkbox select renders checkbox select selections properly when isChec "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -1545,6 +1548,7 @@ exports[`checkbox select renders closed successfully 1`] = ` "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -1786,6 +1790,7 @@ exports[`checkbox select renders expanded successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -2354,6 +2359,7 @@ exports[`checkbox select renders expanded successfully with custom objects 1`] = , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -2906,6 +2912,7 @@ exports[`checkbox select renders expanded with filtering successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -3561,6 +3568,7 @@ exports[`checkbox select renders expanded with filtering successfully 2`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -4458,6 +4466,7 @@ exports[`renders select with favorites successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -5992,6 +6001,7 @@ exports[`select custom select filter filters properly 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -6591,6 +6601,7 @@ exports[`select renders select groups successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -7449,6 +7460,7 @@ exports[`select renders up direction successfully 1`] = ` "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -7634,6 +7646,7 @@ exports[`select single select renders closed successfully 1`] = ` "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -7819,6 +7832,7 @@ exports[`select single select renders disabled successfully 1`] = ` "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -8062,6 +8076,7 @@ exports[`select single select renders expanded successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -8613,6 +8628,7 @@ exports[`select single select renders expanded successfully with custom objects , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -9078,6 +9094,7 @@ exports[`select with custom content renders closed successfully 1`] = ` "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -9262,6 +9279,7 @@ exports[`select with custom content renders expanded successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -9593,6 +9611,7 @@ exports[`typeahead multi select renders closed successfully 1`] = ` "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -9855,6 +9874,7 @@ exports[`typeahead multi select renders expanded successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -10484,6 +10504,7 @@ exports[`typeahead multi select renders selected successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -11474,6 +11495,7 @@ exports[`typeahead select renders closed successfully 1`] = ` "current": null, } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -11734,6 +11756,7 @@ exports[`typeahead select renders expanded successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -12337,6 +12360,7 @@ exports[`typeahead select renders selected successfully 1`] = ` , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} @@ -12948,6 +12972,7 @@ exports[`typeahead select test select existing option on a non-creatable select , } } + moveFocusToLastMenuItem={[Function]} onClickTypeaheadToggleButton={[Function]} onClose={[Function]} onEnter={[Function]} diff --git a/packages/react-core/src/components/Select/examples/Select.md b/packages/react-core/src/components/Select/examples/Select.md index 676fca8d872..5f63e5217b9 100644 --- a/packages/react-core/src/components/Select/examples/Select.md +++ b/packages/react-core/src/components/Select/examples/Select.md @@ -2400,7 +2400,11 @@ class SelectWithFooter extends React.Component { aria-labelledby={titleId} isDisabled={isDisabled} direction={direction} - footer={} + footer={ <> + + } > {this.options} diff --git a/packages/react-core/src/components/Select/selectConstants.tsx b/packages/react-core/src/components/Select/selectConstants.tsx index a8c1a520d68..8d8bc6d63d9 100644 --- a/packages/react-core/src/components/Select/selectConstants.tsx +++ b/packages/react-core/src/components/Select/selectConstants.tsx @@ -45,3 +45,5 @@ export const KeyTypes = { ArrowLeft: 'ArrowLeft', ArrowRight: 'ArrowRight' }; + +export const SelectFooterTabbableItems = 'input, button, select, textarea, a[href]'; diff --git a/packages/react-core/src/helpers/util.ts b/packages/react-core/src/helpers/util.ts index 603876f0b1f..1de34857cf3 100644 --- a/packages/react-core/src/helpers/util.ts +++ b/packages/react-core/src/helpers/util.ts @@ -196,6 +196,19 @@ export function keyHandler( } } +/** This function returns a list of tabbable items in a container + * + * @param {any} containerRef to the container + * @param {string} tababbleSelectors CSS selector string of tabbable items + */ +export function findTabbableElements(containerRef: any, tababbleSelectors: string): any[] { + const tabbable = containerRef.current.querySelectorAll(tababbleSelectors); + const list = Array.prototype.filter.call(tabbable, function(item) { + return item.tabIndex >= '0'; + }); + return list; +} + /** This function is a helper for keyboard navigation through dropdowns. * * @param {number} index The index of the element you're on diff --git a/packages/react-integration/demo-app-ts/src/Demos.ts b/packages/react-integration/demo-app-ts/src/Demos.ts index 8a03e941020..ef1c2c68316 100644 --- a/packages/react-integration/demo-app-ts/src/Demos.ts +++ b/packages/react-integration/demo-app-ts/src/Demos.ts @@ -326,9 +326,14 @@ export const Demos: DemoInterface[] = [ componentType: Examples.SelectFavoritesDemo }, { - id: 'select-view-more-grouped-demo', - name: 'Select View More Grouped Demo', - componentType: Examples.SelectViewMoreGroupedDemo + id: 'select-typeahead-footer-filtering-demo', + name: 'Select Footer filtering Demo', + componentType: Examples.SelectFooterFilteringDemo + }, + { + id: 'select-typeahead-footer-demo', + name: 'Select Typeahead Footer Demo', + componentType: Examples.SelectTypeaheadFooterDemo }, { id: 'select-validated-demo', @@ -340,6 +345,11 @@ export const Demos: DemoInterface[] = [ name: 'Select View More Demo', componentType: Examples.SelectViewMoreDemo }, + { + id: 'select-view-more-grouped-demo', + name: 'Select View More Grouped Demo', + componentType: Examples.SelectViewMoreGroupedDemo + }, { id: 'select-view-more-typeahead-grouped-demo', name: 'Select View More Typeahead Grouped Demo', diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectFooterFilteringDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectFooterFilteringDemo.tsx new file mode 100644 index 00000000000..3edf458becc --- /dev/null +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectFooterFilteringDemo.tsx @@ -0,0 +1,126 @@ +import { + Select, + SelectOption, + SelectOptionObject, + SelectGroup, + SelectVariant, + Checkbox, + Button +} from '@patternfly/react-core'; +import React, { Component } from 'react'; + +/* eslint-disable no-console */ +export interface SelectFooterFilteringDemoState { + isOpen: boolean; + selections: string[]; +} + +export class SelectFooterFilteringDemo extends Component { + state = { + isOpen: false, + selections: [''], + isSingle: false + }; + + options = [ + + {['Running', 'Stopped', 'Down', 'Degraded', 'Needs-Maintenence'].map((option, index) => ( + + ))} + , + + + + + + ]; + + onToggle = (isOpen: boolean) => { + this.setState({ + isOpen + }); + }; + + onSelect = (_event: React.MouseEvent | React.ChangeEvent, selection: string | SelectOptionObject) => { + const { selections } = this.state; + if (selections.includes(selection.toString())) { + this.setState( + (prevState: SelectFooterFilteringDemoState) => ({ + selections: prevState.selections.filter(item => item !== selection) + }), + () => console.log('selections: ', this.state.selections) + ); + } else { + this.setState( + (prevState: SelectFooterFilteringDemoState) => ({ selections: [...prevState.selections, selection] }), + () => console.log('selections: ', this.state.selections) + ); + } + }; + + onFilter = (_: any, textInput: string) => { + if (textInput === '') { + return this.options; + } else { + return this.options + .map((group: React.ReactElement) => { + const filteredGroup = React.cloneElement(group, { + children: group.props.children.filter((item: React.ReactElement) => + item.props.value.toLowerCase().includes(textInput.toLowerCase()) + ) + }); + if (filteredGroup.props.children.length > 0) { + return filteredGroup; + } else { + return <>; + } + }) + .filter(newGroup => newGroup.props.children); + } + }; + + clearSelection = () => { + this.setState({ + selections: [] + }); + }; + + render() { + const { isOpen, selections, isSingle } = this.state; + const titleId = 'checkbox-select-id'; + return ( +
+ + + this.setState({ isSingle: !isSingle, selections: [] })} + /> +
+ ); + } +} diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectTypeaheadFooterDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectTypeaheadFooterDemo.tsx new file mode 100644 index 00000000000..963a6ccda65 --- /dev/null +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectTypeaheadFooterDemo.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { Select, SelectOption, SelectVariant, Button } from '@patternfly/react-core'; + +/* eslint-disable no-console */ +export interface SelectTypeaheadFooterDemoState { + isOpen: boolean; + selected: string[]; +} + +export class SelectTypeaheadFooterDemo extends React.Component { + static displayName = 'SelectTypeaheadFooterDemo'; + state = { + isOpen: false, + selected: [] as string[] + }; + + options = [ + , + , + , + , + , + , + + ]; + + onToggle = (isOpen: boolean) => { + this.setState({ + isOpen + }); + }; + + clearSelection = () => { + this.setState({ + selected: null, + isOpen: false + }); + }; + + onSelect = (_event: React.MouseEvent | React.ChangeEvent, selection: string, isPlaceholder: boolean) => { + if (isPlaceholder) { + this.clearSelection(); + } else { + this.setState({ + selected: selection, + isOpen: false + }); + console.log('selected:', selection); + } + }; + + render() { + const { isOpen, selected } = this.state; + const titleId = 'footer-typeahead-select-id'; + return ( +
+ + +
+ ); + } +} diff --git a/packages/react-integration/demo-app-ts/src/components/demos/index.ts b/packages/react-integration/demo-app-ts/src/components/demos/index.ts index b38a5eb7489..6f67e7f41e5 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/index.ts +++ b/packages/react-integration/demo-app-ts/src/components/demos/index.ts @@ -63,9 +63,11 @@ export * from './SelectDemo/FilteringSelectDemo'; export * from './SelectDemo/FilteringSelectLiveUpdateDemo'; export * from './SelectDemo/SelectDemo'; export * from './SelectDemo/SelectFavoritesDemo'; -export * from './SelectDemo/SelectViewMoreGroupedDemo'; +export * from './SelectDemo/SelectFooterFilteringDemo'; +export * from './SelectDemo/SelectTypeaheadFooterDemo'; export * from './SelectDemo/SelectValidatedDemo'; export * from './SelectDemo/SelectViewMoreDemo'; +export * from './SelectDemo/SelectViewMoreGroupedDemo'; export * from './SelectDemo/SelectViewMoreTypeaheadGroupedDemo'; export * from './SimpleList/SimpleListDemo'; export * from './SliderDemo/SliderDemo';