feat: add emoji picker to chat room message input
- Integrated emoji-picker-element library for emoji selection - Added emoji toggle button in message input container - Implemented emoji picker show/hide functionality - Created CSS styles for emoji picker positioning - Added event handling for emoji selection and outside click - Updated package.json to include emoji-picker-element dependency - Modified Prisma schema to use TEXT type for message content - Updated Prisma migration scripts to use dotenv for environment configuration
This commit is contained in:
parent
1e3b188d90
commit
3d41e2cc42
7 changed files with 111 additions and 11 deletions
|
@ -10,9 +10,9 @@
|
|||
"test": "ts-node --test test/**/*.test.ts",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "nodemon --delay 2000ms src/index.ts",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev",
|
||||
"prisma:init": "prisma migrate dev --name init",
|
||||
"prisma:generate": "dotenv -e ../../.env -- prisma generate",
|
||||
"prisma:migrate": "dotenv -e ../../.env -- prisma migrate dev",
|
||||
"prisma:init": "dotenv -e ../../.env -- prisma migrate dev --name init",
|
||||
"build": "tsc"
|
||||
},
|
||||
"keywords": [],
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE `Message` MODIFY `content` TEXT NOT NULL;
|
|
@ -40,7 +40,7 @@ model Room {
|
|||
|
||||
model Message {
|
||||
id String @id @default(uuid())
|
||||
content String
|
||||
content String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
userId String
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"@urql/core": "^5.1.1",
|
||||
"@urql/solid": "^0.1.2",
|
||||
"emoji-picker-element": "^1.26.1",
|
||||
"graphql-ws": "^6.0.4",
|
||||
"solid-js": "^1.9.5",
|
||||
"zod": "^3.24.2"
|
||||
|
|
|
@ -449,3 +449,32 @@ button:disabled {
|
|||
height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Emoji Picker Styles */
|
||||
.message-input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.emoji-toggle-button {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0.25rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.emoji-picker-container {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.message-form {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { createSignal, createEffect, For, Show } from 'solid-js';
|
||||
import { createSignal, createEffect, For, Show, onMount } from 'solid-js';
|
||||
import { gql } from '@urql/core';
|
||||
import { createQuery, createMutation, createSubscription } from '@urql/solid';
|
||||
import { Message } from '../types';
|
||||
import 'emoji-picker-element';
|
||||
import { EmojiClickEvent } from 'emoji-picker-element/shared';
|
||||
import { Picker } from 'emoji-picker-element';
|
||||
|
||||
const ROOM_QUERY = gql`
|
||||
query GetRoom($id: ID!) {
|
||||
|
@ -80,7 +83,13 @@ export function ChatRoom(props: ChatRoomProps) {
|
|||
const [messages, setMessages] = createSignal<Message[]>([]);
|
||||
const [confirmLeave, setConfirmLeave] = createSignal(false);
|
||||
const [leaveError, setLeaveError] = createSignal<string | null>(null);
|
||||
const [showEmojiPicker, setShowEmojiPicker] = createSignal(false);
|
||||
const [emojiPickerRef, setPickerRef] = createSignal<Picker | undefined>(
|
||||
undefined
|
||||
);
|
||||
let messagesContainer: HTMLDivElement | undefined;
|
||||
let messageInput: HTMLInputElement | undefined;
|
||||
let emojiPickerContainer: HTMLDivElement | undefined;
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (messagesContainer) {
|
||||
|
@ -198,6 +207,41 @@ export function ChatRoom(props: ChatRoomProps) {
|
|||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
};
|
||||
|
||||
const toggleEmojiPicker = () => {
|
||||
setShowEmojiPicker(!showEmojiPicker());
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
const handleEmojiSelect = (event: EmojiClickEvent) => {
|
||||
setMessage((prev) => prev + event.detail.unicode);
|
||||
messageInput?.focus();
|
||||
};
|
||||
const ref = emojiPickerRef();
|
||||
if (ref) {
|
||||
ref.addEventListener('emoji-click', handleEmojiSelect);
|
||||
return () => {
|
||||
ref.removeEventListener('emoji-click', handleEmojiSelect);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Close emoji picker when clicking outside
|
||||
onMount(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
emojiPickerContainer &&
|
||||
!emojiPickerContainer.contains(event.target as Node)
|
||||
) {
|
||||
setShowEmojiPicker(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div class='chat-room'>
|
||||
<Show when={roomQuery.data?.room} fallback={<div>Loading room...</div>}>
|
||||
|
@ -246,13 +290,30 @@ export function ChatRoom(props: ChatRoomProps) {
|
|||
</div>
|
||||
|
||||
<form class='message-form' onSubmit={handleSendMessage}>
|
||||
<input
|
||||
type='text'
|
||||
value={message()}
|
||||
onInput={(e) => setMessage(e.currentTarget.value)}
|
||||
placeholder='Type a message...'
|
||||
/>
|
||||
<div class='message-input-container'>
|
||||
<input
|
||||
ref={messageInput}
|
||||
type='text'
|
||||
value={message()}
|
||||
onInput={(e) => setMessage(e.currentTarget.value)}
|
||||
placeholder='Type a message...'
|
||||
/>
|
||||
<button
|
||||
type='button'
|
||||
class='emoji-toggle-button'
|
||||
onClick={toggleEmojiPicker}
|
||||
>
|
||||
😊
|
||||
</button>
|
||||
</div>
|
||||
<button type='submit'>Send</button>
|
||||
|
||||
{showEmojiPicker() && (
|
||||
<div ref={emojiPickerContainer} class='emoji-picker-container'>
|
||||
{/* @ts-ignore */}
|
||||
<emoji-picker ref={(el: Picker) => setPickerRef(el)}></emoji-picker>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
|
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -48,6 +48,7 @@
|
|||
"dependencies": {
|
||||
"@urql/core": "^5.1.1",
|
||||
"@urql/solid": "^0.1.2",
|
||||
"emoji-picker-element": "^1.26.1",
|
||||
"graphql-ws": "^6.0.4",
|
||||
"solid-js": "^1.9.5",
|
||||
"zod": "^3.24.2"
|
||||
|
@ -4381,6 +4382,12 @@
|
|||
"integrity": "sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/emoji-picker-element": {
|
||||
"version": "1.26.1",
|
||||
"resolved": "https://registry.npmjs.org/emoji-picker-element/-/emoji-picker-element-1.26.1.tgz",
|
||||
"integrity": "sha512-XgQ9s2JdmworiqLfJC7eGbzQHGv8yb8U9XofjeRAnOMYaeLh0MfwVAz9oG1YE2U2WnzU0Pys1axMjYtPKJ7YSg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
|
|
Loading…
Add table
Reference in a new issue