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; } 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