diff --git a/README.md b/README.md index c54d2ea..b3c96e4 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ Ararat International School - платформа по обучению игры - Express.js - Mongo DB +#### V 0.06 +- [Messenger] Добавленно контектное меню по нажатию ПКМ на сообщения +- [Messenger] Добавлен EmojiPicker + #### V 0.05 - [Messenger] Добавлена заглушка когда чат не выбран - [Messenger] Чаты теперь открываются по маршруту /messenger/chat/:id diff --git a/client/package-lock.json b/client/package-lock.json index 2d61cae..1a3295c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,7 @@ "name": "araratchess", "version": "0.0.0", "dependencies": { + "@headlessui/react": "^1.7.15", "@react-icons/all-files": "https://github.com/react-icons/react-icons/releases/download/v4.10.1/react-icons-all-files-4.10.1.tgz", "@reduxjs/toolkit": "^1.9.5", "@types/axios": "^0.14.0", @@ -17,6 +18,7 @@ "axios": "^1.4.0", "body-scroll-lock": "^4.0.0-beta.0", "date-fns": "^2.30.0", + "emoji-picker-react": "^4.4.10", "framer-motion": "^10.13.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -889,6 +891,21 @@ "npm": ">=6.14.13" } }, + "node_modules/@headlessui/react": { + "version": "1.7.15", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.15.tgz", + "integrity": "sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw==", + "dependencies": { + "client-only": "^0.0.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -1678,6 +1695,19 @@ "node": ">= 6" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1844,6 +1874,20 @@ "integrity": "sha512-fT3hvdUWLjDbaTGzyOjng/CQhQJSQP8ThO3XZAoaxHvHo2kUXiRQVMj9M235l8uDFiNPsPa6KHT1p3RaR6ugRw==", "dev": true }, + "node_modules/emoji-picker-react": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.4.10.tgz", + "integrity": "sha512-/5o57w8BKiCHklyqfYCeUlm00R9tdm2ZmJd0p6Q3/Ul7E7eMh+/FduuRFn3UVQCl5F6i4kOdREMz7EJ7YI1vMg==", + "dependencies": { + "clsx": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16" + } + }, "node_modules/esbuild": { "version": "0.18.14", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.14.tgz", diff --git a/client/package.json b/client/package.json index 09f3259..0e99946 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@headlessui/react": "^1.7.15", "@react-icons/all-files": "https://github.com/react-icons/react-icons/releases/download/v4.10.1/react-icons-all-files-4.10.1.tgz", "@reduxjs/toolkit": "^1.9.5", "@types/axios": "^0.14.0", @@ -19,6 +20,7 @@ "axios": "^1.4.0", "body-scroll-lock": "^4.0.0-beta.0", "date-fns": "^2.30.0", + "emoji-picker-react": "^4.4.10", "framer-motion": "^10.13.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/client/public/chat-pattern.webp b/client/public/chat-pattern.webp index 61408ea..2eda8cd 100644 Binary files a/client/public/chat-pattern.webp and b/client/public/chat-pattern.webp differ diff --git a/client/src/components/Messenger/Chat/Chat.tsx b/client/src/components/Messenger/Chat/Chat.tsx index c7eac16..9f7dfaa 100644 --- a/client/src/components/Messenger/Chat/Chat.tsx +++ b/client/src/components/Messenger/Chat/Chat.tsx @@ -1,11 +1,19 @@ -import { FC, useRef, useEffect } from 'react' +import { FC, useRef, useEffect, useState } from 'react' import TopInfo from './TopInfo'; import Message from './Message'; import EmptyChat from './EmptyChat'; import SendMessage from './SendMessage'; import { useAppSelector } from '../../../hooks/redux'; +import MessageMenu from './MessageMenu/MessageMenu'; +import { IContext } from '../../../models/IContext'; const Chat: FC = () => { + const [context, setContext] = useState({ + active: false, + x: 0, + y: 0, + message_id: '' + }); const messagesEndRef = useRef(null) const { user } = useAppSelector(state => state.UserSlice) const { chat } = useAppSelector(state => state.MessengerSlice) @@ -14,19 +22,19 @@ const Chat: FC = () => { messagesEndRef.current.scrollIntoView() } }, [chat.messages]) - return (
-
+
0 ? 'mt-10' : 'h-full justify-center'].join(' ')}> {chat.messages.length > 0 ? <> {chat.messages.map(message=> - + )} +
: diff --git a/client/src/components/Messenger/Chat/Message.tsx b/client/src/components/Messenger/Chat/Message.tsx index 72e9eb2..3308a33 100644 --- a/client/src/components/Messenger/Chat/Message.tsx +++ b/client/src/components/Messenger/Chat/Message.tsx @@ -4,18 +4,25 @@ import { User } from '../../../models/User'; import format from 'date-fns/format'; import { BsCheckAll } from '@react-icons/all-files/bs/BsCheckAll' import Avatar from '../../UI/Avatar'; +import { IContext } from '../../../models/IContext'; interface MessageProps { msg: IMessage; user?: User; isMe: boolean; + setContext: (obj: IContext) => void; } -const Message: FC = ({msg, user, isMe}) => { +const Message: FC = ({msg, user, isMe, setContext}) => { + const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); + setContext({active: true, x: e.clientX, y: e.clientY, message_id: msg._id}); + } + return (
-
+
handleClick(e)} className={['shadow-md rounded-md py-2 px-4 relative before:absolute before:border-[10px] before:border-transparent before:border-t-[10px] before:top-0', isMe ? 'bg-apricot order-1 mr-5 before:border-t-apricot before:right-0 before:translate-x-1/2' : 'bg-zinc-400 order-3 ml-5 before:zinc-t-gray-400 before:left-0 before:-translate-x-1/2'].join(' ')}>

{user?.name} {user?.sname}

{msg.msg}

@@ -24,8 +31,8 @@ const Message: FC = ({msg, user, isMe}) => { }
-
+
) } diff --git a/client/src/components/Messenger/Chat/MessageMenu/MessageMenu.tsx b/client/src/components/Messenger/Chat/MessageMenu/MessageMenu.tsx new file mode 100644 index 0000000..ae1fc0f --- /dev/null +++ b/client/src/components/Messenger/Chat/MessageMenu/MessageMenu.tsx @@ -0,0 +1,42 @@ +import { FC, useRef, useEffect } from 'react' +import { MdOutlineEdit } from '@react-icons/all-files/md/MdOutlineEdit' +import { BsArrow90DegLeft } from '@react-icons/all-files/bs/BsArrow90DegLeft' +import { BsArrow90DegRight } from '@react-icons/all-files/bs/BsArrow90DegRight' +import { MdContentCopy } from '@react-icons/all-files/md/MdContentCopy' +import { AiOutlineDelete } from '@react-icons/all-files/ai/AiOutlineDelete' +import { AiOutlineCheckCircle } from '@react-icons/all-files/ai/AiOutlineCheckCircle' +import MessageMenuItem from './MessageMenuItem'; +import { IContext } from '../../../../models/IContext' +import { clickOuter } from '../../../../utils/clickOuter' + +interface MessageMenuProps { + context: IContext; + setContext: (obj: IContext) => void; +} + +const MessageMenu: FC = ({ context, setContext }) => { + const menuRef = useRef(null); + useEffect(() => { + if(menuRef.current) { + return clickOuter(menuRef.current, ()=>setContext({...context, active: false})); + } + }, [context, setContext]); + return ( + <> + {context.active && +
+
    + }>Reply + }>Edit + }>Resend + }>Copy text + }>Delete + }>Select +
+
+ } + + ) +} + +export default MessageMenu; \ No newline at end of file diff --git a/client/src/components/Messenger/Chat/MessageMenu/MessageMenuItem.tsx b/client/src/components/Messenger/Chat/MessageMenu/MessageMenuItem.tsx new file mode 100644 index 0000000..9d51b2e --- /dev/null +++ b/client/src/components/Messenger/Chat/MessageMenu/MessageMenuItem.tsx @@ -0,0 +1,14 @@ +import { FC, PropsWithChildren } from 'react' + +interface MessageMenuItemProps { + icon: React.JSX.Element; + className?: string; +} + +const MessageMenuItem: FC> = ({children, icon, className}) => { + return ( + + ) +} + +export default MessageMenuItem; \ No newline at end of file diff --git a/client/src/components/Messenger/Chat/SendMessage.tsx b/client/src/components/Messenger/Chat/SendMessage.tsx index 9b305d8..5aa2dfa 100644 --- a/client/src/components/Messenger/Chat/SendMessage.tsx +++ b/client/src/components/Messenger/Chat/SendMessage.tsx @@ -1,17 +1,22 @@ -import { FC, useState } from 'react' +import { FC, useState, useRef } from 'react' import Textarea from './Textarea'; import IcoButton from '../../UI/IcoButton'; import { BsMic } from '@react-icons/all-files/bs/BsMic'; import { VscSend } from '@react-icons/all-files/vsc/VscSend'; import {BsEmojiSmile} from '@react-icons/all-files/bs/BsEmojiSmile'; import {ImAttachment} from '@react-icons/all-files/im/ImAttachment'; +import EmojiPicker from '../../UI/EmojiPicker'; const SendMessage: FC = () => { + const ref = useRef(null); const [msg, setMsg] = useState(''); + const [pickerActive, setPickerActive] = useState(false); + return ( -
+
- } className='!px-3 !py-2'/> + {pickerActive ? setPickerActive(false) : setPickerActive(true)}} icon={} className='!px-3 !py-2'/> + } className='!px-3 !py-2'/>