From 32cf466fb46d6fd71dd19b2bc1184841a9b99c6a Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 25 Feb 2026 11:03:29 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=86=99=20Added=20emoji=20to=20the=20chat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 ++ .../chat-input/ChatInputEmojiSelectorView.tsx | 37 +++++++++++++++++++ .../room/widgets/chat-input/ChatInputView.tsx | 13 ++++++- src/css/room/RoomWidgets.css | 28 ++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx diff --git a/package.json b/package.json index b389d84..6f71c76 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,13 @@ }, "dependencies": { "@babel/runtime": "^7.26.9", + "@emoji-mart/data": "^1.2.1", + "@emoji-mart/react": "^1.1.1", "@tanstack/react-virtual": "3.2.0", "@types/react-transition-group": "^4.4.10", "dompurify": "^3.1.5", + "emoji-mart": "^5.6.0", + "emoji-toolkit": "10.0.0", "framer-motion": "^11.2.12", "react": "^19.2.4", "react-bootstrap": "^2.10.10", diff --git a/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx b/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx new file mode 100644 index 0000000..f547e46 --- /dev/null +++ b/src/components/room/widgets/chat-input/ChatInputEmojiSelectorView.tsx @@ -0,0 +1,37 @@ +import data from '@emoji-mart/data'; +import Picker from '@emoji-mart/react'; +import { FC, useState } from 'react'; +import { Popover } from 'react-tiny-popover'; + +interface ChatInputEmojiSelectorViewProps +{ + addChatEmoji: (emoji: string) => void; +} + +export const ChatInputEmojiSelectorView: FC = props => +{ + const { addChatEmoji = null } = props; + const [ selectorVisible, setSelectorVisible ] = useState(false); + + const handleEmojiSelect = (emoji: any) => + { + addChatEmoji(emoji.native); + setSelectorVisible(false); + }; + + const toggleSelector = () => setSelectorVisible(prev => !prev); + + return ( +
+ } + isOpen={ selectorVisible } + positions={ [ 'top' ] } + onClickOutside={ () => setSelectorVisible(false) } + > +
🙂
+
+
+ ); +}; diff --git a/src/components/room/widgets/chat-input/ChatInputView.tsx b/src/components/room/widgets/chat-input/ChatInputView.tsx index 03eadce..3abe197 100644 --- a/src/components/room/widgets/chat-input/ChatInputView.tsx +++ b/src/components/room/widgets/chat-input/ChatInputView.tsx @@ -4,6 +4,7 @@ import { createPortal } from 'react-dom'; import { ChatMessageTypeEnum, GetClubMemberLevel, GetConfigurationValue, LocalizeText, RoomWidgetUpdateChatInputContentEvent } from '../../../../api'; import { Text } from '../../../../common'; import { useChatInputWidget, useRoom, useSessionInfo, useUiEvent } from '../../../../hooks'; +import { ChatInputEmojiSelectorView } from './ChatInputEmojiSelectorView'; import { ChatInputStyleSelectorView } from './ChatInputStyleSelectorView'; export const ChatInputView: FC<{}> = props => @@ -119,6 +120,13 @@ export const ChatInputView: FC<{}> = props => setChatValue(value); }, [ setIsTyping, setIsIdle ]); + const addChatEmoji = useCallback((emoji: string) => + { + setChatValue(prev => prev + emoji); + setIsTyping(true); + inputRef.current?.focus(); + }, [ setIsTyping, inputRef ]); + const onKeyDownEvent = useCallback((event: KeyboardEvent) => { if(floodBlocked || !inputRef.current || anotherInputHasFocus()) return; @@ -235,13 +243,14 @@ export const ChatInputView: FC<{}> = props => return ( createPortal( -
-
+
+
{ !floodBlocked && updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> } { floodBlocked && { LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } }
+
, document.getElementById('toolbar-chat-input-container')) ); diff --git a/src/css/room/RoomWidgets.css b/src/css/room/RoomWidgets.css index 34310fb..9723ff4 100644 --- a/src/css/room/RoomWidgets.css +++ b/src/css/room/RoomWidgets.css @@ -159,4 +159,32 @@ &:hover { color: #419AD2; } +} + +.nitro-chat-input-container { + .input-sizer { + display: inline-grid; + vertical-align: top; + height: 100%; + padding: 0 10px; + + &::after, + input { + width: auto; + min-width: 1em; + grid-area: 1 / 2; + margin: 0; + resize: none; + background: none; + appearance: none; + border: none; + outline: none; + } + + &::after { + content: attr(data-value) ' '; + visibility: hidden; + white-space: pre-wrap; + } + } } \ No newline at end of file