diff --git a/web/package-lock.json b/web/package-lock.json index fe35c60..1f32593 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@livekit/components-react": "^2.9.21", "@livekit/track-processors": "^0.7.2", + "emoji-picker-react": "^4.19.1", "livekit-client": "^2.20.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1862,6 +1863,21 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-picker-react": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.19.1.tgz", + "integrity": "sha512-BmDdqInKFVYJpv7qS9WI6L9656cDAC+FkDvUjJds56nKHbaVTBNeDmLwKBytRnzu37zWHs9Isg7gt5PT43y6xA==", + "license": "MIT", + "dependencies": { + "flairup": "1.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16" + } + }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -2011,6 +2027,12 @@ "node": ">=8" } }, + "node_modules/flairup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flairup/-/flairup-1.0.0.tgz", + "integrity": "sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA==", + "license": "MIT" + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", diff --git a/web/package.json b/web/package.json index 96e053d..db7abd7 100644 --- a/web/package.json +++ b/web/package.json @@ -12,6 +12,7 @@ "dependencies": { "@livekit/components-react": "^2.9.21", "@livekit/track-processors": "^0.7.2", + "emoji-picker-react": "^4.19.1", "livekit-client": "^2.20.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/web/src/components/ChatArea.tsx b/web/src/components/ChatArea.tsx index 517fe64..e0f5cb3 100644 --- a/web/src/components/ChatArea.tsx +++ b/web/src/components/ChatArea.tsx @@ -20,7 +20,8 @@ import { useContextMenu } from "./ContextMenu.tsx"; import { PinnedMessages } from "./PinnedMessages.tsx"; import { FeatureRequestsPanel } from "./FeatureRequestsPanel.tsx"; import { ReactionBar } from "./ReactionBar.tsx"; -import { EmojiPicker } from "./EmojiPicker.tsx"; +import { EmojiPicker as KaomojiPicker } from "./EmojiPicker.tsx"; +import Picker, { Theme } from 'emoji-picker-react'; import { FormatToolbar } from "./FormatToolbar.tsx"; import { ReplyBar } from "./ReplyBar.tsx"; import { useLayoutStore } from "../stores/layout.ts"; @@ -160,6 +161,8 @@ const MessageItem = memo(({ currentUserId, activeReactionMessageId, setActiveReactionMessageId, + activeNativeReactionMessageId, + setActiveNativeReactionMessageId, members, parentMessage, onJumpToParent, @@ -175,6 +178,8 @@ const MessageItem = memo(({ currentUserId?: string; activeReactionMessageId: string | null; setActiveReactionMessageId: (id: string | null) => void; + activeNativeReactionMessageId: string | null; + setActiveNativeReactionMessageId: (id: string | null) => void; members: any[]; parentMessage: any | null; onJumpToParent: (id: string) => void; @@ -243,11 +248,22 @@ const MessageItem = memo(({ {/* Kaomoji picker popover */} {activeReactionMessageId === message.id && ( - onAddReaction(message.id, emoji)} onClose={() => setActiveReactionMessageId(null)} /> )} + {activeNativeReactionMessageId === message.id && ( +
+ { + onAddReaction(message.id, emoji.emoji); + setActiveNativeReactionMessageId(null); + }} + /> +
+ )} ); }); @@ -274,6 +290,7 @@ export function ChatArea() { const [error, setError] = useState(null); const [showGifPicker, setShowGifPicker] = useState(false); const [showKaomoji, setShowKaomoji] = useState(false); + const [showNativeEmoji, setShowNativeEmoji] = useState(false); const [showSearch, setShowSearch] = useState(false); const [mentionQuery, setMentionQuery] = useState(null); const [commandQuery, setCommandQuery] = useState(null); @@ -313,6 +330,7 @@ export function ChatArea() { const [showPinned, setShowPinned] = useState(false); const [showFeatureRequests, setShowFeatureRequests] = useState(false); const [activeReactionMessageId, setActiveReactionMessageId] = useState(null); + const [activeNativeReactionMessageId, setActiveNativeReactionMessageId] = useState(null); const [replyToMessage, setReplyToMessage] = useState(null); const pinMessage = useMessageStore((s) => s.pinMessage); const unpinMessage = useMessageStore((s) => s.unpinMessage); @@ -337,6 +355,7 @@ export function ChatArea() { try { await api.post(`/messages/${messageId}/reactions`, { emoji }); setActiveReactionMessageId(null); + setActiveNativeReactionMessageId(null); } catch (err) { setError(err instanceof Error ? err.message : "Failed to add reaction"); } @@ -352,10 +371,18 @@ export function ChatArea() { } }, { - label: "[ADD REACTION]", + label: "[ADD KAOMOJI REACTION]", onClick: () => { setActiveReactionMessageId(msg.id); - } + setActiveNativeReactionMessageId(null); + }, + }, + { + label: "[ADD EMOJI REACTION]", + onClick: () => { + setActiveNativeReactionMessageId(msg.id); + setActiveReactionMessageId(null); + }, }, { label: msg.pinned ? "[UNPIN MESSAGE]" : "[PIN MESSAGE]", @@ -851,6 +878,8 @@ export function ChatArea() { currentUserId={currentUser?.id} activeReactionMessageId={activeReactionMessageId} setActiveReactionMessageId={setActiveReactionMessageId} + activeNativeReactionMessageId={activeNativeReactionMessageId} + setActiveNativeReactionMessageId={setActiveNativeReactionMessageId} members={members} parentMessage={message.reply_to ? messages.find((m) => m.id === message.reply_to) : null} onJumpToParent={handleJumpToMessage} @@ -934,21 +963,45 @@ export function ChatArea() { > [GIF] + {showKaomoji && ( - setInput((prev) => prev + emoji)} onClose={() => setShowKaomoji(false)} /> )} + {showNativeEmoji && ( +
+ { + setInput((prev) => prev + emoji.emoji); + setShowNativeEmoji(false); + }} + /> +
+ )} {activeThread && ( diff --git a/web/src/components/DMChat.tsx b/web/src/components/DMChat.tsx index 6add601..069ab98 100644 --- a/web/src/components/DMChat.tsx +++ b/web/src/components/DMChat.tsx @@ -5,7 +5,8 @@ import { useAuthStore } from "../stores/auth.ts"; import { useTypingStore } from "../stores/typing.ts"; import { useLayoutStore } from "../stores/layout.ts"; import { GiphyPicker, type Gif } from "./GiphyPicker.tsx"; -import { EmojiPicker } from "./EmojiPicker.tsx"; +import { EmojiPicker as KaomojiPicker } from "./EmojiPicker.tsx"; +import Picker, { Theme } from 'emoji-picker-react'; import { FormatToolbar } from "./FormatToolbar.tsx"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -76,6 +77,7 @@ export function DMChat() { const [error, setError] = useState(null); const [showGifPicker, setShowGifPicker] = useState(false); const [showKaomoji, setShowKaomoji] = useState(false); + const [showNativeEmoji, setShowNativeEmoji] = useState(false); const bottomRef = useRef(null); const scrollContainerRef = useRef(null); const inputRef = useRef(null); @@ -282,6 +284,19 @@ export function DMChat() { > [GIF] + {showKaomoji && ( - setInput((prev) => prev + emoji)} onClose={() => setShowKaomoji(false)} /> )} + {showNativeEmoji && ( +
+ { + setInput((prev) => prev + emoji.emoji); + setShowNativeEmoji(false); + }} + /> +
+ )} );