214 lines
7.5 KiB
JavaScript
214 lines
7.5 KiB
JavaScript
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import './EmojiBoard.scss';
|
|
|
|
import EventEmitter from 'events';
|
|
|
|
import parse from 'html-react-parser';
|
|
import twemoji from 'twemoji';
|
|
import { emojiGroups, searchEmoji } from './emoji';
|
|
|
|
import Text from '../../atoms/text/Text';
|
|
import RawIcon from '../../atoms/system-icons/RawIcon';
|
|
import IconButton from '../../atoms/button/IconButton';
|
|
import Input from '../../atoms/input/Input';
|
|
import ScrollView from '../../atoms/scroll/ScrollView';
|
|
|
|
import SearchIC from '../../../../public/res/ic/outlined/search.svg';
|
|
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
|
|
import DogIC from '../../../../public/res/ic/outlined/dog.svg';
|
|
import CupIC from '../../../../public/res/ic/outlined/cup.svg';
|
|
import BallIC from '../../../../public/res/ic/outlined/ball.svg';
|
|
import PhotoIC from '../../../../public/res/ic/outlined/photo.svg';
|
|
import BulbIC from '../../../../public/res/ic/outlined/bulb.svg';
|
|
import PeaceIC from '../../../../public/res/ic/outlined/peace.svg';
|
|
import FlagIC from '../../../../public/res/ic/outlined/flag.svg';
|
|
|
|
const viewEvent = new EventEmitter();
|
|
|
|
function EmojiGroup({ name, emojis }) {
|
|
function getEmojiBoard() {
|
|
const emojiBoard = [];
|
|
const ROW_EMOJIS_COUNT = 7;
|
|
const totalEmojis = emojis.length;
|
|
|
|
for (let r = 0; r < totalEmojis; r += ROW_EMOJIS_COUNT) {
|
|
const emojiRow = [];
|
|
for (let c = r; c < r + ROW_EMOJIS_COUNT; c += 1) {
|
|
const emojiIndex = c;
|
|
if (emojiIndex >= totalEmojis) break;
|
|
const emoji = emojis[emojiIndex];
|
|
emojiRow.push(
|
|
<span key={emojiIndex}>
|
|
{
|
|
parse(twemoji.parse(
|
|
emoji.unicode,
|
|
{
|
|
attributes: () => ({
|
|
unicode: emoji.unicode,
|
|
shortcodes: emoji.shortcodes?.toString(),
|
|
hexcode: emoji.hexcode,
|
|
}),
|
|
},
|
|
))
|
|
}
|
|
</span>,
|
|
);
|
|
}
|
|
emojiBoard.push(<div key={r} className="emoji-row">{emojiRow}</div>);
|
|
}
|
|
return emojiBoard;
|
|
}
|
|
|
|
return (
|
|
<div className="emoji-group">
|
|
<Text className="emoji-group__header" variant="b2">{name}</Text>
|
|
<div className="emoji-set">{getEmojiBoard()}</div>
|
|
</div>
|
|
);
|
|
}
|
|
EmojiGroup.propTypes = {
|
|
name: PropTypes.string.isRequired,
|
|
emojis: PropTypes.arrayOf(PropTypes.shape({
|
|
length: PropTypes.number,
|
|
unicode: PropTypes.string,
|
|
hexcode: PropTypes.string,
|
|
shortcodes: PropTypes.oneOfType([
|
|
PropTypes.string,
|
|
PropTypes.arrayOf(PropTypes.string),
|
|
]),
|
|
})).isRequired,
|
|
};
|
|
|
|
function SearchedEmoji() {
|
|
const [searchedEmojis, setSearchedEmojis] = useState([]);
|
|
|
|
function handleSearchEmoji(term) {
|
|
if (term.trim() === '') {
|
|
setSearchedEmojis([]);
|
|
return;
|
|
}
|
|
setSearchedEmojis(searchEmoji(term).map((finding) => finding.item));
|
|
}
|
|
|
|
useEffect(() => {
|
|
viewEvent.on('search-emoji', handleSearchEmoji);
|
|
return () => {
|
|
viewEvent.removeListener('search-emoji', handleSearchEmoji);
|
|
};
|
|
}, []);
|
|
|
|
return searchedEmojis.length !== 0 && <EmojiGroup key="-1" name="Search results" emojis={searchedEmojis} />;
|
|
}
|
|
|
|
function EmojiBoard({ onSelect }) {
|
|
const searchRef = useRef(null);
|
|
const scrollEmojisRef = useRef(null);
|
|
const emojiInfo = useRef(null);
|
|
|
|
function isTargetNotEmoji(target) {
|
|
return target.classList.contains('emoji') === false;
|
|
}
|
|
function getEmojiDataFromTarget(target) {
|
|
const unicode = target.getAttribute('unicode');
|
|
const hexcode = target.getAttribute('hexcode');
|
|
let shortcodes = target.getAttribute('shortcodes');
|
|
if (typeof shortcodes === 'undefined') shortcodes = undefined;
|
|
else shortcodes = shortcodes.split(',');
|
|
return { unicode, hexcode, shortcodes };
|
|
}
|
|
|
|
function selectEmoji(e) {
|
|
if (isTargetNotEmoji(e.target)) return;
|
|
|
|
const emoji = e.target;
|
|
onSelect(getEmojiDataFromTarget(emoji));
|
|
}
|
|
|
|
function setEmojiInfo(emoji) {
|
|
const infoEmoji = emojiInfo.current.firstElementChild.firstElementChild;
|
|
const infoShortcode = emojiInfo.current.lastElementChild;
|
|
|
|
const emojiSrc = infoEmoji.src;
|
|
infoEmoji.src = `${emojiSrc.slice(0, emojiSrc.lastIndexOf('/') + 1)}${emoji.hexcode.toLowerCase()}.png`;
|
|
infoShortcode.textContent = `:${emoji.shortcode}:`;
|
|
}
|
|
|
|
function hoverEmoji(e) {
|
|
if (isTargetNotEmoji(e.target)) return;
|
|
|
|
const emoji = e.target;
|
|
const { shortcodes, hexcode } = getEmojiDataFromTarget(emoji);
|
|
|
|
if (typeof shortcodes === 'undefined') {
|
|
searchRef.current.placeholder = 'Search';
|
|
setEmojiInfo({ hexcode: '1f643', shortcode: 'slight_smile' });
|
|
return;
|
|
}
|
|
if (searchRef.current.placeholder === shortcodes[0]) return;
|
|
searchRef.current.setAttribute('placeholder', `:${shortcodes[0]}:`);
|
|
setEmojiInfo({ hexcode, shortcode: shortcodes[0] });
|
|
}
|
|
|
|
function handleSearchChange(e) {
|
|
const term = e.target.value;
|
|
setTimeout(() => {
|
|
if (e.target.value !== term) return;
|
|
viewEvent.emit('search-emoji', term);
|
|
scrollEmojisRef.current.scrollTop = 0;
|
|
}, 500);
|
|
}
|
|
|
|
function openGroup(groupOrder) {
|
|
let tabIndex = groupOrder;
|
|
const $emojiContent = scrollEmojisRef.current.firstElementChild;
|
|
const groupCount = $emojiContent.childElementCount;
|
|
if (groupCount > emojiGroups.length) tabIndex += groupCount - emojiGroups.length;
|
|
$emojiContent.children[tabIndex].scrollIntoView();
|
|
}
|
|
|
|
return (
|
|
<div id="emoji-board" className="emoji-board">
|
|
<div className="emoji-board__content">
|
|
<div className="emoji-board__content__search">
|
|
<RawIcon size="small" src={SearchIC} />
|
|
<Input onChange={handleSearchChange} forwardRef={searchRef} placeholder="Search" />
|
|
</div>
|
|
<div className="emoji-board__content__emojis">
|
|
<ScrollView ref={scrollEmojisRef} autoHide>
|
|
<div onMouseMove={hoverEmoji} onClick={selectEmoji}>
|
|
<SearchedEmoji />
|
|
{
|
|
emojiGroups.map((group) => (
|
|
<EmojiGroup key={group.name} name={group.name} emojis={group.emojis} />
|
|
))
|
|
}
|
|
</div>
|
|
</ScrollView>
|
|
</div>
|
|
<div ref={emojiInfo} className="emoji-board__content__info">
|
|
<div>{ parse(twemoji.parse('🙂')) }</div>
|
|
<Text>:slight_smile:</Text>
|
|
</div>
|
|
</div>
|
|
<div className="emoji-board__nav">
|
|
<IconButton onClick={() => openGroup(0)} src={EmojiIC} tooltip="Smileys" tooltipPlacement="right" />
|
|
<IconButton onClick={() => openGroup(1)} src={DogIC} tooltip="Animals" tooltipPlacement="right" />
|
|
<IconButton onClick={() => openGroup(2)} src={CupIC} tooltip="Food" tooltipPlacement="right" />
|
|
<IconButton onClick={() => openGroup(3)} src={BallIC} tooltip="Activity" tooltipPlacement="right" />
|
|
<IconButton onClick={() => openGroup(4)} src={PhotoIC} tooltip="Travel" tooltipPlacement="right" />
|
|
<IconButton onClick={() => openGroup(5)} src={BulbIC} tooltip="Objects" tooltipPlacement="right" />
|
|
<IconButton onClick={() => openGroup(6)} src={PeaceIC} tooltip="Symbols" tooltipPlacement="right" />
|
|
<IconButton onClick={() => openGroup(7)} src={FlagIC} tooltip="Flags" tooltipPlacement="right" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
EmojiBoard.propTypes = {
|
|
onSelect: PropTypes.func.isRequired,
|
|
};
|
|
|
|
export default EmojiBoard;
|