// Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { createContext, memo, useCallback, useContext, useState, } from 'react'; import { strictAssert } from '../../util/assert.js'; import type { LocalizerType } from '../../types/I18N.js'; import type { StickerPackType, StickerType, } from '../../state/ducks/stickers.js'; import type { EmojiSkinTone, EmojiParentKey } from './data/emojis.js'; import type { FunGifSelection, GifType } from './panels/FunPanelGifs.js'; import { FunPickerTabKey } from './constants.js'; import type { fetchGifsFeatured, fetchGifsSearch } from './data/gifs.js'; import type { tenorDownload } from './data/tenor.js'; import type { FunEmojiSelection } from './panels/FunPanelEmojis.js'; import type { FunStickerSelection } from './panels/FunPanelStickers.js'; import { FunEmojiLocalizationProvider } from './FunEmojiLocalizationProvider.js'; export type FunContextSmartProps = Readonly<{ i18n: LocalizerType; // Recents recentEmojis: ReadonlyArray; recentStickers: ReadonlyArray; recentGifs: ReadonlyArray; // Emojis emojiSkinToneDefault: EmojiSkinTone | null; onEmojiSkinToneDefaultChange: (emojiSkinTone: EmojiSkinTone) => void; onOpenCustomizePreferredReactionsModal: () => void; onSelectEmoji: (emojiSelection: FunEmojiSelection) => void; // Stickers installedStickerPacks: ReadonlyArray; showStickerPickerHint: boolean; onClearStickerPickerHint: () => unknown; onSelectSticker: (stickerSelection: FunStickerSelection) => void; // GIFs fetchGifsFeatured: typeof fetchGifsFeatured; fetchGifsSearch: typeof fetchGifsSearch; fetchGif: typeof tenorDownload; onSelectGif: (gifSelection: FunGifSelection) => void; }>; export type FunContextProps = FunContextSmartProps & Readonly<{ // Open state onOpenChange: (open: boolean) => void; // Current Tab tab: FunPickerTabKey; onChangeTab: (key: FunPickerTabKey) => unknown; // Search storedSearchInput: string; onStoredSearchInputChange: (nextSearchInput: string) => void; shouldAutoFocus: boolean; onChangeShouldAutoFocus: (shouldAutoFocus: boolean) => void; }>; export const FunContext = createContext(null); export function useFunContext(): FunContextProps { const fun = useContext(FunContext); strictAssert(fun != null, 'Must be wrapped with '); return fun; } type FunProviderInnerProps = FunContextProps & { children: React.ReactNode; }; const FunProviderInner = memo(function FunProviderInner( props: FunProviderInnerProps ): JSX.Element { return ( {props.children} ); }); export type FunProviderProps = FunContextSmartProps & { children: React.ReactNode; }; export const FunProvider = memo(function FunProvider( props: FunProviderProps ): JSX.Element { // Current Tab const [tab, setTab] = useState(FunPickerTabKey.Emoji); const handleChangeTab = useCallback((key: FunPickerTabKey) => { setTab(key); }, []); // Search Input const [storedSearchInput, setStoredSearchInput] = useState(''); const handleStoredSearchInputChange = useCallback( (newSearchInput: string) => { setStoredSearchInput(newSearchInput); }, [] ); const [shouldAutoFocus, setShouldAutoFocus] = useState(true); const handleChangeShouldAutofocus = useCallback( (nextShouldAutoFocus: boolean) => { setShouldAutoFocus(nextShouldAutoFocus); }, [] ); const handleOpenChange = useCallback((open: boolean) => { if (open) { return; } setStoredSearchInput(''); setShouldAutoFocus(true); }, []); return ( {props.children} ); });