cinny/src/app/organisms/emoji-board/EmojiBoard.jsx

214 lines
7.7 KiB
React
Raw Normal View History

2021-07-28 16:15:52 +03:00
/* 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 parse from 'html-react-parser';
import twemoji from 'twemoji';
2021-08-25 12:30:40 +03:00
import { emojiGroups, emojis } from './emoji';
import AsyncSearch from '../../../util/AsyncSearch';
2021-07-28 16:15:52 +03:00
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';
2021-08-25 12:30:40 +03:00
function EmojiGroup({ name, groupEmojis }) {
2021-07-28 16:15:52 +03:00
function getEmojiBoard() {
2021-08-13 14:01:22 +03:00
const emojiBoard = [];
2021-07-28 16:15:52 +03:00
const ROW_EMOJIS_COUNT = 7;
2021-08-25 12:30:40 +03:00
const totalEmojis = groupEmojis.length;
2021-07-28 16:15:52 +03:00
for (let r = 0; r < totalEmojis; r += ROW_EMOJIS_COUNT) {
const emojiRow = [];
for (let c = r; c < r + ROW_EMOJIS_COUNT; c += 1) {
2021-08-13 14:01:22 +03:00
const emojiIndex = c;
2021-07-28 16:15:52 +03:00
if (emojiIndex >= totalEmojis) break;
2021-08-25 12:30:40 +03:00
const emoji = groupEmojis[emojiIndex];
2021-07-28 16:15:52 +03:00
emojiRow.push(
<span key={emojiIndex}>
{
parse(twemoji.parse(
emoji.unicode,
{
attributes: () => ({
unicode: emoji.unicode,
shortcodes: emoji.shortcodes?.toString(),
2021-08-13 14:01:22 +03:00
hexcode: emoji.hexcode,
2021-07-28 16:15:52 +03:00
}),
},
))
}
</span>,
);
}
2021-08-13 14:01:22 +03:00
emojiBoard.push(<div key={r} className="emoji-row">{emojiRow}</div>);
2021-07-28 16:15:52 +03:00
}
2021-08-13 14:01:22 +03:00
return emojiBoard;
2021-07-28 16:15:52 +03:00
}
return (
<div className="emoji-group">
<Text className="emoji-group__header" variant="b2">{name}</Text>
2021-08-25 12:30:40 +03:00
{groupEmojis.length !== 0 && <div className="emoji-set">{getEmojiBoard()}</div>}
2021-07-28 16:15:52 +03:00
</div>
);
}
EmojiGroup.propTypes = {
name: PropTypes.string.isRequired,
2021-08-25 12:30:40 +03:00
groupEmojis: PropTypes.arrayOf(PropTypes.shape({
2021-07-28 16:15:52 +03:00
length: PropTypes.number,
unicode: PropTypes.string,
2021-08-13 14:01:22 +03:00
hexcode: PropTypes.string,
2021-07-28 16:15:52 +03:00
shortcodes: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
]),
})).isRequired,
};
2021-08-25 12:30:40 +03:00
const asyncSearch = new AsyncSearch();
asyncSearch.setup(emojis, { keys: ['shortcode'], limit: 30 });
2021-07-28 16:15:52 +03:00
function SearchedEmoji() {
2021-08-25 12:30:40 +03:00
const [searchedEmojis, setSearchedEmojis] = useState(null);
2021-07-28 16:15:52 +03:00
2021-08-25 12:30:40 +03:00
function handleSearchEmoji(resultEmojis, term) {
if (term === '' || resultEmojis.length === 0) {
if (term === '') setSearchedEmojis(null);
else setSearchedEmojis([]);
2021-07-28 16:15:52 +03:00
return;
}
2021-08-25 12:30:40 +03:00
setSearchedEmojis(resultEmojis);
2021-07-28 16:15:52 +03:00
}
useEffect(() => {
2021-08-25 12:30:40 +03:00
asyncSearch.on(asyncSearch.RESULT_SENT, handleSearchEmoji);
2021-07-28 16:15:52 +03:00
return () => {
2021-08-25 12:30:40 +03:00
asyncSearch.removeListener(asyncSearch.RESULT_SENT, handleSearchEmoji);
2021-07-28 16:15:52 +03:00
};
}, []);
2021-08-25 12:30:40 +03:00
if (searchedEmojis === null) return false;
return <EmojiGroup key="-1" name={searchedEmojis.length === 0 ? 'No search result found' : 'Search results'} groupEmojis={searchedEmojis} />;
2021-07-28 16:15:52 +03:00
}
function EmojiBoard({ onSelect }) {
const searchRef = useRef(null);
const scrollEmojisRef = useRef(null);
2021-08-13 14:01:22 +03:00
const emojiInfo = useRef(null);
2021-07-28 16:15:52 +03:00
function isTargetNotEmoji(target) {
return target.classList.contains('emoji') === false;
}
function getEmojiDataFromTarget(target) {
const unicode = target.getAttribute('unicode');
2021-08-13 14:01:22 +03:00
const hexcode = target.getAttribute('hexcode');
2021-07-28 16:15:52 +03:00
let shortcodes = target.getAttribute('shortcodes');
if (typeof shortcodes === 'undefined') shortcodes = undefined;
else shortcodes = shortcodes.split(',');
2021-08-13 14:01:22 +03:00
return { unicode, hexcode, shortcodes };
2021-07-28 16:15:52 +03:00
}
function selectEmoji(e) {
if (isTargetNotEmoji(e.target)) return;
const emoji = e.target;
onSelect(getEmojiDataFromTarget(emoji));
}
2021-08-13 14:01:22 +03:00
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}:`;
}
2021-07-28 16:15:52 +03:00
function hoverEmoji(e) {
if (isTargetNotEmoji(e.target)) return;
const emoji = e.target;
2021-08-13 14:01:22 +03:00
const { shortcodes, hexcode } = getEmojiDataFromTarget(emoji);
2021-07-28 16:15:52 +03:00
if (typeof shortcodes === 'undefined') {
searchRef.current.placeholder = 'Search';
2021-08-13 14:01:22 +03:00
setEmojiInfo({ hexcode: '1f643', shortcode: 'slight_smile' });
2021-07-28 16:15:52 +03:00
return;
}
if (searchRef.current.placeholder === shortcodes[0]) return;
2021-08-25 12:30:40 +03:00
searchRef.current.setAttribute('placeholder', shortcodes[0]);
2021-08-13 14:01:22 +03:00
setEmojiInfo({ hexcode, shortcode: shortcodes[0] });
2021-07-28 16:15:52 +03:00
}
function handleSearchChange(e) {
const term = e.target.value;
2021-08-25 12:30:40 +03:00
asyncSearch.search(term);
scrollEmojisRef.current.scrollTop = 0;
2021-07-28 16:15:52 +03:00
}
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">
2021-08-13 14:01:22 +03:00
<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">
2021-07-28 16:15:52 +03:00
<ScrollView ref={scrollEmojisRef} autoHide>
<div onMouseMove={hoverEmoji} onClick={selectEmoji}>
<SearchedEmoji />
{
emojiGroups.map((group) => (
2021-08-25 12:30:40 +03:00
<EmojiGroup key={group.name} name={group.name} groupEmojis={group.emojis} />
2021-07-28 16:15:52 +03:00
))
}
</div>
</ScrollView>
</div>
2021-08-13 14:01:22 +03:00
<div ref={emojiInfo} className="emoji-board__content__info">
<div>{ parse(twemoji.parse('🙂')) }</div>
<Text>:slight_smile:</Text>
2021-07-28 16:15:52 +03:00
</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;