uui/components/pickers/PickerInput.tsx (186 lines of code) (raw):

import React, { useImperativeHandle, useRef } from 'react'; import { PickerTogglerRenderItemParams, PickerBodyBaseProps, PickerInputBaseProps, PickerTogglerProps, usePickerInput } from '@epam/uui-components'; import { Dropdown } from '../overlays/Dropdown'; import { EditMode, IHasEditMode, SizeMod } from '../types'; import { DataRowProps, DataSourceListProps, DataSourceState, DropdownBodyProps, IDropdownToggler, IEditableDebouncer, PickerInputElement, isMobile, Overwrite, } from '@epam/uui-core'; import { PickerModal } from './PickerModal'; import { PickerToggler, PickerTogglerMods } from './PickerToggler'; import { PickerBodyMobileView } from './PickerBodyMobileView'; import { DataPickerBody } from './DataPickerBody'; import { DataPickerRow, DataPickerRowProps } from './DataPickerRow'; import { DataPickerFooter } from './DataPickerFooter'; import { PickerItem, PickerItemProps } from './PickerItem'; import { settings } from '../../settings'; export interface PickerInputModsOverride {} interface PickerInputMods extends SizeMod {} export type PickerInputProps<TItem, TId> = Overwrite<PickerInputMods, PickerInputModsOverride> & IHasEditMode & PickerInputBaseProps<TItem, TId> & { /** * Render callback for picker toggler selection tag * If omitted, default `PickerTogglerTag` component will be rendered */ renderTag?: (props: PickerTogglerRenderItemParams<TItem, TId>) => JSX.Element; }; function PickerInputComponent<TItem, TId>({ highlightSearchMatches = true, ...props }: PickerInputProps<TItem, TId>, ref: React.ForwardedRef<PickerInputElement>) { const toggleModalOpening = () => { const { renderFooter, rawProps, ...restProps } = props; context.uuiModals .show((modalProps) => ( <PickerModal<TItem, TId> { ...restProps } rawProps={ rawProps?.body } { ...modalProps } caption={ getPlaceholder() } initialValue={ props.value as any } renderRow={ renderRow } selectionMode={ props.selectionMode } valueType={ props.valueType } /> )) .then((newSelection) => { handleSelectionValueChange(newSelection); }) .catch(() => {}); }; const { view, context, popperModifiers, getName, getPlaceholder, handleSelectionValueChange, getTogglerProps, getRows, handleTogglerSearchChange, toggleBodyOpening, dataSourceState, getFooterProps, getPickerBodyProps, getListProps, shouldShowBody, getSearchPosition, closePickerBody, openPickerBody, handlePickerInputKeyboard, } = usePickerInput<TItem, TId, PickerInputProps<TItem, TId>>({ ...props, toggleModalOpening }); const dropdownRef = useRef(null); useImperativeHandle(ref, () => { if (dropdownRef.current) { dropdownRef.current.closePickerBody = closePickerBody; dropdownRef.current.openPickerBody = openPickerBody; } return dropdownRef.current; }, [closePickerBody, openPickerBody]); const getTogglerMods = (): PickerTogglerMods => { return { size: props.size as PickerTogglerMods['size'], mode: props.mode ? props.mode : EditMode.FORM, }; }; const renderTarget = (targetProps: IDropdownToggler & PickerTogglerProps<TItem, TId>) => { const renderTargetFn = props.renderToggler || ((props) => <PickerToggler { ...props } />); return ( <IEditableDebouncer value={ targetProps.value } onValueChange={ handleTogglerSearchChange } debounceDelay={ props.searchDebounceDelay } render={ (editableProps) => renderTargetFn({ ...getTogglerMods(), ...targetProps, ...editableProps, onKeyDown: (e) => handlePickerInputKeyboard(rows, e, editableProps.value), }) } /> ); }; const renderFooter = () => { const footerProps = getFooterProps(); return props.renderFooter ? props.renderFooter(footerProps) : <DataPickerFooter { ...footerProps } size={ props.size } />; }; const getRowSize = () => { if (isMobile()) { return settings.sizes.pickerInput.body.mobile.row; } return props.editMode === 'modal' ? settings.sizes.pickerInput.body.modal.row : (props.size || settings.sizes.pickerInput.body.dropdown.row.default); }; const getSubtitle = ({ path }: DataRowProps<TItem, TId>, { search }: DataSourceState) => { if (!search) return; return path .map(({ value }) => getName(value)) .filter(Boolean) .join(' / '); }; const renderRowItem = (item: TItem, rowProps: DataRowProps<TItem, TId>, dsState: DataSourceState) => { const { flattenSearchResults } = view.getConfig(); return ( <PickerItem title={ getName(item) } size={ getRowSize() as PickerItemProps<any, any>['size'] } dataSourceState={ dsState } highlightSearchMatches={ highlightSearchMatches } { ...(flattenSearchResults ? { subtitle: getSubtitle(rowProps, dataSourceState) } : {}) } { ...rowProps } /> ); }; const renderRow = (rowProps: DataRowProps<TItem, TId>, dsState: DataSourceState) => { return props.renderRow ? ( props.renderRow(rowProps, dsState) ) : ( <DataPickerRow { ...rowProps } key={ rowProps.rowKey } size={ getRowSize() as DataPickerRowProps<any, any>['size'] } padding={ (props.editMode === 'modal' ? settings.sizes.pickerInput.body.modal.padding : settings.sizes.pickerInput.body.dropdown.padding) as DataPickerRowProps<any, any>['padding'] } renderItem={ (item, itemProps) => renderRowItem(item, itemProps, dsState) } /> ); }; const renderBody = (bodyProps: DropdownBodyProps & DataSourceListProps & Omit<PickerBodyBaseProps, 'rows'>, rows: DataRowProps<TItem, TId>[]) => { const renderedDataRows = rows.map((row) => renderRow(row, dataSourceState)); const bodyHeight = isMobile() ? document.documentElement.clientHeight : props.dropdownHeight || settings.sizes.pickerInput.body.dropdown.height; const minBodyWidth = props.minBodyWidth || settings.sizes.pickerInput.body.dropdown.width; return ( <PickerBodyMobileView title={ props.entityName } onClose={ () => toggleBodyOpening(false) } cx={ [props.bodyCx] } onKeyDown={ bodyProps.onKeyDown } width={ bodyProps.togglerWidth > minBodyWidth ? bodyProps.togglerWidth : minBodyWidth } focusLock={ getSearchPosition() === 'body' } > <DataPickerBody { ...bodyProps } rows={ renderedDataRows } maxHeight={ bodyHeight } searchSize={ props.size } editMode="dropdown" selectionMode={ props.selectionMode } /> { renderFooter() } </PickerBodyMobileView> ); }; const rows = getRows(); const renderItem = props.renderTag ? props.renderTag : null; return ( <Dropdown renderTarget={ (dropdownProps) => { const targetProps = getTogglerProps(); return renderTarget({ ...dropdownProps, ...targetProps, renderItem }); } } renderBody={ (bodyProps) => renderBody({ ...bodyProps, ...getPickerBodyProps(rows), ...getListProps() }, rows) } value={ shouldShowBody() } onValueChange={ !props.isDisabled && toggleBodyOpening } placement={ props.dropdownPlacement } modifiers={ popperModifiers } closeBodyOnTogglerHidden={ !isMobile() } portalTarget={ props.portalTarget } ref={ dropdownRef } /> ); } export const PickerInput = React.forwardRef(PickerInputComponent) as <TItem, TId>( props: PickerInputProps<TItem, TId>, ref: React.ForwardedRef<PickerInputElement> ) => JSX.Element;