/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ import * as EmojiPickerActionCreators from '@app/actions/EmojiPickerActionCreators'; import {type AutocompleteOption, isEmoji, isMeme, isSticker} from '@app/components/channel/Autocomplete'; import styles from '@app/components/channel/AutocompleteEmoji.module.css'; import {AutocompleteItem} from '@app/components/channel/AutocompleteItem'; import GuildStore from '@app/stores/GuildStore'; import {shouldUseNativeEmoji} from '@app/utils/EmojiUtils'; import {getEmojiDisplayData} from '@app/utils/SkinToneUtils'; import {useLingui} from '@lingui/react/macro'; import {MusicNoteIcon} from '@phosphor-icons/react'; import {observer} from 'mobx-react-lite'; import type React from 'react'; const SectionHeading = observer(({children}: {children: React.ReactNode}) => (
{children}
)); export const AutocompleteEmoji = observer( ({ onSelect, keyboardFocusIndex, hoverIndex, options, onMouseEnter, onMouseLeave, rowRefs, }: { onSelect: (option: AutocompleteOption) => void; keyboardFocusIndex: number; hoverIndex: number; options: Array; onMouseEnter: (index: number) => void; onMouseLeave: () => void; rowRefs?: React.MutableRefObject>; }) => { const {t} = useLingui(); const emojis = options.filter(isEmoji); const stickers = options.filter(isSticker); const memes = options.filter(isMeme); const handleEmojiSelect = (option: AutocompleteOption) => { if (isEmoji(option)) EmojiPickerActionCreators.trackEmojiUsage(option.emoji); onSelect(option); }; return ( <> {emojis.length > 0 && ( <> {t`Emojis`} {emojis.map((option, index) => { const isUnicodeEmoji = !option.emoji.guildId && !option.emoji.id; const useNativeRendering = shouldUseNativeEmoji && isUnicodeEmoji; const {surrogates: displaySurrogates, url: displayUrl} = getEmojiDisplayData(option.emoji); return ( {displaySurrogates} ) : ( {option.emoji.name} ) } isKeyboardSelected={index === keyboardFocusIndex} isHovered={index === hoverIndex} onSelect={() => handleEmojiSelect(option)} onMouseEnter={() => onMouseEnter(index)} onMouseLeave={onMouseLeave} innerRef={ rowRefs ? (node) => { rowRefs.current[index] = node; } : undefined } /> ); })} {(stickers.length > 0 || memes.length > 0) &&
} )} {stickers.length > 0 && ( <> {t`Stickers`} {stickers.map((option, index) => { const currentIndex = emojis.length + index; return ( 0 ? option.sticker.tags.join(', ') : option.sticker.description || undefined } icon={
{option.sticker.name}
} isKeyboardSelected={currentIndex === keyboardFocusIndex} isHovered={currentIndex === hoverIndex} onSelect={() => onSelect(option)} onMouseEnter={() => onMouseEnter(currentIndex)} onMouseLeave={onMouseLeave} innerRef={ rowRefs ? (node) => { rowRefs.current[currentIndex] = node; } : undefined } /> ); })} {memes.length > 0 &&
} )} {memes.length > 0 && ( <> {t`Media`} {memes.map((option, index) => { const currentIndex = emojis.length + stickers.length + index; return ( 0 ? option.meme.tags.join(', ') : undefined} icon={
{option.meme.contentType.startsWith('video/') || option.meme.contentType.includes('gif') ? (
} isKeyboardSelected={currentIndex === keyboardFocusIndex} isHovered={currentIndex === hoverIndex} onSelect={() => onSelect(option)} onMouseEnter={() => onMouseEnter(currentIndex)} onMouseLeave={onMouseLeave} innerRef={ rowRefs ? (node) => { rowRefs.current[currentIndex] = node; } : undefined } /> ); })} )} ); }, );