stream-chat-react
Version:
React components to create chat conversations or livestream style chat
90 lines (89 loc) • 4.47 kB
JavaScript
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 })))));
};