// Copyright 2020-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; import classNames from 'classnames'; import * as log from '../../logging/log'; import { Emoji } from '../emoji/Emoji'; import { convertShortName } from '../emoji/lib'; import { Props as EmojiPickerProps } from '../emoji/EmojiPicker'; import { missingCaseError } from '../../util/missingCaseError'; import { useRestoreFocus } from '../../util/hooks/useRestoreFocus'; import { LocalizerType } from '../../types/Util'; export enum ReactionPickerSelectionStyle { Picker, Menu, } export type RenderEmojiPickerProps = Pick & Pick & { ref: React.Ref; }; export type OwnProps = { hasMoreButton?: boolean; i18n: LocalizerType; selected?: string; selectionStyle: ReactionPickerSelectionStyle; onClose?: () => unknown; onPick: (emoji: string) => unknown; openCustomizePreferredReactionsModal?: () => unknown; preferredReactionEmoji: Array; renderEmojiPicker: (props: RenderEmojiPickerProps) => React.ReactElement; skinTone: number; }; export type Props = OwnProps & Pick, 'style'>; const EmojiButton = React.forwardRef< HTMLButtonElement, { emoji: string; onSelect: () => unknown; selected: boolean; title?: string; } >(({ emoji, onSelect, selected, title }, ref) => ( )); export const ReactionPicker = React.forwardRef( ( { hasMoreButton = true, i18n, onClose, onPick, openCustomizePreferredReactionsModal, preferredReactionEmoji, renderEmojiPicker, selected, selectionStyle, skinTone, style, }, ref ) => { const [pickingOther, setPickingOther] = React.useState(false); // Handle escape key React.useEffect(() => { const handler = (e: KeyboardEvent) => { if (onClose && e.key === 'Escape') { onClose(); } }; document.addEventListener('keydown', handler); return () => { document.removeEventListener('keydown', handler); }; }, [onClose]); // Handle EmojiPicker::onPickEmoji const onPickEmoji: EmojiPickerProps['onPickEmoji'] = React.useCallback( ({ shortName, skinTone: pickedSkinTone }) => { onPick(convertShortName(shortName, pickedSkinTone)); }, [onPick] ); // Focus first button and restore focus on unmount const [focusRef] = useRestoreFocus(); if (pickingOther) { return renderEmojiPicker({ onClickSettings: openCustomizePreferredReactionsModal, onClose, onPickEmoji, ref, style, }); } const emojis = preferredReactionEmoji.map(shortName => convertShortName(shortName, skinTone) ); const otherSelected = selected && !emojis.includes(selected); let moreButton: React.ReactNode; if (!hasMoreButton) { moreButton = undefined; } else if (otherSelected) { moreButton = ( { onPick(selected); }} selected title={i18n('Reactions--remove')} /> ); } else { moreButton = ( ); } let selectionStyleClassName: string; switch (selectionStyle) { case ReactionPickerSelectionStyle.Picker: selectionStyleClassName = 'module-ReactionPicker--picker-style'; break; case ReactionPickerSelectionStyle.Menu: selectionStyleClassName = 'module-ReactionPicker--menu-style'; break; default: log.error(missingCaseError(selectionStyle)); selectionStyleClassName = 'module-ReactionPicker--picker-style'; break; } return (
{emojis.map((emoji, index) => { const maybeFocusRef = index === 0 ? focusRef : undefined; return ( { onPick(emoji); }} ref={maybeFocusRef} selected={emoji === selected} /> ); })} {moreButton}
); } );