handle favourite toggling
Some checks failed
Chromatic / Run Chromatic (push) Has been cancelled

This commit is contained in:
ChaosExAnima 2025-11-25 16:23:19 +01:00
parent e9e15d5a6a
commit 1f5a52a1fc
No known key found for this signature in database
GPG Key ID: 8F2B333100FB6117
4 changed files with 64 additions and 11 deletions

View File

@ -10,6 +10,7 @@ export interface PickerContext {
hiddenGroups: string[];
recentlyUsed: string[];
favourites: string[];
setFavourite: (emojiCode: string) => void;
}
const pickerContext = createContext<PickerContext>({
@ -17,6 +18,9 @@ const pickerContext = createContext<PickerContext>({
hiddenGroups: [],
recentlyUsed: [],
favourites: [],
setFavourite: () => {
throw new Error('setFavourite not implemented');
},
});
export const PickerContextProvider = pickerContext.Provider;

View File

@ -70,13 +70,30 @@ export const MockEmojiPicker: FC<MockEmojiPickerProps> = ({
);
}, []);
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: Set up favourites management
const [favourites, setFavourites] = useState<string[]>(['🖤']);
const handleSetFavourite = useCallback((emojiCode: string) => {
const emojiKey = emojiCode;
setFavourites((prev) => {
const prevCodes = new Set(prev);
if (prevCodes.has(emojiKey)) {
prevCodes.delete(emojiKey);
} else {
prevCodes.add(emojiKey);
}
return Array.from(prevCodes);
});
}, []);
return (
<CustomEmojiProvider emojis={mockCustomEmojis}>
<PickerContextProvider
value={{ skinTone, hiddenGroups, favourites, recentlyUsed }}
value={{
skinTone,
hiddenGroups,
favourites,
recentlyUsed,
setFavourite: handleSetFavourite,
}}
>
<div className={classes.wrapper}>
{showSettings ? (

View File

@ -15,7 +15,11 @@ import ArrowIcon from '@/material-icons/400-24px/arrow_drop_down.svg?react';
import { Emoji } from '..';
import { groupKeysToNumber, mockCustomEmojis } from './constants';
import {
groupKeysToNumber,
mockCustomEmojis,
usePickerContext,
} from './constants';
import classes from './styles.module.css';
interface PickerGroupListProps {
@ -57,7 +61,7 @@ export const PickerGroupCustomList: FC<
// Determine which missing keys are Unicode and which are custom.
const [missingUnicodeKeys, missingCustomKeys] = useMemo(() => {
const emojisToKeys = emojis?.map(emojiToKey) ?? [];
const emojisToKeys = emojis?.map((e) => emojiToKey(e)) ?? [];
const unicodeKeys = emojiKeys
.filter((key) => !isCustomEmoji(key))
.map((code) => emojiToUnicodeHex(code))
@ -94,11 +98,23 @@ export const PickerGroupCustomList: FC<
});
}
// If there is an emoji but no key, remove it.
if (emojis !== null && emojis.length > emojiKeys.length) {
setEmojis(
(prevEmojis) =>
prevEmojis?.filter((emoji) => emojiKeys.includes(emojiToKey(emoji))) ??
[],
);
}
return <PickerGroupListInner emojis={emojis} {...props} />;
};
function emojiToKey(emoji: AnyEmojiData): string {
return 'hexcode' in emoji ? emoji.hexcode : `:${emoji.shortcode}:`;
function emojiToKey(emoji: AnyEmojiData, hexcode = true): string {
if ('shortcode' in emoji) {
return `:${emoji.shortcode}:`;
}
return hexcode ? emoji.hexcode : emoji.unicode;
}
function mergeNewEmojis(
@ -106,6 +122,9 @@ function mergeNewEmojis(
newEmojis: AnyEmojiData[],
emojiKeys: string[],
): AnyEmojiData[] {
if (newEmojis.length === 0) {
return currentEmojis;
}
const allEmojis = new Map([
...currentEmojis.map(
(emoji) => [emojiToKey(emoji), emoji] satisfies [string, AnyEmojiData],
@ -117,11 +136,7 @@ function mergeNewEmojis(
return (
emojiKeys
.map((key) =>
isCustomEmoji(key)
? allEmojis.get(key)
: allEmojis.get(emojiToUnicodeHex(key)),
)
.map((key) => allEmojis.get(emojiToUnicodeHex(key)))
// Discard any missing emojis.
.filter((e) => !!e)
);
@ -177,12 +192,21 @@ const PickerListEmoji: FC<PickerListEmojiProps> = ({ emoji, onClick }) => {
const handleClick: MouseEventHandler = useCallback(() => {
onClick(emoji);
}, [emoji, onClick]);
const { setFavourite } = usePickerContext();
const handleContextMenu: MouseEventHandler = useCallback(
(event) => {
event.preventDefault();
setFavourite(emojiToKey(emoji, false));
},
[emoji, setFavourite],
);
return (
<li>
<button
type='button'
title={'unicode' in emoji ? emoji.label : `:${emoji.shortcode}:`}
onClick={handleClick}
onContextMenu={handleContextMenu}
className={classes.listButton}
>
<Emoji

View File

@ -12,6 +12,7 @@ import {
EMOJIS_WITH_LIGHT_BORDER,
} from './constants';
import type { CustomEmojiMapArg, ExtraCustomEmojiMap } from './types';
import { isCustomEmoji } from './utils';
// Misc codes that have special handling
const SKIER_CODE = 0x26f7;
@ -23,6 +24,9 @@ const SPEECH_BUBBLE_CODE = 0x1f5e8;
const MS_CLAUS_CODE = 0x1f936;
export function emojiToUnicodeHex(emoji: string): string {
if (isCustomEmoji(emoji)) {
return emoji;
}
const codes: number[] = [];
for (const char of emoji) {
const code = char.codePointAt(0);
@ -30,6 +34,10 @@ export function emojiToUnicodeHex(emoji: string): string {
codes.push(code);
}
}
// Strip trailing variation selector.
if (codes.at(-1) === VARIATION_SELECTOR_CODE) {
codes.pop();
}
return hexNumbersToString(codes);
}