import type { ReactNode } from 'react' import React, { useMemo } from 'react' import type { BaseProps } from '@toptal/picasso-shared' import { MenuItem } from '@toptal/picasso-menu' import { Typography } from '@toptal/picasso-typography' import { NonNativeSelectOption } from '../NonNativeSelectOption' import { SelectOptions } from '../SelectOptions' import type { FocusEventType, ItemProps, Option, OptionGroups, SelectProps, Selection, } from '../SelectBase' import { flattenOptions, isOptionsType } from '../SelectBase' // TODO: Replace with a real component as soon as it's implemented // https://toptal-core.atlassian.net/browse/FX-1479 // Note: In the current implementation children are siblings for the group node. // If in the new MenuGroup children are inside the group node, that will // brake the current implementation of highlightedIndex calculations in this // component and selectedIndex in ScrollMenu component. interface MenuGroupProps extends BaseProps { group: string children: ReactNode } const MenuGroup = (props: MenuGroupProps) => { const { group, children, ...rest } = props return ( <> {group} {children} ) } export type Props = Pick< SelectProps, 'multiple' | 'noOptionsText' | 'renderOption' > & { options: Option[] | OptionGroups highlightedIndex: number | null filterOptionsValue: string getItemProps: (option: Option, index: number) => ItemProps onBlur?: FocusEventType fixedHeader?: ReactNode fixedFooter?: ReactNode selection: Selection testIds?: { noOptions?: string } } const renderOptions = ({ options, getItemProps, selection, highlightedIndex, offset = 0, renderOption, }: Pick< Props, 'getItemProps' | 'selection' | 'highlightedIndex' | 'renderOption' > & { options: Option[]; offset?: number }) => { return options.map((option, index) => { return ( {renderOption?.(option)} ) }) } const renderGroups = ({ groups, getItemProps, selection, highlightedIndex, renderOption, }: Pick< Props, 'getItemProps' | 'selection' | 'highlightedIndex' | 'renderOption' > & { groups: OptionGroups }) => { let optionsCount = 0 return Object.keys(groups).map(group => { const menuGroups = ( {renderOptions({ options: groups[group], getItemProps, selection, highlightedIndex, offset: optionsCount, renderOption, })} ) optionsCount += groups[group].length return menuGroups }) } const addOffsetToHighlightedIndex = ( groups: OptionGroups, highlightedIndex: number | null ) => { if (!highlightedIndex) { return highlightedIndex } let optionsCount = 0 const offset = Object.values(groups).findIndex(group => { optionsCount += group.length const isHighlightedOptionInGroup = highlightedIndex < optionsCount return isHighlightedOptionInGroup }) + 1 return highlightedIndex + offset } const NonNativeSelectOptions = ({ options, renderOption = () => null, highlightedIndex, getItemProps, onBlur, selection, filterOptionsValue, noOptionsText, fixedHeader, fixedFooter, testIds, }: Props) => { const flatOptions: Option[] = useMemo( () => flattenOptions(options), [options] ) if (!flatOptions.length && filterOptionsValue) { return ( {noOptionsText} ) } return ( {isOptionsType(options) ? renderOptions({ options, getItemProps, selection, highlightedIndex, renderOption, }) : renderGroups({ groups: options, getItemProps, selection, highlightedIndex, renderOption, })} ) } NonNativeSelectOptions.displayName = 'NonNativeSelectOptions' export default NonNativeSelectOptions