UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

90 lines (89 loc) 4.47 kB
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import clsx from 'clsx'; import { useComponentContext } from '../../context/ComponentContext'; import { Item } from './Item'; import { escapeRegExp } from '../Message/renderText'; export const List = ({ className, component, currentTrigger, dropdownScroll, getSelectedItem, getTextToReplace, itemClassName, itemStyle, onSelect, selectionEnd, style, SuggestionItem: PropSuggestionItem, value: propValue, values, }) => { const { AutocompleteSuggestionItem } = useComponentContext('SuggestionList'); const SuggestionItem = PropSuggestionItem || AutocompleteSuggestionItem || Item; const [selectedItemIndex, setSelectedItemIndex] = useState(undefined); const itemsRef = []; const isSelected = (item) => selectedItemIndex === values.findIndex((value) => getId(value) === getId(item)); const getId = (item) => { const textToReplace = getTextToReplace(item); if (textToReplace.key) { return textToReplace.key; } if (typeof item === 'string' || !item.key) { return textToReplace.text; } return item.key; }; const findItemIndex = useCallback((item) => values.findIndex((value) => value.id ? value.id === item.id : value.name === item.name), [values]); const modifyText = (value) => { if (!value) return; onSelect(getTextToReplace(value)); if (getSelectedItem) getSelectedItem(value); }; const handleClick = useCallback((e, item) => { e?.preventDefault(); const index = findItemIndex(item); modifyText(values[index]); }, // eslint-disable-next-line react-hooks/exhaustive-deps [modifyText, findItemIndex]); const selectItem = useCallback((item) => { const index = findItemIndex(item); setSelectedItemIndex(index); }, [findItemIndex]); const handleKeyDown = useCallback((event) => { if (event.key === 'ArrowUp') { setSelectedItemIndex((prevSelected) => { if (prevSelected === undefined) return 0; const newIndex = prevSelected === 0 ? values.length - 1 : prevSelected - 1; dropdownScroll(itemsRef[newIndex]); return newIndex; }); } if (event.key === 'ArrowDown') { setSelectedItemIndex((prevSelected) => { if (prevSelected === undefined) return 0; const newIndex = prevSelected === values.length - 1 ? 0 : prevSelected + 1; dropdownScroll(itemsRef[newIndex]); return newIndex; }); } if ((event.key === 'Enter' || event.key === 'Tab') && selectedItemIndex !== undefined) { handleClick(event, values[selectedItemIndex]); } return null; }, // eslint-disable-next-line react-hooks/exhaustive-deps [selectedItemIndex, values]); useEffect(() => { document.addEventListener('keydown', handleKeyDown, false); return () => document.removeEventListener('keydown', handleKeyDown); }, [handleKeyDown]); useEffect(() => { if (values?.length) selectItem(values[0]); }, [values]); // eslint-disable-line const restructureItem = useCallback((item) => { const matched = item.name || item.id; const textBeforeCursor = propValue.slice(0, selectionEnd); const triggerIndex = textBeforeCursor.lastIndexOf(currentTrigger); const editedPropValue = escapeRegExp(textBeforeCursor.slice(triggerIndex + 1)); const parts = matched.split(new RegExp(`(${editedPropValue})`, 'gi')); const itemNameParts = { match: editedPropValue, parts }; return { ...item, itemNameParts }; }, [propValue, selectionEnd, currentTrigger]); const restructuredValues = useMemo(() => values.map(restructureItem), [values, restructureItem]); return (React.createElement("ul", { className: clsx('str-chat__suggestion-list', className), style: style }, restructuredValues.map((item, i) => (React.createElement(SuggestionItem, { className: itemClassName, component: component, item: item, key: getId(item), onClickHandler: handleClick, onSelectHandler: selectItem, ref: (ref) => { itemsRef[i] = ref; }, selected: isSelected(item), style: itemStyle, value: propValue }))))); };