Merge branch 'next-emoji-reacts' into 'next'

Next: emoji reacts part 1

See merge request soapbox-pub/soapbox-fe!1161
This commit is contained in:
Alex Gleason
2022-04-03 18:09:27 +00:00
52 changed files with 2034 additions and 1639 deletions

View File

@@ -0,0 +1,55 @@
import classNames from 'classnames';
import React from 'react';
import { Emoji, HStack } from 'soapbox/components/ui';
interface IEmojiButton {
emoji: string,
onClick: React.EventHandler<React.MouseEvent>,
className?: string,
tabIndex?: number,
}
const EmojiButton: React.FC<IEmojiButton> = ({ emoji, className, onClick, tabIndex }): JSX.Element => {
return (
<button className={classNames(className)} onClick={onClick} tabIndex={tabIndex}>
<Emoji className='w-8 h-8 duration-100 hover:scale-125' emoji={emoji} />
</button>
);
};
interface IEmojiSelector {
emojis: string[],
onReact: (emoji: string) => void,
visible?: boolean,
focused?: boolean,
}
const EmojiSelector: React.FC<IEmojiSelector> = ({ emojis, onReact, visible = false, focused = false }): JSX.Element => {
const handleReact = (emoji: string): React.EventHandler<React.MouseEvent> => {
return (e) => {
onReact(emoji);
e.preventDefault();
e.stopPropagation();
};
};
return (
<HStack
space={2}
className={classNames('bg-white dark:bg-slate-900 p-3 rounded-full shadow-md z-[999] w-max')}
>
{emojis.map((emoji, i) => (
<EmojiButton
key={i}
emoji={emoji}
onClick={handleReact(emoji)}
tabIndex={(visible || focused) ? 0 : -1}
/>
))}
</HStack>
);
};
export default EmojiSelector;

View File

@@ -0,0 +1,53 @@
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={className}
alt={emoji}
src={joinPublicPath(`packs/emoji/${filename}.svg`)}
/>
);
};
export default Emoji;

View File

@@ -4,15 +4,10 @@ import InlineSVG from 'react-inlinesvg';
import Text from '../text/text';
interface IIconButton {
alt?: string,
className?: string,
interface IIconButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
iconClassName?: string,
disabled?: boolean,
src: string,
onClick?: () => void,
text?: string,
title?: string,
transparent?: boolean
}

View File

@@ -2,6 +2,8 @@ export { default as Avatar } from './avatar/avatar';
export { default as Button } from './button/button';
export { Card, CardBody, CardHeader, CardTitle } from './card/card';
export { default as Column } from './column/column';
export { default as Emoji } from './emoji/emoji';
export { default as EmojiSelector } from './emoji-selector/emoji-selector';
export { default as Form } from './form/form';
export { default as FormActions } from './form-actions/form-actions';
export { default as FormGroup } from './form-group/form-group';