🆙 Added emoji to the chat

This commit is contained in:
duckietm
2026-02-25 11:03:29 +01:00
parent 3aaa3c4d2d
commit 32cf466fb4
4 changed files with 80 additions and 2 deletions
+4
View File
@@ -11,9 +11,13 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.26.9", "@babel/runtime": "^7.26.9",
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@tanstack/react-virtual": "3.2.0", "@tanstack/react-virtual": "3.2.0",
"@types/react-transition-group": "^4.4.10", "@types/react-transition-group": "^4.4.10",
"dompurify": "^3.1.5", "dompurify": "^3.1.5",
"emoji-mart": "^5.6.0",
"emoji-toolkit": "10.0.0",
"framer-motion": "^11.2.12", "framer-motion": "^11.2.12",
"react": "^19.2.4", "react": "^19.2.4",
"react-bootstrap": "^2.10.10", "react-bootstrap": "^2.10.10",
@@ -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<ChatInputEmojiSelectorViewProps> = 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 (
<div>
<Popover
containerClassName="z-[1070]"
content={ <Picker data={ data } onEmojiSelect={ handleEmojiSelect } /> }
isOpen={ selectorVisible }
positions={ [ 'top' ] }
onClickOutside={ () => setSelectorVisible(false) }
>
<div className="cursor-pointer text-lg select-none px-1" onClick={ toggleSelector }>🙂</div>
</Popover>
</div>
);
};
@@ -4,6 +4,7 @@ import { createPortal } from 'react-dom';
import { ChatMessageTypeEnum, GetClubMemberLevel, GetConfigurationValue, LocalizeText, RoomWidgetUpdateChatInputContentEvent } from '../../../../api'; import { ChatMessageTypeEnum, GetClubMemberLevel, GetConfigurationValue, LocalizeText, RoomWidgetUpdateChatInputContentEvent } from '../../../../api';
import { Text } from '../../../../common'; import { Text } from '../../../../common';
import { useChatInputWidget, useRoom, useSessionInfo, useUiEvent } from '../../../../hooks'; import { useChatInputWidget, useRoom, useSessionInfo, useUiEvent } from '../../../../hooks';
import { ChatInputEmojiSelectorView } from './ChatInputEmojiSelectorView';
import { ChatInputStyleSelectorView } from './ChatInputStyleSelectorView'; import { ChatInputStyleSelectorView } from './ChatInputStyleSelectorView';
export const ChatInputView: FC<{}> = props => export const ChatInputView: FC<{}> = props =>
@@ -119,6 +120,13 @@ export const ChatInputView: FC<{}> = props =>
setChatValue(value); setChatValue(value);
}, [ setIsTyping, setIsIdle ]); }, [ setIsTyping, setIsIdle ]);
const addChatEmoji = useCallback((emoji: string) =>
{
setChatValue(prev => prev + emoji);
setIsTyping(true);
inputRef.current?.focus();
}, [ setIsTyping, inputRef ]);
const onKeyDownEvent = useCallback((event: KeyboardEvent) => const onKeyDownEvent = useCallback((event: KeyboardEvent) =>
{ {
if(floodBlocked || !inputRef.current || anotherInputHasFocus()) return; if(floodBlocked || !inputRef.current || anotherInputHasFocus()) return;
@@ -235,13 +243,14 @@ export const ChatInputView: FC<{}> = props =>
return ( return (
createPortal( createPortal(
<div className="nitro-chat-input-container flex justify-center items-center relative h-10 border-2 border-black bg-gray-200 pr-2.5 w-full overflow-hidden rounded-lg"> <div className="nitro-chat-input-container flex justify-between items-center relative h-10 border-2 border-black bg-gray-200 pr-2.5 w-full overflow-hidden rounded-lg">
<div className="items-center input-sizer"> <div className="flex-1 items-center input-sizer">
{ !floodBlocked && { !floodBlocked &&
<input ref={ inputRef } className="[font-size:inherit] placeholder-[#6c757d] bg-transparent border-none focus:border-current focus:shadow-none focus:ring-0 " maxLength={ maxChatLength } placeholder={ LocalizeText('widgets.chatinput.default') } type="text" value={ chatValue } onChange={ event => updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> } <input ref={ inputRef } className="[font-size:inherit] placeholder-[#6c757d] bg-transparent border-none focus:border-current focus:shadow-none focus:ring-0 " maxLength={ maxChatLength } placeholder={ LocalizeText('widgets.chatinput.default') } type="text" value={ chatValue } onChange={ event => updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> }
{ floodBlocked && { floodBlocked &&
<Text variant="danger">{ LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } </Text> } <Text variant="danger">{ LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } </Text> }
</div> </div>
<ChatInputEmojiSelectorView addChatEmoji={ addChatEmoji } />
<ChatInputStyleSelectorView chatStyleId={ chatStyleId } chatStyleIds={ chatStyleIds } selectChatStyleId={ updateChatStyleId } /> <ChatInputStyleSelectorView chatStyleId={ chatStyleId } chatStyleIds={ chatStyleIds } selectChatStyleId={ updateChatStyleId } />
</div>, document.getElementById('toolbar-chat-input-container')) </div>, document.getElementById('toolbar-chat-input-container'))
); );
+28
View File
@@ -159,4 +159,32 @@
&:hover { &:hover {
color: #419AD2; 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;
}
}
} }