fix: emoji picker

This commit is contained in:
Tristan Yang
2024-03-19 11:21:16 +08:00
parent 86bc9b8c29
commit fafd367bfb
7 changed files with 488 additions and 490 deletions
+8 -8
View File
@@ -1,6 +1,6 @@
{ {
"name": "vocechat-web", "name": "vocechat-web",
"version": "0.7.3", "version": "0.7.4",
"homepage": "https://voce.chat", "homepage": "https://voce.chat",
"dependencies": { "dependencies": {
"@metamask/onboarding": "^1.0.1", "@metamask/onboarding": "^1.0.1",
@@ -75,7 +75,7 @@
"slate-react": "0.102.0", "slate-react": "0.102.0",
"source-map-loader": "^5.0.0", "source-map-loader": "^5.0.0",
"style-loader": "^3.3.4", "style-loader": "^3.3.4",
"tailwind-merge": "2.2.1", "tailwind-merge": "2.2.2",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"typescript": "^5.4.2", "typescript": "^5.4.2",
@@ -116,15 +116,15 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.24.0", "@babel/core": "^7.24.0",
"@ianvs/prettier-plugin-sort-imports": "^4.2.0", "@ianvs/prettier-plugin-sort-imports": "^4.2.1",
"@types/masonry-layout": "^4.2.7", "@types/masonry-layout": "^4.2.7",
"@types/node": "^20.11.28", "@types/node": "^20.11.29",
"@types/react": "18.2.66", "@types/react": "18.2.67",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",
"@types/react-syntax-highlighter": "^15.5.11", "@types/react-syntax-highlighter": "^15.5.11",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^7.3.1",
"autoprefixer": "^10.4.18", "autoprefixer": "^10.4.18",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"babel-plugin-named-asset-import": "^0.3.8", "babel-plugin-named-asset-import": "^0.3.8",
@@ -137,7 +137,7 @@
"gh-pages": "^6.1.1", "gh-pages": "^6.1.1",
"md5-file": "^5.0.0", "md5-file": "^5.0.0",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"postcss": "^8.4.35", "postcss": "^8.4.36",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
+381 -376
View File
File diff suppressed because it is too large Load Diff
@@ -1,24 +1,19 @@
import React, { memo, useCallback } from 'react'; import { memo, useCallback } from "react";
import { import { Emoji, EmojiSettings, GridRow, UseEmojiPickerType } from "@udecode/plate-emoji";
Emoji,
EmojiSettings,
GridRow,
UseEmojiPickerType,
} from '@udecode/plate-emoji';
import { cn } from '@/utils'; import { cn } from "@/utils";
export type EmojiPickerContentProps = Pick< export type EmojiPickerContentProps = Pick<
UseEmojiPickerType, UseEmojiPickerType,
| 'i18n' | "i18n"
| 'onMouseOver' | "onMouseOver"
| 'onSelectEmoji' | "onSelectEmoji"
| 'emojiLibrary' | "emojiLibrary"
| 'isSearching' | "isSearching"
| 'searchResult' | "searchResult"
| 'visibleCategories' | "visibleCategories"
| 'refs' | "refs"
| 'settings' | "settings"
>; >;
export type EmojiButtonProps = { export type EmojiButtonProps = {
@@ -30,36 +25,34 @@ export type EmojiButtonProps = {
export type RowOfButtonsProps = Pick< export type RowOfButtonsProps = Pick<
UseEmojiPickerType, UseEmojiPickerType,
'onMouseOver' | 'onSelectEmoji' | 'emojiLibrary' "onMouseOver" | "onSelectEmoji" | "emojiLibrary"
> & { > & {
row: GridRow; row: GridRow;
}; };
const Button = memo( const Button = memo(({ index, emoji, onSelect, onMouseOver }: EmojiButtonProps) => {
({ index, emoji, onSelect, onMouseOver }: EmojiButtonProps) => { return (
return ( <button
<button type="button"
type="button" aria-label={emoji.skins[0].native}
aria-label={emoji.skins[0].native} tabIndex={-1}
tabIndex={-1} data-index={index}
data-index={index} onClick={() => onSelect(emoji)}
onClick={() => onSelect(emoji)} onMouseEnter={() => onMouseOver(emoji)}
onMouseEnter={() => onMouseOver(emoji)} onMouseLeave={() => onMouseOver()}
onMouseLeave={() => onMouseOver()} className="group relative flex h-[36px] w-[36px] cursor-pointer items-center justify-center border-none bg-transparent text-2xl leading-none"
className="group relative flex h-[36px] w-[36px] cursor-pointer items-center justify-center border-none bg-transparent text-2xl leading-none" >
> <div
<div aria-hidden="true"
aria-hidden="true" className="absolute inset-0 rounded-full bg-[rgba(0,0,0,0.05)] opacity-0 group-hover:opacity-100"
className="absolute inset-0 rounded-full bg-[rgba(0,0,0,0.05)] opacity-0 group-hover:opacity-100" />
/> <span data-emoji-set="native" style={{ position: "relative" }}>
<span data-emoji-set="native" style={{ position: 'relative' }}> {emoji.skins[0].native}
{emoji.skins[0].native} </span>
</span> </button>
</button> );
); });
} Button.displayName = "Button";
);
Button.displayName = 'Button';
const RowOfButtons = memo( const RowOfButtons = memo(
({ row, emojiLibrary, onSelectEmoji, onMouseOver }: RowOfButtonsProps) => ( ({ row, emojiLibrary, onSelectEmoji, onMouseOver }: RowOfButtonsProps) => (
@@ -76,7 +69,7 @@ const RowOfButtons = memo(
</div> </div>
) )
); );
RowOfButtons.displayName = 'RowOfButtons'; RowOfButtons.displayName = "RowOfButtons";
export function EmojiPickerContent({ export function EmojiPickerContent({
i18n, i18n,
@@ -87,15 +80,13 @@ export function EmojiPickerContent({
searchResult, searchResult,
visibleCategories, visibleCategories,
refs, refs,
settings = EmojiSettings, settings = EmojiSettings
}: EmojiPickerContentProps) { }: EmojiPickerContentProps) {
const getRowWidth = settings.perLine.value * settings.buttonSize.value; const getRowWidth = settings.perLine.value * settings.buttonSize.value;
const isCategoryVisible = useCallback( const isCategoryVisible = useCallback(
(categoryId: any) => { (categoryId: any) => {
return visibleCategories.has(categoryId) return visibleCategories.has(categoryId) ? visibleCategories.get(categoryId) : false;
? visibleCategories.get(categoryId)
: false;
}, },
[visibleCategories] [visibleCategories]
); );
@@ -145,7 +136,7 @@ export function EmojiPickerContent({
isCategoryVisible, isCategoryVisible,
onSelectEmoji, onSelectEmoji,
onMouseOver, onMouseOver,
settings, settings
]); ]);
const SearchList = useCallback(() => { const SearchList = useCallback(() => {
@@ -167,24 +158,17 @@ export function EmojiPickerContent({
</div> </div>
</div> </div>
); );
}, [ }, [emojiLibrary, getRowWidth, i18n.searchResult, searchResult, onSelectEmoji, onMouseOver]);
emojiLibrary,
getRowWidth,
i18n.searchResult,
searchResult,
onSelectEmoji,
onMouseOver,
]);
return ( return (
<div <div
className={cn( className={cn(
'h-full min-h-[50%] overflow-y-auto overflow-x-hidden px-3', "h-full min-h-[50%] overflow-y-auto overflow-x-hidden px-3",
'[&::-webkit-scrollbar]:w-4', "[&::-webkit-scrollbar]:w-4",
'[&::-webkit-scrollbar-button]:hidden [&::-webkit-scrollbar-button]:h-0 [&::-webkit-scrollbar-button]:w-0', "[&::-webkit-scrollbar-button]:hidden [&::-webkit-scrollbar-button]:h-0 [&::-webkit-scrollbar-button]:w-0",
':hover:[&::-webkit-scrollbar-thumb]:bg-[#f3f4f6]', ":hover:[&::-webkit-scrollbar-thumb]:bg-[#f3f4f6]",
'[&::-webkit-scrollbar-thumb]:min-h-[65px] [&::-webkit-scrollbar-thumb]:rounded-2xl [&::-webkit-scrollbar-thumb]:border-4 [&::-webkit-scrollbar-thumb]:border-white', "[&::-webkit-scrollbar-thumb]:min-h-[65px] [&::-webkit-scrollbar-thumb]:rounded-2xl [&::-webkit-scrollbar-thumb]:border-4 [&::-webkit-scrollbar-thumb]:border-white",
'[&::-webkit-scrollbar-track]:border-0' "[&::-webkit-scrollbar-track]:border-0"
)} )}
data-id="scroll" data-id="scroll"
ref={refs.current.contentRoot} ref={refs.current.contentRoot}
@@ -1,26 +1,22 @@
import React from 'react'; import React from "react";
import { UseEmojiPickerType } from '@udecode/plate-emoji'; import { UseEmojiPickerType } from "@udecode/plate-emoji";
import IconSearch from '@/assets/icons/search.svg' import IconSearch from "@/assets/icons/search.svg";
import IconClose from '@/assets/icons/close.circle.svg' import IconClose from "@/assets/icons/close.circle.svg";
import { cn } from '@/utils'; import { cn } from "@/utils";
export type EmojiPickerSearchAndClearProps = Pick< export type EmojiPickerSearchAndClearProps = Pick<
UseEmojiPickerType, UseEmojiPickerType,
'i18n' | 'searchValue' | 'clearSearch' "i18n" | "searchValue" | "clearSearch"
>; >;
export function EmojiPickerSearchAndClear({ export function EmojiPickerSearchAndClear({
i18n, i18n,
searchValue, searchValue,
clearSearch, clearSearch
}: EmojiPickerSearchAndClearProps) { }: EmojiPickerSearchAndClearProps) {
return ( return (
<> <>
<span <span className={cn("absolute left-2 top-1/2 z-10 flex h-5 w-5 -translate-y-1/2")}>
className={cn(
'absolute left-2 top-1/2 z-10 flex h-5 w-5 -translate-y-1/2'
)}
>
<IconSearch className="w-full h-full" /> <IconSearch className="w-full h-full" />
</span> </span>
{searchValue && ( {searchValue && (
@@ -29,7 +25,7 @@ export function EmojiPickerSearchAndClear({
aria-label="Clear" aria-label="Clear"
type="button" type="button"
className={cn( className={cn(
'absolute right-0 top-1/2 flex h-8 w-8 -translate-y-1/2 cursor-pointer border-none bg-transparent justify-center items-center' "absolute right-0 top-1/2 flex h-8 w-8 -translate-y-1/2 cursor-pointer border-none bg-transparent justify-center items-center"
)} )}
onClick={clearSearch} onClick={clearSearch}
> >
@@ -1,5 +1,5 @@
import React, { ReactNode } from 'react'; import { ReactNode } from "react";
import * as Popover from '@radix-ui/react-popover'; import * as Popover from "@radix-ui/react-popover";
type EmojiPopupProps = { type EmojiPopupProps = {
control: ReactNode; control: ReactNode;
@@ -8,16 +8,10 @@ type EmojiPopupProps = {
children: ReactNode; children: ReactNode;
}; };
export function EmojiPopup({ export function EmojiPopup({ control, isOpen, setIsOpen, children }: EmojiPopupProps) {
control,
isOpen,
setIsOpen,
children,
}: EmojiPopupProps) {
return ( return (
<Popover.Root open={isOpen} onOpenChange={setIsOpen}> <Popover.Root open={isOpen} onOpenChange={setIsOpen}>
<Popover.Trigger asChild>{control}</Popover.Trigger> <Popover.Trigger asChild>{control}</Popover.Trigger>
<Popover.Portal> <Popover.Portal>
<Popover.Content <Popover.Content
align="start" align="start"
@@ -4,40 +4,55 @@ import { EmojiPopup } from "./emoji-popup";
import { emojiCategoryIcons, emojiSearchIcons } from "./emoji-icons"; import { emojiCategoryIcons, emojiSearchIcons } from "./emoji-icons";
import { EmojiPicker } from "./emoji-picker"; import { EmojiPicker } from "./emoji-picker";
import { Plate } from "@udecode/plate-common";
type EmojiDropdownMenuProps = { type EmojiDropdownMenuProps = {
context?: "markdown" | "plate";
options?: EmojiDropdownMenuOptions; options?: EmojiDropdownMenuOptions;
} & { } & {
onSelectEmoji?: (emoji: Emoji) => void; onSelectEmoji?: (emoji: Emoji) => void;
}; };
export function EmojiInputPicker({ options, onSelectEmoji, ...props }: EmojiDropdownMenuProps) { const PickerWrapper = (props: EmojiDropdownMenuProps) => {
const { options, onSelectEmoji, ...others } = props;
const { isOpen, setIsOpen, emojiPickerState } = useEmojiDropdownMenuState(options); const { isOpen, setIsOpen, emojiPickerState } = useEmojiDropdownMenuState(options);
const handleClick = () => { const handleClick = () => {
setIsOpen(!isOpen); setIsOpen(!isOpen);
}; };
const rest = onSelectEmoji ? { onSelectEmoji } : {}; const rest = onSelectEmoji ? { onSelectEmoji } : {};
return ( return (
<EmojiPopup <Plate key={"just_for_emoji_picker"}>
control={ <EmojiPopup
<button className="relative h-6 w-6" onClick={handleClick} {...props}> control={
<IconSmile className="w-full h-full" /> <button className="relative h-6 w-6" onClick={handleClick} {...others}>
</button> <IconSmile className="w-full h-full" />
} </button>
isOpen={isOpen} }
setIsOpen={setIsOpen}
>
<EmojiPicker
{...emojiPickerState}
isOpen={isOpen} isOpen={isOpen}
setIsOpen={setIsOpen} setIsOpen={setIsOpen}
icons={{ >
categories: emojiCategoryIcons, <EmojiPicker
search: emojiSearchIcons {...emojiPickerState}
}} isOpen={isOpen}
settings={options?.settings} setIsOpen={setIsOpen}
{...rest} icons={{
/> categories: emojiCategoryIcons,
</EmojiPopup> search: emojiSearchIcons
}}
settings={options?.settings}
{...rest}
/>
</EmojiPopup>
</Plate>
); );
};
export function EmojiInputPicker({ context, ...props }: EmojiDropdownMenuProps) {
if (context === "markdown")
return (
<Plate key={"just_for_emoji_picker"}>
<PickerWrapper {...props} />
</Plate>
);
return <PickerWrapper {...props} />;
} }
+5 -1
View File
@@ -176,7 +176,11 @@ const Send: FC<IProps> = ({
)} )}
> >
{mode == Modes.markdown && ( {mode == Modes.markdown && (
<EmojiInputPicker options={{ closeOnSelect: false }} onSelectEmoji={insertEmoji} /> <EmojiInputPicker
context="markdown"
options={{ closeOnSelect: false }}
onSelectEmoji={insertEmoji}
/>
)} )}
{mode == Modes.text && ( {mode == Modes.text && (
<MessageInput <MessageInput