'use client'; import * as React from 'react'; import type { Emoji } from '@emoji-mart/data'; import { type EmojiCategoryList, type EmojiIconList, type GridRow, EmojiSettings } from '@platejs/emoji'; import { type EmojiDropdownMenuOptions, type UseEmojiPickerType, useEmojiDropdownMenuState } from '@platejs/emoji/react'; import * as Popover from '@radix-ui/react-popover'; import { Apple, Flag, Magnifier, Leaf, Lightbulb, Music, Star, Xmark, FaceGrin, Clock, Compass } from '../../icons'; import { Button } from '@/components/ui/buttons'; import { TooltipBase, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { cn } from '@/lib/utils'; import { ToolbarButton } from '@/components/ui/toolbar'; export function EmojiToolbarButton({ options, ...props }: { options?: EmojiDropdownMenuOptions; } & React.ComponentPropsWithoutRef) { const { emojiPickerState, isOpen, setIsOpen } = useEmojiDropdownMenuState(options); return ( } isOpen={isOpen} setIsOpen={setIsOpen}> ); } export function EmojiPopover({ children, control, isOpen, setIsOpen }: { children: React.ReactNode; control: React.ReactNode; isOpen: boolean; setIsOpen: (open: boolean) => void; }) { return ( {control} {children} ); } export function EmojiPicker({ clearSearch, emoji, emojiLibrary, focusedCategory, hasFound, i18n, icons = { categories: emojiCategoryIcons, search: emojiSearchIcons }, isSearching, refs, searchResult, searchValue, setSearch, settings = EmojiSettings, visibleCategories, handleCategoryClick, onMouseOver, onSelectEmoji }: Omit & { icons?: EmojiIconList; }) { return (
); } const EmojiButton = React.memo(function EmojiButton({ emoji, index, onMouseOver, onSelect }: { emoji: Emoji; index: number; onMouseOver: (emoji?: Emoji) => void; onSelect: (emoji: Emoji) => void; }) { return ( ); }); const RowOfButtons = React.memo(function RowOfButtons({ emojiLibrary, row, onMouseOver, onSelectEmoji }: { row: GridRow; } & Pick) { return (
{row.elements.map((emojiId, index) => ( ))}
); }); function EmojiPickerContent({ emojiLibrary, i18n, isSearching = false, refs, searchResult, settings = EmojiSettings, visibleCategories, onMouseOver, onSelectEmoji }: Pick< UseEmojiPickerType, | 'emojiLibrary' | 'i18n' | 'isSearching' | 'onMouseOver' | 'onSelectEmoji' | 'refs' | 'searchResult' | 'settings' | 'visibleCategories' >) { const getRowWidth = settings.perLine.value * settings.buttonSize.value; const isCategoryVisible = React.useCallback( (categoryId: any) => { return visibleCategories.has(categoryId) ? visibleCategories.get(categoryId) : false; }, [visibleCategories] ); const EmojiList = React.useCallback(() => { return emojiLibrary .getGrid() .sections() .map(({ id: categoryId }) => { const section = emojiLibrary.getGrid().section(categoryId); const { buttonSize } = settings; return (
} style={{ width: getRowWidth }} data-id={categoryId}>
{i18n.categories[categoryId]}
{isCategoryVisible(categoryId) && section .getRows() .map((row: GridRow) => ( ))}
); }); }, [ emojiLibrary, getRowWidth, i18n.categories, isCategoryVisible, onSelectEmoji, onMouseOver, settings ]); const SearchList = React.useCallback(() => { return (
{i18n.searchResult}
{searchResult.map((emoji: Emoji, index: number) => ( ))}
); }, [emojiLibrary, getRowWidth, i18n.searchResult, searchResult, onSelectEmoji, onMouseOver]); return (
} className={cn( 'h-full min-h-[50%] overflow-x-hidden overflow-y-auto px-2', '[&::-webkit-scrollbar]:w-4', '[&::-webkit-scrollbar-button]:hidden [&::-webkit-scrollbar-button]:size-0', '[&::-webkit-scrollbar-thumb]:bg-muted [&::-webkit-scrollbar-thumb]:hover:bg-muted-foreground/25 [&::-webkit-scrollbar-thumb]:min-h-11 [&::-webkit-scrollbar-thumb]:rounded-full', '[&::-webkit-scrollbar-thumb]:border-popover [&::-webkit-scrollbar-thumb]:border-4 [&::-webkit-scrollbar-thumb]:border-solid [&::-webkit-scrollbar-thumb]:bg-clip-padding' )} data-id="scroll">
} className="h-full"> {isSearching ? SearchList() : EmojiList()}
); } function EmojiPickerSearchBar({ children, i18n, searchValue, setSearch }: { children: React.ReactNode; } & Pick) { return (
setSearch(event.target.value)} placeholder={i18n.search} aria-label="Search" autoComplete="off" type="text" autoFocus /> {children}
); } function EmojiPickerSearchAndClear({ clearSearch, i18n, searchValue }: Pick) { return (
{emojiSearchIcons.loupe}
{searchValue && (
); } function EmojiPreview({ emoji }: Pick) { return (
{emoji?.skins[0].native}
{emoji?.name}
{`:${emoji?.id}:`}
); } function NoEmoji({ i18n }: Pick) { return (
😢
{i18n.searchNoResultsTitle}
{i18n.searchNoResultsSubtitle}
); } function PickAnEmoji({ i18n }: Pick) { return (
☝️
{i18n.pick}
); } function EmojiPickerPreview({ emoji, hasFound = true, i18n, isSearching = false, ...props }: Pick) { const showPickEmoji = !emoji && (!isSearching || hasFound); const showNoEmoji = isSearching && !hasFound; const showPreview = emoji && !showNoEmoji && !showNoEmoji; return ( <> {showPreview && } {showPickEmoji && } {showNoEmoji && } ); } function EmojiPickerNavigation({ emojiLibrary, focusedCategory, i18n, icons, onClick }: { onClick: (id: EmojiCategoryList) => void; } & Pick) { return ( ); } const emojiCategoryIcons: Record< EmojiCategoryList, { outline: React.ReactElement; solid: React.ReactElement; // Needed to add another solid variant - outline will be used for now } > = { activity: { outline: ( ), solid: ( ) }, custom: { outline: , solid: }, flags: { outline: , solid: }, foods: { outline: , solid: }, frequent: { outline: , solid: }, nature: { outline: , solid: }, objects: { outline: , solid: }, people: { outline: , solid: }, places: { outline: , solid: }, symbols: { outline: , solid: } }; const emojiSearchIcons = { delete: , loupe: };