import * as React from "react"; import { useRef, useState } from "react"; import { Typography, Box, TextField, ClickAwayListener, Fade, InputAdornment, styled, IconButton, Popper } from "@mui/material"; import { Close } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; import { splitNoEmpty } from "../app/utils"; import { rawEmojis } from "../app/emojis"; // Create emoji list by category and create a search base (string with all search words) // // This also filters emojis that are not supported by Desktop Chrome. // This is a hack, but on Ubuntu 18.04, with Chrome 99, only Emoji <= 11 are supported. const emojisByCategory = {}; const isDesktopChrome = /Chrome/.test(navigator.userAgent) && !/Mobile/.test(navigator.userAgent); const maxSupportedVersionForDesktopChrome = 11; rawEmojis.forEach((emoji) => { if (!emojisByCategory[emoji.category]) { emojisByCategory[emoji.category] = []; } try { const unicodeVersion = parseFloat(emoji.unicode_version); const supportedEmoji = unicodeVersion <= maxSupportedVersionForDesktopChrome || !isDesktopChrome; if (supportedEmoji) { const searchBase = `${emoji.description.toLowerCase()} ${emoji.aliases.join(" ")} ${emoji.tags.join(" ")}`; const emojiWithSearchBase = { ...emoji, searchBase }; emojisByCategory[emoji.category].push(emojiWithSearchBase); } } catch (e) { // Nothing. Ignore. } }); const EmojiPicker = (props) => { const { t } = useTranslation(); const open = Boolean(props.anchorEl); const [search, setSearch] = useState(""); const searchRef = useRef(null); const searchFields = splitNoEmpty(search.toLowerCase(), " "); const handleSearchClear = () => { setSearch(""); searchRef.current?.focus(); }; return ( <Popper open={open} anchorEl={props.anchorEl} placement="bottom-start" sx={{ zIndex: 10005 }} transition> {({ TransitionProps }) => ( <ClickAwayListener onClickAway={props.onClose}> <Fade {...TransitionProps} timeout={350}> <Box sx={{ boxShadow: 3, padding: 2, paddingRight: 0, paddingBottom: 1, width: "380px", maxHeight: "300px", backgroundColor: "background.paper", overflowY: "scroll", }} > <TextField inputRef={searchRef} margin="dense" size="small" placeholder={t("emoji_picker_search_placeholder")} value={search} onChange={(ev) => setSearch(ev.target.value)} type="text" variant="standard" fullWidth sx={{ marginTop: 0, marginBottom: "12px", paddingRight: 2 }} inputProps={{ role: "searchbox", "aria-label": t("emoji_picker_search_placeholder"), }} InputProps={{ endAdornment: ( <InputAdornment position="end" sx={{ display: search ? "" : "none" }}> <IconButton size="small" onClick={handleSearchClear} edge="end" aria-label={t("emoji_picker_search_clear")}> <Close /> </IconButton> </InputAdornment> ), }} /> <Box sx={{ display: "flex", flexWrap: "wrap", paddingRight: 0, marginTop: 1, }} > {Object.keys(emojisByCategory).map((category) => ( <Category key={category} title={category} emojis={emojisByCategory[category]} search={searchFields} onPick={props.onEmojiPick} /> ))} </Box> </Box> </Fade> </ClickAwayListener> )} </Popper> ); }; const Category = (props) => { const showTitle = props.search.length === 0; return ( <> {showTitle && ( <Typography variant="body1" sx={{ width: "100%", marginBottom: 1 }}> {props.title} </Typography> )} {props.emojis.map((emoji) => ( <Emoji key={emoji.aliases[0]} emoji={emoji} search={props.search} onClick={() => props.onPick(emoji.aliases[0])} /> ))} </> ); }; const emojiMatches = (emoji, words) => words.length === 0 || words.some((word) => emoji.searchBase.includes(word)); const Emoji = (props) => { const { emoji } = props; const matches = emojiMatches(emoji, props.search); const title = `${emoji.description} (${emoji.aliases[0]})`; return ( <EmojiDiv onClick={props.onClick} title={title} aria-label={title} style={{ display: matches ? "" : "none" }}> {props.emoji.emoji} </EmojiDiv> ); }; const EmojiDiv = styled("div")({ fontSize: "30px", width: "30px", height: "30px", marginTop: "8px", marginBottom: "8px", marginRight: "8px", lineHeight: "30px", cursor: "pointer", opacity: 0.85, "&:hover": { opacity: 1, }, }); export default EmojiPicker;