mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-24 20:26:24 +00:00
Fun picker improvements
This commit is contained in:
@@ -2,35 +2,170 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import type { EmojiVariantData } from './data/emojis';
|
||||
import type { FunImageAriaProps } from './types';
|
||||
|
||||
export type FunEmojiSize = 16 | 32;
|
||||
export const FUN_STATIC_EMOJI_CLASS = 'FunStaticEmoji';
|
||||
export const FUN_INLINE_EMOJI_CLASS = 'FunInlineEmoji';
|
||||
|
||||
export type FunEmojiProps = Readonly<{
|
||||
role: 'img' | 'presentation';
|
||||
'aria-label': string;
|
||||
size: FunEmojiSize;
|
||||
emoji: EmojiVariantData;
|
||||
}>;
|
||||
function getEmojiJumboUrl(emoji: EmojiVariantData): string {
|
||||
return `emoji://jumbo?emoji=${encodeURIComponent(emoji.value)}`;
|
||||
}
|
||||
|
||||
const sizeToClassName: Record<FunEmojiSize, string> = {
|
||||
16: 'FunEmoji--Size16',
|
||||
32: 'FunEmoji--Size32',
|
||||
};
|
||||
export type FunStaticEmojiSize =
|
||||
| 16
|
||||
| 18
|
||||
| 20
|
||||
| 24
|
||||
| 28
|
||||
| 32
|
||||
| 36
|
||||
| 40
|
||||
| 48
|
||||
| 56
|
||||
| 64
|
||||
| 66;
|
||||
|
||||
export function FunEmoji(props: FunEmojiProps): JSX.Element {
|
||||
export enum FunJumboEmojiSize {
|
||||
Small = 32,
|
||||
Medium = 36,
|
||||
Large = 40,
|
||||
ExtraLarge = 48,
|
||||
Max = 56,
|
||||
}
|
||||
|
||||
const funStaticEmojiSizeClasses = {
|
||||
16: 'FunStaticEmoji--Size16',
|
||||
18: 'FunStaticEmoji--Size18',
|
||||
20: 'FunStaticEmoji--Size20',
|
||||
24: 'FunStaticEmoji--Size24',
|
||||
28: 'FunStaticEmoji--Size28',
|
||||
32: 'FunStaticEmoji--Size32',
|
||||
36: 'FunStaticEmoji--Size36',
|
||||
40: 'FunStaticEmoji--Size40',
|
||||
48: 'FunStaticEmoji--Size48',
|
||||
56: 'FunStaticEmoji--Size56',
|
||||
64: 'FunStaticEmoji--Size64',
|
||||
66: 'FunStaticEmoji--Size66',
|
||||
} satisfies Record<FunStaticEmojiSize, string>;
|
||||
|
||||
export type FunStaticEmojiProps = FunImageAriaProps &
|
||||
Readonly<{
|
||||
size: FunStaticEmojiSize;
|
||||
emoji: EmojiVariantData;
|
||||
}>;
|
||||
|
||||
export function FunStaticEmoji(props: FunStaticEmojiProps): JSX.Element {
|
||||
const emojiJumboUrl = useMemo(() => {
|
||||
return getEmojiJumboUrl(props.emoji);
|
||||
}, [props.emoji]);
|
||||
return (
|
||||
<div
|
||||
role={props.role}
|
||||
aria-label={props['aria-label']}
|
||||
className={classNames('FunEmoji', sizeToClassName[props.size])}
|
||||
data-emoji-key={props.emoji.key}
|
||||
data-emoji-value={props.emoji.value}
|
||||
className={classNames(
|
||||
FUN_STATIC_EMOJI_CLASS,
|
||||
funStaticEmojiSizeClasses[props.size]
|
||||
)}
|
||||
style={
|
||||
{
|
||||
'--fun-emoji-sheet-x': props.emoji.sheetX,
|
||||
'--fun-emoji-sheet-y': props.emoji.sheetY,
|
||||
'--fun-emoji-jumbo-image': `url(${emojiJumboUrl})`,
|
||||
} as CSSProperties
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export type StaticEmojiBlotProps = FunStaticEmojiProps;
|
||||
|
||||
const TRANSPARENT_PIXEL =
|
||||
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E";
|
||||
|
||||
/**
|
||||
* This is for Quill. It should stay in sync with <FunStaticEmoji> as much as possible.
|
||||
*
|
||||
* The biggest difference between them is that the emoji blot uses an `<img>`
|
||||
* tag with a single transparent pixel in order to render the selection cursor
|
||||
* correctly in the browser when using `contenteditable`
|
||||
*
|
||||
* We need to use the `<img>` bec ause
|
||||
*/
|
||||
export function createStaticEmojiBlot(
|
||||
node: HTMLImageElement,
|
||||
props: StaticEmojiBlotProps
|
||||
): void {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
node.src = TRANSPARENT_PIXEL;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
node.role = props.role;
|
||||
node.classList.add(FUN_STATIC_EMOJI_CLASS);
|
||||
node.classList.add(funStaticEmojiSizeClasses[props.size]);
|
||||
node.classList.add('FunStaticEmoji--Blot');
|
||||
if (props['aria-label'] != null) {
|
||||
node.setAttribute('aria-label', props['aria-label']);
|
||||
}
|
||||
node.style.setProperty('--fun-emoji-sheet-x', `${props.emoji.sheetX}`);
|
||||
node.style.setProperty('--fun-emoji-sheet-y', `${props.emoji.sheetY}`);
|
||||
node.style.setProperty(
|
||||
'--fun-emoji-jumbo-image',
|
||||
`url(${getEmojiJumboUrl(props.emoji)})`
|
||||
);
|
||||
}
|
||||
|
||||
export type FunInlineEmojiProps = FunImageAriaProps &
|
||||
Readonly<{
|
||||
size?: number | null;
|
||||
emoji: EmojiVariantData;
|
||||
}>;
|
||||
|
||||
export function FunInlineEmoji(props: FunInlineEmojiProps): JSX.Element {
|
||||
const emojiJumboUrl = useMemo(() => {
|
||||
return getEmojiJumboUrl(props.emoji);
|
||||
}, [props.emoji]);
|
||||
return (
|
||||
<svg
|
||||
role="none"
|
||||
className={FUN_INLINE_EMOJI_CLASS}
|
||||
width={64}
|
||||
height={64}
|
||||
viewBox="0 0 64 64"
|
||||
data-emoji-key={props.emoji.key}
|
||||
data-emoji-value={props.emoji.value}
|
||||
style={
|
||||
{
|
||||
'--fun-inline-emoji-size':
|
||||
props.size != null ? `${props.size}px` : null,
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
{/*
|
||||
<foreignObject> is used to embed HTML+CSS within SVG, the HTML+CSS gets
|
||||
rendered at a normal size then scaled by the SVG. This allows us to make
|
||||
use of CSS features that are not supported by SVG while still using SVG's
|
||||
ability to scale relative to the parent's font-size.
|
||||
*/}
|
||||
<foreignObject x={0} y={0} width={64} height={64}>
|
||||
<span aria-hidden className="FunEmojiSelectionText">
|
||||
{props.emoji.value}
|
||||
</span>
|
||||
<span
|
||||
role={props.role}
|
||||
aria-label={props['aria-label']}
|
||||
className="FunInlineEmoji__Image"
|
||||
style={
|
||||
{
|
||||
'--fun-emoji-sheet-x': props.emoji.sheetX,
|
||||
'--fun-emoji-sheet-y': props.emoji.sheetY,
|
||||
'--fun-emoji-jumbo-image': `url(${emojiJumboUrl})`,
|
||||
} as CSSProperties
|
||||
}
|
||||
/>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user