// Copyright 2019-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; import classNames from 'classnames'; import { get, noop } from 'lodash'; import { Manager, Popper, Reference } from 'react-popper'; import { createPortal } from 'react-dom'; import { Emoji } from './Emoji'; import type { Props as EmojiPickerProps } from './EmojiPicker'; import { EmojiPicker } from './EmojiPicker'; import type { LocalizerType } from '../../types/Util'; import * as KeyboardLayout from '../../services/keyboardLayout'; export type OwnProps = { readonly className?: string; readonly closeOnPick?: boolean; readonly emoji?: string; readonly i18n: LocalizerType; readonly onClose?: () => unknown; }; export type Props = OwnProps & Pick< EmojiPickerProps, 'doSend' | 'onPickEmoji' | 'onSetSkinTone' | 'recentEmojis' | 'skinTone' >; export const EmojiButton = React.memo( ({ className, closeOnPick, emoji, i18n, doSend, onClose, onPickEmoji, skinTone, onSetSkinTone, recentEmojis, }: Props) => { const [open, setOpen] = React.useState(false); const [popperRoot, setPopperRoot] = React.useState( null ); const handleClickButton = React.useCallback(() => { if (popperRoot) { setOpen(false); } else { setOpen(true); } }, [popperRoot, setOpen]); const handleClose = React.useCallback(() => { setOpen(false); if (onClose) { onClose(); } }, [setOpen, onClose]); // Create popper root and handle outside clicks React.useEffect(() => { if (open) { const root = document.createElement('div'); setPopperRoot(root); document.body.appendChild(root); const handleOutsideClick = (event: MouseEvent) => { if (!root.contains(event.target as Node)) { handleClose(); event.stopPropagation(); event.preventDefault(); } }; document.addEventListener('click', handleOutsideClick); return () => { document.body.removeChild(root); document.removeEventListener('click', handleOutsideClick); setPopperRoot(null); }; } return noop; }, [open, setOpen, setPopperRoot, handleClose]); // Install keyboard shortcut to open emoji picker React.useEffect(() => { const handleKeydown = (event: KeyboardEvent) => { const { ctrlKey, metaKey, shiftKey } = event; const commandKey = get(window, 'platform') === 'darwin' && metaKey; const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey; const commandOrCtrl = commandKey || controlKey; const key = KeyboardLayout.lookup(event); // We don't want to open up if the conversation has any panels open const panels = document.querySelectorAll('.conversation .panel'); if (panels && panels.length > 1) { return; } if (commandOrCtrl && shiftKey && (key === 'j' || key === 'J')) { event.stopPropagation(); event.preventDefault(); setOpen(!open); } }; document.addEventListener('keydown', handleKeydown); return () => { document.removeEventListener('keydown', handleKeydown); }; }, [open, setOpen]); return ( {({ ref }) => ( )} {open && popperRoot ? createPortal( {({ ref, style }) => ( { onPickEmoji(ev); if (closeOnPick) { handleClose(); } }} doSend={doSend} onClose={handleClose} skinTone={skinTone} onSetSkinTone={onSetSkinTone} recentEmojis={recentEmojis} /> )} , popperRoot ) : null} ); } );