[CH] Reduce emoji mart css footprint

This commit is contained in:
Clovis 2022-12-31 01:15:34 +01:00
parent cc7e4b485e
commit c53454a919
9 changed files with 131 additions and 715 deletions

View File

@ -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>

View File

@ -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>
);
}
}

View File

@ -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

View File

@ -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,
};

View File

@ -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';

View File

@ -1,7 +1,3 @@
export function EmojiPicker() {
return import(/* webpackChunkName: "emoji_picker" */'../../emoji/emoji_picker');
}
export function Notifications() {
return import(/* webpackChunkName: "features/notifications" */'../../notifications');
}

View File

@ -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;
}

View File

@ -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",

View File

@ -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"