import type { JSX, Resource, } from 'solid-js'; import { For, Show, createMemo, createResource, } from 'solid-js'; const ORIGIN_GROUP_DATA_KEY = 'data-by-group.json'; const ORIGIN_COMPONENTS_KEY = 'data-emoji-components.json'; const ORIGIN_EMOJI_KEY = 'data-by-emoji.json'; const CDN_URL = 'https://unpkg.com/unicode-emoji-json@0.6.0/'; let GROUP_DATA_KEY = `${CDN_URL}${ORIGIN_GROUP_DATA_KEY}`; let COMPONENTS_KEY = `${CDN_URL}${ORIGIN_COMPONENTS_KEY}`; let EMOJI_KEY = `${CDN_URL}${ORIGIN_EMOJI_KEY}`; export function setCDN(url: string): void { GROUP_DATA_KEY = `${url}${ORIGIN_GROUP_DATA_KEY}`; COMPONENTS_KEY = `${url}${ORIGIN_COMPONENTS_KEY}`; EMOJI_KEY = `${url}${ORIGIN_EMOJI_KEY}`; } export function setComponentsURL(url: string): void { COMPONENTS_KEY = url; } export function setEmojiURL(url: string): void { EMOJI_KEY = url; } export function setEmojiGroupURL(url: string): void { GROUP_DATA_KEY = url; } export interface Emoji { emoji: string; skin_tone_support: boolean; name: string; slug: string; unicode_version: string; emoji_version: string; } export type EmojiData = Record; export interface EmojiGroupData { name: string; slug: string; emojis: Emoji[]; } export type EmojiComponents = Record; let EMOJI_DATA: EmojiData | undefined; let EMOJI_COMPONENTS: EmojiComponents | undefined; let EMOJI_GROUP_DATA: EmojiGroupData[] | undefined; export async function loadEmojiData(): Promise { if (!EMOJI_DATA) { const response = await fetch(EMOJI_KEY); EMOJI_DATA = await response.json() as EmojiData; } return EMOJI_DATA; } export async function loadEmojiGroupData(): Promise { if (!EMOJI_GROUP_DATA) { const response = await fetch(GROUP_DATA_KEY); EMOJI_GROUP_DATA = await response.json() as EmojiGroupData[]; } return EMOJI_GROUP_DATA; } export async function loadEmojiComponents(): Promise { if (!EMOJI_COMPONENTS) { const response = await fetch(COMPONENTS_KEY); EMOJI_COMPONENTS = await response.json() as EmojiComponents; } return EMOJI_COMPONENTS; } export function setEmojiData(data: EmojiData): void { EMOJI_DATA = data; } export function setEmojiComponents(data: EmojiComponents): void { EMOJI_COMPONENTS = data; } export function useEmojiData(): Resource { const [data] = createResource(loadEmojiData); return data; } export function useEmojiComponents(): Resource { const [data] = createResource(loadEmojiComponents); return data; } export function useEmojiGroupData(): Resource { const [data] = createResource(loadEmojiGroupData); return data; } export type EmojiSkinTone = | 'light' | 'medium-light' | 'medium' | 'medium-dark' | 'dark'; const SKIN_TONE_TO_COMPONENT: Record = { light: 'light_skin_tone', 'medium-light': 'medium_light_skin_tone', medium: 'medium_skin_tone', 'medium-dark': 'medium_dark_skin_tone', dark: 'dark_skin_tone', }; export function convertSkinToneToComponent( components: EmojiComponents, skinTone?: EmojiSkinTone, ): string | undefined { if (skinTone) { return components[SKIN_TONE_TO_COMPONENT[skinTone]]; } return undefined; } const VARIATION = '\uFE0F'; const ZWJ = '\u200D'; export function getEmojiWithSkinTone( emojis: EmojiData, emoji: Emoji, skinTone?: string, ): string { if (!(skinTone && emoji.skin_tone_support)) { return emoji.emoji; } const emojiWithSkinTone = emoji.emoji.split(ZWJ) .map((chunk) => { if (chunk in emojis && emojis[chunk].skin_tone_support) { return `${chunk}${skinTone}`; } return chunk; }) .join(ZWJ); const emojiWithoutVariation = emojiWithSkinTone.replaceAll(`${VARIATION}${skinTone}`, `${skinTone}`); return emojiWithoutVariation; } function DEFAULT_EMOJI_RENDER( emojis: EmojiData, emoji: Emoji, components: EmojiComponents, skinTone?: EmojiSkinTone, ): JSX.Element { return ( {getEmojiWithSkinTone( emojis, emoji, convertSkinToneToComponent(components, skinTone), )} ); } export type EmojiEventHandler = (emoji: Emoji, event: T & { currentTarget: HTMLButtonElement; target: Element; }) => void; export interface EmojiPickerProps { skinTone?: EmojiSkinTone; filter?: (emoji: Emoji) => boolean; renderEmoji?: ( emojis: EmojiData, emoji: Emoji, components: EmojiComponents, skinTone?: EmojiSkinTone, ) => JSX.Element; onEmojiClick?: EmojiEventHandler; onEmojiFocus?: EmojiEventHandler; onEmojiHover?: EmojiEventHandler; } export function EmojiPicker(props: EmojiPickerProps): JSX.Element { const emojiData = useEmojiData(); const componentData = useEmojiComponents(); const emojiGroupData = useEmojiGroupData(); const renderEmoji = createMemo(() => props.renderEmoji || DEFAULT_EMOJI_RENDER); return (
{createMemo(() => { const emoji = emojiData(); const components = componentData(); const emojiGroup = emojiGroupData(); if (emoji && components && emojiGroup) { return ( {(group): JSX.Element => (
{group.name}
{(emojiItem): JSX.Element => ( )}
)}
); } return null; })() }
); }