mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-05 15:00:30 +03:00
initial commit
This commit is contained in:
commit
026f835a87
176 changed files with 10613 additions and 0 deletions
195
src/app/organisms/emoji-board/EmojiBoard.jsx
Normal file
195
src/app/organisms/emoji-board/EmojiBoard.jsx
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/* 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 ROW_EMOJIS_COUNT = 7;
|
||||
const emojiRows = [];
|
||||
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 = r + 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(),
|
||||
}),
|
||||
},
|
||||
))
|
||||
}
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
emojiRows.push(<div key={r} className="emoji-row">{emojiRow}</div>);
|
||||
}
|
||||
return emojiRows;
|
||||
}
|
||||
|
||||
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,
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
function isTargetNotEmoji(target) {
|
||||
return target.classList.contains('emoji') === false;
|
||||
}
|
||||
function getEmojiDataFromTarget(target) {
|
||||
const unicode = target.getAttribute('unicode');
|
||||
let shortcodes = target.getAttribute('shortcodes');
|
||||
if (typeof shortcodes === 'undefined') shortcodes = undefined;
|
||||
else shortcodes = shortcodes.split(',');
|
||||
return { unicode, shortcodes };
|
||||
}
|
||||
|
||||
function selectEmoji(e) {
|
||||
if (isTargetNotEmoji(e.target)) return;
|
||||
|
||||
const emoji = e.target;
|
||||
onSelect(getEmojiDataFromTarget(emoji));
|
||||
}
|
||||
|
||||
function hoverEmoji(e) {
|
||||
if (isTargetNotEmoji(e.target)) return;
|
||||
|
||||
const emoji = e.target;
|
||||
const { shortcodes } = getEmojiDataFromTarget(emoji);
|
||||
|
||||
if (typeof shortcodes === 'undefined') {
|
||||
searchRef.current.placeholder = 'Search';
|
||||
return;
|
||||
}
|
||||
if (searchRef.current.placeholder === shortcodes[0]) return;
|
||||
searchRef.current.setAttribute('placeholder', `:${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__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 className="emoji-board__search">
|
||||
<RawIcon size="small" src={SearchIC} />
|
||||
<Input onChange={handleSearchChange} forwardRef={searchRef} placeholder="Search" />
|
||||
</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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue