import classNames from 'classnames'; import { supportsPassiveEvents } from 'detect-passive-events'; import React, { useEffect, useState, useLayoutEffect } from 'react'; import { createPortal } from 'react-dom'; import { defineMessages, useIntl } from 'react-intl'; import { usePopper } from 'react-popper'; import { IconButton } from 'soapbox/components/ui'; import { useSettings } from 'soapbox/hooks'; import { buildCustomEmojis } from '../../emoji'; import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'; // import { Picker as EmojiPicker } from '../../emoji/emoji_picker'; import type { List } from 'immutable'; import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji'; import type { EmojiPick } from 'emoji-mart'; let EmojiPicker: any; // load asynchronously const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search…' }, emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emoji\'s found.' }, custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' }, recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' }, search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' }, people: { id: 'emoji_button.people', defaultMessage: 'People' }, nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' }, food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' }, activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' }, travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' }, objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' }, symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' }, flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }, }); interface IEmojiPickerDropdown { custom_emojis: List, frequentlyUsedEmojis: string[], intl: any, onPickEmoji: (emoji: Emoji) => void, onSkinTone: () => void, condensed: boolean, } // Fixes render bug where popover has a delayed position update const RenderAfter = ({ children, update }: any) => { const [nextTick, setNextTick] = useState(false); useEffect(() => { setTimeout(() => { setNextTick(true); }, 0); }, []); useLayoutEffect(() => { if (nextTick) { update(); } }, [nextTick, update]); return nextTick ? children : null; }; const listenerOptions = supportsPassiveEvents ? { passive: true } : false; const EmojiPickerDropdown: React.FC = ({ custom_emojis, frequentlyUsedEmojis, onPickEmoji, onSkinTone, condensed }) => { const intl = useIntl(); const settings = useSettings(); const title = intl.formatMessage(messages.emoji); const userTheme = settings.get('themeMode'); const theme = (userTheme === 'dark' || userTheme === 'light') ? userTheme : 'auto'; const [popperElement, setPopperElement] = useState(null); const [popperReference, setPopperReference] = useState(null); const [containerElement, setContainerElement] = useState(null); const [visible, setVisible] = useState(false); const [loading, setLoading] = useState(false); const { styles, attributes, update } = usePopper(popperReference, popperElement, { placement: condensed ? 'bottom-start' : 'top-start', }); const handleToggle = () => { setVisible(!visible); }; const handleDocClick = (e: any) => { if (!containerElement?.contains(e.target) && !popperElement?.contains(e.target)) { setVisible(false); } }; const handlePick = (emoji: EmojiPick) => { setVisible(false); if (emoji.native) { onPickEmoji({ id: emoji.id, colons: emoji.shortcodes, custom: false, native: emoji.native, unified: emoji.unified, } as NativeEmoji); } else { onPickEmoji({ id: emoji.id, colons: emoji.shortcodes, custom: true, imageUrl: emoji.src, } as CustomEmoji); } }; const getI18n = () => { return { search: intl.formatMessage(messages.emoji_search), notfound: intl.formatMessage(messages.emoji_not_found), categories: { search: intl.formatMessage(messages.search_results), recent: intl.formatMessage(messages.recent), people: intl.formatMessage(messages.people), nature: intl.formatMessage(messages.nature), foods: intl.formatMessage(messages.food), activity: intl.formatMessage(messages.activity), places: intl.formatMessage(messages.travel), objects: intl.formatMessage(messages.objects), symbols: intl.formatMessage(messages.symbols), flags: intl.formatMessage(messages.flags), custom: intl.formatMessage(messages.custom), }, }; }; useEffect(() => { document.addEventListener('click', handleDocClick, false); document.addEventListener('touchend', handleDocClick, listenerOptions); return function cleanup() { document.removeEventListener('click', handleDocClick); document.removeEventListener('touchend', handleDocClick); }; }); useEffect(() => { if (!EmojiPicker) { setLoading(true); EmojiPickerAsync().then(EmojiMart => { EmojiPicker = EmojiMart.Picker; setLoading(false); }).catch(() => { setLoading(false); }); } }, [visible]); let Popup; if (loading) { Popup = () =>
; } else { Popup = () => ( ); } return (
{createPortal(
{visible && ( )}
, document.body, )}
); }; export default EmojiPickerDropdown;