import React, { useCallback, useRef, useEffect, memo, useMemo, } from '../../lib/teact/teact'; import { requestMutation } from '../../lib/fasterdom/fasterdom'; import type { FC } from '../../lib/teact/teact'; import { isUserId } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; import { MEMO_EMPTY_ARRAY } from '../../util/memo'; import useInfiniteScroll from '../../hooks/useInfiniteScroll'; import useLang from '../../hooks/useLang'; import InfiniteScroll from '../ui/InfiniteScroll'; import Checkbox from '../ui/Checkbox'; import InputText from '../ui/InputText'; import ListItem from '../ui/ListItem'; import PrivateChatInfo from './PrivateChatInfo'; import GroupChatInfo from './GroupChatInfo'; import PickerSelectedItem from './PickerSelectedItem'; import Loading from '../ui/Loading'; import './Picker.scss'; type OwnProps = { itemIds: string[]; selectedIds: string[]; filterValue?: string; filterPlaceholder?: string; notFoundText?: string; searchInputId?: string; isLoading?: boolean; noScrollRestore?: boolean; isSearchable?: boolean; isRoundCheckbox?: boolean; lockedIds?: string[]; onSelectedIdsChange?: (ids: string[]) => void; onFilterChange?: (value: string) => void; onDisabledClick?: (id: string) => void; onLoadMore?: () => void; }; // Focus slows down animation, also it breaks transition layout in Chrome const FOCUS_DELAY_MS = 500; const MAX_FULL_ITEMS = 10; const ALWAYS_FULL_ITEMS_COUNT = 5; const Picker: FC = ({ itemIds, selectedIds, filterValue, filterPlaceholder, notFoundText, searchInputId, isLoading, noScrollRestore, isSearchable, isRoundCheckbox, lockedIds, onSelectedIdsChange, onFilterChange, onDisabledClick, onLoadMore, }) => { // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); const shouldMinimize = selectedIds.length > MAX_FULL_ITEMS; useEffect(() => { if (!isSearchable) return; setTimeout(() => { requestMutation(() => { inputRef.current!.focus(); }); }, FOCUS_DELAY_MS); }, [isSearchable]); const [lockedSelectedIds, unlockedSelectedIds] = useMemo(() => { if (!lockedIds?.length) return [MEMO_EMPTY_ARRAY, selectedIds]; const unlockedIds = selectedIds.filter((id) => !lockedIds.includes(id)); return [lockedIds, unlockedIds]; }, [selectedIds, lockedIds]); const lockedIdsSet = useMemo(() => new Set(lockedIds), [lockedIds]); const sortedItemIds = useMemo(() => { return itemIds.sort((a, b) => { const aIsLocked = lockedIdsSet.has(a); const bIsLocked = lockedIdsSet.has(b); if (aIsLocked && !bIsLocked) { return -1; } if (!aIsLocked && bIsLocked) { return 1; } return 0; }); }, [itemIds, lockedIdsSet]); const handleItemClick = useCallback((id: string) => { if (lockedIdsSet.has(id)) { onDisabledClick?.(id); return; } const newSelectedIds = selectedIds.slice(); if (newSelectedIds.includes(id)) { newSelectedIds.splice(newSelectedIds.indexOf(id), 1); } else { newSelectedIds.push(id); } onSelectedIdsChange?.(newSelectedIds); onFilterChange?.(''); }, [lockedIdsSet, selectedIds, onSelectedIdsChange, onFilterChange, onDisabledClick]); const handleFilterChange = useCallback((e: React.ChangeEvent) => { const { value } = e.currentTarget; onFilterChange?.(value); }, [onFilterChange]); const [viewportIds, getMore] = useInfiniteScroll(onLoadMore, sortedItemIds, Boolean(filterValue)); const lang = useLang(); return (
{isSearchable && (
{lockedSelectedIds.map((id, i) => ( ))} {unlockedSelectedIds.map((id, i) => ( ))}
)} {viewportIds?.length ? ( {viewportIds.map((id) => { const renderCheckbox = () => { return ( ); }; return ( handleItemClick(id)} ripple > {!isRoundCheckbox ? renderCheckbox() : undefined} {isUserId(id) ? ( ) : ( )} {isRoundCheckbox ? renderCheckbox() : undefined} ); })} ) : !isLoading && viewportIds && !viewportIds.length ? (

{notFoundText || 'Sorry, nothing found.'}

) : ( )}
); }; export default memo(Picker);