Alexander Zinchuk f638ae3717 Revert "Revert "Composer: Add Emoji Picker (#1061)""
This reverts commit b1a2e27576b3bbcc5d319a506540042462e8b105.
2021-05-09 04:03:12 +03:00

164 lines
4.9 KiB
TypeScript

import React, {
FC, useCallback, useEffect, useState, useRef, memo,
} from '../../../lib/teact/teact';
import usePrevious from '../../../hooks/usePrevious';
import { ApiChatMember, ApiUser } from '../../../api/types';
import useShowTransition from '../../../hooks/useShowTransition';
import buildClassName from '../../../util/buildClassName';
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
import findInViewport from '../../../util/findInViewport';
import isFullyVisible from '../../../util/isFullyVisible';
import fastSmoothScroll from '../../../util/fastSmoothScroll';
import cycleRestrict from '../../../util/cycleRestrict';
import ListItem from '../../ui/ListItem';
import PrivateChatInfo from '../../common/PrivateChatInfo';
import './MentionTooltip.scss';
const VIEWPORT_MARGIN = 8;
const SCROLL_MARGIN = 10;
function setItemVisible(index: number, containerRef: Record<string, any>) {
const container = containerRef.current!;
if (!container || index < 0) {
return;
}
const { visibleIndexes, allElements } = findInViewport(
container,
'.chat-item-clickable',
VIEWPORT_MARGIN,
true,
true,
);
if (!allElements.length || !allElements[index]) {
return;
}
const first = visibleIndexes[0];
if (!visibleIndexes.includes(index)
|| (index === first && !isFullyVisible(container, allElements[first]))) {
const position = index > visibleIndexes[visibleIndexes.length - 1] ? 'start' : 'end';
fastSmoothScroll(container, allElements[index], position, SCROLL_MARGIN);
}
}
export type OwnProps = {
isOpen: boolean;
filter: string;
onClose: () => void;
onInsertUserName: (user: ApiUser, forceFocus?: boolean) => void;
filteredChatMembers?: ApiChatMember[];
usersById?: Record<number, ApiUser>;
};
const MentionTooltip: FC<OwnProps> = ({
isOpen,
filter,
onClose,
onInsertUserName,
usersById,
filteredChatMembers,
}) => {
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null);
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
const getSelectedIndex = useCallback((newIndex: number) => {
if (!filteredChatMembers) {
return -1;
}
const membersCount = filteredChatMembers!.length;
return cycleRestrict(membersCount, newIndex);
}, [filteredChatMembers]);
const [selectedMentionIndex, setSelectedMentionIndex] = useState(-1);
const handleArrowKey = useCallback((value: number, e: KeyboardEvent) => {
e.preventDefault();
setSelectedMentionIndex((index) => (getSelectedIndex(index + value)));
}, [setSelectedMentionIndex, getSelectedIndex]);
const handleUserSelect = useCallback((userId: number, forceFocus = false) => {
const user = usersById && usersById[userId];
if (!user) {
return;
}
onInsertUserName(user, forceFocus);
}, [usersById, onInsertUserName]);
const handleSelectMention = useCallback((e: KeyboardEvent) => {
if (filteredChatMembers && filteredChatMembers.length && selectedMentionIndex > -1) {
const member = filteredChatMembers[selectedMentionIndex];
if (member) {
e.preventDefault();
handleUserSelect(member.userId, true);
}
}
}, [filteredChatMembers, selectedMentionIndex, handleUserSelect]);
useEffect(() => (isOpen ? captureKeyboardListeners({
onEsc: onClose,
onUp: (e: KeyboardEvent) => handleArrowKey(-1, e),
onDown: (e: KeyboardEvent) => handleArrowKey(1, e),
onEnter: handleSelectMention,
onTab: handleSelectMention,
}) : undefined), [isOpen, onClose, handleArrowKey, handleSelectMention]);
useEffect(() => {
if (filteredChatMembers && !filteredChatMembers.length) {
onClose();
}
}, [filteredChatMembers, onClose]);
useEffect(() => {
setSelectedMentionIndex(0);
}, [filter]);
useEffect(() => {
setItemVisible(selectedMentionIndex, containerRef);
}, [selectedMentionIndex]);
const prevChatMembers = usePrevious(
filteredChatMembers && filteredChatMembers.length
? filteredChatMembers
: undefined,
shouldRender,
);
const renderedChatMembers = filteredChatMembers && !filteredChatMembers.length
? prevChatMembers
: filteredChatMembers;
if (!shouldRender || (renderedChatMembers && !renderedChatMembers.length)) {
return undefined;
}
const className = buildClassName(
'MentionTooltip composer-tooltip custom-scroll',
transitionClassNames,
);
return (
<div className={className} ref={containerRef}>
{renderedChatMembers && renderedChatMembers.map(({ userId }, index) => (
<ListItem
key={userId}
className="chat-item-clickable scroll-item"
onClick={() => handleUserSelect(userId)}
focus={selectedMentionIndex === index}
>
<PrivateChatInfo
userId={userId}
avatarSize="small"
withUsername
/>
</ListItem>
))}
</div>
);
};
export default memo(MentionTooltip);