put actual emojis

This commit is contained in:
Red Adaya 2024-10-08 13:14:36 +08:00
parent 45b8470355
commit 2bc8295fdb
7 changed files with 378 additions and 14 deletions

View File

@ -4,6 +4,7 @@
import clsx from "clsx";
import React, { memo, useEffect, useLayoutEffect, useRef, useState } from "react";
import ReactDOM from "react-dom";
import { throttle } from "throttle-debounce";
import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions";
import "./contextmenu.less";
@ -269,7 +270,7 @@ const ContextMenu = memo(
parentRef: React.RefObject<HTMLDivElement>,
label: string
) => {
setTimeout(() => {
throttle(0, () => {
const subContextMenuRef = subContextMenuRefs.current[key]?.current;
if (!subContextMenuRef) return;
@ -296,7 +297,7 @@ const ContextMenu = memo(
...prev,
[key]: { top, left, label },
}));
}, 0);
})();
};
const handleMouseEnterItem = (

View File

@ -0,0 +1,43 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
.emoji-palette-content {
padding: 10px;
max-height: 250px;
width: 250px;
display: flex;
flex-direction: column;
// > input {
// margin-top: 10px;
// }
}
.emoji-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(30px, 1fr));
gap: 5px;
justify-content: center;
align-items: center;
margin-top: 5px;
}
.emoji-button {
font-size: 24px;
padding: 5px;
cursor: pointer;
background: none;
border: none;
transition: background-color 0.3s ease;
&:hover {
background-color: rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
}
.no-emojis {
font-size: 14px;
color: #888;
text-align: center;
}

View File

@ -0,0 +1,37 @@
import type { Meta, StoryObj } from "@storybook/react";
import { useRef } from "react";
import { EmojiPalette } from "./emojipalette";
const meta: Meta<typeof EmojiPalette> = {
title: "Elements/EmojiPalette",
component: EmojiPalette,
args: {
className: "custom-emoji-palette-class",
},
argTypes: {
scopeRef: {
description: "Reference to the outer container element for positioning",
},
className: {
description: "Custom class for emoji palette styling",
},
},
};
export default meta;
type Story = StoryObj<typeof EmojiPalette>;
export const DefaultEmojiPalette: Story = {
render: (args) => {
const scopeRef = useRef<HTMLDivElement>(null);
return (
<div ref={scopeRef} style={{ padding: "20px", height: "300px", border: "2px solid black" }}>
<EmojiPalette {...args} scopeRef={scopeRef} />
</div>
);
},
args: {
className: "custom-emoji-palette-class",
},
};

View File

@ -0,0 +1,280 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import clsx from "clsx";
import React, { memo, useEffect, useRef, useState } from "react";
import { Button } from "./button";
import { Input } from "./input";
import { Palette } from "./palette";
import "./emojiPalette.less";
interface EmojiPaletteProps {
scopeRef: React.RefObject<HTMLElement>;
className?: string;
}
const emojiList = [
// Smileys & Emotion
{ emoji: "😀", name: "grinning face" },
{ emoji: "😁", name: "beaming face with smiling eyes" },
{ emoji: "😂", name: "face with tears of joy" },
{ emoji: "🤣", name: "rolling on the floor laughing" },
{ emoji: "😃", name: "grinning face with big eyes" },
{ emoji: "😄", name: "grinning face with smiling eyes" },
{ emoji: "😅", name: "grinning face with sweat" },
{ emoji: "😆", name: "grinning squinting face" },
{ emoji: "😉", name: "winking face" },
{ emoji: "😊", name: "smiling face with smiling eyes" },
{ emoji: "😋", name: "face savoring food" },
{ emoji: "😎", name: "smiling face with sunglasses" },
{ emoji: "😍", name: "smiling face with heart-eyes" },
{ emoji: "😘", name: "face blowing a kiss" },
{ emoji: "😗", name: "kissing face" },
{ emoji: "😙", name: "kissing face with smiling eyes" },
{ emoji: "😚", name: "kissing face with closed eyes" },
{ emoji: "🙂", name: "slightly smiling face" },
{ emoji: "🤗", name: "hugging face" },
{ emoji: "🤔", name: "thinking face" },
{ emoji: "😐", name: "neutral face" },
{ emoji: "😑", name: "expressionless face" },
{ emoji: "😶", name: "face without mouth" },
{ emoji: "🙄", name: "face with rolling eyes" },
{ emoji: "😏", name: "smirking face" },
{ emoji: "😣", name: "persevering face" },
{ emoji: "😥", name: "sad but relieved face" },
{ emoji: "😮", name: "face with open mouth" },
{ emoji: "🤐", name: "zipper-mouth face" },
{ emoji: "😯", name: "hushed face" },
{ emoji: "😪", name: "sleepy face" },
{ emoji: "😫", name: "tired face" },
{ emoji: "🥱", name: "yawning face" },
{ emoji: "😴", name: "sleeping face" },
{ emoji: "😌", name: "relieved face" },
{ emoji: "😛", name: "face with tongue" },
{ emoji: "😜", name: "winking face with tongue" },
{ emoji: "😝", name: "squinting face with tongue" },
{ emoji: "🤤", name: "drooling face" },
{ emoji: "😒", name: "unamused face" },
{ emoji: "😓", name: "downcast face with sweat" },
{ emoji: "😔", name: "pensive face" },
{ emoji: "😕", name: "confused face" },
{ emoji: "🙃", name: "upside-down face" },
{ emoji: "🫠", name: "melting face" },
{ emoji: "😲", name: "astonished face" },
{ emoji: "☹️", name: "frowning face" },
{ emoji: "🙁", name: "slightly frowning face" },
{ emoji: "😖", name: "confounded face" },
{ emoji: "😞", name: "disappointed face" },
{ emoji: "😟", name: "worried face" },
{ emoji: "😤", name: "face with steam from nose" },
{ emoji: "😢", name: "crying face" },
{ emoji: "😭", name: "loudly crying face" },
{ emoji: "😦", name: "frowning face with open mouth" },
{ emoji: "😧", name: "anguished face" },
{ emoji: "😨", name: "fearful face" },
{ emoji: "😩", name: "weary face" },
{ emoji: "🤯", name: "exploding head" },
{ emoji: "😬", name: "grimacing face" },
{ emoji: "😰", name: "anxious face with sweat" },
{ emoji: "😱", name: "face screaming in fear" },
{ emoji: "🥵", name: "hot face" },
{ emoji: "🥶", name: "cold face" },
{ emoji: "😳", name: "flushed face" },
{ emoji: "🤪", name: "zany face" },
{ emoji: "😵", name: "dizzy face" },
{ emoji: "🥴", name: "woozy face" },
{ emoji: "😠", name: "angry face" },
{ emoji: "😡", name: "pouting face" },
{ emoji: "🤬", name: "face with symbols on mouth" },
{ emoji: "🤮", name: "face vomiting" },
{ emoji: "🤢", name: "nauseated face" },
{ emoji: "😷", name: "face with medical mask" },
// Gestures & Hand Signs
{ emoji: "👋", name: "waving hand" },
{ emoji: "🤚", name: "raised back of hand" },
{ emoji: "🖐️", name: "hand with fingers splayed" },
{ emoji: "✋", name: "raised hand" },
{ emoji: "👌", name: "OK hand" },
{ emoji: "✌️", name: "victory hand" },
{ emoji: "🤞", name: "crossed fingers" },
{ emoji: "🤟", name: "love-you gesture" },
{ emoji: "🤘", name: "sign of the horns" },
{ emoji: "🤙", name: "call me hand" },
{ emoji: "👈", name: "backhand index pointing left" },
{ emoji: "👉", name: "backhand index pointing right" },
{ emoji: "👆", name: "backhand index pointing up" },
{ emoji: "👇", name: "backhand index pointing down" },
{ emoji: "👍", name: "thumbs up" },
{ emoji: "👎", name: "thumbs down" },
{ emoji: "👏", name: "clapping hands" },
{ emoji: "🙌", name: "raising hands" },
{ emoji: "👐", name: "open hands" },
{ emoji: "🙏", name: "folded hands" },
// Animals & Nature
{ emoji: "🐶", name: "dog face" },
{ emoji: "🐱", name: "cat face" },
{ emoji: "🐭", name: "mouse face" },
{ emoji: "🐹", name: "hamster face" },
{ emoji: "🐰", name: "rabbit face" },
{ emoji: "🦊", name: "fox face" },
{ emoji: "🐻", name: "bear face" },
{ emoji: "🐼", name: "panda face" },
{ emoji: "🐨", name: "koala" },
{ emoji: "🐯", name: "tiger face" },
{ emoji: "🦁", name: "lion" },
{ emoji: "🐮", name: "cow face" },
{ emoji: "🐷", name: "pig face" },
{ emoji: "🐸", name: "frog face" },
{ emoji: "🐵", name: "monkey face" },
{ emoji: "🦄", name: "unicorn face" },
{ emoji: "🐢", name: "turtle" },
{ emoji: "🐍", name: "snake" },
{ emoji: "🦋", name: "butterfly" },
{ emoji: "🐝", name: "honeybee" },
{ emoji: "🐞", name: "lady beetle" },
{ emoji: "🦀", name: "crab" },
{ emoji: "🐠", name: "tropical fish" },
{ emoji: "🐟", name: "fish" },
{ emoji: "🐬", name: "dolphin" },
{ emoji: "🐳", name: "spouting whale" },
{ emoji: "🐋", name: "whale" },
{ emoji: "🦈", name: "shark" },
// Food & Drink
{ emoji: "🍏", name: "green apple" },
{ emoji: "🍎", name: "red apple" },
{ emoji: "🍐", name: "pear" },
{ emoji: "🍊", name: "tangerine" },
{ emoji: "🍋", name: "lemon" },
{ emoji: "🍌", name: "banana" },
{ emoji: "🍉", name: "watermelon" },
{ emoji: "🍇", name: "grapes" },
{ emoji: "🍓", name: "strawberry" },
{ emoji: "🫐", name: "blueberries" },
{ emoji: "🍈", name: "melon" },
{ emoji: "🍒", name: "cherries" },
{ emoji: "🍑", name: "peach" },
{ emoji: "🥭", name: "mango" },
{ emoji: "🍍", name: "pineapple" },
{ emoji: "🥥", name: "coconut" },
{ emoji: "🥑", name: "avocado" },
{ emoji: "🥦", name: "broccoli" },
{ emoji: "🥕", name: "carrot" },
{ emoji: "🌽", name: "corn" },
{ emoji: "🌶️", name: "hot pepper" },
{ emoji: "🍔", name: "hamburger" },
{ emoji: "🍟", name: "french fries" },
{ emoji: "🍕", name: "pizza" },
{ emoji: "🌭", name: "hot dog" },
{ emoji: "🥪", name: "sandwich" },
{ emoji: "🍿", name: "popcorn" },
{ emoji: "🥓", name: "bacon" },
{ emoji: "🥚", name: "egg" },
{ emoji: "🍰", name: "cake" },
{ emoji: "🎂", name: "birthday cake" },
{ emoji: "🍦", name: "ice cream" },
{ emoji: "🍩", name: "doughnut" },
{ emoji: "🍪", name: "cookie" },
{ emoji: "🍫", name: "chocolate bar" },
{ emoji: "🍬", name: "candy" },
{ emoji: "🍭", name: "lollipop" },
// Activities
{ emoji: "⚽", name: "soccer ball" },
{ emoji: "🏀", name: "basketball" },
{ emoji: "🏈", name: "american football" },
{ emoji: "⚾", name: "baseball" },
{ emoji: "🥎", name: "softball" },
{ emoji: "🎾", name: "tennis" },
{ emoji: "🏐", name: "volleyball" },
{ emoji: "🎳", name: "bowling" },
{ emoji: "⛳", name: "flag in hole" },
{ emoji: "🚴", name: "person biking" },
{ emoji: "🎮", name: "video game" },
{ emoji: "🎲", name: "game die" },
{ emoji: "🎸", name: "guitar" },
{ emoji: "🎺", name: "trumpet" },
// Miscellaneous
{ emoji: "🚀", name: "rocket" },
{ emoji: "💖", name: "sparkling heart" },
{ emoji: "🎉", name: "party popper" },
{ emoji: "🔥", name: "fire" },
{ emoji: "🎁", name: "gift" },
{ emoji: "❤️", name: "red heart" },
{ emoji: "🧡", name: "orange heart" },
{ emoji: "💛", name: "yellow heart" },
{ emoji: "💚", name: "green heart" },
{ emoji: "💙", name: "blue heart" },
{ emoji: "💜", name: "purple heart" },
{ emoji: "🤍", name: "white heart" },
{ emoji: "🤎", name: "brown heart" },
{ emoji: "💔", name: "broken heart" },
];
const EmojiPalette = memo(({ scopeRef, className }: EmojiPaletteProps) => {
const anchorRef = useRef<HTMLButtonElement>(null);
const [isPaletteVisible, setIsPaletteVisible] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (anchorRef.current && !anchorRef.current.contains(event.target as Node)) {
setIsPaletteVisible(false);
}
};
scopeRef?.current?.addEventListener("mousedown", handleClickOutside);
return () => {
scopeRef?.current?.removeEventListener("mousedown", handleClickOutside);
};
}, []);
const handleAnchorClick = () => {
setIsPaletteVisible((prev) => !prev);
};
const handleSearchChange = (val: string) => {
setSearchTerm(val.toLowerCase());
};
const filteredEmojis = emojiList.filter((item) => item.name.includes(searchTerm));
return (
<div className={clsx("emoji-palette", className)}>
<Button ref={anchorRef} className="ghost grey" onClick={handleAnchorClick}>
<i className="fa-sharp fa-solid fa-face-smile"></i>
</Button>
{isPaletteVisible && (
<Palette anchorRef={anchorRef} scopeRef={scopeRef} className="emoji-palette-content">
<Input placeholder="Search emojis..." value={searchTerm} onChange={handleSearchChange} />
<div className="emoji-grid">
{filteredEmojis.length > 0 ? (
filteredEmojis.map((item, index) => (
<Button
key={index}
className="ghost emoji-button"
onClick={() => {
console.log(`Emoji selected: ${item.emoji}`);
setIsPaletteVisible(false);
}}
>
{item.emoji}
</Button>
))
) : (
<div className="no-emojis">No emojis found</div>
)}
</div>
</Palette>
)}
</div>
);
});
EmojiPalette.displayName = "EmojiPalette";
export { EmojiPalette };

View File

@ -13,4 +13,5 @@
border: 1px solid rgba(255, 255, 255, 0.15);
background: #212121;
box-shadow: 0px 8px 24px 0px rgba(0, 0, 0, 0.3);
visibility: hidden;
}

View File

@ -33,13 +33,13 @@ export const DefaultPalette: Story = {
setIsMenuVisible((prev) => !prev);
};
const handleClickOutside = (event: MouseEvent) => {
if (anchorRef.current && !anchorRef.current.contains(event.target as Node)) {
setIsMenuVisible(false);
}
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (anchorRef.current && !anchorRef.current.contains(event.target as Node)) {
setIsMenuVisible(false);
}
};
scopeRef?.current?.addEventListener("mousedown", handleClickOutside);
return () => {
scopeRef?.current?.removeEventListener("mousedown", handleClickOutside);

View File

@ -32,7 +32,7 @@ const Palette = memo(({ children, className, anchorRef, scopeRef }: PaletteProps
// Check if the palette goes beyond the right edge of the window
const rightEdge = left + paletteEl.offsetWidth;
if (rightEdge > window.innerWidth) {
left = window.innerWidth - paletteEl.offsetWidth - 15;
left = window.innerWidth - paletteEl.offsetWidth - 10;
}
// Check if the palette goes beyond the bottom edge of the window
@ -44,12 +44,14 @@ const Palette = memo(({ children, className, anchorRef, scopeRef }: PaletteProps
}
}, [anchorRef, scopeRef, width, height]);
useEffect(() => {
if (position.top > 0 && paletteRef.current?.style.visibility !== "visible") {
paletteRef.current.style.visibility = "visible";
}
}, [position.top]);
return createPortal(
<div
ref={paletteRef}
style={{ top: `${position.top}px`, left: `${position.left}px` }}
className={clsx("palette", className)}
>
<div ref={paletteRef} style={{ top: position.top, left: position.left }} className={clsx("palette", className)}>
{children}
</div>,
document.body