import { forwardRef, memo, useCallback, useMemo, useRef, useState } from 'react'; import type { KeyboardEventHandler, MouseEventHandler } from 'react'; import { SortableContainer as sortableContainer } from 'react-sortable-hoc'; import clsx from 'clsx'; import debounce from 'lodash/debounce'; import get from 'lodash/get'; import { Button } from '../button'; import { Grid } from '../@layout/grid'; import { SearchField } from '../@forms/search-field'; import type { SearchFieldProps } from '../@forms/search-field'; import { isElementOverflowing, useResizeObserverEffect } from '../../utils'; import ListItems from './ListItems'; import { filterDropdownMenuItemsRecursive, highlightDropdownMenuItem, processDropdownMenuItemsRecursive } from './helpers'; import type { DropdownMenuItem, MenuContentProps, SortableProps } from './types'; import createClasses from './styles'; const spaceKeyStopPropagation: KeyboardEventHandler = e => e.code === 'Space' && e.stopPropagation(); const stopPropagation: MouseEventHandler = e => e.stopPropagation(); const SortableContainer = sortableContainer(({ children }: SortableProps) => children); // eslint-disable-next-line const MenuContent = forwardRef((props, ref) => { const { search, handleTextChange, searchDebounceTime = 0, searchHighlight = false, searchPlaceholder, searchBy = ['name'], classes, sortable, handleClick, listItems, modifyMenuItem, InsertBeforeList, InsertAfterList, multiple, deletable, handleDelete, onSortEnd, showSelectedIcon, bottomButtonText, bottomButtonClickHandler, bottomButtonProps, showSort = true, ariaLabel, noOptionsText, noResultsText, loadingText, clearSelectItems, variableSizeList, variableSizeListProps, menuListHeader, menuListFooter, loading, menuListLoader, infinityScrollProps } = props; const styles = createClasses({ search, showSort }); if (infinityScrollProps && search) console.warn( `The "infinityScrollProps" and "search" are incompatible. May occur unexpected behaviour` ); if (multiple && showSelectedIcon) console.warn( `The "multiple" and "showSelectedIcon" props were provided. Ignoring conflicting "showSelectedIcon"` ); const [text, setText] = useState(''); const handleSearchTextChange: SearchFieldProps['onChange'] = useCallback( e => { setText(e.target.value); if (handleTextChange) { typeof searchDebounceTime !== 'undefined' && searchDebounceTime >= 0 ? debounce(handleTextChange, searchDebounceTime)(e) : handleTextChange(e); } }, [handleTextChange, searchDebounceTime] ); const handleClear: SearchFieldProps['handleClear'] = useCallback(() => { setText(''); if (clearSelectItems) { clearSelectItems?.(); } else { handleTextChange?.(''); } }, [handleTextChange, clearSelectItems]); const filteredListItems = useMemo(() => { if (!text || !searchBy?.length) { return listItems; } const filteredListItems = filterDropdownMenuItemsRecursive(listItems, item => searchBy.some(field => (field in item ? item[field as keyof DropdownMenuItem] : get(item, field)) ?.toString() .toLowerCase() .includes(text.toLowerCase()) ) ); return searchHighlight ? processDropdownMenuItemsRecursive(filteredListItems, highlightDropdownMenuItem([text])) : filteredListItems; }, [listItems, searchBy, searchHighlight, text]); const listItemsRef = useRef(null); const [isOverflowing, setIsOverflowing] = useState(false); const debouncedOverflowCheck = useMemo( () => debounce(() => { const listElement = variableSizeList ? (document.querySelector('.variable-size-list-wrapper') as HTMLElement) : listItemsRef?.current; if (listElement) setIsOverflowing(isElementOverflowing(listElement)); }), [variableSizeList] ); useResizeObserverEffect(debouncedOverflowCheck, listItemsRef?.current); if (loading) { return ( {menuListLoader || 'Loading...'} ); } return ( {search && ( )} {menuListHeader && (
{menuListHeader}
)} {sortable ? (
((variableSizeList && document.querySelector('.variable-size-list-wrapper')) || document.documentElement) as HTMLElement } useDragHandle onSortEnd={onSortEnd} >
) : (
)} {menuListFooter && (
{menuListFooter}
)} {(bottomButtonText || bottomButtonProps) && ( )}
); }); export default memo(MenuContent);