chore: Update emoji picker in widget (#14741)
This commit is contained in:
@@ -7,7 +7,7 @@ const selectedColor = defineModel({ type: String, default: '' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between gap-1 px-0.5 mx-2 py-1">
|
||||
<div class="flex items-center justify-between gap-1 px-0.5 mx-2 py-1 mb-px">
|
||||
<button
|
||||
v-for="color in ICON_COLORS"
|
||||
:key="color.value"
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
getEmojiTint,
|
||||
getRecentEmojis,
|
||||
addRecentEmoji,
|
||||
} from './helper';
|
||||
} from 'shared/components/emoji/pickerHelper';
|
||||
|
||||
const props = defineProps({
|
||||
// 'both' shows Icons + Emojis tabs; 'emoji' shows only the emoji panel.
|
||||
@@ -229,7 +229,7 @@ const selectEmoji = emoji => {
|
||||
:key="icon.name"
|
||||
type="button"
|
||||
:title="icon.name"
|
||||
class="flex items-center justify-center !p-0 w-full max-w-[2rem] aspect-square transition-colors rounded-lg hover:bg-[var(--ep-tint)] active:enabled:scale-[0.97]"
|
||||
class="flex items-center justify-center !p-0 size-8 transition-colors rounded-lg aspect-square hover:bg-[var(--ep-tint)] active:enabled:scale-[0.97]"
|
||||
@click="selectIcon(icon)"
|
||||
>
|
||||
<span
|
||||
@@ -287,7 +287,7 @@ const selectEmoji = emoji => {
|
||||
:key="`${section.name}-${emoji.slug}`"
|
||||
type="button"
|
||||
:title="emoji.name"
|
||||
class="flex items-center justify-center !p-0 w-full max-w-[2rem] text-xl transition-colors rounded-lg aspect-square hover:bg-[var(--ep-tint)] active:enabled:scale-[0.97]"
|
||||
class="flex items-center justify-center !p-0 size-8 w-full max-w-[2rem] text-xl transition-colors rounded-lg aspect-square hover:bg-[var(--ep-tint)] active:enabled:scale-[0.97]"
|
||||
@mouseenter="applyEmojiTint($event, emoji.emoji)"
|
||||
@click="selectEmoji(emoji)"
|
||||
>
|
||||
|
||||
@@ -29,10 +29,6 @@ export const ICON_COLORS = [
|
||||
|
||||
export const DEFAULT_ICON_COLOR = '#3B82F6';
|
||||
|
||||
// Recently used emojis persisted in localStorage.
|
||||
export const RECENT_EMOJI_KEY = 'emoji-icon-picker.recent-emojis';
|
||||
export const MAX_RECENT_EMOJIS = 16;
|
||||
|
||||
export const PICKER_MODE = {
|
||||
BOTH: 'both',
|
||||
EMOJI: 'emoji',
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import emojiGroups from 'shared/components/emoji/emojisGroup.json';
|
||||
import { MAX_RECENT_EMOJIS, RECENT_EMOJI_KEY } from './constants';
|
||||
|
||||
const matchesSearch = (emoji, term) =>
|
||||
emoji.slug.replaceAll('_', ' ').includes(term) ||
|
||||
emoji.name.toLowerCase().includes(term);
|
||||
|
||||
// Emoji sections for the search term; prepends "Frequently used" when idle.
|
||||
export const buildEmojiSections = (search, recentEmojis, frequentLabel) => {
|
||||
const term = search.trim().toLowerCase();
|
||||
if (term) {
|
||||
return emojiGroups
|
||||
.map(({ name, emojis }) => ({
|
||||
name,
|
||||
emojis: emojis.filter(e => matchesSearch(e, term)),
|
||||
}))
|
||||
.filter(group => group.emojis.length);
|
||||
}
|
||||
return [
|
||||
...(recentEmojis.length
|
||||
? [{ name: frequentLabel, emojis: recentEmojis }]
|
||||
: []),
|
||||
...emojiGroups,
|
||||
];
|
||||
};
|
||||
|
||||
// Samples an emoji's average color (via canvas) as a translucent hover tint. Cached per emoji.
|
||||
const emojiTintCache = new Map();
|
||||
const NEUTRAL_TINT = 'rgb(var(--slate-9) / 0.12)';
|
||||
|
||||
const sampleEmojiTint = emoji => {
|
||||
const canvas = Object.assign(document.createElement('canvas'), {
|
||||
width: 16,
|
||||
height: 16,
|
||||
});
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
ctx.font = '14px serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(emoji, 8, 9);
|
||||
|
||||
const { data } = ctx.getImageData(0, 0, 16, 16);
|
||||
const pixels = Array.from(
|
||||
{ length: data.length / 4 },
|
||||
(_, i) => i * 4
|
||||
).filter(i => data[i + 3] > 16);
|
||||
if (!pixels.length) return NEUTRAL_TINT;
|
||||
|
||||
const avg = offset =>
|
||||
Math.round(
|
||||
pixels.reduce((sum, i) => sum + data[i + offset], 0) / pixels.length
|
||||
);
|
||||
|
||||
return `rgba(${avg(0)}, ${avg(1)}, ${avg(2)}, 0.16)`;
|
||||
};
|
||||
|
||||
export const getEmojiTint = emoji => {
|
||||
if (!emojiTintCache.has(emoji)) {
|
||||
let tint = NEUTRAL_TINT;
|
||||
try {
|
||||
tint = sampleEmojiTint(emoji);
|
||||
} catch {
|
||||
/* canvas unavailable */
|
||||
}
|
||||
emojiTintCache.set(emoji, tint);
|
||||
}
|
||||
return emojiTintCache.get(emoji);
|
||||
};
|
||||
|
||||
export const getRecentEmojis = () => {
|
||||
try {
|
||||
const stored = JSON.parse(localStorage.getItem(RECENT_EMOJI_KEY) ?? '[]');
|
||||
return Array.isArray(stored) ? stored : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const addRecentEmoji = emoji => {
|
||||
const updated = [
|
||||
emoji,
|
||||
...getRecentEmojis().filter(item => item.slug !== emoji.slug),
|
||||
].slice(0, MAX_RECENT_EMOJIS);
|
||||
try {
|
||||
localStorage.setItem(RECENT_EMOJI_KEY, JSON.stringify(updated));
|
||||
} catch {
|
||||
/* private mode; recents are best-effort */
|
||||
}
|
||||
return updated;
|
||||
};
|
||||
Reference in New Issue
Block a user