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:
55
app/soapbox/components/ui/emoji-selector/emoji-selector.tsx
Normal file
55
app/soapbox/components/ui/emoji-selector/emoji-selector.tsx
Normal 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;
|
||||
53
app/soapbox/components/ui/emoji/emoji.tsx
Normal file
53
app/soapbox/components/ui/emoji/emoji.tsx
Normal 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;
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user