import { ComponentType, forwardRef, memo, PropsWithoutRef, ReactElement, ReactNode, RefAttributes, useCallback, useMemo, } from 'react'; import { FlatList, View, type ListRenderItemInfo, type ViewStyle } from 'react-native'; import { typedMemo } from '../../hocs'; import withKeyboardAccessibility, { useCurrentIndexStoreValue, } from '../../hocs/withKeyboardAccessibility'; import { useControlledValue, UseSearchableProps } from '../../hooks'; import { useSearchInputProps } from '../../hooks/useSearchable'; import { TextInput } from '../TextInput'; import { TouchableRipple } from '../TouchableRipple'; import { optionFlatListStyles } from './utils'; type DefaultItemT = { id: string | number; selectable?: boolean; [key: string]: any; }; // To make a correct type inference export type IOptionFlatList = ( props: PropsWithoutRef & RefAttributes>>, ) => ReactElement; export type OptionFlatListRenderItemInfo = ListRenderItemInfo & { focused: boolean; onPress: () => void; }; export type Props = UseSearchableProps & Omit, 'data' | 'renderItem'> & { records: TItem[]; containerStyle?: ViewStyle; searchInputContainerStyle?: ViewStyle; /* * when set to true, the items will be selectable. Each item will be wrapped around by TouchableRipple component. Whenever they're pressed, onSelectedItem function will trigger * */ selectable?: boolean; /* * when set to true, multiple items can be selected and selectedItem will be an array. onSelectItem's argument will be an array. * */ multiple?: boolean; /* * Expects an array of TItem in multiple mode. If the item already exists in the array, it will be removed. * */ selection?: TItem | TItem[]; /* * passes the current selectedItem. Will be an array in multiple mode. Item is the specific item which is pressed * */ onSelectionChange?: (selection: TItem | TItem[], item: TItem) => void; customFlatList?: ComponentType>; renderItem: ( info: ListRenderItemInfo & { onPress: () => void; focused: boolean; }, ) => ReactNode; enableKeyboardNavigation?: boolean; onCancel?: () => void; testID?: string; }; const OptionFlatList = ( { query, onQueryChange, searchInputProps: dirtySearchInputProps, searchable, containerStyle = {}, searchInputContainerStyle = {}, records, multiple = false, selectable, selection: selectionProp, onSelectionChange: onSelectionChangeProp, renderItem: renderItemProp, customFlatList: CustomFlatList, testID, ...rest }: Props, ref: any, ) => { const FlatListComponent = CustomFlatList || FlatList; const [selection, onSelectionChange] = useControlledValue({ value: selectionProp, onChange: onSelectionChangeProp, }); // const componentStyles = useComponentStyles('OptionFlatList', [ // { container: containerStyle, searchInputContainer: searchInputContainerStyle }, // ]); const { containerStyles, searchInputContainerStyles } = useMemo(() => { const { container, searchInputContainer } = optionFlatListStyles; return { containerStyles: [container, containerStyle], searchInputContainerStyles: [searchInputContainer, searchInputContainerStyle], }; }, [containerStyle, searchInputContainerStyle]); const searchInputProps = useSearchInputProps(dirtySearchInputProps); const onPressItem = useCallback( (item: TItem) => { const isSelected = Array.isArray(selection) ? selection.find(sItem => sItem?.id === item.id) : selection?.id === item.id; onSelectionChange( // if multiple we push the item into an array and if it's already exists we filter them multiple ? Array.isArray(selection) ? isSelected ? selection.filter(sItem => sItem.id !== item.id) : [...selection, item] : [item] : item, item, ); }, [multiple, onSelectionChange, selection], ); const renderItem = useCallback( (info: ListRenderItemInfo) => { return ( ); }, [onPressItem, renderItemProp, selectable, testID], ); const keyExtractor = useCallback((item: TItem) => `${item.id}`, []); return ( <> {searchable && ( )} ); }; type OptionListItemProps = Pick< Props, 'renderItem' > & { info: ListRenderItemInfo; onPressItem?: (item: TItem) => void; selectable?: boolean; testID?: string; }; const OptionListItem = typedMemo( ({ info, renderItem, selectable, onPressItem, testID, }: OptionListItemProps) => { const focused = useCurrentIndexStoreValue(state => { return state.currentIndex === info.index; }); const onPress = useCallback(() => { onPressItem?.(info.item); }, [info.item, onPressItem]); const renderItemInfo = useMemo( () => ({ ...info, focused, onPress, }), [focused, info, onPress], ); if (selectable && info.item?.selectable !== false) { return ( {renderItem(renderItemInfo)} ); } return <>{renderItem(renderItemInfo)}; }, ); export default memo( withKeyboardAccessibility(forwardRef(OptionFlatList), 'records', true), ) as IOptionFlatList;