diff --git a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx index e0093cb58f7..38e70fad421 100644 --- a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx @@ -1,7 +1,8 @@ import React, { useEffect } from 'react'; -import { TextInput } from '../TextInput/TextInput'; -import { Button } from '../Button/Button'; -import { Select, SelectOption } from '../Select'; +import { TextInput } from '../TextInput'; +import { Button } from '../Button'; +import { Select, SelectList, SelectOption } from '../Select'; +import { MenuToggle, MenuToggleElement } from '../MenuToggle'; import { InputGroup } from '../InputGroup'; import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon'; import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon'; @@ -263,13 +264,21 @@ export const CalendarMonth = ({ Month
diff --git a/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap b/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap index 0a190bf7690..a7876dbb096 100644 --- a/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap +++ b/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap @@ -103,45 +103,36 @@ exports[`With popover opened 1`] = ` > Month -
+
diff --git a/packages/react-core/src/components/LoginPage/examples/LoginPageLanguageSelect.tsx b/packages/react-core/src/components/LoginPage/examples/LoginPageLanguageSelect.tsx index 849b6880ef3..6d24a04e6f6 100644 --- a/packages/react-core/src/components/LoginPage/examples/LoginPageLanguageSelect.tsx +++ b/packages/react-core/src/components/LoginPage/examples/LoginPageLanguageSelect.tsx @@ -8,9 +8,11 @@ import { LoginPage, ListItem, ListVariant, + MenuToggle, + MenuToggleElement, Select, - SelectOption, - SelectOptionObject + SelectList, + SelectOption } from '@patternfly/react-core'; import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; @@ -22,7 +24,7 @@ export const LoginPageLanguageSelect: React.FunctionComponent = () => { const [isValidPassword, setIsValidPassword] = React.useState(true); const [isRememberMeChecked, setIsRememberMeChecked] = React.useState(false); const [isHeaderUtilsOpen, setIsHeaderUtilsOpen] = React.useState(false); - const [selectedHeaderUtils, setSelectedHeaderUtils] = React.useState('English'); + const [selectedHeaderUtils, setSelectedHeaderUtils] = React.useState('English'); /** i18n object is used to simulate i18n integration of native language translation */ const i18n = { @@ -35,23 +37,21 @@ export const LoginPageLanguageSelect: React.FunctionComponent = () => { Bengali: 'বাংলা' }; - const headerUtilsOptions = [ - , - , - , - , - , - , - - ]; - - const onHeaderUtilsToggle = (_event: any, isExpanded: boolean) => { - setIsHeaderUtilsOpen(isExpanded); - }; + const headerUtilsOptions = ( + + {i18n.English} + {i18n.Mandarin} + {i18n.Hindi} + {i18n.Spanish} + {i18n.Portuguese} + {i18n.Arabic} + {i18n.Bengali} + + ); const onHeaderUtilsSelect = ( _event: React.MouseEvent | React.ChangeEvent, - value: string | SelectOptionObject + value: string ) => { setSelectedHeaderUtils(value); setIsHeaderUtilsOpen(false); @@ -60,9 +60,18 @@ export const LoginPageLanguageSelect: React.FunctionComponent = () => { const headerUtils = ( { - if (event.key === KeyTypes.ArrowUp) { - this.handleMenuKeys(0, 0, 'up'); - event.preventDefault(); - } else if (event.key === KeyTypes.ArrowDown) { - this.handleMenuKeys(0, 0, 'down'); - event.preventDefault(); - } else if (event.key === KeyTypes.ArrowLeft) { - this.handleMenuKeys(0, 0, 'left'); - event.preventDefault(); - } else if (event.key === KeyTypes.ArrowRight) { - this.handleMenuKeys(0, 0, 'right'); - event.preventDefault(); - } else if (event.key === KeyTypes.Tab && variant !== SelectVariant.checkbox && this.props.footer) { - // tab to footer or close menu if shift key - if (event.shiftKey) { - this.onToggle(event, false); - } else { - const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems); - if (tabbableItems.length > 0) { - tabbableItems[0].focus(); - event.stopPropagation(); - event.preventDefault(); - } else { - this.onToggle(event, false); - } - } - } else if (event.key === KeyTypes.Tab && variant === SelectVariant.checkbox) { - // More modal-like experience for checkboxes - // Let SelectOption handle this - if (event.shiftKey) { - this.handleMenuKeys(0, 0, 'up'); - } else { - this.handleMenuKeys(0, 0, 'down'); - } - event.stopPropagation(); - event.preventDefault(); - } - }} - ref={this.filterRef} - autoComplete={inputAutoComplete} - /> -
- - - ); - renderableItems = [filterBox, ...(typeaheadFilteredChildren as React.ReactElement[])].map((option, index) => - React.cloneElement(option, { key: index }) - ); - } - - let variantProps: any; - let variantChildren: any; - if (customContent) { - variantProps = { - selected: selections, - openedOnEnter, - isCustomContent: true - }; - variantChildren = customContent; - } else { - switch (variant) { - case 'single': - variantProps = { - selected: selections[0], - hasInlineFilter, - openedOnEnter - }; - variantChildren = renderableItems; - break; - case 'checkbox': - variantProps = { - checked: selections, - isGrouped, - hasInlineFilter, - openedOnEnter - }; - variantChildren = renderableItems; - break; - case 'typeahead': - variantProps = { - selected: selections[0], - openedOnEnter - }; - variantChildren = onFavorite ? renderableItems : this.extendTypeaheadChildren(typeaheadCurrIndex); - if (variantChildren.length === 0) { - variantChildren.push(); - } - break; - case 'typeaheadmulti': - variantProps = { - selected: selections, - openedOnEnter - }; - variantChildren = onFavorite ? renderableItems : this.extendTypeaheadChildren(typeaheadCurrIndex); - if (variantChildren.length === 0) { - variantChildren.push(); - } - break; - } - } - - const isStatic = isFlipEnabled && menuAppendTo !== 'inline'; - const innerMenu = ( - - {variantChildren} - - ); - - const menuContainer = footer ?
{innerMenu}
: innerMenu; - - const popperRef = React.createRef(); - const popperContainer = ( -
- {isOpen && menuContainer} -
- ); + }; - const mainContainer = ( -
- - {customContent && ( -
- {toggleIcon && {toggleIcon}} - {placeholderText} -
- )} - {variant === SelectVariant.single && !customContent && ( - -
- {toggleIcon && {toggleIcon}} - - {this.getDisplay(selections[0] as string, 'node') || placeholderText || childPlaceholderText} - -
- {hasOnClear && hasAnySelections && clearBtn} -
- )} - {variant === SelectVariant.checkbox && !customContent && ( - -
- {toggleIcon && {toggleIcon}} - {placeholderText} - {!isCheckboxSelectionBadgeHidden && hasAnySelections && ( -
- - {this.generateSelectedBadge()} - -
- )} -
- {hasOnClear && hasAnySelections && clearBtn} -
- )} - {variant === SelectVariant.typeahead && !customContent && ( - -
- {toggleIcon && {toggleIcon}} - -
- {hasOnClear && (selections[0] || typeaheadInputValue) && clearBtn} -
- )} - {variant === SelectVariant.typeaheadMulti && !customContent && ( - -
- {toggleIcon && {toggleIcon}} - {selections && Array.isArray(selections) && selections.length > 0 && selectedChips} - -
- {hasOnClear && ((selections && selections.length > 0) || typeaheadInputValue) && clearBtn} -
- )} - {validated === ValidatedOptions.success && ( - - - )} - {validated === ValidatedOptions.error && ( - - - )} - {validated === ValidatedOptions.warning && ( - - - )} -
- {isOpen && menuAppendTo === 'inline' && menuContainer} -
- ); + window.addEventListener('keydown', handleMenuKeys); + window.addEventListener('click', handleClick); - const getParentElement = () => { - if (this.parentRef && this.parentRef.current) { - return this.parentRef.current.parentElement; - } - return null; + return () => { + window.removeEventListener('keydown', handleMenuKeys); + window.removeEventListener('click', handleClick); }; - - return ( - - {randomId => ( - - {menuAppendTo === 'inline' ? ( - mainContainer - ) : ( - - )} - - )} - - ); - } -} + }, [isOpen, menuRef, onOpenChange]); + + const menu = ( + onSelect && onSelect(event, itemId)} + isPlain={isPlain} + selected={selected} + {...getOUIAProps( + Select.displayName, + props.ouiaId !== undefined ? props.ouiaId : getDefaultOUIAId(Select.displayName), + props.ouiaSafe !== undefined ? props.ouiaSafe : true + )} + {...props} + > + {children} + + ); + return ( +
+ +
+ ); +}; + +export const Select = React.forwardRef((props: SelectProps, ref: React.Ref) => ( + +)); + +Select.displayName = 'Select'; diff --git a/packages/react-core/src/components/Select/SelectGroup.tsx b/packages/react-core/src/components/Select/SelectGroup.tsx index 82556acb4bf..645f5b2aa00 100644 --- a/packages/react-core/src/components/Select/SelectGroup.tsx +++ b/packages/react-core/src/components/Select/SelectGroup.tsx @@ -1,36 +1,24 @@ -import * as React from 'react'; -import styles from '@patternfly/react-styles/css/components/Select/select'; +import React from 'react'; import { css } from '@patternfly/react-styles'; +import { MenuGroupProps, MenuGroup } from '../Menu'; -import { SelectConsumer, SelectVariant } from './selectConstants'; - -export interface SelectGroupProps extends React.HTMLProps { - /** Checkboxes within group. Must be React.ReactElement[] */ - children?: React.ReactNode; - /** Additional classes added to the CheckboxSelectGroup control */ +export interface SelectGroupProps extends Omit { + /** Anything which can be rendered in a select group */ + children: React.ReactNode; + /** Classes applied to root element of select group */ className?: string; - /** Group label */ + /** Label of the select group */ label?: string; - /** ID for title label */ - titleId?: string; } export const SelectGroup: React.FunctionComponent = ({ - children = [], - className = '', - label = '', - titleId = '', + children, + className, + label, ...props }: SelectGroupProps) => ( - - {({ variant }) => ( -
-
- {label} -
- {variant === SelectVariant.checkbox ? children :
    {children}
} -
- )} -
+ + {children} + ); SelectGroup.displayName = 'SelectGroup'; diff --git a/packages/react-core/src/next/components/Select/SelectList.tsx b/packages/react-core/src/components/Select/SelectList.tsx similarity index 92% rename from packages/react-core/src/next/components/Select/SelectList.tsx rename to packages/react-core/src/components/Select/SelectList.tsx index 39b15ca7bb6..54d8d93a490 100644 --- a/packages/react-core/src/next/components/Select/SelectList.tsx +++ b/packages/react-core/src/components/Select/SelectList.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { css } from '@patternfly/react-styles'; -import { MenuListProps, MenuList } from '../../../components/Menu'; +import { MenuListProps, MenuList } from '../Menu'; export interface SelectListProps extends MenuListProps { /** Anything which can be rendered in a select list */ diff --git a/packages/react-core/src/components/Select/SelectOption.tsx b/packages/react-core/src/components/Select/SelectOption.tsx index 0eb7d3594de..72f4fd2e0ae 100644 --- a/packages/react-core/src/components/Select/SelectOption.tsx +++ b/packages/react-core/src/components/Select/SelectOption.tsx @@ -1,461 +1,31 @@ -import * as React from 'react'; -import styles from '@patternfly/react-styles/css/components/Select/select'; -import checkStyles from '@patternfly/react-styles/css/components/Check/check'; +import React from 'react'; import { css } from '@patternfly/react-styles'; -import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon'; -import { SelectConsumer, SelectVariant } from './selectConstants'; -import StarIcon from '@patternfly/react-icons/dist/esm/icons/star-icon'; -import { getUniqueId } from '../../helpers/util'; -import { KeyTypes } from '../../helpers/constants'; +import { MenuItemProps, MenuItem } from '../Menu'; -export interface SelectOptionObject { - /** Function returns a string to represent the select option object */ - toString(): string; - /** Function returns a true if the passed in select option is equal to this select option object, false otherwise */ - compareTo?(selectOption: any): boolean; -} -export interface SelectOptionProps extends Omit, 'type' | 'ref' | 'value'> { - /** Optional alternate display for the option */ +export interface SelectOptionProps extends Omit { + /** Anything which can be rendered in a select option */ children?: React.ReactNode; - /** Additional classes added to the Select Option */ + /** Classes applied to root element of select option */ className?: string; - /** Description of the item for single and both typeahead select variants */ - description?: React.ReactNode; - /** Number of items matching the option */ - itemCount?: number; - /** @hide Internal index of the option */ - index?: number; - /** Indicates which component will be used as select item */ - component?: React.ReactNode; - /** The value for the option, can be a string or select option object */ - value: string | SelectOptionObject; - /** Flag indicating if the option is disabled */ + /** Identifies the component in the Select onSelect callback */ + itemId?: any; + /** Indicates the option has a checkbox */ + hasCheckbox?: boolean; + /** Indicates the option is disabled */ isDisabled?: boolean; - /** Flag indicating if the option acts as a placeholder */ - isPlaceholder?: boolean; - /** Flag indicating if the option acts as a "no results" indicator */ - isNoResultsOption?: boolean; - /** @hide Internal flag indicating if the option is selected */ + /** Indicates the option is selected */ isSelected?: boolean; - /** @hide Internal flag indicating if the option is checked */ - isChecked?: boolean; - /** Flag forcing the focused state */ + /** Indicates the option is focused */ isFocused?: boolean; - /** @hide Internal callback for ref tracking */ - sendRef?: ( - ref: React.ReactNode, - favoriteRef: React.ReactNode, - optionContainerRef: React.ReactNode, - index: number - ) => void; - /** @hide Internal callback for keyboard navigation */ - keyHandler?: (index: number, innerIndex: number, position: string) => void; - /** Optional callback for click event */ - onClick?: (event: React.MouseEvent | React.ChangeEvent) => void; - /** Id of the checkbox input */ - inputId?: string; - /** @hide Internal Flag indicating if the option is favorited */ - isFavorite?: boolean; - /** Aria label text for favoritable button when favorited */ - ariaIsFavoriteLabel?: string; - /** Aria label text for favoritable button when not favorited */ - ariaIsNotFavoriteLabel?: string; - /** ID of the item. Required for tracking favorites */ - id?: string; - /** @hide Internal flag to apply the load styling to view more option */ - isLoad?: boolean; - /** @hide Internal flag to apply the loading styling to spinner */ - isLoading?: boolean; - /** @hide Internal callback for the setting the index of the next item to focus after view more is clicked */ - setViewMoreNextIndex?: () => void; - /** @hide Flag indicating this is the last option when there is a footer */ - isLastOptionBeforeFooter?: (index: number) => boolean; - /** @hide Flag indicating that the the option loading variant is in a grouped select */ - isGrouped?: boolean; } -export class SelectOption extends React.Component { - static displayName = 'SelectOption'; - private ref = React.createRef(); - private liRef = React.createRef(); - private favoriteRef = React.createRef(); - static defaultProps: SelectOptionProps = { - className: '', - value: '', - index: 0, - isDisabled: false, - isPlaceholder: false, - isSelected: false, - isChecked: false, - isNoResultsOption: false, - component: 'button', - onClick: () => {}, - sendRef: () => {}, - keyHandler: () => {}, - inputId: '', - isFavorite: null, - isLoad: false, - isLoading: false, - setViewMoreNextIndex: () => {}, - isLastOptionBeforeFooter: () => false - }; - - componentDidMount() { - this.props.sendRef( - this.props.isDisabled ? null : this.ref.current, - this.props.isDisabled ? null : this.favoriteRef.current, - this.props.isDisabled ? null : this.liRef.current, - this.props.index - ); - } - - componentDidUpdate() { - this.props.sendRef( - this.props.isDisabled ? null : this.ref.current, - this.props.isDisabled ? null : this.favoriteRef.current, - this.props.isDisabled ? null : this.liRef.current, - this.props.index - ); - } - - onKeyDown = (event: React.KeyboardEvent, innerIndex: number, onEnter?: any, isCheckbox?: boolean) => { - const { index, keyHandler, isLastOptionBeforeFooter } = this.props; - let isLastItemBeforeFooter = false; - if (isLastOptionBeforeFooter !== undefined) { - isLastItemBeforeFooter = isLastOptionBeforeFooter(index); - } - - if (event.key === KeyTypes.Tab) { - // More modal-like experience for checkboxes - if (isCheckbox && !isLastItemBeforeFooter) { - if (event.shiftKey) { - keyHandler(index, innerIndex, 'up'); - } else { - keyHandler(index, innerIndex, 'down'); - } - event.stopPropagation(); - } else { - if (event.shiftKey) { - keyHandler(index, innerIndex, 'up'); - } else { - keyHandler(index, innerIndex, 'tab'); - } - } - } - event.preventDefault(); - if (event.key === KeyTypes.ArrowUp) { - keyHandler(index, innerIndex, 'up'); - } else if (event.key === KeyTypes.ArrowDown) { - keyHandler(index, innerIndex, 'down'); - } else if (event.key === KeyTypes.ArrowLeft) { - keyHandler(index, innerIndex, 'left'); - } else if (event.key === KeyTypes.ArrowRight) { - keyHandler(index, innerIndex, 'right'); - } else if (event.key === KeyTypes.Enter) { - if (onEnter !== undefined) { - onEnter(); - } else { - this.ref.current.click(); - } - } - }; - - render() { - /* eslint-disable @typescript-eslint/no-unused-vars */ - const { - children, - className, - id, - description, - itemCount, - value, - onClick, - isDisabled, - isPlaceholder, - isNoResultsOption, - isSelected, - isChecked, - isFocused, - sendRef, - keyHandler, - index, - component, - inputId, - isFavorite, - ariaIsFavoriteLabel = 'starred', - ariaIsNotFavoriteLabel = 'not starred', - isLoad, - isLoading, - setViewMoreNextIndex, - // eslint-disable-next-line no-console - isLastOptionBeforeFooter, - isGrouped = false, - ...props - } = this.props; - /* eslint-enable @typescript-eslint/no-unused-vars */ - const Component = component as any; - - if (!id && isFavorite !== null) { - // eslint-disable-next-line no-console - console.error('Please provide an id to use the favorites feature.'); - } - - const generatedId = id || getUniqueId('select-option'); - const favoriteButton = (onFavorite: any) => ( - - ); - - const itemDisplay = itemCount ? ( - - - {children || (value && value.toString && value.toString())} - - {itemCount} - - ) : ( - children || value.toString() - ); - - const onViewMoreClick = (event: any) => { - // Set the index for the next item to focus after view more clicked, then call view more callback - setViewMoreNextIndex(); - onClick(event); - }; - - const renderOption = ( - onSelect: ( - event: React.MouseEvent | React.ChangeEvent, - value: string | SelectOptionObject, - isPlaceholder?: boolean - ) => void, - onClose: () => void, - variant: string, - inputIdPrefix: string, - onFavorite: (itemId: string, isFavorite: boolean) => void, - shouldResetOnSelect: boolean - ) => { - if (variant !== SelectVariant.checkbox && isLoading && isGrouped) { - return ( -
- {children} -
- ); - } else if (variant !== SelectVariant.checkbox && isLoad && isGrouped) { - return ( -
- -
- ); - } else if (variant !== SelectVariant.checkbox) { - return ( - - ); - } else if (variant === SelectVariant.checkbox && isLoad) { - return ( - - ); - } else if (variant === SelectVariant.checkbox && isLoading) { - return ( -
{children}
- ); - } else if (variant === SelectVariant.checkbox && !isNoResultsOption && !isLoading && !isLoad) { - return ( - - ); - } else if (variant === SelectVariant.checkbox && isNoResultsOption && !isLoading && !isLoad) { - return ( -
- { - this.onKeyDown(event, 0, undefined, true); - }} - type="button" - > - {itemDisplay} - -
- ); - } - }; - - return ( - - {({ onSelect, onClose, variant, inputIdPrefix, onFavorite, shouldResetOnSelect }) => ( - - {renderOption(onSelect, onClose, variant, inputIdPrefix, onFavorite, shouldResetOnSelect)} - - )} - - ); - } -} +export const SelectOption: React.FunctionComponent = ({ + children, + className, + ...props +}: SelectOptionProps) => ( + + {children} + +); +SelectOption.displayName = 'SelectOption'; diff --git a/packages/react-core/src/components/Select/examples/Select.md b/packages/react-core/src/components/Select/examples/Select.md index 1118086adf5..782430b7170 100644 --- a/packages/react-core/src/components/Select/examples/Select.md +++ b/packages/react-core/src/components/Select/examples/Select.md @@ -3,2921 +3,35 @@ id: Select section: components subsection: menus cssPrefix: pf-c-select -propComponents: ['Select', 'SelectOption', 'SelectGroup', 'SelectOptionObject', 'SelectViewMoreObject'] +propComponents: ['Select', 'SelectOption', 'SelectGroup', 'SelectList', 'MenuToggle'] ouia: true --- -import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; ## Examples -### Single select +### Single -To let users select a single item from a list, use a single select list. - -A select list may use other properties for additional customization. Select each checkbox in the example below to visualize the following behavior: - -- To prevent a toggle click from opening a select list, use the `isDisabled` property. -- To adjust the direction a select menu opens, use the `direction` property. The menu in the following example expands upwards. By default, select lists open upwards. -- To add an icon to a select toggle, use the `toggleIcon` property. - -```js -import React from 'react'; -import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; -import { Select, SelectOption, SelectVariant, SelectDirection, Checkbox, Divider } from '@patternfly/react-core'; - -class SingleSelectInput extends React.Component { - constructor(props) { - super(props); - this.options = [ - , - , - , - , - , - , - , - - ]; - - this.toggleRef = React.createRef(); - - this.state = { - isToggleIcon: false, - isOpen: false, - selected: null, - isDisabled: false, - direction: SelectDirection.down - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection, isPlaceholder) => { - if (isPlaceholder) this.clearSelection(); - else { - this.setState({ - selected: selection, - isOpen: false - }); - console.log('selected:', selection); - this.toggleRef.current.focus(); - } - }; - - this.clearSelection = () => { - this.setState({ - selected: null, - isOpen: false - }); - }; - - this.toggleDisabled = checked => { - this.setState({ - isDisabled: checked - }); - }; - - this.setIcon = checked => { - this.setState({ - isToggleIcon: checked - }); - }; - - this.toggleDirection = () => { - if (this.state.direction === SelectDirection.up) { - this.setState({ - direction: SelectDirection.down - }); - } else { - this.setState({ - direction: SelectDirection.up - }); - } - }; - } - - render() { - const { isOpen, selected, isDisabled, direction, isToggleIcon } = this.state; - const titleId = 'title-id-1'; - return ( -
- - - this.toggleDisabled(checked)} - aria-label="disabled checkbox" - id="toggle-disabled" - name="toggle-disabled" - /> - - this.setIcon(checked)} - aria-label="show icon checkbox" - id="toggle-icon" - name="toggle-icon" - /> -
- ); - } -} -``` - -### With item descriptions - -To give more context to a `` in a list, use the `description` property. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -class SingleSelectDescription extends React.Component { - constructor(props) { - super(props); - this.options = [ - { value: 'Mr', disabled: false }, - { value: 'Miss', disabled: false }, - { value: 'Mrs', disabled: false }, - { value: 'Ms', disabled: false }, - { value: 'Dr', disabled: false }, - { value: 'Other', disabled: false } - ]; - - this.toggleRef = React.createRef(); - - this.state = { - isOpen: false, - selected: null, - isDisabled: false - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection, isPlaceholder) => { - if (isPlaceholder) this.clearSelection(); - else { - this.setState({ - selected: selection, - isOpen: false - }); - console.log('selected:', selection); - this.toggleRef.current.focus(); - } - }; - - this.clearSelection = () => { - this.setState({ - selected: null, - isOpen: false - }); - }; - } - - render() { - const { isOpen, selected, isDisabled, direction, isToggleIcon } = this.state; - const titleId = 'select-descriptions-title'; - return ( -
- - -
- ); - } -} -``` - -### With grouped items - -To group related select options together, use 1 or more `` components and title each group using the `label` property. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant, SelectGroup, Divider } from '@patternfly/react-core'; - -class GroupedSingleSelectInput extends React.Component { - constructor(props) { - super(props); - this.state = { - isOpen: false, - selected: null - }; - - this.toggleRef = React.createRef(); - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection) => { - this.setState({ - selected: selection, - isOpen: false - }); - this.toggleRef.current.focus(); - }; - - this.clearSelection = () => { - this.setState({ - selected: null - }); - }; - - this.options = [ - - - - - - - , - , - - - - - - ]; - } - - render() { - const { isOpen, selected } = this.state; - const titleId = 'grouped-single-select-id'; - return ( -
- - -
- ); - } -} -``` - -### Favoriting items - -To allow users to favorite items in a select list, use the `onFavorite` callback. When users click the favorite button, the item is duplicated and placed in a separated group at the top of the menu. To change the name of the group use the `favoritesLabel` property. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant, SelectGroup } from '@patternfly/react-core'; - -class FavoritesSelect extends React.Component { - constructor(props) { - super(props); - this.state = { - isOpen: false, - selected: null, - favorites: [] - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection, isPlaceholder) => { - if (isPlaceholder) this.clearSelection(); - else { - this.setState({ - selected: selection, - isOpen: false - }); - console.log('selected:', selection); - } - }; - - this.clearSelection = () => { - this.setState({ - selected: null, - isOpen: false - }); - }; - - this.onFavorite = (itemId, isFavorite) => { - if (isFavorite) { - this.setState({ - favorites: this.state.favorites.filter(id => id !== itemId) - }); - } else - this.setState({ - favorites: [...this.state.favorites, itemId] - }); - }; - - this.options = [ - - - - - - - , - - - - - - ]; - } - - render() { - const { isOpen, selected, favorites } = this.state; - const titleId = 'grouped-single-select-id'; - return ( - - ); - } -} -``` - -### Validated selections - -To validate selections that users make, pass a validation state to the `validated` property. Validating selections can let users know if the selections they make would cause issues or errors. - -The example below passes an "error" state when you choose “select a title”, a "warning" state when you choose "other", and a "success" state for any other item selected from the menu. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -class ValidatedSelect extends React.Component { - constructor(props) { - super(props); - this.options = [ - , - , - , - , - , - , - - ]; - - this.toggleRef = React.createRef(); - - this.state = { - isOpen: false, - selected: null, - isDisabled: false, - validated: 'default' - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection, isPlaceholder) => { - let validatedState = 'success'; - if (isPlaceholder) { - this.clearSelection(); - validatedState = 'error'; - } else { - if (selection === 'Other') { - validatedState = 'warning'; - } else { - validatedState = 'success'; - } - this.setState({ - selected: selection, - isOpen: false - }); - console.log('selected:', selection); - } - this.setState({ - validated: validatedState - }); - this.toggleRef.current.focus(); - }; - - this.clearSelection = () => { - this.setState({ - selected: null, - isOpen: false - }); - }; - } - - render() { - const { isOpen, selected, isDisabled, direction, isToggleIcon, validated } = this.state; - const titleId = 'select-validated-title'; - return ( -
- - - -
- ); - } -} -``` - -### Styled placeholder text - -To add a toggle label to a select, use the `placeholderText` property. The following example displays "Filter by status" in the toggle before a selection is made. - -To fade the color of `placeholderText` to gray, use the `hasPlaceholderStyle` property. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -function SelectWithPlaceholderStyle() { - const [isOpen, setIsOpen] = React.useState(false); - const [selected, setSelected] = React.useState([]); - - const options = [ - , - , - - ]; - - const onToggle = (_event, isOpen) => setIsOpen(isOpen); - - const onSelect = (event, selection, isPlaceholder) => { - setSelected(selection); - setIsOpen(false); - }; - - const clearSelection = () => { - setSelected(null); - setIsOpen(false); - }; - - const titleId = 'placeholder-style-select-id'; - - return ( -
- - -
- ); -} -``` - -### Placeholder select options - -To set a `` as a placeholder, use the `isPlaceholder` property. The following example sets the "Filter by status" as a placeholder so that it is pre-selected. - -```js - -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -function SelectWithPlaceholderStyle() { - const [isOpen, setIsOpen] = React.useState(false); - const [selected, setSelected] = React.useState([]); - - const options = [ - , - , - , - - ]; - - const onToggle = (_event, isOpen) => setIsOpen(isOpen); - - const onSelect = (event, selection, isPlaceholder) => { - setSelected(selection); - setIsOpen(false); - }; - - const clearSelection = () => { - setSelected(null); - setIsOpen(false); - }; - - const titleId = 'placeholder-style-select-option-id'; - - return ( -
- - -
- ); -} -``` - -### With a footer - -You can add a `footer` to a `} - toggleRef={this.toggleRef} - variant={SelectVariant.single} - aria-label="Select Input" - onToggle={this.onToggle} - onSelect={this.onSelect} - selections={selected} - isOpen={isOpen} - aria-labelledby={titleId} - isDisabled={isDisabled} - direction={direction} - footer={ - <> - - - } - > - {this.options} - -
- ); - } -} -``` - -### With view more - -To reduce the processing load for long select lists, replace overflow items with a "View more" link at the bottom of the select menu. - -Adjust the number of items shown above the "View more" link with the `numOptions` property. The following example passes 3 items into this property. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -class SelectViewMore extends React.Component { - constructor(props) { - super(props); - this.options = [ - , - , - , - , - , - , - - ]; - - this.toggleRef = React.createRef(); - - this.state = { - isOpen: false, - selected: null, - numOptions: 3, - isLoading: false - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection, isPlaceholder) => { - if (isPlaceholder) this.clearSelection(); - else { - this.setState({ - selected: selection, - isOpen: false - }); - console.log('selected:', selection); - this.toggleRef.current.focus(); - } - }; - - this.clearSelection = () => { - this.setState({ - selected: null, - isOpen: false - }); - }; - - this.simulateNetworkCall = callback => { - setTimeout(callback, 2000); - }; - - this.onViewMoreClick = () => { - // Set select loadingVariant to spinner then simulate network call before loading more options - this.setState({ isLoading: true }); - this.simulateNetworkCall(() => { - const newLength = - this.state.numOptions + 3 <= this.options.length ? this.state.numOptions + 3 : this.options.length; - this.setState({ numOptions: newLength, isLoading: false }); - }); - }; - } - - render() { - const { isOpen, selected, isToggleIcon, numOptions, loadingVariant, isLoading } = this.state; - const titleId = 'title-id-view-more'; - return ( -
- - -
- ); - } -} -``` - -### Checkbox select - -To let users select multiple list options via checkbox input, use a checkbox select. To create a checkbox select, pass `variant={SelectVariant.checkbox}` into the ` - {this.options} - - - ); - } -} -``` - -### Checkbox select with grouped items - -You can use groups alongside checkbox input. The item count badge will display the number of items selected across all groups. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant, SelectGroup } from '@patternfly/react-core'; - -class GroupedCheckboxSelectInput extends React.Component { - constructor(props) { - super(props); - this.state = { - isOpen: false, - selected: [] - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - 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: [] - }); - }; - - this.options = [ - - - - - - - , - - - - - - ]; - } - - render() { - const { isOpen, selected } = this.state; - const titleId = 'grouped-checkbox-select-id-1'; - return ( -
- - -
- ); - } -} +```ts file="./SelectBasic.tsx" ``` -### Checkbox select with custom badge - -To change the default badge text for a checkbox select, use the `customBadgeText` property. The following example uses `customBadgeText` to display "all" in the badge once all menu items are selected. - -```js -import React from 'react'; -import { Select, SelectOption, SelectGroup, SelectVariant } from '@patternfly/react-core'; - -class FilteringCheckboxSelectInputWithBadging extends React.Component { - constructor(props) { - super(props); - - this.state = { - isOpen: false, - selected: [], - customBadgeText: 0 - }; - - this.options = [ - - - - - - - , - - - - - - ]; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection) => { - const { selected } = this.state; - if (selected.includes(selection)) { - this.setState( - prevState => ({ - selected: prevState.selected.filter(item => item !== selection), - customBadgeText: this.setBadgeText(prevState.selected.length - 1) - }), - () => console.log('selections: ', this.state.selected) - ); - } else { - this.setState( - prevState => ({ - selected: [...prevState.selected, selection], - customBadgeText: this.setBadgeText(prevState.selected.length + 1) - }), - () => console.log('selections: ', this.state.selected) - ); - } - }; - - this.onFilter = (_, textInput) => { - if (textInput === '') { - return this.options; - } else { - let filteredGroups = this.options - .map(group => { - let filteredGroup = React.cloneElement(group, { - children: group.props.children.filter(item => { - return item.props.value.toLowerCase().includes(textInput.toLowerCase()); - }) - }); - if (filteredGroup.props.children.length > 0) return filteredGroup; - }) - .filter(newGroup => newGroup); - return filteredGroups; - } - }; - - this.clearSelection = () => { - this.setState({ - selected: [], - customBadgeText: this.setBadgeText(0) - }); - }; - - this.setBadgeText = selected => { - if (selected === 7) { - return 'All'; - } - if (selected === 0) { - return 0; - } - return null; - }; - } - - render() { - const { isOpen, selected, filteredOptions, customBadgeText } = this.state; - const titleId = 'checkbox-filtering-custom-badging-select-id'; - return ( -
- - -
- ); - } -} -``` - -### Checkbox select without selected count - -To remove the default item count badge, use the `isCheckboxSelectionBadgeHidden` property. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -class CheckboxSelectInputNoBadge extends React.Component { - constructor(props) { - super(props); - - this.state = { - isOpen: false, - selected: [] - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; +### Grouped single - 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: [] - }); - }; - - this.options = [ - , - , - , - - ]; - } - - render() { - const { isOpen, selected } = this.state; - const titleId = 'checkbox-no-badge-select-id'; - return ( -
- - -
- ); - } -} +```ts file="./SelectGrouped.tsx" ``` -### Checkbox select with item counts - -To show users the number of items that a `` would match, use the `itemCount` property. The numerical value you pass into `itemCount` is displayed to the right of each menu item. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant, Divider } from '@patternfly/react-core'; - -class CheckboxSelectWithCounts extends React.Component { - constructor(props) { - super(props); - - this.state = { - isOpen: false, - selected: [] - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - 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: [] - }); - }; - - this.options = [ - , - , - , - , - - ]; - } +### Checkbox - render() { - const { isOpen, selected } = this.state; - const titleId = 'checkbox-select-with-counts-id'; - return ( -
- - -
- ); - } -} -``` - -### Checkbox select with a footer - -You can combine a footer with checkbox input to allow users to apply an action to multiple items. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant, Button } from '@patternfly/react-core'; - -class SelectWithFooterCheckbox extends React.Component { - constructor(props) { - super(props); - this.state = { - isOpen: false, - selected: [], - numOptions: 3, - isLoading: false - }; - - this.toggleRef = React.createRef(); - - this.options = [ - , - , - , - , - - ]; - - this.onToggle = (_event, isOpen) => { - this.setState({ isOpen }); - }; - - this.onSelect = (event, selection) => { - this.setState({ selected: selection, isOpen: false }), console.log('selected: ', selection); - this.toggleRef.current.focus(); - }; - - this.onFilter = (_, textInput) => { - if (textInput === '') { - return this.options; - } else { - this.setState( - prevState => ({ selected: [...prevState.selected, selection] }), - () => console.log('selections: ', this.state.selected) - ); - } - }; - - this.clearSelection = () => { - this.setState({ - selected: [] - }); - }; - } - - render() { - const { isOpen, selected, isDisabled, direction, isToggleIcon } = this.state; - const titleId = 'title-id-footer-checkbox'; - return ( -
- - -
- ); - } -} -``` - -### Checkbox select with view more - -When a "view more" link is used alongside checkbox input, selections that users make prior to clicking "view more" are persisted after the click. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -class SelectViewMoreCheckbox extends React.Component { - constructor(props) { - super(props); - - this.state = { - isOpen: false, - selected: [], - numOptions: 3, - isLoading: false - }; - - this.options = [ - , - , - , - , - , - , - , - , - - ]; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - 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: [] - }); - }; - - this.simulateNetworkCall = callback => { - setTimeout(callback, 2000); - }; - - this.onViewMoreClick = () => { - // Set select loadingVariant to spinner then simulate network call before loading more options - this.setState({ isLoading: true }); - this.simulateNetworkCall(() => { - const newLength = - this.state.numOptions + 3 <= this.options.length ? this.state.numOptions + 3 : this.options.length; - this.setState({ numOptions: newLength, isLoading: false }); - }); - }; - } - - render() { - const { isOpen, selected, numOptions, isLoading } = this.state; - const titleId = 'view-more-checkbox-select-id'; - return ( -
- - -
- ); - } -} -``` - -### Filtering with placeholder text - -To preload a filter search bar with placeholder text, use the `inlineFilterPlaceholderText` property. The following example preloads the search bar with "Filter by status". - -```js -import React from 'react'; -import { Select, SelectOption, SelectGroup, SelectVariant } from '@patternfly/react-core'; - -class FilteringCheckboxSelectInputWithPlaceholder extends React.Component { - constructor(props) { - super(props); - - this.state = { - isOpen: false, - selected: [] - }; - - this.options = [ - - - - - - - , - - - - - - ]; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - 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.onFilter = (_, textInput) => { - if (textInput === '') { - return this.options; - } else { - let filteredGroups = this.options - .map(group => { - let filteredGroup = React.cloneElement(group, { - children: group.props.children.filter(item => { - return item.props.value.toLowerCase().includes(textInput.toLowerCase()); - }) - }); - if (filteredGroup.props.children.length > 0) return filteredGroup; - }) - .filter(newGroup => newGroup); - return filteredGroups; - } - }; - - this.clearSelection = () => { - this.setState({ - selected: [] - }); - }; - } - - render() { - const { isOpen, selected, filteredOptions } = this.state; - const titleId = 'checkbox-filtering-with-placeholder-select-id'; - return ( -
- - -
- ); - } -} -``` - -### Inline filtering - -To allow users to filter select lists using text input, use the `hasInlineFilter` property. Filtering behavior can be further customized with other properties, as shown in the example below. Select each checkbox to visualize the following behavior: - -- To persist filter results on blur, use the `isInputValuePersisted` property. -- To persist a filter that a user has searched, use the `isInputFilterPersisted` property. -- To allow users to add new items to a select list, use the `isCreatable` property. When this property is applied and a user searches for an option that doesn't exist, they will be prompted to "create" the item. - -```js -import React from 'react'; -import { Select, SelectOption, SelectGroup, SelectVariant, Checkbox } from '@patternfly/react-core'; - -class FilteringSingleSelectInput extends React.Component { - constructor(props) { - super(props); - - this.state = { - isOpen: false, - selected: '', - isCreatable: false, - isInputValuePersisted: false, - isInputFilterPersisted: false - }; - - this.options = [ - - - - - - - , - - - - - - ]; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection) => { - this.setState({ selected: selection, isOpen: false }), console.log('selected: ', selection); - }; - - this.onFilter = (_, textInput) => { - if (textInput === '') { - return this.options; - } else { - let filteredGroups = this.options - .map(group => { - let filteredGroup = React.cloneElement(group, { - children: group.props.children.filter(item => { - return item.props.value.toLowerCase().includes(textInput.toLowerCase()); - }) - }); - if (filteredGroup.props.children.length > 0) return filteredGroup; - }) - .filter(Boolean); - return filteredGroups; - } - }; - - this.toggleCreatable = (_, checked) => { - this.setState({ - isCreatable: checked - }); - }; - - this.toggleInputValuePersisted = (_, checked) => { - this.setState({ - isInputValuePersisted: checked - }); - }; - - this.toggleInputFilterPersisted = (_, checked) => { - this.setState({ - isInputFilterPersisted: checked - }); - }; - } - - render() { - const { - isOpen, - selected, - filteredOptions, - isInputValuePersisted, - isInputFilterPersisted, - isCreatable - } = this.state; - const titleId = 'single-filtering-select-id'; - return ( -
- - - - - -
- ); - } -} +```ts file="./SelectCheckbox.tsx" ``` ### Typeahead -Typeahead is a select variant that replaces the typical button toggle for opening the select menu with a text input and button toggle combo. As a user types in the text input, the select menu will provide suggestions by filtering the select options. - -To make a typeahead, pass `variant=typeahead` into the ` - {options.map((option, index) => ( - - ))} - - this.toggleDisabled(checked)} - aria-label="toggle disabled checkbox" - id="toggle-disabled-typeahead" - name="toggle-disabled-typeahead" - /> - this.toggleCreatable(checked)} - aria-label="toggle creatable checkbox" - id="toggle-creatable-typeahead" - name="toggle-creatable-typeahead" - /> - this.toggleCreateOptionOnTop(checked)} - aria-label="toggle createOptionOnTop checkbox" - id="toggle-create-option-on-top-typeahead" - name="toggle-create-option-on-top-typeahead" - /> - this.toggleCreateNew(checked)} - aria-label="toggle new checkbox" - id="toggle-new-typeahead" - name="toggle-new-typeahead" - /> - this.toggleInputValuePersisted(checked)} - aria-label="toggle input value persisted" - id="toggle-input-value-persisted" - name="toggle-input-value-persisted" - /> - this.toggleInputFilterPersisted(checked)} - aria-label="toggle input filter persisted" - id="toggle-input-filter-persisted" - name="toggle-input-filter-persisted" - /> - this.toggleResetOnSelect(checked)} - aria-label="toggle reset checkbox" - id="toggle-reset-typeahead" - name="toggle-reset-typeahead" - /> - - ); - } -} -``` - -### Grouped typeahead - -Typeahead matches items with user input across groups. - -```js -import React from 'react'; -import { Checkbox, Select, SelectGroup, SelectOption, SelectVariant, Divider } from '@patternfly/react-core'; - -class GroupedTypeaheadSelectInput extends React.Component { - constructor(props) { - super(props); - - this.state = { - options: [ - - - - - - - , - , - - - - - - ], - newOptions: [], - isOpen: false, - selected: null, - isCreatable: false, - hasOnCreateOption: false - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection, isPlaceholder) => { - if (isPlaceholder) this.clearSelection(); - else { - this.setState({ - selected: selection, - isOpen: false - }); - console.log('selected:', selection); - } - }; - - this.onCreateOption = newValue => { - this.setState({ - newOptions: [...this.state.newOptions, ] - }); - }; - - this.clearSelection = () => { - this.setState({ - selected: null, - isOpen: false - }); - }; - - this.toggleCreatable = checked => { - this.setState({ - isCreatable: checked - }); - }; - - this.toggleCreateNew = checked => { - this.setState({ - hasOnCreateOption: checked - }); - }; - } - - render() { - const { isOpen, selected, isDisabled, isCreatable, hasOnCreateOption, options, newOptions } = this.state; - const titleId = 'grouped-typeahead-select-id'; - const allOptions = - newOptions.length > 0 - ? options.concat( - - {newOptions} - - ) - : options; - return ( -
- - - this.toggleCreatable(checked)} - aria-label="toggle creatable checkbox" - id="toggle-creatable-grouped-typeahead" - name="toggle-creatable-grouped-typeahead" - /> - this.toggleCreateNew(checked)} - aria-label="toggle new checkbox" - id="toggle-new-grouped-typeahead" - name="toggle-new-grouped-typeahead" - /> -
- ); - } -} -``` - -### Typeahead with custom filtering - -You can add custom filtering to a select list to better fit needs that aren't covered by inline filtering. If you use custom filtering, use the `onFilter` property to trigger a callback with your custom filter implementation. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -class TypeaheadSelectInput extends React.Component { - constructor(props) { - super(props); - this.options = [ - , - , - , - , - , - - ]; - this.state = { - isOpen: false, - selected: null - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.onSelect = (event, selection, isPlaceholder) => { - if (isPlaceholder) this.clearSelection(); - else { - this.setState({ - selected: selection, - isOpen: false - }); - console.log('selected:', selection); - } - }; - - this.clearSelection = () => { - this.setState({ - selected: null, - isOpen: false - }); - }; - - this.customFilter = (_, value) => { - if (!value) { - return this.options; - } - - const input = new RegExp(value, 'i'); - return this.options.filter(child => input.test(child.props.value)); - }; - } - - render() { - const { isOpen, selected } = this.state; - const titleId = 'typeahead-select-id-2'; - return ( -
- - -
- ); - } -} +```ts file="./SelectTypeahead.tsx" ``` -### Multiple typeahead - -To create a multiple typeahead select variant, pass `variant={SelectVariant.typeaheadMulti}` into the ` - {options.map((option, index) => ( - - ))} - - this.toggleCreatable(checked)} - aria-label="toggle creatable checkbox" - id="toggle-creatable-typeahead-multi" - name="toggle-creatable-typeahead-multi" - /> - this.toggleCreateNew(checked)} - aria-label="toggle new checkbox" - id="toggle-new-typeahead-multi" - name="toggle-new-typeahead-multi" - /> - - this.toggleResetOnSelect(checked)} - aria-label="toggle multi reset checkbox" - id="toggle-reset-multi-typeahead" - name="toggle-reset-multi-typeahead" - /> - - ); - } -} -``` - -### Multiple typeahead with custom chips - -To customize the appearance of chips, use the `chipGroupProps` property. The `numChips` property allows you to control the number of items shown, while the `expandedText` and `collapsedText` properties allow you to control the labels of the expansion and collapse chips. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -class MultiTypeaheadSelectInputWithChipGroupProps extends React.Component { - constructor(props) { - super(props); - - this.state = { - options: [ - { value: 'Alabama', disabled: false }, - { value: 'Florida', disabled: false }, - { value: 'New Jersey', disabled: false }, - { value: 'New Mexico', disabled: false, description: 'This is a description' }, - { value: 'New York', disabled: false }, - { value: 'North Carolina', disabled: false } - ], - isOpen: false, - selected: [] - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - 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: [], - isOpen: false - }); - }; - } - - render() { - const { isOpen, selected, isCreatable, hasOnCreateOption } = this.state; - const titleId = 'multi-typeahead-custom-chip-group-props-id-1'; - - return ( -
- - -
- ); - } -} -``` - -### Multiple typeahead with chip group - -To customize chips even more, render a [``](/components/chip-group) component and pass it into the `chipGroupComponent` property of the ` - {this.state.options.map((option, index) => ( - - ))} - - - ); - } -} -``` - -### Multiple typeahead with custom objects - -A `` can have an object passed into the `value` property in order to store additional data beyond just a string value. The object passed in must have a `toString` function that returns a string to display in the `SelectMenu`. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant, Divider } from '@patternfly/react-core'; - -class MultiTypeaheadSelectInputCustomObjects extends React.Component { - constructor(props) { - super(props); - this.createState = (name, abbreviation, capital, founded) => { - return { - name: name, - abbreviation: abbreviation, - capital: capital, - founded: founded, - toString: function() { - return `${this.name} (${this.abbreviation}) - Founded: ${this.founded}`; - }, - compareTo: function(value) { - return this.toString() - .toLowerCase() - .includes(value.toString().toLowerCase()); - } - }; - }; - this.options = [ - , - , - , - , - , - , - - ]; - - this.state = { - isOpen: false, - selected: [] - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - 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: [], - isOpen: false - }); - }; - } - - render() { - const { isOpen, selected } = this.state; - const titleId = 'multi-typeahead-select-id-2'; - - return ( -
- - -
- ); - } -} -``` - -### Plain multiple typeahead - -To plainly style a typeahead, use the `isPlain` property. - -```js -import React from 'react'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; - -class PlainSelectInput 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 = { - isOpen: false, - isPlain: true, - selected: [] - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - 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: [], - isOpen: false - }); - }; - } - - render() { - const { isOpen, isPlain, selected } = this.state; - const titleId = 'plain-typeahead-select-id'; - - return ( -
- - -
- ); - } -} -``` - -### Custom menu content - -To add custom menu content, use the `customContent` property. - -```js -import React from 'react'; -import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; -import { Select, SelectOption, SelectDirection, Checkbox } from '@patternfly/react-core'; - -class SingleSelectInput extends React.Component { - constructor(props) { - super(props); - this.state = { - isOpen: false, - isDisabled: false, - direction: SelectDirection.down - }; - - this.onToggle = (_event, isOpen) => { - this.setState({ - isOpen - }); - }; - - this.toggleDisabled = checked => { - this.setState({ - isDisabled: checked - }); - }; - - this.toggleDirection = () => { - if (this.state.direction === SelectDirection.up) { - this.setState({ - direction: SelectDirection.down - }); - } else { - this.setState({ - direction: SelectDirection.up - }); - } - }; - } - - render() { - const { isOpen, selected, isDisabled, direction } = this.state; - const titleId = 'title-id-2'; - return ( -
- - document.body} - > - {this.bodyOptions} - - - - - - - ); - } -} +```ts file="./SelectMultiTypeahead.tsx" ``` diff --git a/packages/react-core/src/next/components/Select/examples/SelectBasic.tsx b/packages/react-core/src/components/Select/examples/SelectBasic.tsx similarity index 89% rename from packages/react-core/src/next/components/Select/examples/SelectBasic.tsx rename to packages/react-core/src/components/Select/examples/SelectBasic.tsx index c1519ea0de6..f4084a53049 100644 --- a/packages/react-core/src/next/components/Select/examples/SelectBasic.tsx +++ b/packages/react-core/src/components/Select/examples/SelectBasic.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Select, SelectOption, SelectList } from '@patternfly/react-core/next'; -import { MenuToggle, MenuToggleElement } from '@patternfly/react-core'; +import { Select, SelectOption, SelectList, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; export const SelectBasic: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); diff --git a/packages/react-core/src/next/components/Select/examples/SelectCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx similarity index 92% rename from packages/react-core/src/next/components/Select/examples/SelectCheckbox.tsx rename to packages/react-core/src/components/Select/examples/SelectCheckbox.tsx index 026300c4d8b..70b6a1b9d3b 100644 --- a/packages/react-core/src/next/components/Select/examples/SelectCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Select, SelectOption, SelectList } from '@patternfly/react-core/next'; -import { MenuToggle, MenuToggleElement, Badge } from '@patternfly/react-core'; +import { Select, SelectOption, SelectList, MenuToggle, MenuToggleElement, Badge } from '@patternfly/react-core'; export const SelectCheckbox: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); diff --git a/packages/react-core/src/next/components/Select/examples/SelectGrouped.tsx b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx similarity index 91% rename from packages/react-core/src/next/components/Select/examples/SelectGrouped.tsx rename to packages/react-core/src/components/Select/examples/SelectGrouped.tsx index 852df961785..184188499ca 100644 --- a/packages/react-core/src/next/components/Select/examples/SelectGrouped.tsx +++ b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Select, SelectOption, SelectList, SelectGroup } from '@patternfly/react-core/next'; -import { MenuToggle, MenuToggleElement } from '@patternfly/react-core'; +import { Select, SelectOption, SelectList, SelectGroup, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; export const SelectBasic: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); diff --git a/packages/react-core/src/next/components/Select/examples/SelectMultiTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx similarity index 98% rename from packages/react-core/src/next/components/Select/examples/SelectMultiTypeahead.tsx rename to packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx index 4f647853d81..6bf61e9fc1f 100644 --- a/packages/react-core/src/next/components/Select/examples/SelectMultiTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx @@ -1,6 +1,9 @@ import React from 'react'; -import { Select, SelectOption, SelectList, SelectOptionProps } from '@patternfly/react-core/next'; import { + Select, + SelectOption, + SelectList, + SelectOptionProps, MenuToggle, MenuToggleElement, TextInputGroup, diff --git a/packages/react-core/src/next/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx similarity index 98% rename from packages/react-core/src/next/components/Select/examples/SelectTypeahead.tsx rename to packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index bee36e891bb..b360398d10c 100644 --- a/packages/react-core/src/next/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -1,6 +1,9 @@ import React from 'react'; -import { Select, SelectOption, SelectList, SelectOptionProps } from '@patternfly/react-core/next'; import { + Select, + SelectOption, + SelectList, + SelectOptionProps, MenuToggle, MenuToggleElement, TextInputGroup, diff --git a/packages/react-core/src/components/Select/index.ts b/packages/react-core/src/components/Select/index.ts index dd66dd96b7c..5a8bca40468 100644 --- a/packages/react-core/src/components/Select/index.ts +++ b/packages/react-core/src/components/Select/index.ts @@ -1,4 +1,4 @@ export * from './Select'; export * from './SelectGroup'; +export * from './SelectList'; export * from './SelectOption'; -export * from './selectConstants'; diff --git a/packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx b/packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx index 3da12a652da..582485299aa 100644 --- a/packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx +++ b/packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx @@ -8,8 +8,7 @@ import { ToolbarItem } from '../ToolbarItem'; import { ToolbarContent } from '../ToolbarContent'; import { ToolbarFilter } from '../ToolbarFilter'; import { ToolbarGroup } from '../ToolbarGroup'; -import { Select, SelectVariant, SelectOption } from '../../Select'; -import { Button } from '../../Button'; +import { Button } from '../../Button/Button'; describe('Toolbar', () => { it('should render inset', () => { @@ -59,13 +58,6 @@ describe('Toolbar', () => { }); it('should render with custom chip content', () => { - const statusMenuItems = [ - , - , - , - - ]; - const items = ( } breakpoint="xl"> @@ -76,17 +68,7 @@ describe('Toolbar', () => { deleteChipGroup={category => {}} categoryName="Status" > - + test content @@ -119,9 +101,9 @@ describe('Toolbar', () => { ); + expect(asFragment()).toMatchSnapshot(); // Expecting 2 matches for text because the buttons also exist in hidden expandable content for mobile view expect(screen.getAllByRole('button', { name: 'Save filters' }).length).toBe(2); expect(screen.getAllByRole('button', { name: 'Clear all filters' }).length).toBe(2); - expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/packages/react-core/src/components/Toolbar/__tests__/__snapshots__/Toolbar.test.tsx.snap b/packages/react-core/src/components/Toolbar/__tests__/__snapshots__/Toolbar.test.tsx.snap index bdfc3f8b52e..881f026cb92 100644 --- a/packages/react-core/src/components/Toolbar/__tests__/__snapshots__/Toolbar.test.tsx.snap +++ b/packages/react-core/src/components/Toolbar/__tests__/__snapshots__/Toolbar.test.tsx.snap @@ -94,119 +94,7 @@ exports[`Toolbar should render with custom chip content 1`] = `
-
- -
    - - - - -
-
+ test content
@@ -267,7 +155,7 @@ exports[`Toolbar should render with custom chip content 1`] = ` class="pf-c-toolbar__item pf-m-chip-group" >
Status
    @@ -301,7 +189,7 @@ exports[`Toolbar should render with custom chip content 1`] = ` > New @@ -312,12 +200,12 @@ exports[`Toolbar should render with custom chip content 1`] = `
diff --git a/packages/react-core/src/demos/Card/Card.md b/packages/react-core/src/demos/Card/Card.md index 65ccac01b12..5f387ba4225 100644 --- a/packages/react-core/src/demos/Card/Card.md +++ b/packages/react-core/src/demos/Card/Card.md @@ -39,6 +39,7 @@ This demonstrates how you can assemble a full page view that contains a grid of ```js isFullscreen import React from 'react'; import { + Badge, Bullseye, Button, Card, @@ -63,16 +64,16 @@ import { PageSection, PageSectionVariants, Pagination, - Select, - SelectOption, - SelectVariant, TextContent, Text, Title, Toolbar, ToolbarItem, ToolbarFilter, - ToolbarContent + ToolbarContent, + Select, + SelectList, + SelectOption } from '@patternfly/react-core'; import { Dropdown as DropdownDeprecated, @@ -131,9 +132,9 @@ class CardViewBasic extends React.Component { return selected === total; }; - this.onToolbarDropdownToggle = (_event, isLowerToolbarDropdownOpen) => { + this.onToolbarDropdownToggle = () => { this.setState((prevState) => ({ - isLowerToolbarDropdownOpen + isLowerToolbarDropdownOpen: !prevState.isLowerToolbarDropdownOpen })); }; @@ -462,29 +463,44 @@ class CardViewBasic extends React.Component { buildFilterDropdown() { const { isLowerToolbarDropdownOpen, filters } = this.state; - const filterDropdownItems = [ - , - , - , - , - , - , - , - , - , - - ]; + const filterDropdownItems = ( + + PatternFly + ActiveMQ + Apache Spark + Avro + Azure Services + Crypto + DropBox + JBoss Data Grid + REST + SWAGGER + + ); return ( diff --git a/packages/react-core/src/demos/CardDemos.md b/packages/react-core/src/demos/CardDemos.md index f9e0b30c034..87a96b6de55 100644 --- a/packages/react-core/src/demos/CardDemos.md +++ b/packages/react-core/src/demos/CardDemos.md @@ -1254,7 +1254,10 @@ import { StackItem, Divider, Select, - SelectOption + SelectList, + SelectOption, + MenuToggle, + MenuToggleElement } from '@patternfly/react-core'; import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import { Chart, ChartStack, ChartBar, ChartTooltip } from '@patternfly/react-charts'; @@ -1266,21 +1269,32 @@ import chart_color_red_100 from '@patternfly/react-tokens/dist/esm/chart_color_r const UtilizationCard3: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); - const selectItems = [ - , - , - , - - ]; + const selectItems = ( + + Last hour + Last 6 hours + Last 24 hours + Last 7 days + + ); + + const toggle = (toggleRef: React.Ref) => ( + setIsOpen(!isOpen)} + isExpanded={isOpen} + variant="plainText" + > + Filter + + ); const headerActions = ( @@ -1454,7 +1468,6 @@ import { ChartDonutThreshold, ChartDonutUtilization } from '@patternfly/react-ch ; ``` - ### Nested cards ```js @@ -2523,28 +2536,42 @@ import { FlexItem, Divider, Select, - SelectOption + SelectList, + SelectOption, + MenuToggle, + MenuToggleElement } from '@patternfly/react-core'; import { ChartArea, ChartContainer, ChartGroup, ChartLabel, ChartVoronoiContainer } from '@patternfly/react-charts'; const TrendCard1: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); - const selectItems = [ - , - , - , - - ]; + const selectItems = ( + + Last hour + Last 6 hours + Last 24 hours + Last 7 days + + ); + + const toggle = (toggleRef: React.Ref) => ( + setIsOpen(!isOpen)} + isExpanded={isOpen} + variant="plainText" + > + Filter + + ); const headerActions = ( @@ -2701,8 +2728,10 @@ import { DescriptionListTerm, DescriptionListDescription, Select, + SelectList, SelectOption, - Divider + Divider, + MenuToggle } from '@patternfly/react-core'; CardLogViewDemo = () => { @@ -2716,21 +2745,32 @@ CardLogViewDemo = () => { setIsOpen(isOpen); }; - const selectItems = [ - , - , - , - - ]; + const selectItems = ( + + Last hour + Last 6 hours + Last 24 hours + Last 7 days + + ); + + const toggle = (toggleRef) => ( + setIsOpen(!isOpen)} + isExpanded={isOpen} + variant="plainText" + > + Filter + + ); const headerActions = ( @@ -2819,8 +2859,10 @@ import { DescriptionListDescription, Spinner, Select, + SelectList, SelectOption, - Divider + Divider, + MenuToggle } from '@patternfly/react-core'; import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; @@ -2836,20 +2878,31 @@ CardEventViewDemo = () => { setIsOpen(isOpen); }; - const selectItems = [ - , - , - - ]; + const selectItems = ( + + Success + Error + Error + + ); + + const toggle = (toggleRef) => ( + setIsOpen(!isOpen)} + isExpanded={isOpen} + variant="plainText" + > + Status + + ); const headerActions = ( diff --git a/packages/react-core/src/demos/Toolbar.md b/packages/react-core/src/demos/Toolbar.md index 60598e6203f..11c48b3f685 100644 --- a/packages/react-core/src/demos/Toolbar.md +++ b/packages/react-core/src/demos/Toolbar.md @@ -30,16 +30,22 @@ This is an example of toolbar usage in log viewer. ```js isFullscreen import React from 'react'; -import { Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem, ToolbarToggleGroup } from '@patternfly/react-core'; import { Badge, Button, Checkbox, + MenuToggle, SearchInput, Select, + SelectList, SelectOption, PageSection, PageSectionVariants, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, + ToolbarToggleGroup, Tooltip } from '@patternfly/react-core'; import { @@ -90,15 +96,19 @@ class ConsoleLogViewerToolbar extends React.Component { mobileView: window.innerWidth >= 1450 ? false : true }; - this.onContainerToggle = (_event, isExpanded) => { - this.setState({ - containerExpanded: isExpanded + this.onContainerToggle = () => { + this.setState(prevState => { + return { + containerExpanded: !prevState.containerExpanded + } }); }; - this.onContainerToggleMobile = (_event, isExpanded) => { - this.setState({ - containerExpandedMobile: isExpanded + this.onContainerToggleMobile = () => { + this.setState(prevState => { + return { + containerExpandedMobile: !prevState.containerExpandedMobile + } }); }; @@ -332,19 +342,19 @@ class ConsoleLogViewerToolbar extends React.Component { ]; const selectDropdownContent = ( - + {Object.entries(this.firstOptions).map(([value, { type }]) => ( {type} {` ${value}`} ))} - + ); const selectToggleContent = ({ showText }) => { @@ -379,13 +389,23 @@ class ConsoleLogViewerToolbar extends React.Component { Select container}> diff --git a/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx b/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx index 1b9791fdb37..c21b5f24f5a 100644 --- a/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx +++ b/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx @@ -27,8 +27,6 @@ import { PageSectionVariants, Progress, ProgressSize, - Select, - SelectVariant, Tabs, Tab, TabContent, @@ -58,6 +56,7 @@ import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon'; import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; import FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon'; +import SortAmountDownIcon from '@patternfly/react-icons/dist/esm/icons/sort-amount-down-icon'; interface Repository { name: string; @@ -152,28 +151,12 @@ export const TablesAndTabs = () => { } breakpoint="xl"> - { + if (event.key === KeyTypes.ArrowUp) { + this.handleMenuKeys(0, 0, 'up'); + event.preventDefault(); + } else if (event.key === KeyTypes.ArrowDown) { + this.handleMenuKeys(0, 0, 'down'); + event.preventDefault(); + } else if (event.key === KeyTypes.ArrowLeft) { + this.handleMenuKeys(0, 0, 'left'); + event.preventDefault(); + } else if (event.key === KeyTypes.ArrowRight) { + this.handleMenuKeys(0, 0, 'right'); + event.preventDefault(); + } else if (event.key === KeyTypes.Tab && variant !== SelectVariant.checkbox && this.props.footer) { + // tab to footer or close menu if shift key + if (event.shiftKey) { + this.onToggle(event, false); + } else { + const tabbableItems = findTabbableElements(this.footerRef, SelectFooterTabbableItems); + if (tabbableItems.length > 0) { + tabbableItems[0].focus(); + event.stopPropagation(); + event.preventDefault(); + } else { + this.onToggle(event, false); + } + } + } else if (event.key === KeyTypes.Tab && variant === SelectVariant.checkbox) { + // More modal-like experience for checkboxes + // Let SelectOption handle this + if (event.shiftKey) { + this.handleMenuKeys(0, 0, 'up'); + } else { + this.handleMenuKeys(0, 0, 'down'); + } + event.stopPropagation(); + event.preventDefault(); + } + }} + ref={this.filterRef} + autoComplete={inputAutoComplete} + /> + + + + ); + renderableItems = [filterBox, ...(typeaheadFilteredChildren as React.ReactElement[])].map((option, index) => + React.cloneElement(option, { key: index }) + ); + } + + let variantProps: any; + let variantChildren: any; + if (customContent) { + variantProps = { + selected: selections, + openedOnEnter, + isCustomContent: true + }; + variantChildren = customContent; + } else { + switch (variant) { + case 'single': + variantProps = { + selected: selections[0], + hasInlineFilter, + openedOnEnter + }; + variantChildren = renderableItems; + break; + case 'checkbox': + variantProps = { + checked: selections, + isGrouped, + hasInlineFilter, + openedOnEnter + }; + variantChildren = renderableItems; + break; + case 'typeahead': + variantProps = { + selected: selections[0], + openedOnEnter + }; + variantChildren = onFavorite ? renderableItems : this.extendTypeaheadChildren(typeaheadCurrIndex); + if (variantChildren.length === 0) { + variantChildren.push(); + } + break; + case 'typeaheadmulti': + variantProps = { + selected: selections, + openedOnEnter + }; + variantChildren = onFavorite ? renderableItems : this.extendTypeaheadChildren(typeaheadCurrIndex); + if (variantChildren.length === 0) { + variantChildren.push(); + } + break; + } + } + + const isStatic = isFlipEnabled && menuAppendTo !== 'inline'; + const innerMenu = ( + + {variantChildren} + + ); + + const menuContainer = footer ?
{innerMenu}
: innerMenu; + + const popperRef = React.createRef(); + const popperContainer = ( +
+ {isOpen && menuContainer} +
+ ); + + const mainContainer = ( +
+ + {customContent && ( +
+ {toggleIcon && {toggleIcon}} + {placeholderText} +
+ )} + {variant === SelectVariant.single && !customContent && ( + +
+ {toggleIcon && {toggleIcon}} + + {this.getDisplay(selections[0] as string, 'node') || placeholderText || childPlaceholderText} + +
+ {hasOnClear && hasAnySelections && clearBtn} +
+ )} + {variant === SelectVariant.checkbox && !customContent && ( + +
+ {toggleIcon && {toggleIcon}} + {placeholderText} + {!isCheckboxSelectionBadgeHidden && hasAnySelections && ( +
+ + {this.generateSelectedBadge()} + +
+ )} +
+ {hasOnClear && hasAnySelections && clearBtn} +
+ )} + {variant === SelectVariant.typeahead && !customContent && ( + +
+ {toggleIcon && {toggleIcon}} + +
+ {hasOnClear && (selections[0] || typeaheadInputValue) && clearBtn} +
+ )} + {variant === SelectVariant.typeaheadMulti && !customContent && ( + +
+ {toggleIcon && {toggleIcon}} + {selections && Array.isArray(selections) && selections.length > 0 && selectedChips} + +
+ {hasOnClear && ((selections && selections.length > 0) || typeaheadInputValue) && clearBtn} +
+ )} + {validated === ValidatedOptions.success && ( + + + )} + {validated === ValidatedOptions.error && ( + + + )} + {validated === ValidatedOptions.warning && ( + + + )} +
+ {isOpen && menuAppendTo === 'inline' && menuContainer} +
+ ); + + const getParentElement = () => { + if (this.parentRef && this.parentRef.current) { + return this.parentRef.current.parentElement; + } + return null; + }; + + return ( + + {randomId => ( + + {menuAppendTo === 'inline' ? ( + mainContainer + ) : ( + + )} + + )} + + ); + } +} diff --git a/packages/react-core/src/deprecated/components/Select/SelectGroup.tsx b/packages/react-core/src/deprecated/components/Select/SelectGroup.tsx new file mode 100644 index 00000000000..82556acb4bf --- /dev/null +++ b/packages/react-core/src/deprecated/components/Select/SelectGroup.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import styles from '@patternfly/react-styles/css/components/Select/select'; +import { css } from '@patternfly/react-styles'; + +import { SelectConsumer, SelectVariant } from './selectConstants'; + +export interface SelectGroupProps extends React.HTMLProps { + /** Checkboxes within group. Must be React.ReactElement[] */ + children?: React.ReactNode; + /** Additional classes added to the CheckboxSelectGroup control */ + className?: string; + /** Group label */ + label?: string; + /** ID for title label */ + titleId?: string; +} + +export const SelectGroup: React.FunctionComponent = ({ + children = [], + className = '', + label = '', + titleId = '', + ...props +}: SelectGroupProps) => ( + + {({ variant }) => ( +
+
+ {label} +
+ {variant === SelectVariant.checkbox ? children :
    {children}
} +
+ )} +
+); +SelectGroup.displayName = 'SelectGroup'; diff --git a/packages/react-core/src/components/Select/SelectMenu.tsx b/packages/react-core/src/deprecated/components/Select/SelectMenu.tsx similarity index 98% rename from packages/react-core/src/components/Select/SelectMenu.tsx rename to packages/react-core/src/deprecated/components/Select/SelectMenu.tsx index bbdd822f8ae..c773ab55053 100644 --- a/packages/react-core/src/components/Select/SelectMenu.tsx +++ b/packages/react-core/src/deprecated/components/Select/SelectMenu.tsx @@ -4,10 +4,10 @@ import formStyles from '@patternfly/react-styles/css/components/Form/form'; import { css } from '@patternfly/react-styles'; import { SelectOptionObject, SelectOption } from './SelectOption'; import { SelectConsumer, SelectPosition, SelectVariant, SelectContextInterface } from './selectConstants'; -import { PickOptional } from '../../helpers/typeUtils'; +import { PickOptional } from '../../../helpers/typeUtils'; import { SelectGroup } from './SelectGroup'; -import { Divider } from '../Divider/Divider'; +import { Divider } from '../../../components/Divider/Divider'; export interface SelectMenuProps extends Omit, 'checked' | 'selected' | 'ref'> { /** Content rendered inside the SelectMenu */ diff --git a/packages/react-core/src/deprecated/components/Select/SelectOption.tsx b/packages/react-core/src/deprecated/components/Select/SelectOption.tsx new file mode 100644 index 00000000000..0f80f35494c --- /dev/null +++ b/packages/react-core/src/deprecated/components/Select/SelectOption.tsx @@ -0,0 +1,461 @@ +import * as React from 'react'; +import styles from '@patternfly/react-styles/css/components/Select/select'; +import checkStyles from '@patternfly/react-styles/css/components/Check/check'; +import { css } from '@patternfly/react-styles'; +import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon'; +import { SelectConsumer, SelectVariant } from './selectConstants'; +import StarIcon from '@patternfly/react-icons/dist/esm/icons/star-icon'; +import { getUniqueId } from '../../../helpers/util'; +import { KeyTypes } from '../../../helpers/constants'; + +export interface SelectOptionObject { + /** Function returns a string to represent the select option object */ + toString(): string; + /** Function returns a true if the passed in select option is equal to this select option object, false otherwise */ + compareTo?(selectOption: any): boolean; +} +export interface SelectOptionProps extends Omit, 'type' | 'ref' | 'value'> { + /** Optional alternate display for the option */ + children?: React.ReactNode; + /** Additional classes added to the Select Option */ + className?: string; + /** Description of the item for single and both typeahead select variants */ + description?: React.ReactNode; + /** Number of items matching the option */ + itemCount?: number; + /** @hide Internal index of the option */ + index?: number; + /** Indicates which component will be used as select item */ + component?: React.ReactNode; + /** The value for the option, can be a string or select option object */ + value: string | SelectOptionObject; + /** Flag indicating if the option is disabled */ + isDisabled?: boolean; + /** Flag indicating if the option acts as a placeholder */ + isPlaceholder?: boolean; + /** Flag indicating if the option acts as a "no results" indicator */ + isNoResultsOption?: boolean; + /** @hide Internal flag indicating if the option is selected */ + isSelected?: boolean; + /** @hide Internal flag indicating if the option is checked */ + isChecked?: boolean; + /** Flag forcing the focused state */ + isFocused?: boolean; + /** @hide Internal callback for ref tracking */ + sendRef?: ( + ref: React.ReactNode, + favoriteRef: React.ReactNode, + optionContainerRef: React.ReactNode, + index: number + ) => void; + /** @hide Internal callback for keyboard navigation */ + keyHandler?: (index: number, innerIndex: number, position: string) => void; + /** Optional callback for click event */ + onClick?: (event: React.MouseEvent | React.ChangeEvent) => void; + /** Id of the checkbox input */ + inputId?: string; + /** @hide Internal Flag indicating if the option is favorited */ + isFavorite?: boolean; + /** Aria label text for favoritable button when favorited */ + ariaIsFavoriteLabel?: string; + /** Aria label text for favoritable button when not favorited */ + ariaIsNotFavoriteLabel?: string; + /** ID of the item. Required for tracking favorites */ + id?: string; + /** @hide Internal flag to apply the load styling to view more option */ + isLoad?: boolean; + /** @hide Internal flag to apply the loading styling to spinner */ + isLoading?: boolean; + /** @hide Internal callback for the setting the index of the next item to focus after view more is clicked */ + setViewMoreNextIndex?: () => void; + /** @hide Flag indicating this is the last option when there is a footer */ + isLastOptionBeforeFooter?: (index: number) => boolean; + /** @hide Flag indicating that the the option loading variant is in a grouped select */ + isGrouped?: boolean; +} + +export class SelectOption extends React.Component { + static displayName = 'SelectOption'; + private ref = React.createRef(); + private liRef = React.createRef(); + private favoriteRef = React.createRef(); + static defaultProps: SelectOptionProps = { + className: '', + value: '', + index: 0, + isDisabled: false, + isPlaceholder: false, + isSelected: false, + isChecked: false, + isNoResultsOption: false, + component: 'button', + onClick: () => {}, + sendRef: () => {}, + keyHandler: () => {}, + inputId: '', + isFavorite: null, + isLoad: false, + isLoading: false, + setViewMoreNextIndex: () => {}, + isLastOptionBeforeFooter: () => false + }; + + componentDidMount() { + this.props.sendRef( + this.props.isDisabled ? null : this.ref.current, + this.props.isDisabled ? null : this.favoriteRef.current, + this.props.isDisabled ? null : this.liRef.current, + this.props.index + ); + } + + componentDidUpdate() { + this.props.sendRef( + this.props.isDisabled ? null : this.ref.current, + this.props.isDisabled ? null : this.favoriteRef.current, + this.props.isDisabled ? null : this.liRef.current, + this.props.index + ); + } + + onKeyDown = (event: React.KeyboardEvent, innerIndex: number, onEnter?: any, isCheckbox?: boolean) => { + const { index, keyHandler, isLastOptionBeforeFooter } = this.props; + let isLastItemBeforeFooter = false; + if (isLastOptionBeforeFooter !== undefined) { + isLastItemBeforeFooter = isLastOptionBeforeFooter(index); + } + + if (event.key === KeyTypes.Tab) { + // More modal-like experience for checkboxes + if (isCheckbox && !isLastItemBeforeFooter) { + if (event.shiftKey) { + keyHandler(index, innerIndex, 'up'); + } else { + keyHandler(index, innerIndex, 'down'); + } + event.stopPropagation(); + } else { + if (event.shiftKey) { + keyHandler(index, innerIndex, 'up'); + } else { + keyHandler(index, innerIndex, 'tab'); + } + } + } + event.preventDefault(); + if (event.key === KeyTypes.ArrowUp) { + keyHandler(index, innerIndex, 'up'); + } else if (event.key === KeyTypes.ArrowDown) { + keyHandler(index, innerIndex, 'down'); + } else if (event.key === KeyTypes.ArrowLeft) { + keyHandler(index, innerIndex, 'left'); + } else if (event.key === KeyTypes.ArrowRight) { + keyHandler(index, innerIndex, 'right'); + } else if (event.key === KeyTypes.Enter) { + if (onEnter !== undefined) { + onEnter(); + } else { + this.ref.current.click(); + } + } + }; + + render() { + /* eslint-disable @typescript-eslint/no-unused-vars */ + const { + children, + className, + id, + description, + itemCount, + value, + onClick, + isDisabled, + isPlaceholder, + isNoResultsOption, + isSelected, + isChecked, + isFocused, + sendRef, + keyHandler, + index, + component, + inputId, + isFavorite, + ariaIsFavoriteLabel = 'starred', + ariaIsNotFavoriteLabel = 'not starred', + isLoad, + isLoading, + setViewMoreNextIndex, + // eslint-disable-next-line no-console + isLastOptionBeforeFooter, + isGrouped = false, + ...props + } = this.props; + /* eslint-enable @typescript-eslint/no-unused-vars */ + const Component = component as any; + + if (!id && isFavorite !== null) { + // eslint-disable-next-line no-console + console.error('Please provide an id to use the favorites feature.'); + } + + const generatedId = id || getUniqueId('select-option'); + const favoriteButton = (onFavorite: any) => ( + + ); + + const itemDisplay = itemCount ? ( + + + {children || (value && value.toString && value.toString())} + + {itemCount} + + ) : ( + children || value.toString() + ); + + const onViewMoreClick = (event: any) => { + // Set the index for the next item to focus after view more clicked, then call view more callback + setViewMoreNextIndex(); + onClick(event); + }; + + const renderOption = ( + onSelect: ( + event: React.MouseEvent | React.ChangeEvent, + value: string | SelectOptionObject, + isPlaceholder?: boolean + ) => void, + onClose: () => void, + variant: string, + inputIdPrefix: string, + onFavorite: (itemId: string, isFavorite: boolean) => void, + shouldResetOnSelect: boolean + ) => { + if (variant !== SelectVariant.checkbox && isLoading && isGrouped) { + return ( +
+ {children} +
+ ); + } else if (variant !== SelectVariant.checkbox && isLoad && isGrouped) { + return ( +
+ +
+ ); + } else if (variant !== SelectVariant.checkbox) { + return ( + + ); + } else if (variant === SelectVariant.checkbox && isLoad) { + return ( + + ); + } else if (variant === SelectVariant.checkbox && isLoading) { + return ( +
{children}
+ ); + } else if (variant === SelectVariant.checkbox && !isNoResultsOption && !isLoading && !isLoad) { + return ( + + ); + } else if (variant === SelectVariant.checkbox && isNoResultsOption && !isLoading && !isLoad) { + return ( +
+ { + this.onKeyDown(event, 0, undefined, true); + }} + type="button" + > + {itemDisplay} + +
+ ); + } + }; + + return ( + + {({ onSelect, onClose, variant, inputIdPrefix, onFavorite, shouldResetOnSelect }) => ( + + {renderOption(onSelect, onClose, variant, inputIdPrefix, onFavorite, shouldResetOnSelect)} + + )} + + ); + } +} diff --git a/packages/react-core/src/components/Select/SelectToggle.tsx b/packages/react-core/src/deprecated/components/Select/SelectToggle.tsx similarity index 98% rename from packages/react-core/src/components/Select/SelectToggle.tsx rename to packages/react-core/src/deprecated/components/Select/SelectToggle.tsx index c0b04f98265..2fc07511e15 100644 --- a/packages/react-core/src/components/Select/SelectToggle.tsx +++ b/packages/react-core/src/deprecated/components/Select/SelectToggle.tsx @@ -4,9 +4,9 @@ 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 { SelectVariant, SelectFooterTabbableItems } from './selectConstants'; -import { PickOptional } from '../../helpers/typeUtils'; -import { findTabbableElements } from '../../helpers/util'; -import { KeyTypes } from '../../helpers/constants'; +import { PickOptional } from '../../../helpers/typeUtils'; +import { findTabbableElements } from '../../../helpers/util'; +import { KeyTypes } from '../../../helpers/constants'; export interface SelectToggleProps extends Omit, 'ref'> { /** HTML ID of dropdown toggle */ diff --git a/packages/react-core/src/components/Select/__tests__/Select.test.tsx b/packages/react-core/src/deprecated/components/Select/__tests__/Select.test.tsx similarity index 99% rename from packages/react-core/src/components/Select/__tests__/Select.test.tsx rename to packages/react-core/src/deprecated/components/Select/__tests__/Select.test.tsx index b8d1d991176..d7cfbf7278b 100644 --- a/packages/react-core/src/components/Select/__tests__/Select.test.tsx +++ b/packages/react-core/src/deprecated/components/Select/__tests__/Select.test.tsx @@ -7,7 +7,7 @@ import { Select } from '../Select'; import { SelectOption, SelectOptionObject } from '../SelectOption'; import { SelectGroup } from '../SelectGroup'; import { SelectVariant, SelectDirection } from '../selectConstants'; -import { KeyTypes } from '../../../helpers'; +import { KeyTypes } from '../../../../helpers'; class User implements SelectOptionObject { private firstName: string; diff --git a/packages/react-core/src/components/Select/__tests__/SelectGroup.test.tsx b/packages/react-core/src/deprecated/components/Select/__tests__/SelectGroup.test.tsx similarity index 100% rename from packages/react-core/src/components/Select/__tests__/SelectGroup.test.tsx rename to packages/react-core/src/deprecated/components/Select/__tests__/SelectGroup.test.tsx diff --git a/packages/react-core/src/components/Select/__tests__/SelectOption.test.tsx b/packages/react-core/src/deprecated/components/Select/__tests__/SelectOption.test.tsx similarity index 100% rename from packages/react-core/src/components/Select/__tests__/SelectOption.test.tsx rename to packages/react-core/src/deprecated/components/Select/__tests__/SelectOption.test.tsx diff --git a/packages/react-core/src/components/Select/__tests__/SelectToggle.test.tsx b/packages/react-core/src/deprecated/components/Select/__tests__/SelectToggle.test.tsx similarity index 100% rename from packages/react-core/src/components/Select/__tests__/SelectToggle.test.tsx rename to packages/react-core/src/deprecated/components/Select/__tests__/SelectToggle.test.tsx diff --git a/packages/react-core/src/components/Select/__tests__/__snapshots__/Select.test.tsx.snap b/packages/react-core/src/deprecated/components/Select/__tests__/__snapshots__/Select.test.tsx.snap similarity index 100% rename from packages/react-core/src/components/Select/__tests__/__snapshots__/Select.test.tsx.snap rename to packages/react-core/src/deprecated/components/Select/__tests__/__snapshots__/Select.test.tsx.snap diff --git a/packages/react-core/src/components/Select/__tests__/__snapshots__/SelectGroup.test.tsx.snap b/packages/react-core/src/deprecated/components/Select/__tests__/__snapshots__/SelectGroup.test.tsx.snap similarity index 100% rename from packages/react-core/src/components/Select/__tests__/__snapshots__/SelectGroup.test.tsx.snap rename to packages/react-core/src/deprecated/components/Select/__tests__/__snapshots__/SelectGroup.test.tsx.snap diff --git a/packages/react-core/src/components/Select/__tests__/__snapshots__/SelectOption.test.tsx.snap b/packages/react-core/src/deprecated/components/Select/__tests__/__snapshots__/SelectOption.test.tsx.snap similarity index 100% rename from packages/react-core/src/components/Select/__tests__/__snapshots__/SelectOption.test.tsx.snap rename to packages/react-core/src/deprecated/components/Select/__tests__/__snapshots__/SelectOption.test.tsx.snap diff --git a/packages/react-core/src/components/Select/__tests__/__snapshots__/SelectToggle.test.tsx.snap b/packages/react-core/src/deprecated/components/Select/__tests__/__snapshots__/SelectToggle.test.tsx.snap similarity index 100% rename from packages/react-core/src/components/Select/__tests__/__snapshots__/SelectToggle.test.tsx.snap rename to packages/react-core/src/deprecated/components/Select/__tests__/__snapshots__/SelectToggle.test.tsx.snap diff --git a/packages/react-core/src/deprecated/components/Select/examples/Select.md b/packages/react-core/src/deprecated/components/Select/examples/Select.md new file mode 100644 index 00000000000..dfd1215b557 --- /dev/null +++ b/packages/react-core/src/deprecated/components/Select/examples/Select.md @@ -0,0 +1,2937 @@ +--- +id: Select +section: components +subsection: menus +cssPrefix: pf-c-select +propComponents: ['Select', 'SelectOption', 'SelectGroup', 'SelectOptionObject', 'SelectViewMoreObject'] +ouia: true +--- + +import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; + +## Examples + +### Single select + +To let users select a single item from a list, use a single select list. + +A select list may use other properties for additional customization. Select each checkbox in the example below to visualize the following behavior: + +- To prevent a toggle click from opening a select list, use the `isDisabled` property. +- To adjust the direction a select menu opens, use the `direction` property. The menu in the following example expands upwards. By default, select lists open upwards. +- To add an icon to a select toggle, use the `toggleIcon` property. + +```js +import React from 'react'; +import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; +import { Checkbox, Divider } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant, SelectDirection } from '@patternfly/react-core/deprecated'; + +class SingleSelectInput extends React.Component { + constructor(props) { + super(props); + this.options = [ + , + , + , + , + , + , + , + + ]; + + this.toggleRef = React.createRef(); + + this.state = { + isToggleIcon: false, + isOpen: false, + selected: null, + isDisabled: false, + direction: SelectDirection.down + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection, isPlaceholder) => { + if (isPlaceholder) this.clearSelection(); + else { + this.setState({ + selected: selection, + isOpen: false + }); + console.log('selected:', selection); + this.toggleRef.current.focus(); + } + }; + + this.clearSelection = () => { + this.setState({ + selected: null, + isOpen: false + }); + }; + + this.toggleDisabled = checked => { + this.setState({ + isDisabled: checked + }); + }; + + this.setIcon = checked => { + this.setState({ + isToggleIcon: checked + }); + }; + + this.toggleDirection = () => { + if (this.state.direction === SelectDirection.up) { + this.setState({ + direction: SelectDirection.down + }); + } else { + this.setState({ + direction: SelectDirection.up + }); + } + }; + } + + render() { + const { isOpen, selected, isDisabled, direction, isToggleIcon } = this.state; + const titleId = 'title-id-1'; + return ( +
+ + + this.toggleDisabled(checked)} + aria-label="disabled checkbox" + id="toggle-disabled" + name="toggle-disabled" + /> + + this.setIcon(checked)} + aria-label="show icon checkbox" + id="toggle-icon" + name="toggle-icon" + /> +
+ ); + } +} +``` + +### With item descriptions + +To give more context to a `` in a list, use the `description` property. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class SingleSelectDescription extends React.Component { + constructor(props) { + super(props); + this.options = [ + { value: 'Mr', disabled: false }, + { value: 'Miss', disabled: false }, + { value: 'Mrs', disabled: false }, + { value: 'Ms', disabled: false }, + { value: 'Dr', disabled: false }, + { value: 'Other', disabled: false } + ]; + + this.toggleRef = React.createRef(); + + this.state = { + isOpen: false, + selected: null, + isDisabled: false + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection, isPlaceholder) => { + if (isPlaceholder) this.clearSelection(); + else { + this.setState({ + selected: selection, + isOpen: false + }); + console.log('selected:', selection); + this.toggleRef.current.focus(); + } + }; + + this.clearSelection = () => { + this.setState({ + selected: null, + isOpen: false + }); + }; + } + + render() { + const { isOpen, selected, isDisabled, direction, isToggleIcon } = this.state; + const titleId = 'select-descriptions-title'; + return ( +
+ + +
+ ); + } +} +``` + +### With grouped items + +To group related select options together, use 1 or more `` components and title each group using the `label` property. + +```js +import React from 'react'; +import { Divider } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant, SelectGroup } from '@patternfly/react-core/deprecated'; + +class GroupedSingleSelectInput extends React.Component { + constructor(props) { + super(props); + this.state = { + isOpen: false, + selected: null + }; + + this.toggleRef = React.createRef(); + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection) => { + this.setState({ + selected: selection, + isOpen: false + }); + this.toggleRef.current.focus(); + }; + + this.clearSelection = () => { + this.setState({ + selected: null + }); + }; + + this.options = [ + + + + + + + , + , + + + + + + ]; + } + + render() { + const { isOpen, selected } = this.state; + const titleId = 'grouped-single-select-id'; + return ( +
+ + +
+ ); + } +} +``` + +### Favoriting items + +To allow users to favorite items in a select list, use the `onFavorite` callback. When users click the favorite button, the item is duplicated and placed in a separated group at the top of the menu. To change the name of the group use the `favoritesLabel` property. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant, SelectGroup } from '@patternfly/react-core/deprecated'; + +class FavoritesSelect extends React.Component { + constructor(props) { + super(props); + this.state = { + isOpen: false, + selected: null, + favorites: [] + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection, isPlaceholder) => { + if (isPlaceholder) this.clearSelection(); + else { + this.setState({ + selected: selection, + isOpen: false + }); + console.log('selected:', selection); + } + }; + + this.clearSelection = () => { + this.setState({ + selected: null, + isOpen: false + }); + }; + + this.onFavorite = (itemId, isFavorite) => { + if (isFavorite) { + this.setState({ + favorites: this.state.favorites.filter(id => id !== itemId) + }); + } else + this.setState({ + favorites: [...this.state.favorites, itemId] + }); + }; + + this.options = [ + + + + + + + , + + + + + + ]; + } + + render() { + const { isOpen, selected, favorites } = this.state; + const titleId = 'grouped-single-select-id'; + return ( + + ); + } +} +``` + +### Validated selections + +To validate selections that users make, pass a validation state to the `validated` property. Validating selections can let users know if the selections they make would cause issues or errors. + +The example below passes an "error" state when you choose “select a title”, a "warning" state when you choose "other", and a "success" state for any other item selected from the menu. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class ValidatedSelect extends React.Component { + constructor(props) { + super(props); + this.options = [ + , + , + , + , + , + , + + ]; + + this.toggleRef = React.createRef(); + + this.state = { + isOpen: false, + selected: null, + isDisabled: false, + validated: 'default' + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection, isPlaceholder) => { + let validatedState = 'success'; + if (isPlaceholder) { + this.clearSelection(); + validatedState = 'error'; + } else { + if (selection === 'Other') { + validatedState = 'warning'; + } else { + validatedState = 'success'; + } + this.setState({ + selected: selection, + isOpen: false + }); + console.log('selected:', selection); + } + this.setState({ + validated: validatedState + }); + this.toggleRef.current.focus(); + }; + + this.clearSelection = () => { + this.setState({ + selected: null, + isOpen: false + }); + }; + } + + render() { + const { isOpen, selected, isDisabled, direction, isToggleIcon, validated } = this.state; + const titleId = 'select-validated-title'; + return ( +
+ + + +
+ ); + } +} +``` + +### Styled placeholder text + +To add a toggle label to a select, use the `placeholderText` property. The following example displays "Filter by status" in the toggle before a selection is made. + +To fade the color of `placeholderText` to gray, use the `hasPlaceholderStyle` property. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +function SelectWithPlaceholderStyle() { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState([]); + + const options = [ + , + , + + ]; + + const onToggle = (_event, isOpen) => setIsOpen(isOpen); + + const onSelect = (event, selection, isPlaceholder) => { + setSelected(selection); + setIsOpen(false); + }; + + const clearSelection = () => { + setSelected(null); + setIsOpen(false); + }; + + const titleId = 'placeholder-style-select-id'; + + return ( +
+ + +
+ ); +} +``` + +### Placeholder select options + +To set a `` as a placeholder, use the `isPlaceholder` property. The following example sets the "Filter by status" as a placeholder so that it is pre-selected. + +```js + +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +function SelectWithPlaceholderStyle() { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState([]); + + const options = [ + , + , + , + + ]; + + const onToggle = (_event, isOpen) => setIsOpen(isOpen); + + const onSelect = (event, selection, isPlaceholder) => { + setSelected(selection); + setIsOpen(false); + }; + + const clearSelection = () => { + setSelected(null); + setIsOpen(false); + }; + + const titleId = 'placeholder-style-select-option-id'; + + return ( +
+ + +
+ ); +} +``` + +### With a footer + +You can add a `footer` to a `} + toggleRef={this.toggleRef} + variant={SelectVariant.single} + aria-label="Select Input" + onToggle={this.onToggle} + onSelect={this.onSelect} + selections={selected} + isOpen={isOpen} + aria-labelledby={titleId} + isDisabled={isDisabled} + direction={direction} + footer={ + <> + + + } + > + {this.options} + + + ); + } +} +``` + +### With view more + +To reduce the processing load for long select lists, replace overflow items with a "View more" link at the bottom of the select menu. + +Adjust the number of items shown above the "View more" link with the `numOptions` property. The following example passes 3 items into this property. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class SelectViewMore extends React.Component { + constructor(props) { + super(props); + this.options = [ + , + , + , + , + , + , + + ]; + + this.toggleRef = React.createRef(); + + this.state = { + isOpen: false, + selected: null, + numOptions: 3, + isLoading: false + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection, isPlaceholder) => { + if (isPlaceholder) this.clearSelection(); + else { + this.setState({ + selected: selection, + isOpen: false + }); + console.log('selected:', selection); + this.toggleRef.current.focus(); + } + }; + + this.clearSelection = () => { + this.setState({ + selected: null, + isOpen: false + }); + }; + + this.simulateNetworkCall = callback => { + setTimeout(callback, 2000); + }; + + this.onViewMoreClick = () => { + // Set select loadingVariant to spinner then simulate network call before loading more options + this.setState({ isLoading: true }); + this.simulateNetworkCall(() => { + const newLength = + this.state.numOptions + 3 <= this.options.length ? this.state.numOptions + 3 : this.options.length; + this.setState({ numOptions: newLength, isLoading: false }); + }); + }; + } + + render() { + const { isOpen, selected, isToggleIcon, numOptions, loadingVariant, isLoading } = this.state; + const titleId = 'title-id-view-more'; + return ( +
+ + +
+ ); + } +} +``` + +### Checkbox select + +To let users select multiple list options via checkbox input, use a checkbox select. To create a checkbox select, pass `variant={SelectVariant.checkbox}` into the ` + {this.options} + + + ); + } +} +``` + +### Checkbox select with grouped items + +You can use groups alongside checkbox input. The item count badge will display the number of items selected across all groups. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant, SelectGroup } from '@patternfly/react-core/deprecated'; + +class GroupedCheckboxSelectInput extends React.Component { + constructor(props) { + super(props); + this.state = { + isOpen: false, + selected: [] + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + 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: [] + }); + }; + + this.options = [ + + + + + + + , + + + + + + ]; + } + + render() { + const { isOpen, selected } = this.state; + const titleId = 'grouped-checkbox-select-id-1'; + return ( +
+ + +
+ ); + } +} +``` + +### Checkbox select with custom badge + +To change the default badge text for a checkbox select, use the `customBadgeText` property. The following example uses `customBadgeText` to display "all" in the badge once all menu items are selected. + +```js +import React from 'react'; +import { Select, SelectOption, SelectGroup, SelectVariant } from '@patternfly/react-core/deprecated'; + +class FilteringCheckboxSelectInputWithBadging extends React.Component { + constructor(props) { + super(props); + + this.state = { + isOpen: false, + selected: [], + customBadgeText: 0 + }; + + this.options = [ + + + + + + + , + + + + + + ]; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection) => { + const { selected } = this.state; + if (selected.includes(selection)) { + this.setState( + prevState => ({ + selected: prevState.selected.filter(item => item !== selection), + customBadgeText: this.setBadgeText(prevState.selected.length - 1) + }), + () => console.log('selections: ', this.state.selected) + ); + } else { + this.setState( + prevState => ({ + selected: [...prevState.selected, selection], + customBadgeText: this.setBadgeText(prevState.selected.length + 1) + }), + () => console.log('selections: ', this.state.selected) + ); + } + }; + + this.onFilter = (_, textInput) => { + if (textInput === '') { + return this.options; + } else { + let filteredGroups = this.options + .map(group => { + let filteredGroup = React.cloneElement(group, { + children: group.props.children.filter(item => { + return item.props.value.toLowerCase().includes(textInput.toLowerCase()); + }) + }); + if (filteredGroup.props.children.length > 0) return filteredGroup; + }) + .filter(newGroup => newGroup); + return filteredGroups; + } + }; + + this.clearSelection = () => { + this.setState({ + selected: [], + customBadgeText: this.setBadgeText(0) + }); + }; + + this.setBadgeText = selected => { + if (selected === 7) { + return 'All'; + } + if (selected === 0) { + return 0; + } + return null; + }; + } + + render() { + const { isOpen, selected, filteredOptions, customBadgeText } = this.state; + const titleId = 'checkbox-filtering-custom-badging-select-id'; + return ( +
+ + +
+ ); + } +} +``` + +### Checkbox select without selected count + +To remove the default item count badge, use the `isCheckboxSelectionBadgeHidden` property. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class CheckboxSelectInputNoBadge extends React.Component { + constructor(props) { + super(props); + + this.state = { + isOpen: false, + selected: [] + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + 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: [] + }); + }; + + this.options = [ + , + , + , + + ]; + } + + render() { + const { isOpen, selected } = this.state; + const titleId = 'checkbox-no-badge-select-id'; + return ( +
+ + +
+ ); + } +} +``` + +### Checkbox select with item counts + +To show users the number of items that a `` would match, use the `itemCount` property. The numerical value you pass into `itemCount` is displayed to the right of each menu item. + +```js +import React from 'react'; +import { Divider } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class CheckboxSelectWithCounts extends React.Component { + constructor(props) { + super(props); + + this.state = { + isOpen: false, + selected: [] + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + 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: [] + }); + }; + + this.options = [ + , + , + , + , + + ]; + } + + render() { + const { isOpen, selected } = this.state; + const titleId = 'checkbox-select-with-counts-id'; + return ( +
+ + +
+ ); + } +} +``` + +### Checkbox select with a footer + +You can combine a footer with checkbox input to allow users to apply an action to multiple items. + +```js +import React from 'react'; +import { Button } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class SelectWithFooterCheckbox extends React.Component { + constructor(props) { + super(props); + this.state = { + isOpen: false, + selected: [], + numOptions: 3, + isLoading: false + }; + + this.toggleRef = React.createRef(); + + this.options = [ + , + , + , + , + + ]; + + this.onToggle = (_event, isOpen) => { + this.setState({ isOpen }); + }; + + this.onSelect = (event, selection) => { + this.setState({ selected: selection, isOpen: false }), console.log('selected: ', selection); + this.toggleRef.current.focus(); + }; + + this.onFilter = (_, textInput) => { + if (textInput === '') { + return this.options; + } else { + this.setState( + prevState => ({ selected: [...prevState.selected, selection] }), + () => console.log('selections: ', this.state.selected) + ); + } + }; + + this.clearSelection = () => { + this.setState({ + selected: [] + }); + }; + } + + render() { + const { isOpen, selected, isDisabled, direction, isToggleIcon } = this.state; + const titleId = 'title-id-footer-checkbox'; + return ( +
+ + +
+ ); + } +} +``` + +### Checkbox select with view more + +When a "view more" link is used alongside checkbox input, selections that users make prior to clicking "view more" are persisted after the click. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class SelectViewMoreCheckbox extends React.Component { + constructor(props) { + super(props); + + this.state = { + isOpen: false, + selected: [], + numOptions: 3, + isLoading: false + }; + + this.options = [ + , + , + , + , + , + , + , + , + + ]; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + 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: [] + }); + }; + + this.simulateNetworkCall = callback => { + setTimeout(callback, 2000); + }; + + this.onViewMoreClick = () => { + // Set select loadingVariant to spinner then simulate network call before loading more options + this.setState({ isLoading: true }); + this.simulateNetworkCall(() => { + const newLength = + this.state.numOptions + 3 <= this.options.length ? this.state.numOptions + 3 : this.options.length; + this.setState({ numOptions: newLength, isLoading: false }); + }); + }; + } + + render() { + const { isOpen, selected, numOptions, isLoading } = this.state; + const titleId = 'view-more-checkbox-select-id'; + return ( +
+ + +
+ ); + } +} +``` + +### Filtering with placeholder text + +To preload a filter search bar with placeholder text, use the `inlineFilterPlaceholderText` property. The following example preloads the search bar with "Filter by status". + +```js +import React from 'react'; +import { Select, SelectOption, SelectGroup, SelectVariant } from '@patternfly/react-core/deprecated'; + +class FilteringCheckboxSelectInputWithPlaceholder extends React.Component { + constructor(props) { + super(props); + + this.state = { + isOpen: false, + selected: [] + }; + + this.options = [ + + + + + + + , + + + + + + ]; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + 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.onFilter = (_, textInput) => { + if (textInput === '') { + return this.options; + } else { + let filteredGroups = this.options + .map(group => { + let filteredGroup = React.cloneElement(group, { + children: group.props.children.filter(item => { + return item.props.value.toLowerCase().includes(textInput.toLowerCase()); + }) + }); + if (filteredGroup.props.children.length > 0) return filteredGroup; + }) + .filter(newGroup => newGroup); + return filteredGroups; + } + }; + + this.clearSelection = () => { + this.setState({ + selected: [] + }); + }; + } + + render() { + const { isOpen, selected, filteredOptions } = this.state; + const titleId = 'checkbox-filtering-with-placeholder-select-id'; + return ( +
+ + +
+ ); + } +} +``` + +### Inline filtering + +To allow users to filter select lists using text input, use the `hasInlineFilter` property. Filtering behavior can be further customized with other properties, as shown in the example below. Select each checkbox to visualize the following behavior: + +- To persist filter results on blur, use the `isInputValuePersisted` property. +- To persist a filter that a user has searched, use the `isInputFilterPersisted` property. +- To allow users to add new items to a select list, use the `isCreatable` property. When this property is applied and a user searches for an option that doesn't exist, they will be prompted to "create" the item. + +```js +import React from 'react'; +import { Checkbox } from '@patternfly/react-core'; +import { Select, SelectOption, SelectGroup, SelectVariant } from '@patternfly/react-core/deprecated'; + +class FilteringSingleSelectInput extends React.Component { + constructor(props) { + super(props); + + this.state = { + isOpen: false, + selected: '', + isCreatable: false, + isInputValuePersisted: false, + isInputFilterPersisted: false + }; + + this.options = [ + + + + + + + , + + + + + + ]; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection) => { + this.setState({ selected: selection, isOpen: false }), console.log('selected: ', selection); + }; + + this.onFilter = (_, textInput) => { + if (textInput === '') { + return this.options; + } else { + let filteredGroups = this.options + .map(group => { + let filteredGroup = React.cloneElement(group, { + children: group.props.children.filter(item => { + return item.props.value.toLowerCase().includes(textInput.toLowerCase()); + }) + }); + if (filteredGroup.props.children.length > 0) return filteredGroup; + }) + .filter(Boolean); + return filteredGroups; + } + }; + + this.toggleCreatable = (_, checked) => { + this.setState({ + isCreatable: checked + }); + }; + + this.toggleInputValuePersisted = (_, checked) => { + this.setState({ + isInputValuePersisted: checked + }); + }; + + this.toggleInputFilterPersisted = (_, checked) => { + this.setState({ + isInputFilterPersisted: checked + }); + }; + } + + render() { + const { + isOpen, + selected, + filteredOptions, + isInputValuePersisted, + isInputFilterPersisted, + isCreatable + } = this.state; + const titleId = 'single-filtering-select-id'; + return ( +
+ + + + + +
+ ); + } +} +``` + +### Typeahead + +Typeahead is a select variant that replaces the typical button toggle for opening the select menu with a text input and button toggle combo. As a user types in the text input, the select menu will provide suggestions by filtering the select options. + +To make a typeahead, pass `variant=typeahead` into the ` + {options.map((option, index) => ( + + ))} + + this.toggleDisabled(checked)} + aria-label="toggle disabled checkbox" + id="toggle-disabled-typeahead" + name="toggle-disabled-typeahead" + /> + this.toggleCreatable(checked)} + aria-label="toggle creatable checkbox" + id="toggle-creatable-typeahead" + name="toggle-creatable-typeahead" + /> + this.toggleCreateOptionOnTop(checked)} + aria-label="toggle createOptionOnTop checkbox" + id="toggle-create-option-on-top-typeahead" + name="toggle-create-option-on-top-typeahead" + /> + this.toggleCreateNew(checked)} + aria-label="toggle new checkbox" + id="toggle-new-typeahead" + name="toggle-new-typeahead" + /> + this.toggleInputValuePersisted(checked)} + aria-label="toggle input value persisted" + id="toggle-input-value-persisted" + name="toggle-input-value-persisted" + /> + this.toggleInputFilterPersisted(checked)} + aria-label="toggle input filter persisted" + id="toggle-input-filter-persisted" + name="toggle-input-filter-persisted" + /> + this.toggleResetOnSelect(checked)} + aria-label="toggle reset checkbox" + id="toggle-reset-typeahead" + name="toggle-reset-typeahead" + /> + + ); + } +} +``` + +### Grouped typeahead + +Typeahead matches items with user input across groups. + +```js +import React from 'react'; +import { Checkbox, Divider } from '@patternfly/react-core'; +import { Select, SelectGroup, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class GroupedTypeaheadSelectInput extends React.Component { + constructor(props) { + super(props); + + this.state = { + options: [ + + + + + + + , + , + + + + + + ], + newOptions: [], + isOpen: false, + selected: null, + isCreatable: false, + hasOnCreateOption: false + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection, isPlaceholder) => { + if (isPlaceholder) this.clearSelection(); + else { + this.setState({ + selected: selection, + isOpen: false + }); + console.log('selected:', selection); + } + }; + + this.onCreateOption = newValue => { + this.setState({ + newOptions: [...this.state.newOptions, ] + }); + }; + + this.clearSelection = () => { + this.setState({ + selected: null, + isOpen: false + }); + }; + + this.toggleCreatable = checked => { + this.setState({ + isCreatable: checked + }); + }; + + this.toggleCreateNew = checked => { + this.setState({ + hasOnCreateOption: checked + }); + }; + } + + render() { + const { isOpen, selected, isDisabled, isCreatable, hasOnCreateOption, options, newOptions } = this.state; + const titleId = 'grouped-typeahead-select-id'; + const allOptions = + newOptions.length > 0 + ? options.concat( + + {newOptions} + + ) + : options; + return ( +
+ + + this.toggleCreatable(checked)} + aria-label="toggle creatable checkbox" + id="toggle-creatable-grouped-typeahead" + name="toggle-creatable-grouped-typeahead" + /> + this.toggleCreateNew(checked)} + aria-label="toggle new checkbox" + id="toggle-new-grouped-typeahead" + name="toggle-new-grouped-typeahead" + /> +
+ ); + } +} +``` + +### Typeahead with custom filtering + +You can add custom filtering to a select list to better fit needs that aren't covered by inline filtering. If you use custom filtering, use the `onFilter` property to trigger a callback with your custom filter implementation. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class TypeaheadSelectInput extends React.Component { + constructor(props) { + super(props); + this.options = [ + , + , + , + , + , + + ]; + this.state = { + isOpen: false, + selected: null + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.onSelect = (event, selection, isPlaceholder) => { + if (isPlaceholder) this.clearSelection(); + else { + this.setState({ + selected: selection, + isOpen: false + }); + console.log('selected:', selection); + } + }; + + this.clearSelection = () => { + this.setState({ + selected: null, + isOpen: false + }); + }; + + this.customFilter = (_, value) => { + if (!value) { + return this.options; + } + + const input = new RegExp(value, 'i'); + return this.options.filter(child => input.test(child.props.value)); + }; + } + + render() { + const { isOpen, selected } = this.state; + const titleId = 'typeahead-select-id-2'; + return ( +
+ + +
+ ); + } +} +``` + +### Multiple typeahead + +To create a multiple typeahead select variant, pass `variant={SelectVariant.typeaheadMulti}` into the ` + {options.map((option, index) => ( + + ))} + + this.toggleCreatable(checked)} + aria-label="toggle creatable checkbox" + id="toggle-creatable-typeahead-multi" + name="toggle-creatable-typeahead-multi" + /> + this.toggleCreateNew(checked)} + aria-label="toggle new checkbox" + id="toggle-new-typeahead-multi" + name="toggle-new-typeahead-multi" + /> + + this.toggleResetOnSelect(checked)} + aria-label="toggle multi reset checkbox" + id="toggle-reset-multi-typeahead" + name="toggle-reset-multi-typeahead" + /> + + ); + } +} +``` + +### Multiple typeahead with custom chips + +To customize the appearance of chips, use the `chipGroupProps` property. The `numChips` property allows you to control the number of items shown, while the `expandedText` and `collapsedText` properties allow you to control the labels of the expansion and collapse chips. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class MultiTypeaheadSelectInputWithChipGroupProps extends React.Component { + constructor(props) { + super(props); + + this.state = { + options: [ + { value: 'Alabama', disabled: false }, + { value: 'Florida', disabled: false }, + { value: 'New Jersey', disabled: false }, + { value: 'New Mexico', disabled: false, description: 'This is a description' }, + { value: 'New York', disabled: false }, + { value: 'North Carolina', disabled: false } + ], + isOpen: false, + selected: [] + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + 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: [], + isOpen: false + }); + }; + } + + render() { + const { isOpen, selected, isCreatable, hasOnCreateOption } = this.state; + const titleId = 'multi-typeahead-custom-chip-group-props-id-1'; + + return ( +
+ + +
+ ); + } +} +``` + +### Multiple typeahead with chip group + +To customize chips even more, render a [``](/components/chip-group) component and pass it into the `chipGroupComponent` property of the ` + {this.state.options.map((option, index) => ( + + ))} + + + ); + } +} +``` + +### Multiple typeahead with custom objects + +A `` can have an object passed into the `value` property in order to store additional data beyond just a string value. The object passed in must have a `toString` function that returns a string to display in the `SelectMenu`. + +```js +import React from 'react'; +import { Divider } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class MultiTypeaheadSelectInputCustomObjects extends React.Component { + constructor(props) { + super(props); + this.createState = (name, abbreviation, capital, founded) => { + return { + name: name, + abbreviation: abbreviation, + capital: capital, + founded: founded, + toString: function() { + return `${this.name} (${this.abbreviation}) - Founded: ${this.founded}`; + }, + compareTo: function(value) { + return this.toString() + .toLowerCase() + .includes(value.toString().toLowerCase()); + } + }; + }; + this.options = [ + , + , + , + , + , + , + + ]; + + this.state = { + isOpen: false, + selected: [] + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + 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: [], + isOpen: false + }); + }; + } + + render() { + const { isOpen, selected } = this.state; + const titleId = 'multi-typeahead-select-id-2'; + + return ( +
+ + +
+ ); + } +} +``` + +### Plain multiple typeahead + +To plainly style a typeahead, use the `isPlain` property. + +```js +import React from 'react'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; + +class PlainSelectInput 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 = { + isOpen: false, + isPlain: true, + selected: [] + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + 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: [], + isOpen: false + }); + }; + } + + render() { + const { isOpen, isPlain, selected } = this.state; + const titleId = 'plain-typeahead-select-id'; + + return ( +
+ + +
+ ); + } +} +``` + +### Custom menu content + +To add custom menu content, use the `customContent` property. + +```js +import React from 'react'; +import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; +import { Checkbox } from '@patternfly/react-core'; +import { Select, SelectOption, SelectDirection } from '@patternfly/react-core/deprecated'; + +class SingleSelectInput extends React.Component { + constructor(props) { + super(props); + this.state = { + isOpen: false, + isDisabled: false, + direction: SelectDirection.down + }; + + this.onToggle = (_event, isOpen) => { + this.setState({ + isOpen + }); + }; + + this.toggleDisabled = checked => { + this.setState({ + isDisabled: checked + }); + }; + + this.toggleDirection = () => { + if (this.state.direction === SelectDirection.up) { + this.setState({ + direction: SelectDirection.down + }); + } else { + this.setState({ + direction: SelectDirection.up + }); + } + }; + } + + render() { + const { isOpen, selected, isDisabled, direction } = this.state; + const titleId = 'title-id-2'; + return ( +
+ + document.body} + > + {this.bodyOptions} + + + + + + + ); + } +} +``` diff --git a/packages/react-core/src/next/components/Select/index.ts b/packages/react-core/src/deprecated/components/Select/index.ts similarity index 71% rename from packages/react-core/src/next/components/Select/index.ts rename to packages/react-core/src/deprecated/components/Select/index.ts index 5a8bca40468..dd66dd96b7c 100644 --- a/packages/react-core/src/next/components/Select/index.ts +++ b/packages/react-core/src/deprecated/components/Select/index.ts @@ -1,4 +1,4 @@ export * from './Select'; export * from './SelectGroup'; -export * from './SelectList'; export * from './SelectOption'; +export * from './selectConstants'; diff --git a/packages/react-core/src/components/Select/selectConstants.tsx b/packages/react-core/src/deprecated/components/Select/selectConstants.tsx similarity index 100% rename from packages/react-core/src/components/Select/selectConstants.tsx rename to packages/react-core/src/deprecated/components/Select/selectConstants.tsx diff --git a/packages/react-core/src/deprecated/components/index.ts b/packages/react-core/src/deprecated/components/index.ts index 61d7e9aa0b2..d2f851ff109 100644 --- a/packages/react-core/src/deprecated/components/index.ts +++ b/packages/react-core/src/deprecated/components/index.ts @@ -3,3 +3,4 @@ export * from './ContextSelector'; export * from './Dropdown'; export * from './OptionsMenu'; export * from './PageHeader'; +export * from './Select'; diff --git a/packages/react-core/src/next/components/Select/Select.tsx b/packages/react-core/src/next/components/Select/Select.tsx deleted file mode 100644 index 7b9ccafa950..00000000000 --- a/packages/react-core/src/next/components/Select/Select.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; -import { css } from '@patternfly/react-styles'; -import { Menu, MenuContent, MenuProps } from '../../../components/Menu'; -import { Popper, PopperProps } from '../../../helpers/Popper/Popper'; -import { getOUIAProps, OUIAProps, getDefaultOUIAId } from '../../../helpers'; - -export interface SelectProps extends MenuProps, OUIAProps { - /** Anything which can be rendered in a select */ - children?: React.ReactNode; - /** Classes applied to root element of select */ - className?: string; - /** Flag to indicate if select is open */ - isOpen?: boolean; - /** Single itemId for single select menus, or array of itemIds for multi select. You can also specify isSelected on the SelectOption. */ - selected?: any | any[]; - /** Renderer for a custom select toggle. Forwards a ref to the toggle. */ - toggle: (toggleRef: React.RefObject) => React.ReactNode; - /** Function callback when user selects an option. */ - onSelect?: (event?: React.MouseEvent, itemId?: string | number) => void; - /** Callback to allow the select component to change the open state of the menu. - * Triggered by clicking outside of the menu, or by pressing either tab or escape. */ - onOpenChange?: (isOpen: boolean) => void; - /** Indicates if the select should be without the outer box-shadow */ - isPlain?: boolean; - /** @hide Forwarded ref */ - innerRef?: React.Ref; - /** z-index of the select menu */ - zIndex?: number; - /** @beta Determines the accessible role of the select. For a checkbox select pass in "menu". */ - role?: string; - /** Additional properties to pass to the popper */ - popperProps?: Partial; -} - -const SelectBase: React.FunctionComponent = ({ - children, - className, - onSelect, - isOpen, - selected, - toggle, - onOpenChange, - isPlain, - innerRef, - zIndex = 9999, - role = 'listbox', - popperProps, - ...props -}: SelectProps & OUIAProps) => { - const localMenuRef = React.useRef(); - const toggleRef = React.useRef(); - const containerRef = React.useRef(); - - const menuRef = (innerRef as React.RefObject) || localMenuRef; - React.useEffect(() => { - const handleMenuKeys = (event: KeyboardEvent) => { - // Close the menu on tab or escape if onOpenChange is provided - if ( - (isOpen && onOpenChange && menuRef.current?.contains(event.target as Node)) || - toggleRef.current?.contains(event.target as Node) - ) { - if (event.key === 'Escape' || event.key === 'Tab') { - onOpenChange(false); - toggleRef.current?.focus(); - } - } - }; - - const handleClick = (event: MouseEvent) => { - // toggle was clicked open via keyboard, focus on first menu item - if (isOpen && toggleRef.current?.contains(event.target as Node) && event.detail === 0) { - setTimeout(() => { - const firstElement = menuRef?.current?.querySelector('li button:not(:disabled),li input:not(:disabled)'); - firstElement && (firstElement as HTMLElement).focus(); - }, 0); - } - - // If the event is not on the toggle and onOpenChange callback is provided, close the menu - if (isOpen && onOpenChange && !toggleRef?.current?.contains(event.target as Node)) { - if (isOpen && !menuRef.current?.contains(event.target as Node)) { - onOpenChange(false); - } - } - }; - - window.addEventListener('keydown', handleMenuKeys); - window.addEventListener('click', handleClick); - - return () => { - window.removeEventListener('keydown', handleMenuKeys); - window.removeEventListener('click', handleClick); - }; - }, [isOpen, menuRef, onOpenChange]); - - const menu = ( - onSelect && onSelect(event, itemId)} - isPlain={isPlain} - selected={selected} - {...getOUIAProps( - Select.displayName, - props.ouiaId !== undefined ? props.ouiaId : getDefaultOUIAId(Select.displayName), - props.ouiaSafe !== undefined ? props.ouiaSafe : true - )} - {...props} - > - {children} - - ); - return ( -
- -
- ); -}; - -export const Select = React.forwardRef((props: SelectProps, ref: React.Ref) => ( - -)); - -Select.displayName = 'Select'; diff --git a/packages/react-core/src/next/components/Select/SelectGroup.tsx b/packages/react-core/src/next/components/Select/SelectGroup.tsx deleted file mode 100644 index 4de9b3546b7..00000000000 --- a/packages/react-core/src/next/components/Select/SelectGroup.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { css } from '@patternfly/react-styles'; -import { MenuGroupProps, MenuGroup } from '../../../components/Menu'; - -export interface SelectGroupProps extends Omit { - /** Anything which can be rendered in a select group */ - children: React.ReactNode; - /** Classes applied to root element of select group */ - className?: string; - /** Label of the select group */ - label?: string; -} - -export const SelectGroup: React.FunctionComponent = ({ - children, - className, - label, - ...props -}: SelectGroupProps) => ( - - {children} - -); -SelectGroup.displayName = 'SelectGroup'; diff --git a/packages/react-core/src/next/components/Select/SelectOption.tsx b/packages/react-core/src/next/components/Select/SelectOption.tsx deleted file mode 100644 index c1c5b5dee79..00000000000 --- a/packages/react-core/src/next/components/Select/SelectOption.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { css } from '@patternfly/react-styles'; -import { MenuItemProps, MenuItem } from '../../../components/Menu'; - -export interface SelectOptionProps extends Omit { - /** Anything which can be rendered in a select option */ - children?: React.ReactNode; - /** Classes applied to root element of select option */ - className?: string; - /** Identifies the component in the Select onSelect callback */ - itemId?: any; - /** Indicates the option has a checkbox */ - hasCheckbox?: boolean; - /** Indicates the option is disabled */ - isDisabled?: boolean; - /** Indicates the option is selected */ - isSelected?: boolean; - /** Indicates the option is focused */ - isFocused?: boolean; -} - -export const SelectOption: React.FunctionComponent = ({ - children, - className, - ...props -}: SelectOptionProps) => ( - - {children} - -); -SelectOption.displayName = 'SelectOption'; diff --git a/packages/react-core/src/next/components/Select/examples/Select.md b/packages/react-core/src/next/components/Select/examples/Select.md deleted file mode 100644 index 402229a6869..00000000000 --- a/packages/react-core/src/next/components/Select/examples/Select.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: Select -section: components -subsection: menus -cssPrefix: pf-c-menu -propComponents: ['Select', SelectGroup, 'SelectOption', 'SelectList'] -beta: true -ouia: true ---- - -import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; - -## Examples - -### Single - -```ts file="./SelectBasic.tsx" -``` - -### Grouped single - -```ts file="./SelectGrouped.tsx" -``` - -### Checkbox - -```ts file="./SelectCheckbox.tsx" -``` - -### Typeahead - -```ts file="./SelectTypeahead.tsx" -``` - -### Multiple Typeahead - -```ts file="./SelectMultiTypeahead.tsx" -``` diff --git a/packages/react-core/src/next/components/index.ts b/packages/react-core/src/next/components/index.ts index f54f73d5f4d..4ad68566220 100644 --- a/packages/react-core/src/next/components/index.ts +++ b/packages/react-core/src/next/components/index.ts @@ -1,2 +1 @@ -export * from './Select'; export * from './Wizard'; diff --git a/packages/react-integration/demo-app-ts/src/Demos.ts b/packages/react-integration/demo-app-ts/src/Demos.ts index c3094dc1090..3e9b9d9ad96 100644 --- a/packages/react-integration/demo-app-ts/src/Demos.ts +++ b/packages/react-integration/demo-app-ts/src/Demos.ts @@ -314,57 +314,57 @@ export const Demos: DemoInterface[] = [ }, { id: 'select-demo', - name: 'Select Demo', + name: 'Select Deprecated Demo', componentType: Examples.SelectDemo }, { id: 'select-demo-filtering', - name: 'Select Demo with Filtering', + name: 'Select Deprecated Demo with Filtering', componentType: Examples.FilteringSelectDemo }, { id: 'select-demo-filtering-live-updates', - name: 'Select Demo with Filtering and Live Items Updates', + name: 'Select Deprecated Demo with Filtering and Live Items Updates', componentType: Examples.FilteringSelectLiveUpdateDemo }, { id: 'select-favorites-demo', - name: 'Select Favorites Demo', + name: 'Select Deprecated Favorites Demo', componentType: Examples.SelectFavoritesDemo }, { id: 'select-in-modal-demo', - name: 'Select in modal demo', + name: 'Select Deprecated in modal demo', componentType: Examples.SelectInModal }, { id: 'select-typeahead-footer-filtering-demo', - name: 'Select Footer filtering Demo', + name: 'Select Deprecated Footer filtering Demo', componentType: Examples.SelectFooterFilteringDemo }, { id: 'select-typeahead-footer-demo', - name: 'Select Typeahead Footer Demo', + name: 'Select Deprecated Typeahead Footer Demo', componentType: Examples.SelectTypeaheadFooterDemo }, { id: 'select-validated-demo', - name: 'Select Validated Demo', + name: 'Select Deprecated Validated Demo', componentType: Examples.SelectValidatedDemo }, { id: 'select-view-more-demo', - name: 'Select View More Demo', + name: 'Select Deprecated View More Demo', componentType: Examples.SelectViewMoreDemo }, { id: 'select-view-more-grouped-demo', - name: 'Select View More Grouped Demo', + name: 'Select Deprecated View More Grouped Demo', componentType: Examples.SelectViewMoreGroupedDemo }, { id: 'select-view-more-typeahead-grouped-demo', - name: 'Select View More Typeahead Grouped Demo', + name: 'Select Deprecated View More Typeahead Grouped Demo', componentType: Examples.SelectViewMoreTypeaheadGroupedDemo }, { diff --git a/packages/react-integration/demo-app-ts/src/components/demos/FormDemo/FormDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/FormDemo/FormDemo.tsx index 2be7bc9a582..e44a1c4781b 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/FormDemo/FormDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/FormDemo/FormDemo.tsx @@ -8,15 +8,17 @@ import { TextInput, Checkbox, Popover, - Select, - SelectOption, - SelectOptionObject, - SelectVariant, ValidatedOptions, HelperText, HelperTextItem, FormHelperText } from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectOptionObject, + SelectVariant, +} from '@patternfly/react-core/deprecated'; import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import HelpIcon from '@patternfly/react-icons/dist/esm/icons/help-icon'; diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/FilteringSelectDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/FilteringSelectDemo.tsx similarity index 96% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/FilteringSelectDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/FilteringSelectDemo.tsx index 2dba8a09840..0ebc52ab795 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/FilteringSelectDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/FilteringSelectDemo.tsx @@ -1,4 +1,5 @@ -import { Select, SelectOption, SelectOptionObject, SelectGroup, SelectVariant, Checkbox } from '@patternfly/react-core'; +import { Checkbox } from '@patternfly/react-core'; +import { Select, SelectOption, SelectOptionObject, SelectGroup, SelectVariant } from '@patternfly/react-core/deprecated'; import React, { Component } from 'react'; /* eslint-disable no-console */ diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/FilteringSelectLiveUpdateDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/FilteringSelectLiveUpdateDemo.tsx similarity index 98% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/FilteringSelectLiveUpdateDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/FilteringSelectLiveUpdateDemo.tsx index 4b9b5ccdabe..f582ba29d86 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/FilteringSelectLiveUpdateDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/FilteringSelectLiveUpdateDemo.tsx @@ -1,4 +1,4 @@ -import { Select, SelectOption, SelectOptionObject, SelectVariant } from '@patternfly/react-core'; +import { Select, SelectOption, SelectOptionObject, SelectVariant } from '@patternfly/react-core/deprecated'; import React, { Component } from 'react'; /* eslint-disable no-console */ diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectDemo.tsx similarity index 99% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectDemo.tsx index b2045deb54f..74400446d91 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectDemo.tsx @@ -1,18 +1,20 @@ import { Button, - Select, - SelectOption, - SelectVariant, Stack, StackItem, Title, - SelectOptionObject, Checkbox, - SelectDirection, Form, - Divider, - SelectGroup + Divider } from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant, + SelectOptionObject, + SelectDirection, + SelectGroup +} from '@patternfly/react-core/deprecated'; import React, { Component } from 'react'; import CartArrowDownIcon from '@patternfly/react-icons/dist/esm/icons/cart-arrow-down-icon'; import { State } from '../../../common/State'; diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectFavoritesDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectFavoritesDemo.tsx similarity index 99% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectFavoritesDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectFavoritesDemo.tsx index 09b376371bc..8d1e335324f 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectFavoritesDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectFavoritesDemo.tsx @@ -1,13 +1,15 @@ import React, { Component } from 'react'; +import { + StackItem, + Title +} from '@patternfly/react-core'; import { Select, SelectOption, SelectVariant, SelectGroup, - SelectOptionObject, - StackItem, - Title -} from '@patternfly/react-core'; + SelectOptionObject +} from '@patternfly/react-core/deprecated'; /* eslint-disable no-console */ export interface SelectFavoritesDemoState { 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/SelectDeprecatedDemo/SelectFooterFilteringDemo.tsx similarity index 98% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectFooterFilteringDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectFooterFilteringDemo.tsx index 8da07429db7..e01d5fcec9d 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectFooterFilteringDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectFooterFilteringDemo.tsx @@ -1,12 +1,14 @@ +import { + Checkbox, + Button +} from '@patternfly/react-core'; import { Select, SelectOption, SelectOptionObject, SelectGroup, - SelectVariant, - Checkbox, - Button -} from '@patternfly/react-core'; + SelectVariant +} from '@patternfly/react-core/deprecated'; import React, { Component } from 'react'; /* eslint-disable no-console */ diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectInModal.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectInModal.tsx similarity index 97% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectInModal.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectInModal.tsx index 0a8e1b9717f..68477124691 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectInModal.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectInModal.tsx @@ -4,11 +4,13 @@ import { ModalVariant, Button, TimePicker, - Select, - SelectOption, - SelectOptionObject, InputGroup } from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectOptionObject +} from '@patternfly/react-core/deprecated'; export const SelectInModal = () => { const [isModalOpen, setIsModalOpen] = React.useState(false); 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/SelectDeprecatedDemo/SelectTypeaheadFooterDemo.tsx similarity index 93% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectTypeaheadFooterDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectTypeaheadFooterDemo.tsx index 0bf6f28a1bf..791ab1fa298 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectTypeaheadFooterDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectTypeaheadFooterDemo.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Select, SelectOption, SelectVariant, Button } from '@patternfly/react-core'; +import { Button } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; /* eslint-disable no-console */ export interface SelectTypeaheadFooterDemoState { diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectValidatedDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectValidatedDemo.tsx similarity index 94% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectValidatedDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectValidatedDemo.tsx index 329de1ce4fd..636b4ce97e7 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectValidatedDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectValidatedDemo.tsx @@ -1,5 +1,6 @@ import React, { Component } from 'react'; -import { Select, SelectOption, SelectVariant, StackItem, Title, ValidatedOptions } from '@patternfly/react-core'; +import { StackItem, Title, ValidatedOptions } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; /* eslint-disable no-console */ export interface SelectValidatedDemoState { diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectViewMoreDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectViewMoreDemo.tsx similarity index 98% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectViewMoreDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectViewMoreDemo.tsx index 1649909a1ca..c39997990c7 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectViewMoreDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectViewMoreDemo.tsx @@ -1,5 +1,6 @@ import React, { Component } from 'react'; -import { Select, SelectOption, SelectOptionObject, SelectVariant, StackItem, Title } from '@patternfly/react-core'; +import { StackItem, Title } from '@patternfly/react-core'; +import { Select, SelectOption, SelectOptionObject, SelectVariant } from '@patternfly/react-core/deprecated'; /* eslint-disable no-console */ export interface SelectViewMoreDemoState { diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectViewMoreGroupedDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectViewMoreGroupedDemo.tsx similarity index 98% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectViewMoreGroupedDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectViewMoreGroupedDemo.tsx index b24cef978e6..d1617db8f6f 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectViewMoreGroupedDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectViewMoreGroupedDemo.tsx @@ -1,13 +1,15 @@ import React, { Component } from 'react'; +import { + StackItem, + Title +} from '@patternfly/react-core'; import { Select, SelectGroup, SelectOption, SelectOptionObject, - SelectVariant, - StackItem, - Title -} from '@patternfly/react-core'; + SelectVariant +} from '@patternfly/react-core/deprecated'; /* eslint-disable no-console */ export interface SelectViewMoreGroupedDemoState { diff --git a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectViewMoreTypeaheadGroupedDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectViewMoreTypeaheadGroupedDemo.tsx similarity index 95% rename from packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectViewMoreTypeaheadGroupedDemo.tsx rename to packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectViewMoreTypeaheadGroupedDemo.tsx index 9d9f8ec09e8..2fe39777979 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/SelectDemo/SelectViewMoreTypeaheadGroupedDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/SelectDeprecatedDemo/SelectViewMoreTypeaheadGroupedDemo.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Select, SelectOption, SelectVariant, SelectGroup, StackItem, Title } from '@patternfly/react-core'; +import { StackItem, Title } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant, SelectGroup } from '@patternfly/react-core/deprecated'; /* eslint-disable no-console */ interface TypeAheadOption { diff --git a/packages/react-integration/demo-app-ts/src/components/demos/TableDemo/TableEditableDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/TableDemo/TableEditableDemo.tsx index d0a67c36f5b..3b0a79e5dea 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/TableDemo/TableEditableDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/TableDemo/TableEditableDemo.tsx @@ -13,10 +13,9 @@ import { EditableTextCell, EditableSelectInputCell } from '@patternfly/react-table'; +import { SelectOption } from '@patternfly/react-core/deprecated'; import { Table, TableHeader, TableBody, TableProps } from '@patternfly/react-table/deprecated'; -import { SelectOption } from '@patternfly/react-core'; - const rowLevelValidationRules: IValidatorDef[] = [ { name: 'required', diff --git a/packages/react-integration/demo-app-ts/src/components/demos/ToolbarDemo/ToolbarDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/ToolbarDemo/ToolbarDemo.tsx index dddb377a57a..f545f097067 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/ToolbarDemo/ToolbarDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/ToolbarDemo/ToolbarDemo.tsx @@ -12,13 +12,13 @@ import { ToolbarGroup, ToolbarProps, InputGroup, + TextInput +} from '@patternfly/react-core'; +import { Select, SelectOption, SelectOptionObject, SelectVariant, - TextInput -} from '@patternfly/react-core'; -import { Dropdown as DropdownDeprecated, DropdownItem as DropdownItemDeprecated, DropdownSeparator, diff --git a/packages/react-integration/demo-app-ts/src/components/demos/TopologyDemo/useTopologyOptions.tsx b/packages/react-integration/demo-app-ts/src/components/demos/TopologyDemo/useTopologyOptions.tsx index 69655a0000f..89a31d900ee 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/TopologyDemo/useTopologyOptions.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/TopologyDemo/useTopologyOptions.tsx @@ -7,15 +7,17 @@ import { DropdownPosition, DropdownToggle, Flex, - Select, - SelectOption, - SelectVariant, Split, SplitItem, TextInput, ToolbarItem, Tooltip } from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; import '@patternfly/react-styles/css/components/Topology/topology-components.css'; import { DefaultEdgeOptions, DefaultNodeOptions, GeneratorEdgeOptions, GeneratorNodeOptions } from './data/generator'; import { 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 3b710119afc..56c861ff4f1 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 @@ -58,17 +58,17 @@ export * from './PaginationDemo/PaginationDemo'; export * from './PopoverDemo/PopoverDemo'; export * from './RadioDemo/RadioDemo'; export * from './SearchInputDemo/SearchInputDemo'; -export * from './SelectDemo/FilteringSelectDemo'; -export * from './SelectDemo/FilteringSelectLiveUpdateDemo'; -export * from './SelectDemo/SelectDemo'; -export * from './SelectDemo/SelectFavoritesDemo'; -export * from './SelectDemo/SelectFooterFilteringDemo'; -export * from './SelectDemo/SelectInModal'; -export * from './SelectDemo/SelectTypeaheadFooterDemo'; -export * from './SelectDemo/SelectValidatedDemo'; -export * from './SelectDemo/SelectViewMoreDemo'; -export * from './SelectDemo/SelectViewMoreGroupedDemo'; -export * from './SelectDemo/SelectViewMoreTypeaheadGroupedDemo'; +export * from './SelectDeprecatedDemo/FilteringSelectDemo'; +export * from './SelectDeprecatedDemo/FilteringSelectLiveUpdateDemo'; +export * from './SelectDeprecatedDemo/SelectDemo'; +export * from './SelectDeprecatedDemo/SelectFavoritesDemo'; +export * from './SelectDeprecatedDemo/SelectFooterFilteringDemo'; +export * from './SelectDeprecatedDemo/SelectInModal'; +export * from './SelectDeprecatedDemo/SelectTypeaheadFooterDemo'; +export * from './SelectDeprecatedDemo/SelectValidatedDemo'; +export * from './SelectDeprecatedDemo/SelectViewMoreDemo'; +export * from './SelectDeprecatedDemo/SelectViewMoreGroupedDemo'; +export * from './SelectDeprecatedDemo/SelectViewMoreTypeaheadGroupedDemo'; export * from './SimpleList/SimpleListDemo'; export * from './SliderDemo/SliderDemo'; export * from './SwitchDemo/SwitchDemo'; diff --git a/packages/react-table/src/components/Table/EditableSelectInputCell.tsx b/packages/react-table/src/components/Table/EditableSelectInputCell.tsx index eed09424e26..7e3c21bf5ae 100644 --- a/packages/react-table/src/components/Table/EditableSelectInputCell.tsx +++ b/packages/react-table/src/components/Table/EditableSelectInputCell.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; import { css } from '@patternfly/react-styles'; -import { Select, SelectOptionObject } from '@patternfly/react-core/dist/esm/components/Select'; +import { + Select, + SelectOptionObject +} from '@patternfly/react-core/dist/esm/deprecated/components/Select'; import inlineStyles from '@patternfly/react-styles/css/components/InlineEdit/inline-edit'; import formStyles from '@patternfly/react-styles/css/components/Form/form'; import { EditableSelectInputProps } from './base/types'; diff --git a/packages/react-table/src/components/Table/base/types.tsx b/packages/react-table/src/components/Table/base/types.tsx index cbd3a3a179d..e147be2321f 100644 --- a/packages/react-table/src/components/Table/base/types.tsx +++ b/packages/react-table/src/components/Table/base/types.tsx @@ -9,7 +9,10 @@ import * as React from 'react'; import { TooltipProps } from '@patternfly/react-core/dist/esm/components/Tooltip'; import { PopoverProps } from '@patternfly/react-core/dist/esm/components/Popover'; import { DropdownPosition, DropdownDirection } from '@patternfly/react-core/dist/esm/deprecated/components/Dropdown'; -import { SelectProps, SelectOptionObject } from '@patternfly/react-core/dist/esm/components/Select'; +import { + SelectOptionObject, + SelectProps +} from '@patternfly/react-core/dist/esm/deprecated/components/Select'; import { Table } from '../Table'; import { Thead } from '../Thead'; import { Tbody } from '../Tbody'; diff --git a/packages/react-table/src/components/Table/examples/TableSortableCustom.tsx b/packages/react-table/src/components/Table/examples/TableSortableCustom.tsx index cdb513e3fb8..b2429484928 100644 --- a/packages/react-table/src/components/Table/examples/TableSortableCustom.tsx +++ b/packages/react-table/src/components/Table/examples/TableSortableCustom.tsx @@ -3,7 +3,15 @@ import { Table, Thead, Tr, Th, Tbody, Td, ThProps } from '@patternfly/react-tabl import { Toolbar, ToolbarContent, + ToolbarItem, + Select, + SelectGroup, + SelectList, + SelectOption, + MenuToggle, + MenuToggleElement } from '@patternfly/react-core'; +import SortAmountDownIcon from '@patternfly/react-icons/dist/esm/icons/sort-amount-down-icon'; interface Repository { name: string; @@ -29,6 +37,8 @@ export const TableSortableCustom: React.FunctionComponent = () => { lastCommit: 'Last commit' }; + const [isSortDropdownOpen, setIsSortDropdownOpen] = React.useState(false); + // Index of the currently sorted column // Note: if you intend to make columns reorderable, you may instead want to use a non-numeric key // as the identifier of the sorted column. See the "Compound expandable" example. @@ -78,7 +88,7 @@ export const TableSortableCustom: React.FunctionComponent = () => { }, onSort: (_event, index, direction) => { setActiveSortIndex(index); - setActiveSortDirection(direction); + setActiveSortDirection(direction as 'desc' | 'asc'); }, columnIndex }); @@ -87,56 +97,63 @@ export const TableSortableCustom: React.FunctionComponent = () => { - {/* TODO: replace with select after #8073 - - {Object.values(columnNames).map((columnName, columnIndex) => ( - + + diff --git a/packages/react-table/src/deprecated/components/Table/examples/LegacyTableSortableCustom.tsx b/packages/react-table/src/deprecated/components/Table/examples/LegacyTableSortableCustom.tsx index 85dcb7fb80d..dcfe444f450 100644 --- a/packages/react-table/src/deprecated/components/Table/examples/LegacyTableSortableCustom.tsx +++ b/packages/react-table/src/deprecated/components/Table/examples/LegacyTableSortableCustom.tsx @@ -4,7 +4,18 @@ import { Table, TableHeader, TableBody, TableProps } from '@patternfly/react-tab import { Toolbar, ToolbarContent, + ToolbarItem, + MenuToggle, + MenuToggleElement } from '@patternfly/react-core'; +import { + Select as NewSelect, + SelectGroup as NewSelectGroup, + SelectList as NewSelectList, + SelectOption as NewSelectOption +} from '@patternfly/react-core/dist/esm/components/Select'; + +import SortAmountDownIcon from '@patternfly/react-icons/dist/esm/icons/sort-amount-down-icon'; interface Repository { name: string; @@ -22,13 +33,15 @@ export const LegacyTableSortableCustom: React.FunctionComponent = () => { { name: 'p', branches: 'two', prs: 'b', workspaces: 'four', lastCommit: 'five' } ]; - // const columnNames = { - // name: 'Repositories', - // branches: 'Branches', - // prs: 'Pull requests', - // workspaces: 'Workspaces', - // lastCommit: 'Last commit' - // }; + const columnNames = { + name: 'Repositories', + branches: 'Branches', + prs: 'Pull requests', + workspaces: 'Workspaces', + lastCommit: 'Last commit' + }; + + const [isSortDropdownOpen, setIsSortDropdownOpen] = React.useState(false); // Index of the currently sorted column // Note: if you intend to make columns reorderable, you may instead want to use a non-numeric key @@ -110,56 +123,63 @@ export const LegacyTableSortableCustom: React.FunctionComponent = () => { - {/* TODO: replace with select after #8073 - - {Object.values(columnNames).map((columnName, columnIndex) => ( - + setIsSortDropdownOpen(isOpen)} + onSelect={(event, itemId) => { + if (itemId === 'asc' || itemId === 'desc') { + setActiveSortDirection(itemId as 'desc' | 'asc'); + } else { + setActiveSortIndex(itemId as number); + setActiveSortDirection(activeSortDirection !== null ? activeSortDirection : 'asc'); } + }} + toggle={(toggleRef: React.Ref) => ( + setIsSortDropdownOpen(!isSortDropdownOpen)} + isExpanded={isSortDropdownOpen} + variant="plain" + aria-label="Sort columns" + > + + + )} + > + + + {Object.values(columnNames).map((column, columnIndex) => ( + { - setActiveSortIndex(columnIndex); - setActiveSortDirection(activeSortDirection !== null ? activeSortDirection : 'asc'); - }} > - {columnName} - + {column} + ))} - , - , - - setActiveSortDirection('asc')} + + + + + Ascending - - setActiveSortDirection('desc')} + + Descending - - - ]} - isOpen={isSortDropdownOpen} - toggle={ - setIsSortDropdownOpen(!isSortDropdownOpen)} - toggleTemplate={} - /> - } - isPlain - isGrouped - /> - */} + + + + +
( - ))} - onToggle={isOpen => { + onToggle={(event, isOpen) => { this.onToggle(isOpen, rowIndex, cellIndex); }} selections={props.selected} @@ -486,7 +494,7 @@ class EditableRowsTable extends React.Component { isOpen={props.isSelectOpen} options={props.options.map((option, index) => { return ( - ); })} - onToggle={isOpen => { + onToggle={(event, isOpen) => { this.onToggle(isOpen, rowIndex, cellIndex); }} selections={props.selected} @@ -606,14 +614,14 @@ class EditableRowsTable extends React.Component { clearSelection={this.clearSelection} isOpen={props.isSelectOpen} options={props.options.map((option, index) => ( - ))} - onToggle={isOpen => { + onToggle={(event, isOpen) => { this.onToggle(isOpen, rowIndex, cellIndex); }} selections={props.selected} @@ -684,6 +692,7 @@ class EditableRowsTable extends React.Component { } let newSelected = Array.from(newCellProps.selected); + let newSelectOpen = false; switch (newCellProps.editableSelectProps.variant) { case 'typeaheadmulti': @@ -693,6 +702,7 @@ class EditableRowsTable extends React.Component { } else { newSelected = newSelected.filter(el => el !== newValue); } + newSelectOpen = true; break; } default: { @@ -702,6 +712,7 @@ class EditableRowsTable extends React.Component { newCellProps.editableValue = newSelected; newCellProps.selected = newSelected; + newCellProps.isSelectOpen = newSelectOpen; } this.setState({ @@ -720,6 +731,7 @@ class EditableRowsTable extends React.Component { }; this.onToggle = (isOpen, rowIndex, cellIndex) => { + console.log("isOpen", isOpen); let newRows = Array.from(this.state.rows); newRows[rowIndex].cells[cellIndex].props.isSelectOpen = isOpen; this.setState({ diff --git a/packages/react-table/src/docs/demos/Table.md b/packages/react-table/src/docs/demos/Table.md index f82cd7b20e3..b345fa8ce20 100644 --- a/packages/react-table/src/docs/demos/Table.md +++ b/packages/react-table/src/docs/demos/Table.md @@ -362,8 +362,7 @@ import { PaginationVariant, Text, TextContent, - Select, - SelectVariant + MenuToggle } from '@patternfly/react-core'; import { Table as TableDeprecated, TableHeader, TableBody } from '@patternfly/react-table/deprecated'; import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon'; @@ -1003,33 +1002,11 @@ class ColumnManagementAction extends React.Component { - ) => ( + } + style={ + { + width: '100%', + verticalAlign: 'text-bottom' + } as React.CSSProperties + } + > + {currentCategory} + + )} isOpen={isCategoryDropdownOpen} - toggleIcon={} - style={{ width: '100%' }} > {categoryMenuItems} @@ -1303,19 +1293,54 @@ class FilterTableDemo extends React.Component { const { currentCategory, isFilterDropdownOpen, inputValue, filters } = this.state; const locationMenuItems = [ - , - , - , - , - + Raleigh, + Westford, + Boston, + Brno, + Bangalore ]; const statusMenuItems = [ - , - , - , - , - + + Running + , + + Stopped + , + + Down + , + + Degraded + , + + Needs Maintainence + ]; return ( @@ -1328,11 +1353,25 @@ class FilterTableDemo extends React.Component { > @@ -1361,13 +1400,28 @@ class FilterTableDemo extends React.Component { showToolbarItem={currentCategory === 'Status'} > @@ -1382,7 +1436,13 @@ class FilterTableDemo extends React.Component { } breakpoint="xl"> - + {this.buildCategoryDropdown()} {this.buildFilterDropdown()} diff --git a/packages/react-table/src/docs/demos/table-demos/ColumnManagement.jsx b/packages/react-table/src/docs/demos/table-demos/ColumnManagement.jsx index fbf676c0903..8f7821b215b 100644 --- a/packages/react-table/src/docs/demos/table-demos/ColumnManagement.jsx +++ b/packages/react-table/src/docs/demos/table-demos/ColumnManagement.jsx @@ -11,6 +11,7 @@ import { Toolbar, ToolbarContent, ToolbarItem, + MenuToggle, Modal, OverflowMenu, OverflowMenuGroup, @@ -20,9 +21,7 @@ import { Pagination, PaginationVariant, Text, - TextContent, - Select, - SelectVariant + TextContent } from '@patternfly/react-core'; import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; import FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon'; @@ -390,32 +389,11 @@ export const ColumnManagementAction = () => { - + toggle={(toggleRef: React.Ref) => ( + setIsSelectOpen(!isSelectOpen)} + isExpanded={isSelectOpen} + > Status - - } + + )} isOpen={isSelectOpen} - onToggle={() => setIsSelectOpen(!isSelectOpen)} + onOpenChange={isOpen => setIsSelectOpen(isOpen)} onSelect={() => setIsSelectOpen(!isSelectOpen)} > {[ - , - , - , - + Debug, + Info, + Warn, + Error ]} diff --git a/packages/react-table/src/docs/demos/table-demos/CompoundExpansion.jsx b/packages/react-table/src/docs/demos/table-demos/CompoundExpansion.jsx index f6b06b8b794..1c7352506fc 100644 --- a/packages/react-table/src/docs/demos/table-demos/CompoundExpansion.jsx +++ b/packages/react-table/src/docs/demos/table-demos/CompoundExpansion.jsx @@ -14,15 +14,16 @@ import { Card, Flex, FlexItem, + MenuToggle, + MenuToggleElement, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem, Pagination, + PageSection, Select, - SelectVariant, - SelectOption, - PageSection + SelectOption } from '@patternfly/react-core'; import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon'; @@ -95,22 +96,25 @@ export const CompoundExpandable = () => { diff --git a/packages/react-table/src/docs/demos/table-demos/SortableResponsive.jsx b/packages/react-table/src/docs/demos/table-demos/SortableResponsive.jsx index f6a4c5c368c..463b577ec45 100644 --- a/packages/react-table/src/docs/demos/table-demos/SortableResponsive.jsx +++ b/packages/react-table/src/docs/demos/table-demos/SortableResponsive.jsx @@ -7,20 +7,18 @@ import { Flex, FlexItem, MenuToggle, - Toolbar, - ToolbarContent, - ToolbarGroup, - ToolbarItem, + MenuToggleElement, + PageSection, Pagination, + SelectOption, + SelectList, + SelectGroup, Text, TextContent, - OverflowMenu, - OverflowMenuContent, - OverflowMenuControl, - OverflowMenuDropdownItem, - OverflowMenuGroup, - OverflowMenuItem, - PageSection + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem } from '@patternfly/react-core'; import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; import { rows, columns } from '../../examples/Data.jsx'; @@ -29,6 +27,7 @@ import EditIcon from '@patternfly/react-icons/dist/esm/icons/edit-icon'; import SyncIcon from '@patternfly/react-icons/dist/esm/icons/sync-icon'; import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon'; import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; +import SortAmountDownIcon from '@patternfly/react-icons/dist/esm/icons/sort-amount-down-icon'; import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; import DashboardWrapper from '@patternfly/react-core/src/demos/examples/DashboardWrapper'; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; @@ -125,89 +124,96 @@ export const ComposableTableSortable = () => { const tableToolbar = ( - {/* TODO: replace with select after #8073 - + + + + + + + + + + + + + + + + + setIsKebabDropdownOpen(!isKebabDropdownOpen)} + onOpenChange={(isKebabDropdownOpen) => setIsKebabDropdownOpen(isKebabDropdownOpen)} + toggle={(toggleRef) => ( + setIsKebabDropdownOpen(!isKebabDropdownOpen)} + isExpanded={false} + > + + + )} + isOpen={isKebabDropdownOpen} + > + {kebabDropdownItems} + + + +