import React from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import { StandardProps, findTypes, getFirst } from '../../util/component-types'; import { lucidClassNames } from '../../util/style-helpers'; import { partitionText, propsSearch } from '../../util/text-manipulation'; import { buildModernHybridComponent } from '../../util/state-management'; import * as reducers from './SearchableSelect.reducers'; import ChevronIcon from '../Icon/ChevronIcon/ChevronIcon'; import { DropMenuDumb as DropMenu, IDropMenuOptionProps, IDropMenuOptionGroupProps, IDropMenuProps, IDropMenuState, IOptionsData, } from '../DropMenu/DropMenu'; import LoadingIcon from '../Icon/LoadingIcon/LoadingIcon'; import { SearchFieldDumb as SearchField } from '../SearchField/SearchField'; import { Validation } from '../Validation/Validation'; const cx = lucidClassNames.bind('&-SearchableSelect'); const { any, bool, func, node, number, object, shape, string, oneOfType } = PropTypes; /** Placeholder Child Component */ export interface ISearchableSelectPlaceholderProps extends StandardProps { description?: string; } const Placeholder = (_props: ISearchableSelectPlaceholderProps): null => null; Placeholder.displayName = 'SearchableSelect.Placeholder'; Placeholder.peek = { description: `The content rendered in the control when there is no option is selected. Also rendered in the option list to remove current selection.`, }; Placeholder.propName = 'Placeholder'; Placeholder.propTypes = {}; /** OptionGroup Child Component */ const OptionGroup = (_props: IDropMenuOptionGroupProps): null => null; OptionGroup.displayName = 'SearchableSelect.OptionGroup'; OptionGroup.peek = { description: `A special kind of \`Option\` that is always rendered at the top of the menu and has an \`optionIndex\` of \`null\`. Useful for unselect.`, }; OptionGroup.propName = 'OptionGroup'; OptionGroup.propTypes = DropMenu.OptionGroup.propTypes; OptionGroup.defaultProps = DropMenu.OptionGroup.defaultProps; export interface ISearchableSelectOptionProps extends IDropMenuOptionProps { description?: string; name?: string; Selected?: React.ReactNode; } /** Option.Selected Child Component */ const Selected = (_props: { children?: React.ReactNode }): null => null; Selected.displayName = 'SearchableSelect.Option.Selected'; Selected.peek = { description: `Customizes the rendering of the \`Option\` when it is selected and is displayed instead of the \`Placeholder\`.`, }; Selected.propName = 'Selected'; Selected.propTypes = {}; /** Option Child Component */ const Option = (_props: ISearchableSelectOptionProps): null => null; Option.displayName = 'SearchableSelect.Option'; Option.peek = { description: `A selectable option in the list.`, }; Option.Selected = Selected; Option.propName = 'Option'; Option.propTypes = { /** Customizes the rendering of the Option when it is selected and is displayed instead of the Placeholder. */ Selected: any, value: string, filterText: string, ...DropMenu.Option.propTypes, }; Option.defaultProps = DropMenu.Option.defaultProps; /** Searchable Select */ type ISearchableSelectDropMenuProps = Partial; export interface ISearchableSelectProps extends StandardProps { hasReset: boolean; isDisabled: boolean; isInvisible: boolean; isLoading: boolean; isSelectionHighlighted: boolean; maxMenuHeight?: string | number; selectedIndex: number | null; searchText: string; DropMenu: ISearchableSelectDropMenuProps; Placeholder?: React.ReactNode; Option?: React.ReactNode; OptionGroup?: IDropMenuOptionGroupProps; Error: React.ReactNode; /** Called when an option is clicked, or when an option has focus and the Enter key is pressed. */ onSelect: ( optionIndex: number | null, { props, event, }: { props: IDropMenuOptionProps | undefined; event: React.KeyboardEvent | React.MouseEvent; } ) => void; onSearch: (searchText: string, firstVisibleIndex: number | undefined) => void; optionFilter: (searchValue: string, props: any) => boolean; } export interface ISearchableSelectState { DropMenu: IDropMenuState; selectedIndex: number | null; searchText: string | null; optionGroups: IDropMenuOptionGroupProps[]; flattenedOptionsData: IOptionsData[]; ungroupedOptionData: IOptionsData[]; optionGroupDataLookup: { [key: number]: IOptionsData[] }; isFocusOnSearchFieldRequired: boolean; } const defaultProps = { hasReset: true, isSelectionHighlighted: true, isDisabled: false, isInvisible: false, isLoading: false, optionFilter: propsSearch, searchText: '', selectedIndex: null, DropMenu: DropMenu.defaultProps, Error: null, onSearch: _.noop, onSelect: _.noop, }; /** SearchableSelect Component */ class SearchableSelect extends React.Component< ISearchableSelectProps, ISearchableSelectState > { static displayName = 'SearchableSelect'; static peek = { description: `A selector control (like native \`