diff --git a/.changeset/thirty-cameras-warn.md b/.changeset/thirty-cameras-warn.md new file mode 100644 index 0000000000000..37c9c83761a2a --- /dev/null +++ b/.changeset/thirty-cameras-warn.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Improves the performance of the Emoji Picker. diff --git a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts index 8b24cd0c29c76..bb76f7388c179 100644 --- a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts +++ b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts @@ -46,6 +46,7 @@ export const deleteEmojiCustom = (emojiData: IEmoji) => { } removeFromRecent(emojiData.name, emoji.packages.base.emojisByCategory.recent); + emoji.dispatchUpdate(); }; export const updateEmojiCustom = (emojiData: IEmoji) => { @@ -93,6 +94,8 @@ export const updateEmojiCustom = (emojiData: IEmoji) => { if (previousExists) { replaceEmojiInRecent({ oldEmoji: emojiData.previousName, newEmoji: emojiData.name }); } + + emoji.dispatchUpdate(); }; const customRender = (html: string) => { @@ -103,6 +106,7 @@ const customRender = (html: string) => { `]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(${emojisMatchGroup})`, 'gi', ); + emoji.dispatchUpdate(); } html = html.replace(emoji.packages.emojiCustom._regexp!, (shortname) => { @@ -160,6 +164,7 @@ Meteor.startup(() => { }; } } + emoji.dispatchUpdate(); } catch (e) { console.error('Error getting custom emoji', e); } diff --git a/apps/meteor/app/emoji-emojione/client/hooks/useEmojiOne.ts b/apps/meteor/app/emoji-emojione/client/hooks/useEmojiOne.ts index 6264f361b6712..12e5496073e7a 100644 --- a/apps/meteor/app/emoji-emojione/client/hooks/useEmojiOne.ts +++ b/apps/meteor/app/emoji-emojione/client/hooks/useEmojiOne.ts @@ -35,8 +35,10 @@ export const useEmojiOne = () => { } } } + emoji.dispatchUpdate(); }); }, []); + useEffect(() => { if (emoji.packages.emojione) { // Additional settings -- ascii emojis @@ -51,6 +53,7 @@ export const useEmojiOne = () => { }; void ascii(); + emoji.dispatchUpdate(); } }, [convertAsciiToEmoji]); }; diff --git a/apps/meteor/app/emoji/client/helpers.ts b/apps/meteor/app/emoji/client/helpers.ts index a203216640f5e..7a44ede46d19f 100644 --- a/apps/meteor/app/emoji/client/helpers.ts +++ b/apps/meteor/app/emoji/client/helpers.ts @@ -1,37 +1,69 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; +import type { TranslationKey } from '@rocket.chat/ui-contexts'; import type { EmojiCategory, EmojiItem } from '.'; -import { emoji } from './lib'; +import { emoji, emojiEmitter } from './lib'; export const CUSTOM_CATEGORY = 'rocket'; +type RowItem = Array; +type RowDivider = { category: string; i18n: TranslationKey }; +type LoadMoreItem = { loadMore: true }; +export type EmojiPickerItem = RowItem | RowDivider | LoadMoreItem; + +export type CategoriesIndexes = { key: string; index: number }[]; + +export const isRowDivider = (item: EmojiPickerItem): item is RowDivider => 'i18n' in item; +export const isLoadMore = (item: EmojiPickerItem): item is LoadMoreItem => 'loadMore' in item; + +export const createEmojiListByCategorySubscription = ( + customItemsLimit: number, + actualTone: number, + recentEmojis: string[], + setRecentEmojis: (emojis: string[]) => void, +): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ReturnType] => { + let result: ReturnType = [[], []]; + updateRecent(recentEmojis); + + const sub = (cb: () => void) => { + result = createPickerEmojis(customItemsLimit, actualTone, recentEmojis, setRecentEmojis); + + return emojiEmitter.on('updated', () => { + result = createPickerEmojis(customItemsLimit, actualTone, recentEmojis, setRecentEmojis); + cb(); + }); + }; + + return [sub, () => result]; +}; + export const createPickerEmojis = ( customItemsLimit: number, actualTone: number, recentEmojis: string[], setRecentEmojis: (emojis: string[]) => void, -) => { +): [EmojiPickerItem[], CategoriesIndexes] => { const categories = getCategoriesList(); + const categoriesIndexes: CategoriesIndexes = []; - const mappedCategories = categories.map((category) => ({ - key: category.key, - i18n: category.i18n, - emojis: { - list: createEmojiList(category.key, actualTone, recentEmojis, setRecentEmojis), - limit: category.key === CUSTOM_CATEGORY ? customItemsLimit : null, - }, - })); + const mappedCategories = categories.reduce((acc, category) => { + categoriesIndexes.push({ key: category.key, index: acc.length }); + acc.push({ category: category.key, i18n: category.i18n }); + acc.push(...createEmojiList(customItemsLimit, category.key, actualTone, recentEmojis, setRecentEmojis)); + return acc; + }, []); - return mappedCategories; + return [mappedCategories, categoriesIndexes]; }; export const createEmojiList = ( + customItemsLimit: number, category: string, actualTone: number | null, recentEmojis: string[], setRecentEmojis: (emojis: string[]) => void, -) => { - const emojiList: EmojiItem[] = []; +): (RowItem | LoadMoreItem)[] => { + const items: RowItem = []; const emojiPackages = Object.values(emoji.packages); emojiPackages.forEach((emojiPackage) => { @@ -57,11 +89,23 @@ export const createEmojiList = ( if (!image) { continue; } - emojiList.push({ emoji: current, image }); + items.push({ emoji: current, image, category }); } }); - return emojiList; + const rowCount = 9; + const rowList: Array = Array.from({ length: Math.ceil(items.length / rowCount) }).map(() => []); + + for (let i = 0; i < rowList.length; i++) { + const row = items.slice(i * rowCount, i * rowCount + rowCount); + rowList[i] = row; + } + + if (category === CUSTOM_CATEGORY && customItemsLimit < items.length) { + rowList.push({ loadMore: true }); + } + + return rowList; }; export const getCategoriesList = () => { @@ -149,6 +193,8 @@ export const removeFromRecent = (emoji: string, recentEmojis: string[], setRecen setRecentEmojis?.(recentEmojis); }; +// There's no need to dispatchUpdate here. This helper is called before the list is generated. +// This means that the recent list will always be up to date by the time it is used. export const updateRecent = (recentList: string[]) => { const recentPkgList: string[] = emoji.packages.base.emojisByCategory.recent; recentList?.forEach((_emoji) => { @@ -162,6 +208,7 @@ export const replaceEmojiInRecent = ({ oldEmoji, newEmoji }: { oldEmoji: string; if (pos !== -1) { recentPkgList[pos] = newEmoji; + emoji.dispatchUpdate(); } }; diff --git a/apps/meteor/app/emoji/client/index.ts b/apps/meteor/app/emoji/client/index.ts index 420abe27f211f..65e3b79cd5a6f 100644 --- a/apps/meteor/app/emoji/client/index.ts +++ b/apps/meteor/app/emoji/client/index.ts @@ -1,3 +1,3 @@ export * from './helpers'; export * from './types'; -export { emoji } from './lib'; +export { emoji, emojiEmitter } from './lib'; diff --git a/apps/meteor/app/emoji/client/lib.ts b/apps/meteor/app/emoji/client/lib.ts index 1d2397c9568d3..12f50a2c888f0 100644 --- a/apps/meteor/app/emoji/client/lib.ts +++ b/apps/meteor/app/emoji/client/lib.ts @@ -1,8 +1,11 @@ +import { Emitter } from '@rocket.chat/emitter'; import emojione from 'emojione'; import type { EmojiPackages } from '../lib/rocketchat'; -export const emoji: EmojiPackages = { +export const emojiEmitter = new Emitter<{ updated: void }>(); + +export const emoji: EmojiPackages & { dispatchUpdate: () => void } = { packages: { base: { emojiCategories: [{ key: 'recent', i18n: 'Frequently_Used' }], @@ -23,4 +26,7 @@ export const emoji: EmojiPackages = { }, }, list: {}, + dispatchUpdate() { + emojiEmitter.emit('updated'); + }, }; diff --git a/apps/meteor/client/contexts/EmojiPickerContext.ts b/apps/meteor/client/contexts/EmojiPickerContext.ts index 77adbe419c1fa..b79239441f188 100644 --- a/apps/meteor/client/contexts/EmojiPickerContext.ts +++ b/apps/meteor/client/contexts/EmojiPickerContext.ts @@ -1,12 +1,6 @@ -import type { MutableRefObject } from 'react'; import { createContext, useContext } from 'react'; -import type { EmojiByCategory } from '../../app/emoji/client'; - -type EmojiCategoryPosition = { - key: string; - top: number; -}; +import type { EmojiPickerItem, CategoriesIndexes } from '../../app/emoji/client'; type EmojiPickerContextValue = { open: (ref: Element, callback: (emoji: string) => void) => void; @@ -16,17 +10,17 @@ type EmojiPickerContextValue = { handlePreview: (emoji: string, name: string) => void; handleRemovePreview: () => void; addRecentEmoji: (emoji: string) => void; - getEmojiListsByCategory: () => EmojiByCategory[]; + emojiListByCategory: EmojiPickerItem[]; recentEmojis: string[]; setRecentEmojis: (emoji: string[]) => void; actualTone: number; currentCategory: string; setCurrentCategory: (category: string) => void; - categoriesPosition: MutableRefObject; customItemsLimit: number; setCustomItemsLimit: (limit: number) => void; setActualTone: (tone: number) => void; quickReactions: { emoji: string; image: string }[]; + categoriesIndexes: CategoriesIndexes; }; export const EmojiPickerContext = createContext(undefined); @@ -56,9 +50,9 @@ export const useEmojiPickerData = () => { actualTone, addRecentEmoji, currentCategory, - categoriesPosition, + categoriesIndexes, customItemsLimit, - getEmojiListsByCategory, + emojiListByCategory, quickReactions, recentEmojis, setActualTone, @@ -69,12 +63,12 @@ export const useEmojiPickerData = () => { return { addRecentEmoji, - getEmojiListsByCategory, + emojiListByCategory, recentEmojis, setRecentEmojis, actualTone, currentCategory, - categoriesPosition, + categoriesIndexes, setCurrentCategory, customItemsLimit, setCustomItemsLimit, diff --git a/apps/meteor/client/providers/EmojiPickerProvider/EmojiPickerProvider.tsx b/apps/meteor/client/providers/EmojiPickerProvider/EmojiPickerProvider.tsx index b1db9e481df1a..bf3207bbb8d1d 100644 --- a/apps/meteor/client/providers/EmojiPickerProvider/EmojiPickerProvider.tsx +++ b/apps/meteor/client/providers/EmojiPickerProvider/EmojiPickerProvider.tsx @@ -1,31 +1,38 @@ import { useDebouncedState, useLocalStorage } from '@rocket.chat/fuselage-hooks'; import type { ReactNode, ReactElement, ContextType } from 'react'; -import { useState, useCallback, useMemo, useEffect, useRef } from 'react'; +import { useState, useCallback, useMemo, useSyncExternalStore } from 'react'; import { useUpdateCustomEmoji } from './useUpdateCustomEmoji'; -import type { EmojiByCategory } from '../../../app/emoji/client'; -import { emoji, getFrequentEmoji, updateRecent, createEmojiList, createPickerEmojis, CUSTOM_CATEGORY } from '../../../app/emoji/client'; +import { emoji, getFrequentEmoji, createEmojiListByCategorySubscription } from '../../../app/emoji/client'; import { EmojiPickerContext } from '../../contexts/EmojiPickerContext'; import EmojiPicker from '../../views/composer/EmojiPicker/EmojiPicker'; const DEFAULT_ITEMS_LIMIT = 90; +// limit recent emojis to 27 (3 rows of 9) +const RECENT_EMOJIS_LIMIT = 27; + const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElement => { const [emojiPicker, setEmojiPicker] = useState(null); const [emojiToPreview, setEmojiToPreview] = useDebouncedState<{ emoji: string; name: string } | null>(null, 100); const [recentEmojis, setRecentEmojis] = useLocalStorage('emoji.recent', []); + const [frequentEmojis, setFrequentEmojis] = useLocalStorage<[string, number][]>('emoji.frequent', []); + const [actualTone, setActualTone] = useLocalStorage('emoji.tone', 0); const [currentCategory, setCurrentCategory] = useState('recent'); - const categoriesPosition = useRef([]); const [customItemsLimit, setCustomItemsLimit] = useState(DEFAULT_ITEMS_LIMIT); - const [frequentEmojis, setFrequentEmojis] = useLocalStorage<[string, number][]>('emoji.frequent', []); - const [quickReactions, setQuickReactions] = useState<{ emoji: string; image: string }[]>(() => getFrequentEmoji(frequentEmojis.map(([emoji]) => emoji)), ); + const [sub, getSnapshot] = useMemo(() => { + return createEmojiListByCategorySubscription(customItemsLimit, actualTone, recentEmojis, setRecentEmojis); + }, [customItemsLimit, actualTone, recentEmojis, setRecentEmojis]); + + const [emojiListByCategory, categoriesIndexes] = useSyncExternalStore(sub, getSnapshot); + useUpdateCustomEmoji(); const addFrequentEmojis = useCallback( @@ -44,37 +51,6 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen [frequentEmojis, setFrequentEmojis], ); - const [getEmojiListsByCategory, setEmojiListsByCategoryGetter] = useState<() => EmojiByCategory[]>(() => () => []); - - // TODO: improve this update - const updateEmojiListByCategory = useCallback( - (categoryKey: string, limit: number = DEFAULT_ITEMS_LIMIT) => { - setEmojiListsByCategoryGetter( - (getEmojiListsByCategory) => () => - getEmojiListsByCategory().map((category) => - categoryKey === category.key - ? { - ...category, - emojis: { - list: createEmojiList(category.key, null, recentEmojis, setRecentEmojis), - limit: category.key === CUSTOM_CATEGORY ? limit | customItemsLimit : null, - }, - } - : category, - ), - ); - }, - [customItemsLimit, recentEmojis, setRecentEmojis], - ); - - useEffect(() => { - if (recentEmojis?.length > 0) { - updateRecent(recentEmojis); - } - - setEmojiListsByCategoryGetter(() => () => createPickerEmojis(customItemsLimit, actualTone, recentEmojis, setRecentEmojis)); - }, [actualTone, recentEmojis, customItemsLimit, currentCategory, setRecentEmojis, frequentEmojis]); - const addRecentEmoji = useCallback( (_emoji: string) => { addFrequentEmojis(_emoji); @@ -88,14 +64,13 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen recent.unshift(_emoji); - // limit recent emojis to 27 (3 rows of 9) - recent.splice(27); + recent.splice(RECENT_EMOJIS_LIMIT); - setRecentEmojis(recent); + // If this value is not cloned, the recent list will not be updated + setRecentEmojis([...recent]); emoji.packages.base.emojisByCategory.recent = recent; - updateEmojiListByCategory('recent'); }, - [recentEmojis, setRecentEmojis, updateEmojiListByCategory, addFrequentEmojis], + [recentEmojis, setRecentEmojis, addFrequentEmojis], ); const open = useCallback((ref: Element, callback: (emoji: string) => void) => { @@ -115,13 +90,13 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen handlePreview, handleRemovePreview, addRecentEmoji, - getEmojiListsByCategory, + emojiListByCategory, recentEmojis, setRecentEmojis, actualTone, currentCategory, setCurrentCategory, - categoriesPosition, + categoriesIndexes, customItemsLimit, setCustomItemsLimit, setActualTone, @@ -132,7 +107,8 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen open, emojiToPreview, addRecentEmoji, - getEmojiListsByCategory, + emojiListByCategory, + categoriesIndexes, recentEmojis, setRecentEmojis, actualTone, diff --git a/apps/meteor/client/views/composer/EmojiPicker/CategoriesResult.tsx b/apps/meteor/client/views/composer/EmojiPicker/CategoriesResult.tsx index 57869f5aeafd9..68cb34dd27edf 100644 --- a/apps/meteor/client/views/composer/EmojiPicker/CategoriesResult.tsx +++ b/apps/meteor/client/views/composer/EmojiPicker/CategoriesResult.tsx @@ -1,24 +1,24 @@ import { css } from '@rocket.chat/css-in-js'; import { Box } from '@rocket.chat/fuselage'; -import type { MouseEvent, UIEventHandler } from 'react'; +import type { MouseEvent } from 'react'; import { forwardRef, memo, useRef } from 'react'; -import type { VirtuosoHandle } from 'react-virtuoso'; +import type { ListRange, VirtuosoHandle } from 'react-virtuoso'; import { Virtuoso } from 'react-virtuoso'; import EmojiCategoryRow from './EmojiCategoryRow'; -import type { EmojiByCategory } from '../../../../app/emoji/client'; +import type { EmojiPickerItem } from '../../../../app/emoji/client'; import { VirtualizedScrollbars } from '../../../components/CustomScrollbars'; type CategoriesResultProps = { - emojiListByCategory: EmojiByCategory[]; + items: EmojiPickerItem[]; customItemsLimit: number; handleLoadMore: () => void; handleSelectEmoji: (event: MouseEvent) => void; - handleScroll: UIEventHandler; + handleScroll: (range: ListRange) => void; }; const CategoriesResult = forwardRef(function CategoriesResult( - { emojiListByCategory, customItemsLimit, handleLoadMore, handleSelectEmoji, handleScroll }, + { items, customItemsLimit, handleLoadMore, handleSelectEmoji, handleScroll }, ref, ) { const wrapper = useRef(null); @@ -36,9 +36,9 @@ const CategoriesResult = forwardRef(funct { if (!wrapper.current) { return; @@ -50,13 +50,12 @@ const CategoriesResult = forwardRef(funct wrapper.current.classList.remove('pointer-none'); } }} - itemContent={(_, { key, ...data }) => ( + itemContent={(_, item) => ( )} /> diff --git a/apps/meteor/client/views/composer/EmojiPicker/EmojiCategoryRow.tsx b/apps/meteor/client/views/composer/EmojiPicker/EmojiCategoryRow.tsx index 3dea93f536aa4..76909ba1c78de 100644 --- a/apps/meteor/client/views/composer/EmojiPicker/EmojiCategoryRow.tsx +++ b/apps/meteor/client/views/composer/EmojiPicker/EmojiCategoryRow.tsx @@ -5,20 +5,18 @@ import { memo, type MouseEvent } from 'react'; import { useTranslation } from 'react-i18next'; import EmojiElement from './EmojiElement'; -import { CUSTOM_CATEGORY } from '../../../../app/emoji/client'; -import type { EmojiByCategory } from '../../../../app/emoji/client'; -import { useEmojiPickerData } from '../../../contexts/EmojiPickerContext'; +import { isRowDivider, isLoadMore } from '../../../../app/emoji/client'; +import type { EmojiPickerItem } from '../../../../app/emoji/client'; -type EmojiCategoryRowProps = Omit & { - categoryKey: EmojiByCategory['key']; +type EmojiCategoryRowProps = { customItemsLimit: number; handleLoadMore: () => void; handleSelectEmoji: (e: MouseEvent) => void; + item: EmojiPickerItem; }; -const EmojiCategoryRow = ({ categoryKey, i18n, emojis, customItemsLimit, handleLoadMore, handleSelectEmoji }: EmojiCategoryRowProps) => { +const EmojiCategoryRow = ({ item, handleLoadMore, handleSelectEmoji }: EmojiCategoryRowProps) => { const { t } = useTranslation(); - const { categoriesPosition } = useEmojiPickerData(); const categoryRowStyle = css` button { @@ -30,46 +28,30 @@ const EmojiCategoryRow = ({ categoryKey, i18n, emojis, customItemsLimit, handleL } `; - return ( - - { - if (categoriesPosition.current.find(({ key }) => key === categoryKey)) { - return; - } + if (isRowDivider(item)) { + return ( + <> + + {t(item.i18n)} + + + ); + } + + if (isLoadMore(item)) { + return {t('Load_more')}; + } - categoriesPosition.current.push({ key: categoryKey, top: element?.offsetTop }); - return element; - }} - > - {t(i18n)} - - {emojis.list.length > 0 && ( - - <> - {categoryKey === CUSTOM_CATEGORY && - emojis.list.map( - ({ emoji, image }, index = 1) => - index < customItemsLimit && ( - - ), - )} - {!(categoryKey === CUSTOM_CATEGORY) && - emojis.list.map(({ emoji, image }) => ( - - ))} - - - )} - {emojis.limit && emojis?.limit > 0 && emojis.list.length > emojis.limit && ( - {t('Load_more')} - )} - {emojis.list.length === 0 && {t('No_emojis_found')}} - + if (item.length === 0) { + return {t('No_emojis_found')}; + } + + return ( + + {item.map(({ emoji, image, category }) => ( + + ))} + ); }; diff --git a/apps/meteor/client/views/composer/EmojiPicker/EmojiPicker.tsx b/apps/meteor/client/views/composer/EmojiPicker/EmojiPicker.tsx index ad61d102a03ab..cef5d6c2fcd7c 100644 --- a/apps/meteor/client/views/composer/EmojiPicker/EmojiPicker.tsx +++ b/apps/meteor/client/views/composer/EmojiPicker/EmojiPicker.tsx @@ -10,9 +10,9 @@ import { EmojiPickerPreview, } from '@rocket.chat/ui-client'; import { useTranslation, usePermission, useRoute } from '@rocket.chat/ui-contexts'; -import type { ChangeEvent, KeyboardEvent, MouseEvent, RefObject, UIEvent } from 'react'; +import type { ChangeEvent, KeyboardEvent, MouseEvent, RefObject } from 'react'; import { useLayoutEffect, useState, useEffect, useRef } from 'react'; -import type { VirtuosoHandle } from 'react-virtuoso'; +import type { ListRange, VirtuosoHandle } from 'react-virtuoso'; import CategoriesResult from './CategoriesResult'; import EmojiPickerCategoryItem from './EmojiPickerCategoryItem'; @@ -60,8 +60,8 @@ const EmojiPicker = ({ reference, onClose, onPickEmoji }: EmojiPickerProps) => { setRecentEmojis, actualTone, currentCategory, - categoriesPosition, - getEmojiListsByCategory, + categoriesIndexes, + emojiListByCategory, customItemsLimit, setActualTone, setCustomItemsLimit, @@ -155,24 +155,29 @@ const EmojiPicker = ({ reference, onClose, onPickEmoji }: EmojiPickerProps) => { setCustomItemsLimit(customItemsLimit + 90); }; - const handleScroll = (event: UIEvent) => { - const categoryMargin = 12; - const { scrollTop } = event.currentTarget; + const handleScroll = (range: ListRange) => { + const { startIndex } = range; - const lastCategory = categoriesPosition.current - ?.filter((category, index = 1) => category.top - categoryMargin * index <= scrollTop) - .pop(); + const category = categoriesIndexes.find( + (category, index) => category.index <= startIndex + 1 && categoriesIndexes[index + 1]?.index >= startIndex, + ); - if (!lastCategory) { + if (!category) { return; } - setCurrentCategory(lastCategory.key); + setCurrentCategory(category.key); }; - const handleGoToCategory = (categoryIndex: number) => { + const handleGoToCategory = (category: string) => { setSearching(false); - virtuosoRef.current?.scrollToIndex({ index: categoryIndex }); + const { index } = categoriesIndexes.find((item) => item.key === category) || {}; + + if (index === undefined) { + return; + } + + virtuosoRef.current?.scrollToIndex({ index: index > 0 ? index + 1 : 0 }); }; const handleGoToAddCustom = () => { @@ -196,13 +201,12 @@ const EmojiPicker = ({ reference, onClose, onPickEmoji }: EmojiPickerProps) => { /> - {emojiCategories.map((category, index) => ( + {emojiCategories.map((category) => ( handleGoToCategory(category.key)} /> ))} @@ -212,7 +216,7 @@ const EmojiPicker = ({ reference, onClose, onPickEmoji }: EmojiPickerProps) => { {!searching && ( void; + handleGoToCategory: () => void; } & Omit, 'is'>; const mapCategoryIcon = (category: string) => { @@ -45,7 +44,7 @@ const mapCategoryIcon = (category: string) => { } }; -const EmojiPickerCategoryItem = ({ category, index, active, handleGoToCategory, ...props }: EmojiPickerCategoryItemProps) => { +const EmojiPickerCategoryItem = ({ category, active, handleGoToCategory, ...props }: EmojiPickerCategoryItemProps) => { const { t } = useTranslation(); const icon = mapCategoryIcon(category.key); @@ -58,7 +57,7 @@ const EmojiPickerCategoryItem = ({ category, index, active, handleGoToCategory, className={category.key} small aria-label={t(category.i18n)} - onClick={() => handleGoToCategory(index)} + onClick={handleGoToCategory} icon={icon} {...props} />