[CH] Reduce emoji mart css footprint
This commit is contained in:
parent
cc7e4b485e
commit
c53454a919
|
@ -1,47 +1,59 @@
|
|||
import data from '@emoji-mart/data';
|
||||
import Picker from '@emoji-mart/react';
|
||||
import classNames from 'classnames';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React from 'react';
|
||||
import { Overlay } from 'react-overlays';
|
||||
import React, { MouseEventHandler } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { IconButton } from 'soapbox/components/ui';
|
||||
import { isMobile } from 'soapbox/is_mobile';
|
||||
import { useTheme } from 'soapbox/hooks';
|
||||
|
||||
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
||||
|
||||
import EmojiPickerMenu from './emoji_picker_menu';
|
||||
|
||||
|
||||
let EmojiPicker, Emoji; // 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' },
|
||||
skins: { id: 'emoji_button.skins', defaultMessage: 'Skins' },
|
||||
});
|
||||
|
||||
interface IWrapper {
|
||||
target: any,
|
||||
show: boolean,
|
||||
onClose: MouseEventHandler,
|
||||
children: React.ReactNode,
|
||||
}
|
||||
|
||||
const Wrapper: React.FC<IWrapper> = ({ target, show, children }) => {
|
||||
const placement = React.useMemo(() => target.current?.getBoundingClientRect().top * 2 < window.innerHeight ? 'bottom' : 'top', [target]);
|
||||
|
||||
if (isMobile(window.innerWidth) && show) {
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className='fixed top-0 left-0 w-screen h-screen flex flex-col z-[200]'
|
||||
>
|
||||
const Wrapper: React.FC<IWrapper> = ({ target, show, onClose, children }) => {
|
||||
if (!show) return null;
|
||||
return (
|
||||
<div className='emoji-picker fixed top-0 left-0 w-screen h-screen bg-gray-800 z-[100]'>
|
||||
<div className='bg-white dark:bg-slate-800 flex flex-col overflow-hidden sm:rounded-lg absolute top-1/2 left-1/2 -translate-x-[50%] -translate-y-[50%] '>
|
||||
<div className='p-1'>
|
||||
<IconButton
|
||||
className='ml-auto text-gray-500'
|
||||
src={require('@tabler/icons/x.svg')}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Overlay target={target.current} placement={placement} show={show}>{ children }</Overlay>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface IEmojiPicker {
|
||||
custom_emojis: ImmutableList<string>,
|
||||
frequentlyUsedEmojis: Array<string>,
|
||||
onPickEmoji: Function,
|
||||
onSkinTone: Function,
|
||||
skinTone: number,
|
||||
|
@ -50,35 +62,14 @@ interface IEmojiPicker {
|
|||
|
||||
const EmojiPickerUI : React.FC<IEmojiPicker> = ({
|
||||
custom_emojis,
|
||||
frequentlyUsedEmojis,
|
||||
onPickEmoji,
|
||||
onSkinTone,
|
||||
skinTone,
|
||||
button,
|
||||
}) => {
|
||||
const root = React.useRef<HTMLDivElement>(null);
|
||||
const [active, setActive] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(!EmojiPicker || !Emoji);
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
|
||||
const loadEmojiPicker = React.useCallback(async() => {
|
||||
if (EmojiPicker) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const EmojiMart = await EmojiPickerAsync();
|
||||
EmojiPicker = EmojiMart.Picker;
|
||||
Emoji = EmojiMart.Emoji;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
loadEmojiPicker();
|
||||
}, []);
|
||||
|
||||
const handleClose = React.useCallback((e) => {
|
||||
const handleClose = React.useCallback((e = null) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
@ -87,7 +78,6 @@ const EmojiPickerUI : React.FC<IEmojiPicker> = ({
|
|||
|
||||
const handleToggle = React.useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
if (loading) return;
|
||||
if (e.key === 'Escape') {
|
||||
setActive(false);
|
||||
return;
|
||||
|
@ -96,7 +86,28 @@ const EmojiPickerUI : React.FC<IEmojiPicker> = ({
|
|||
if ((!e.key || e.key === 'Enter')) {
|
||||
setActive(!active);
|
||||
}
|
||||
}, [active, loading]);
|
||||
}, [active]);
|
||||
|
||||
const buildCustomEmojis = React.useCallback((custom_emojis: ImmutableList<any>) => {
|
||||
const emojis = custom_emojis.map((emoji) => (
|
||||
{
|
||||
id: emoji.get('shortcode'),
|
||||
name: emoji.get('shortcode'),
|
||||
keywords: [emoji.get('shortcode')],
|
||||
skins: [{ src: emoji.get('static_url') }],
|
||||
}
|
||||
)).toJS();
|
||||
return [{
|
||||
id: 'custom',
|
||||
name: intl.formatMessage(messages.custom),
|
||||
emojis,
|
||||
}];
|
||||
}, []);
|
||||
|
||||
const handlePick = React.useCallback((emoji) => {
|
||||
onPickEmoji({ ...emoji, native: emoji.native || emoji.shortcodes });
|
||||
handleClose();
|
||||
}, [handleClose, onPickEmoji]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -104,23 +115,40 @@ const EmojiPickerUI : React.FC<IEmojiPicker> = ({
|
|||
<IconButton
|
||||
className={classNames({
|
||||
'text-gray-400 hover:text-gray-600': true,
|
||||
'pulse-loading': active && loading,
|
||||
})}
|
||||
alt='😀'
|
||||
src={require('@tabler/icons/mood-happy.svg')}
|
||||
onClick={handleToggle}
|
||||
/>
|
||||
<Wrapper target={root} show={active}>
|
||||
<EmojiPickerMenu
|
||||
Emoji={Emoji}
|
||||
EmojiPicker={EmojiPicker}
|
||||
custom_emojis={custom_emojis}
|
||||
loading={loading}
|
||||
onClose={handleClose}
|
||||
onPick={onPickEmoji}
|
||||
onSkinTone={onSkinTone}
|
||||
skinTone={skinTone}
|
||||
frequentlyUsedEmojis={frequentlyUsedEmojis}
|
||||
<Wrapper target={root} show={active} onClose={handleClose}>
|
||||
<Picker
|
||||
theme={theme}
|
||||
dynamicWidth={isMobile(window.innerWidth)}
|
||||
categories={['frequent', 'custom', 'people', 'nature', 'foods', 'activity', 'places', 'objects', 'symbols', 'flags']}
|
||||
previewPosition='none'
|
||||
custom={buildCustomEmojis(custom_emojis)}
|
||||
data={data}
|
||||
onEmojiSelect={handlePick}
|
||||
i18n={
|
||||
{
|
||||
search: intl.formatMessage(messages.emoji_search),
|
||||
notfound: intl.formatMessage(messages.emoji_not_found),
|
||||
skins: intl.formatMessage(messages.skins),
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Wrapper>
|
||||
</div>
|
||||
|
|
|
@ -1,291 +0,0 @@
|
|||
import classNames from 'classnames';
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import { IconButton } from 'soapbox/components/ui';
|
||||
|
||||
import { isMobile } from '../../../is_mobile';
|
||||
import { buildCustomEmojis } from '../../emoji/emoji';
|
||||
|
||||
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' },
|
||||
});
|
||||
|
||||
const backgroundImageFn = () => require('emoji-datasource/img/twitter/sheets/32.png');
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
||||
const categoriesSort = [
|
||||
'recent',
|
||||
'custom',
|
||||
'people',
|
||||
'nature',
|
||||
'foods',
|
||||
'activity',
|
||||
'places',
|
||||
'objects',
|
||||
'symbols',
|
||||
'flags',
|
||||
];
|
||||
|
||||
class ModifierPickerMenu extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
Emoji: PropTypes.any.isRequired,
|
||||
};
|
||||
|
||||
handleClick = e => {
|
||||
this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.active) {
|
||||
this.attachListeners();
|
||||
} else {
|
||||
this.removeListeners();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.removeListeners();
|
||||
}
|
||||
|
||||
handleDocumentClick = e => {
|
||||
if (this.node && !this.node.contains(e.target)) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
attachListeners() {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
document.removeEventListener('click', this.handleDocumentClick, false);
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { active, Emoji } = this.props;
|
||||
|
||||
return (
|
||||
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
|
||||
<button onClick={this.handleClick} data-index={1}><Emoji emoji='thumbsup' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button onClick={this.handleClick} data-index={2}><Emoji emoji='thumbsup' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button onClick={this.handleClick} data-index={3}><Emoji emoji='thumbsup' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button onClick={this.handleClick} data-index={4}><Emoji emoji='thumbsup' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button onClick={this.handleClick} data-index={5}><Emoji emoji='thumbsup' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button onClick={this.handleClick} data-index={6}><Emoji emoji='thumbsup' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ModifierPicker extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
modifier: PropTypes.number,
|
||||
onChange: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
onOpen: PropTypes.func,
|
||||
Emoji: PropTypes.any.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
if (this.props.active) {
|
||||
this.props.onClose();
|
||||
} else {
|
||||
this.props.onOpen();
|
||||
}
|
||||
}
|
||||
|
||||
handleSelect = modifier => {
|
||||
this.props.onChange(modifier);
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { active, modifier, Emoji } = this.props;
|
||||
|
||||
return (
|
||||
<div className='emoji-picker-dropdown__modifiers'>
|
||||
<Emoji emoji='thumbsup' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
|
||||
<ModifierPickerMenu Emoji={Emoji} active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default @injectIntl
|
||||
class EmojiPickerMenu extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
custom_emojis: ImmutablePropTypes.list,
|
||||
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
|
||||
loading: PropTypes.bool,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onPick: PropTypes.func.isRequired,
|
||||
style: PropTypes.object,
|
||||
placement: PropTypes.string,
|
||||
arrowOffsetLeft: PropTypes.string,
|
||||
arrowOffsetTop: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
skinTone: PropTypes.number.isRequired,
|
||||
onSkinTone: PropTypes.func.isRequired,
|
||||
EmojiPicker: PropTypes.any.isRequired,
|
||||
Emoji: PropTypes.any.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
style: {},
|
||||
loading: true,
|
||||
frequentlyUsedEmojis: [],
|
||||
};
|
||||
|
||||
state = {
|
||||
modifierOpen: false,
|
||||
placement: null,
|
||||
};
|
||||
|
||||
handleDocumentClick = e => {
|
||||
if (this.node && !this.node.contains(e.target)) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.handleDocumentClick, false);
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
getI18n = () => {
|
||||
const { intl } = this.props;
|
||||
|
||||
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),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
handleClick = emoji => {
|
||||
if (!emoji.native) {
|
||||
emoji.native = emoji.colons;
|
||||
}
|
||||
|
||||
this.props.onClose();
|
||||
this.props.onPick(emoji);
|
||||
}
|
||||
|
||||
handleModifierOpen = () => {
|
||||
this.setState({ modifierOpen: true });
|
||||
}
|
||||
|
||||
handleModifierClose = () => {
|
||||
this.setState({ modifierOpen: false });
|
||||
}
|
||||
|
||||
handleModifierChange = modifier => {
|
||||
this.props.onSkinTone(modifier);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis, EmojiPicker, Emoji, onClose } = this.props;
|
||||
|
||||
if (loading) {
|
||||
return <div style={{ width: 299 }} />;
|
||||
}
|
||||
|
||||
const title = intl.formatMessage(messages.emoji);
|
||||
const { modifierOpen } = this.state;
|
||||
|
||||
return (
|
||||
<div className={classNames('emoji-picker-dropdown__menu grow flex flex-col', { selecting: modifierOpen, 'absolute': !isMobile(window.innerWidth) })} style={style} ref={this.setRef}>
|
||||
<IconButton
|
||||
alt='Close'
|
||||
className='ml-auto'
|
||||
src={require('@tabler/icons/x.svg')}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div className='grow relative'>
|
||||
<EmojiPicker
|
||||
perLine={8}
|
||||
emojiSize={22}
|
||||
sheetSize={32}
|
||||
custom={buildCustomEmojis(custom_emojis)}
|
||||
color=''
|
||||
emoji=''
|
||||
set='twitter'
|
||||
title={title}
|
||||
i18n={this.getI18n()}
|
||||
onClick={this.handleClick}
|
||||
include={categoriesSort}
|
||||
recent={frequentlyUsedEmojis}
|
||||
skin={skinTone}
|
||||
showPreview={false}
|
||||
backgroundImageFn={backgroundImageFn}
|
||||
emojiTooltip
|
||||
/>
|
||||
|
||||
<ModifierPicker
|
||||
Emoji={Emoji}
|
||||
active={modifierOpen}
|
||||
modifier={skinTone}
|
||||
onOpen={this.handleModifierOpen}
|
||||
onClose={this.handleModifierClose}
|
||||
onChange={this.handleModifierChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,50 +1,10 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { useEmoji } from '../../../actions/emojis';
|
||||
import { getSettings, changeSetting } from '../../../actions/settings';
|
||||
import { changeSetting } from '../../../actions/settings';
|
||||
import EmojiPicker from '../components/emoji_picker';
|
||||
|
||||
const perLine = 8;
|
||||
const lines = 2;
|
||||
|
||||
const DEFAULTS = [
|
||||
'+1',
|
||||
'grinning',
|
||||
'kissing_heart',
|
||||
'heart_eyes',
|
||||
'laughing',
|
||||
'stuck_out_tongue_winking_eye',
|
||||
'sweat_smile',
|
||||
'joy',
|
||||
'yum',
|
||||
'disappointed',
|
||||
'thinking_face',
|
||||
'weary',
|
||||
'sob',
|
||||
'sunglasses',
|
||||
'heart',
|
||||
'ok_hand',
|
||||
];
|
||||
|
||||
const getFrequentlyUsedEmojis = createSelector([
|
||||
state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()),
|
||||
], emojiCounters => {
|
||||
let emojis = emojiCounters
|
||||
.keySeq()
|
||||
.sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b))
|
||||
.reverse()
|
||||
.slice(0, perLine * lines)
|
||||
.toArray();
|
||||
|
||||
if (emojis.length < DEFAULTS.length) {
|
||||
const uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji));
|
||||
emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length));
|
||||
}
|
||||
|
||||
return emojis;
|
||||
});
|
||||
|
||||
const getCustomEmojis = createSelector([
|
||||
state => state.get('custom_emojis'),
|
||||
|
@ -63,15 +23,9 @@ const getCustomEmojis = createSelector([
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
custom_emojis: getCustomEmojis(state),
|
||||
skinTone: getSettings(state).get('skinTone'),
|
||||
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, props) => ({
|
||||
onSkinTone: skinTone => {
|
||||
dispatch(changeSetting(['skinTone'], skinTone));
|
||||
},
|
||||
|
||||
onPickEmoji: emoji => {
|
||||
dispatch(useEmoji(emoji)); // eslint-disable-line react-hooks/rules-of-hooks
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import Emoji from 'emoji-mart/dist-es/components/emoji/emoji';
|
||||
import Picker from 'emoji-mart/dist-es/components/picker/picker';
|
||||
|
||||
export {
|
||||
Picker,
|
||||
Emoji,
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import Picker from '@emoji-mart/react';
|
||||
import classNames from 'classnames';
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import Picker from 'emoji-mart/dist-es/components/picker/picker';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
export function EmojiPicker() {
|
||||
return import(/* webpackChunkName: "emoji_picker" */'../../emoji/emoji_picker');
|
||||
}
|
||||
|
||||
export function Notifications() {
|
||||
return import(/* webpackChunkName: "features/notifications" */'../../notifications');
|
||||
}
|
||||
|
|
|
@ -1,302 +1,30 @@
|
|||
.emoji-mart {
|
||||
@apply text-base text-gray-900 dark:text-gray-100 rounded bg-white dark:bg-slate-900 shadow-lg;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
.emoji-picker {
|
||||
|
||||
&.bg-gray-800 {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 630px) {
|
||||
width: initial !important;
|
||||
}
|
||||
}
|
||||
& > div {
|
||||
height: 100vh;
|
||||
|
||||
.emoji-mart-scroll {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
will-change: transform; /* avoids "repaints on scroll" in mobile Chrome */
|
||||
|
||||
@media screen and (min-width: 630px) {
|
||||
min-height: 236px;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart .emoji-mart-emoji {
|
||||
@apply p-1.5;
|
||||
}
|
||||
|
||||
.emoji-mart-bar {
|
||||
@apply border-0 border-solid border-gray-200 dark:border-slate-700;
|
||||
}
|
||||
|
||||
.emoji-mart-bar:first-child {
|
||||
border-bottom-width: 1px;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
.emoji-mart-bar:last-child {
|
||||
border-top-width: 1px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.emoji-mart-anchors {
|
||||
@apply flex flex-row justify-between px-1.5;
|
||||
}
|
||||
|
||||
.emoji-mart-anchor {
|
||||
@apply relative block text-gray-400 dark:text-gray-500 text-center overflow-hidden transition-colors py-3 px-1;
|
||||
}
|
||||
|
||||
.emoji-mart-anchor:focus { outline: 0; }
|
||||
|
||||
.emoji-mart-anchor:hover,
|
||||
.emoji-mart-anchor:focus,
|
||||
.emoji-mart-anchor-selected {
|
||||
@apply text-gray-600 dark:text-gray-300;
|
||||
}
|
||||
|
||||
.emoji-mart-anchor-selected .emoji-mart-anchor-bar {
|
||||
@apply bottom-0;
|
||||
}
|
||||
|
||||
.emoji-mart-anchor-bar {
|
||||
@apply absolute -bottom-0.5 left-0 w-11/12 h-0.5 bg-primary-600;
|
||||
}
|
||||
|
||||
.emoji-mart-anchors i {
|
||||
@apply inline-block w-full;
|
||||
max-width: 22px;
|
||||
}
|
||||
|
||||
.emoji-mart-anchors svg,
|
||||
.emoji-mart-anchors img {
|
||||
fill: currentColor;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.emoji-mart-search {
|
||||
@apply relative mt-1.5 p-2.5 pr-12 bg-white dark:bg-slate-900;
|
||||
}
|
||||
|
||||
.emoji-mart-search input {
|
||||
@apply text-sm pr-9 block w-full border-gray-300 dark:bg-slate-800 dark:border-gray-600 rounded-full focus:ring-primary-500 focus:border-primary-500;
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-webkit-search-cancel-button {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-search input,
|
||||
.emoji-mart-search input::-webkit-search-decoration,
|
||||
.emoji-mart-search input::-webkit-search-cancel-button,
|
||||
.emoji-mart-search input::-webkit-search-results-button,
|
||||
.emoji-mart-search input::-webkit-search-results-decoration {
|
||||
/* remove webkit/blink styles for <input type="search">
|
||||
* via https://stackoverflow.com/a/9422689 */
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.emoji-mart-search-icon {
|
||||
@apply absolute z-10 border-0;
|
||||
top: 20px;
|
||||
right: 56px;
|
||||
padding: 2px 5px 1px;
|
||||
}
|
||||
|
||||
.emoji-mart-search-icon svg {
|
||||
@apply stroke-gray-400;
|
||||
}
|
||||
|
||||
.emoji-mart-search-icon:hover svg {
|
||||
@apply stroke-gray-800;
|
||||
}
|
||||
|
||||
.emoji-mart-category .emoji-mart-emoji img, .emoji-mart-category .emoji-mart-emoji span {
|
||||
@apply relative text-center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.emoji-mart-category .emoji-mart-emoji:hover::before {
|
||||
@apply bg-gray-50 dark:bg-slate-800;
|
||||
z-index: 0;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.emoji-mart-category-label {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-category-label span {
|
||||
@apply bg-white dark:bg-slate-900;
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
padding: 5px 6px;
|
||||
}
|
||||
|
||||
.emoji-mart-category-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-category-list li {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.emoji-mart-emoji {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-size: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.emoji-mart-emoji-native {
|
||||
font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "Android Emoji", sans-serif;
|
||||
}
|
||||
|
||||
.emoji-mart-no-results {
|
||||
@apply text-sm text-center text-gray-600 dark:text-gray-300;
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
.emoji-mart-no-results-img {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.emoji-mart-no-results .emoji-mart-category-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.emoji-mart-no-results .emoji-mart-no-results-label {
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
.emoji-mart-no-results .emoji-mart-emoji:hover::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.emoji-mart-preview {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
/* For screenreaders only, via https://stackoverflow.com/a/19758620 */
|
||||
.emoji-mart-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.emoji-picker-dropdown__menu {
|
||||
@apply rounded-lg;
|
||||
transform: translateX(calc(-1 * env(safe-area-inset-right))); /* iOS PWA */
|
||||
z-index: 20000;
|
||||
|
||||
.emoji-mart-scroll {
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
|
||||
&.selecting .emoji-mart-scroll {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-picker-dropdown__modifiers {
|
||||
position: absolute;
|
||||
top: 65px;
|
||||
right: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.emoji-picker-dropdown__modifiers__menu {
|
||||
@apply absolute bg-white dark:bg-slate-900 rounded-3xl shadow overflow-hidden;
|
||||
z-index: 4;
|
||||
top: -4px;
|
||||
left: -8px;
|
||||
|
||||
button {
|
||||
@apply block cursor-pointer border-0 px-2 py-1 bg-transparent;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
@apply bg-gray-300 dark:bg-slate-600;
|
||||
& > div:last-of-type {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-emoji {
|
||||
height: 22px;
|
||||
em-emoji-picker {
|
||||
--border-radius: 0px;
|
||||
@media screen and (max-width: 630px) {
|
||||
flex-grow: 1;
|
||||
width: 100vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.font-icon-picker {
|
||||
.emoji-mart-search {
|
||||
// Search doesn't work. Hide it for now.
|
||||
display: none;
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.emoji-mart-category-label > span {
|
||||
padding: 9px 6px 5px;
|
||||
}
|
||||
|
||||
.emoji-mart-scroll {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.emoji-mart-search-icon {
|
||||
right: 18px;
|
||||
}
|
||||
|
||||
.emoji-mart-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fa {
|
||||
font-size: 18px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fa-hack {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
.dark .emoji-picker em-emoji-picker {
|
||||
--rgb-background: 30, 41, 59;
|
||||
}
|
|
@ -50,6 +50,8 @@
|
|||
"@babel/preset-react": "^7.17.12",
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emoji-mart/data": "^1.1.1",
|
||||
"@emoji-mart/react": "^1.1.0",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@fontsource/roboto": "^4.5.0",
|
||||
"@gamestdio/websocket": "^0.3.2",
|
||||
|
@ -114,7 +116,7 @@
|
|||
"detect-passive-events": "^2.0.0",
|
||||
"dotenv": "^8.0.0",
|
||||
"emoji-datasource": "5.0.0",
|
||||
"emoji-mart": "npm:emoji-mart-lazyload",
|
||||
"emoji-mart": "^5.4.0",
|
||||
"entities": "^3.0.1",
|
||||
"es6-symbol": "^3.1.1",
|
||||
"escape-html": "^1.0.3",
|
||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -1271,7 +1271,7 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.2.0", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.2.0", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.15.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
|
||||
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
|
||||
|
@ -1382,6 +1382,16 @@
|
|||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3"
|
||||
integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==
|
||||
|
||||
"@emoji-mart/data@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.1.1.tgz#8b49cdfa85be8f1893abfca4cd0fa78e34e49672"
|
||||
integrity sha512-yn1LiT9QA0GHN+Xkv0ZkZYS0+SyopNItMEi7z/f9Se8yA5TMjlcq0jisseqeO0MdG/x8P5Pgqluzi6rOr5HUkw==
|
||||
|
||||
"@emoji-mart/react@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@emoji-mart/react/-/react-1.1.0.tgz#afeaac086c951a0b6d782ff461955be1c9904706"
|
||||
integrity sha512-6MnXNSfR69hmgH/mZIux4NGAjmS90f+rtJu8Soq1rCb27JWmYCQLAP7BqOVPUN4BL+c4ONUK+PPPihQho0eIxg==
|
||||
|
||||
"@es-joy/jsdoccomment@~0.29.0":
|
||||
version "0.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.29.0.tgz#527c7eefadeaf5c5d0c3b2721b5fa425d2119e98"
|
||||
|
@ -5073,14 +5083,10 @@ emoji-datasource@5.0.0:
|
|||
resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-5.0.0.tgz#1522fdba3c52223a1cf5a1c1fc282935400eaa06"
|
||||
integrity sha512-LuvLWFnxznTH++GytEzpzOPUo1SB+6CUFqIlVETJJ3x9fpyMCKFfyqberbhMLOpT1qcNe+km+zoyBeUSC3u5Rw==
|
||||
|
||||
"emoji-mart@npm:emoji-mart-lazyload":
|
||||
version "3.0.1-j"
|
||||
resolved "https://registry.yarnpkg.com/emoji-mart-lazyload/-/emoji-mart-lazyload-3.0.1-j.tgz#87a90d30b79d9145ece078d53e3e683c1a10ce9c"
|
||||
integrity sha512-0wKF7MR0/iAeCIoiBLY+JjXCugycTgYRC2SL0y9/bjNSQlbeMdzILmPQJAufU/mgLFDUitOvjxLDhOZ9yxZ48g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.0.0"
|
||||
intersection-observer "^0.12.0"
|
||||
prop-types "^15.6.0"
|
||||
emoji-mart@^5.4.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.4.0.tgz#441f3b89452d44516a70d88afe085a82806d757e"
|
||||
integrity sha512-xrRrUmMqZG64oRxmUZcf8zSMUGQtIUYUL3aZD5iMkqAve+I9wMNh3OVOXL7NW9fEm48L2LI3BUPpj/DUIAJrVg==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
|
|
Loading…
Reference in New Issue