Add Emoji functional component
This commit is contained in:
parent
ee1ef09201
commit
52982706fe
3 changed files with 61 additions and 8 deletions
|
@ -5,7 +5,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
import emojify from 'soapbox/features/emoji/emoji';
|
import { Emoji } from 'soapbox/components/ui';
|
||||||
|
|
||||||
import type { List as ImmutableList } from 'immutable';
|
import type { List as ImmutableList } from 'immutable';
|
||||||
import type { RootState } from 'soapbox/store';
|
import type { RootState } from 'soapbox/store';
|
||||||
|
@ -109,12 +109,9 @@ class EmojiSelector extends ImmutablePureComponent<IEmojiSelector> {
|
||||||
const { visible, focused, allowedEmoji } = this.props;
|
const { visible, focused, allowedEmoji } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys
|
<HotKeys handlers={this.handlers}>
|
||||||
handlers={this.handlers}
|
|
||||||
className='emoji-react-selector-container'
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={classNames('emoji-react-selector w-max', { 'emoji-react-selector--visible': visible, 'emoji-react-selector--focused': focused })}
|
className={classNames('flex absolute bg-white dark:bg-slate-500 px-2 py-3 rounded-full shadow-md opacity-0 pointer-events-none duration-100 w-max', { 'opacity-100 pointer-events-auto z-[999]': visible || focused })}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
>
|
>
|
||||||
|
@ -122,11 +119,12 @@ class EmojiSelector extends ImmutablePureComponent<IEmojiSelector> {
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
className='emoji-react-selector__emoji'
|
className='emoji-react-selector__emoji'
|
||||||
dangerouslySetInnerHTML={{ __html: emojify(emoji) }}
|
|
||||||
onClick={this.handleReact(emoji)}
|
onClick={this.handleReact(emoji)}
|
||||||
onKeyDown={this.handleKeyDown(i)}
|
onKeyDown={this.handleKeyDown(i)}
|
||||||
tabIndex={(visible || focused) ? 0 : -1}
|
tabIndex={(visible || focused) ? 0 : -1}
|
||||||
/>
|
>
|
||||||
|
<Emoji emoji={emoji} />
|
||||||
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
|
|
54
app/soapbox/components/ui/emoji/emoji.tsx
Normal file
54
app/soapbox/components/ui/emoji/emoji.tsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { joinPublicPath } from 'soapbox/utils/static';
|
||||||
|
|
||||||
|
// Taken from twemoji-parser
|
||||||
|
// https://github.com/twitter/twemoji-parser/blob/a97ef3994e4b88316812926844d51c296e889f76/src/index.js
|
||||||
|
const removeVS16s = (rawEmoji: string): string => {
|
||||||
|
const vs16RegExp = /\uFE0F/g;
|
||||||
|
const zeroWidthJoiner = String.fromCharCode(0x200d);
|
||||||
|
return rawEmoji.indexOf(zeroWidthJoiner) < 0 ? rawEmoji.replace(vs16RegExp, '') : rawEmoji;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toCodePoints = (unicodeSurrogates: string): string[] => {
|
||||||
|
const points = [];
|
||||||
|
let char = 0;
|
||||||
|
let previous = 0;
|
||||||
|
let i = 0;
|
||||||
|
while (i < unicodeSurrogates.length) {
|
||||||
|
char = unicodeSurrogates.charCodeAt(i++);
|
||||||
|
if (previous) {
|
||||||
|
points.push((0x10000 + ((previous - 0xd800) << 10) + (char - 0xdc00)).toString(16));
|
||||||
|
previous = 0;
|
||||||
|
} else if (char > 0xd800 && char <= 0xdbff) {
|
||||||
|
previous = char;
|
||||||
|
} else {
|
||||||
|
points.push(char.toString(16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IEmoji {
|
||||||
|
className?: string,
|
||||||
|
emoji: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Emoji: React.FC<IEmoji> = ({ className, emoji }): JSX.Element | null => {
|
||||||
|
const codepoints = toCodePoints(removeVS16s(emoji));
|
||||||
|
const filename = codepoints.join('-');
|
||||||
|
|
||||||
|
if (!filename) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
draggable='false'
|
||||||
|
className={classNames('emojione', className)}
|
||||||
|
alt={emoji}
|
||||||
|
src={joinPublicPath(`packs/emoji/${filename}.svg`)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Emoji;
|
|
@ -2,6 +2,7 @@ export { default as Avatar } from './avatar/avatar';
|
||||||
export { default as Button } from './button/button';
|
export { default as Button } from './button/button';
|
||||||
export { Card, CardBody, CardHeader, CardTitle } from './card/card';
|
export { Card, CardBody, CardHeader, CardTitle } from './card/card';
|
||||||
export { default as Column } from './column/column';
|
export { default as Column } from './column/column';
|
||||||
|
export { default as Emoji } from './emoji/emoji';
|
||||||
export { default as Form } from './form/form';
|
export { default as Form } from './form/form';
|
||||||
export { default as FormActions } from './form-actions/form-actions';
|
export { default as FormActions } from './form-actions/form-actions';
|
||||||
export { default as FormGroup } from './form-group/form-group';
|
export { default as FormGroup } from './form-group/form-group';
|
||||||
|
|
Loading…
Reference in a new issue