From 464f58363b3ba40938d7b875f132b7b60803438e Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Tue, 8 Feb 2022 23:53:27 +0300 Subject: [PATCH] feat: added chat window layout --- src/components/Chat/ChatContextProvider.tsx | 10 ++ src/components/Chat/Sidebar.tsx | 12 --- src/components/Chat/Sidebar/Heading.tsx | 50 ++++++++++ src/components/Chat/Sidebar/Sidebar.tsx | 30 ++++++ .../Chat/Sidebar/Users/User/Avatar.tsx | 21 +++++ .../Chat/Sidebar/Users/User/Info.tsx | 22 +++++ .../Chat/Sidebar/Users/User/Time.tsx | 18 ++++ .../Chat/Sidebar/Users/User/User.tsx | 55 +++++++++++ .../Chat/Sidebar/Users/User/index.ts | 1 + src/components/Chat/Sidebar/Users/Users.tsx | 39 ++++++++ src/components/Chat/Sidebar/Users/index.ts | 1 + src/components/Chat/Sidebar/index.ts | 1 + src/components/Chat/Window.tsx | 8 -- src/components/Chat/Window/Message/Avatar.tsx | 16 ++++ .../Chat/Window/Message/Message.tsx | 29 ++++++ src/components/Chat/Window/Message/Text.tsx | 38 ++++++++ src/components/Chat/Window/Message/context.ts | 17 ++++ src/components/Chat/Window/Message/index.ts | 1 + src/components/Chat/Window/Window.tsx | 64 +++++++++++++ src/components/Chat/Window/index.ts | 1 + src/components/Chat/context.ts | 21 +++++ src/components/Chat/index.tsx | 12 ++- .../UserProfile/Avatar/ChatButton.tsx | 2 +- src/pages/messages/messages.tsx | 93 +------------------ 24 files changed, 447 insertions(+), 115 deletions(-) create mode 100644 src/components/Chat/ChatContextProvider.tsx delete mode 100644 src/components/Chat/Sidebar.tsx create mode 100644 src/components/Chat/Sidebar/Heading.tsx create mode 100644 src/components/Chat/Sidebar/Sidebar.tsx create mode 100644 src/components/Chat/Sidebar/Users/User/Avatar.tsx create mode 100644 src/components/Chat/Sidebar/Users/User/Info.tsx create mode 100644 src/components/Chat/Sidebar/Users/User/Time.tsx create mode 100644 src/components/Chat/Sidebar/Users/User/User.tsx create mode 100644 src/components/Chat/Sidebar/Users/User/index.ts create mode 100644 src/components/Chat/Sidebar/Users/Users.tsx create mode 100644 src/components/Chat/Sidebar/Users/index.ts create mode 100644 src/components/Chat/Sidebar/index.ts delete mode 100644 src/components/Chat/Window.tsx create mode 100644 src/components/Chat/Window/Message/Avatar.tsx create mode 100644 src/components/Chat/Window/Message/Message.tsx create mode 100644 src/components/Chat/Window/Message/Text.tsx create mode 100644 src/components/Chat/Window/Message/context.ts create mode 100644 src/components/Chat/Window/Message/index.ts create mode 100644 src/components/Chat/Window/Window.tsx create mode 100644 src/components/Chat/Window/index.ts create mode 100644 src/components/Chat/context.ts diff --git a/src/components/Chat/ChatContextProvider.tsx b/src/components/Chat/ChatContextProvider.tsx new file mode 100644 index 0000000..ad1d2f6 --- /dev/null +++ b/src/components/Chat/ChatContextProvider.tsx @@ -0,0 +1,10 @@ +import React, { useState } from 'react'; +import { ChatContext } from './context'; + +const ChatContextProvider: React.FC = ({ children }) => { + const [search, setSearch] = useState(''); + + return {children}; +}; + +export { ChatContextProvider }; diff --git a/src/components/Chat/Sidebar.tsx b/src/components/Chat/Sidebar.tsx deleted file mode 100644 index 55aa13f..0000000 --- a/src/components/Chat/Sidebar.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { GridItem, GridItemProps } from '@chakra-ui/react'; - -const Sidebar: React.FC = props => { - return ( - - {props.children} - - ); -}; - -export { Sidebar }; diff --git a/src/components/Chat/Sidebar/Heading.tsx b/src/components/Chat/Sidebar/Heading.tsx new file mode 100644 index 0000000..d752efa --- /dev/null +++ b/src/components/Chat/Sidebar/Heading.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { + Flex, + Heading as ChakraHeading, + Input, + InputGroup, + InputLeftElement, + HStack, + Button, + Box, + BoxProps, +} from '@chakra-ui/react'; +import { SearchIcon } from '@chakra-ui/icons'; + +/** + * Hooks + */ +import { useChatContext } from '../context'; + +const Heading: React.FC = props => { + const { search, setSearch } = useChatContext(); + + const onSearch = (e: React.ChangeEvent) => setSearch(e.target.value); + + return ( + + + + Chats + + + + + + + + + + + + + + ); +}; + +export { Heading }; diff --git a/src/components/Chat/Sidebar/Sidebar.tsx b/src/components/Chat/Sidebar/Sidebar.tsx new file mode 100644 index 0000000..e346039 --- /dev/null +++ b/src/components/Chat/Sidebar/Sidebar.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { BoxProps, GridItem, GridItemProps } from '@chakra-ui/react'; +import { Heading } from './Heading'; +import { Users } from './Users/Users'; + +export interface ISidebarComposition { + Heading: React.FC; + Users: React.FC; +} + +const Sidebar: React.FC & ISidebarComposition = props => { + return ( + + {props.children} + + ); + + return null; +}; + +Sidebar.Heading = Heading; +Sidebar.Users = Users; + +export { Sidebar }; diff --git a/src/components/Chat/Sidebar/Users/User/Avatar.tsx b/src/components/Chat/Sidebar/Users/User/Avatar.tsx new file mode 100644 index 0000000..3e7ceac --- /dev/null +++ b/src/components/Chat/Sidebar/Users/User/Avatar.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Avatar as ChakraAvatar, AvatarBadge, WrapItem } from '@chakra-ui/react'; + +export interface IAvatarProps { + fullname: string; + isOnline: boolean; +} + +const Avatar: React.FC = ({ fullname: name, isOnline }) => { + const onlineBg = isOnline ? 'green.500' : 'red.500'; + + return ( + + + + + + ); +}; + +export { Avatar }; diff --git a/src/components/Chat/Sidebar/Users/User/Info.tsx b/src/components/Chat/Sidebar/Users/User/Info.tsx new file mode 100644 index 0000000..9b74a71 --- /dev/null +++ b/src/components/Chat/Sidebar/Users/User/Info.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Box, Text } from '@chakra-ui/react'; + +export interface IInfoProps { + fullname: string; + lastMessage: string; +} + +const Info: React.FC = ({ fullname, lastMessage }) => { + return ( + + + {fullname}
+ + {lastMessage} + +
+
+ ); +}; + +export { Info }; diff --git a/src/components/Chat/Sidebar/Users/User/Time.tsx b/src/components/Chat/Sidebar/Users/User/Time.tsx new file mode 100644 index 0000000..c7fa5c3 --- /dev/null +++ b/src/components/Chat/Sidebar/Users/User/Time.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { Box, Text } from '@chakra-ui/react'; + +export interface ITimeProps { + time: string; +} + +const Time: React.FC = ({ time }) => { + return ( + + + {time} + + + ); +}; + +export { Time }; diff --git a/src/components/Chat/Sidebar/Users/User/User.tsx b/src/components/Chat/Sidebar/Users/User/User.tsx new file mode 100644 index 0000000..aba67e8 --- /dev/null +++ b/src/components/Chat/Sidebar/Users/User/User.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Box, Flex } from '@chakra-ui/react'; +import { IInfoProps, Info } from './Info'; +import { ITimeProps, Time } from './Time'; +import { IAvatarProps, Avatar } from './Avatar'; + +/** + * Hooks + */ +import { useHistory, useLocation } from 'react-router-dom'; +import { useColorModeValue } from '@chakra-ui/react'; + +interface IUserComposition { + Avatar: React.FC; + Info: React.FC; + Time: React.FC; +} + +interface IUserProps { + id: string; +} + +const User: React.FC & IUserComposition = props => { + const history = useHistory(); + const location = useLocation(); + const chatId = new URLSearchParams(location.search).get('chat_id'); + const bg = useColorModeValue('gray.50', 'gray.700'); + + const handleChat = () => { + history.replace({ pathname: location.pathname, search: new URLSearchParams({ ['chat_id']: props.id }).toString() }); + }; + + return ( + + + {props.children} + + + ); +}; + +User.Avatar = Avatar; +User.Info = Info; +User.Time = Time; + +export { User }; diff --git a/src/components/Chat/Sidebar/Users/User/index.ts b/src/components/Chat/Sidebar/Users/User/index.ts new file mode 100644 index 0000000..403fc94 --- /dev/null +++ b/src/components/Chat/Sidebar/Users/User/index.ts @@ -0,0 +1 @@ +export { User } from './User'; diff --git a/src/components/Chat/Sidebar/Users/Users.tsx b/src/components/Chat/Sidebar/Users/Users.tsx new file mode 100644 index 0000000..8b79d7c --- /dev/null +++ b/src/components/Chat/Sidebar/Users/Users.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Box, Stack, Text } from '@chakra-ui/react'; +import { User } from './User'; +import { useChatContext } from '../../context'; + +const tempDb = [ + { id: 'user-1', name: 'Trace Kerluke', lastMessage: 'Hello.', time: '2g' }, + { id: 'user-2', name: 'Dalton Conn ', lastMessage: 'Hello, how you doing ?', time: '30m' }, + { id: 'user-3', name: 'Cleo Hane', lastMessage: 'Hi.', time: '1h' }, +]; + +const Users: React.FC = () => { + const { search } = useChatContext(); + + const filtered = !search ? tempDb : tempDb.filter(u => u.name.toLocaleLowerCase().includes(search)); + const isEmpty = filtered.length === 0; + + return ( + + {!isEmpty ? ( + filtered.map(u => ( + + + + + + )) + ) : ( + + + Cannot find any chat + + + )} + + ); +}; + +export { Users }; diff --git a/src/components/Chat/Sidebar/Users/index.ts b/src/components/Chat/Sidebar/Users/index.ts new file mode 100644 index 0000000..a87a5f5 --- /dev/null +++ b/src/components/Chat/Sidebar/Users/index.ts @@ -0,0 +1 @@ +export { Users } from './Users'; diff --git a/src/components/Chat/Sidebar/index.ts b/src/components/Chat/Sidebar/index.ts new file mode 100644 index 0000000..d440597 --- /dev/null +++ b/src/components/Chat/Sidebar/index.ts @@ -0,0 +1 @@ +export { Sidebar } from './Sidebar'; diff --git a/src/components/Chat/Window.tsx b/src/components/Chat/Window.tsx deleted file mode 100644 index 125d022..0000000 --- a/src/components/Chat/Window.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { GridItem, GridItemProps } from '@chakra-ui/react'; - -const Window: React.FC = props => { - return {props.children}; -}; - -export { Window }; diff --git a/src/components/Chat/Window/Message/Avatar.tsx b/src/components/Chat/Window/Message/Avatar.tsx new file mode 100644 index 0000000..525c1a2 --- /dev/null +++ b/src/components/Chat/Window/Message/Avatar.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { WrapItem, Avatar as ChakraAvatar } from '@chakra-ui/react'; + +export interface IAvatarProps { + fullname: string; +} + +const Avatar: React.FC = ({ fullname: name }) => { + return ( + + + + ); +}; + +export { Avatar }; diff --git a/src/components/Chat/Window/Message/Message.tsx b/src/components/Chat/Window/Message/Message.tsx new file mode 100644 index 0000000..75b66e5 --- /dev/null +++ b/src/components/Chat/Window/Message/Message.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Flex } from '@chakra-ui/react'; +import { Avatar, IAvatarProps } from './Avatar'; +import { Text, ITextProps } from './Text'; +import { MessageContext } from './context'; + +interface IMessageComposition { + Avatar: React.FC; + Text: React.FC; +} + +interface IMyMessageProps { + isMyMessage: boolean; +} + +const Message: React.FC & IMessageComposition = ({ children, isMyMessage }) => { + return ( + + + {children} + + + ); +}; + +Message.Avatar = Avatar; +Message.Text = Text; + +export { Message }; diff --git a/src/components/Chat/Window/Message/Text.tsx b/src/components/Chat/Window/Message/Text.tsx new file mode 100644 index 0000000..14e4bad --- /dev/null +++ b/src/components/Chat/Window/Message/Text.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Box, Text as ChakraText, useColorModeValue } from '@chakra-ui/react'; +import { useMessageContext } from './context'; + +export interface ITextProps { + content: string; + time: string; +} + +const Text: React.FC = ({ content, time }) => { + const { isMyMessage } = useMessageContext(); + + const bgColor = !isMyMessage ? useColorModeValue('gray.50', 'gray.700') : useColorModeValue('gray.100', 'gray.600'); + + return ( + + + {content} + + {time} + + + + ); +}; + +export { Text }; diff --git a/src/components/Chat/Window/Message/context.ts b/src/components/Chat/Window/Message/context.ts new file mode 100644 index 0000000..8dd77ca --- /dev/null +++ b/src/components/Chat/Window/Message/context.ts @@ -0,0 +1,17 @@ +import { createContext, useContext } from 'react'; + +export type MessageContextType = { + isMyMessage: boolean; +}; + +export const MessageContext = createContext(undefined); + +export const useMessageContext = () => { + const context = useContext(MessageContext); + + if (!context) { + throw new Error('MessageContext was outside of its provider'); + } + + return context; +}; diff --git a/src/components/Chat/Window/Message/index.ts b/src/components/Chat/Window/Message/index.ts new file mode 100644 index 0000000..f2d89d2 --- /dev/null +++ b/src/components/Chat/Window/Message/index.ts @@ -0,0 +1 @@ +export { Message } from './Message'; diff --git a/src/components/Chat/Window/Window.tsx b/src/components/Chat/Window/Window.tsx new file mode 100644 index 0000000..2f267fc --- /dev/null +++ b/src/components/Chat/Window/Window.tsx @@ -0,0 +1,64 @@ +import React, { useEffect } from 'react'; +import { GridItem, GridItemProps, Flex, Box, Icon, Text, Input, Heading, IconButton, Stack } from '@chakra-ui/react'; +import { MdOutlineChat, MdSend } from 'react-icons/md'; +import { Message } from './Message'; + +const Window: React.FC = props => { + const chatId = new URLSearchParams(location.search).get('chat_id'); + + useEffect(() => { + console.log('chat id changed!'); + }, [chatId]); + + return ( + + {chatId ? ( + + + + John Doe + + + + + + + + + + + + + + + } + aria-label='Send message' + variant={'ghost'} + _hover={{ bg: 'inherit' }} + _active={{ bg: 'inherit' }} + _focus={{ outline: 'none' }} + /> + + + ) : ( + + + + Select a chat to start messaging + + + )} + + ); +}; + +export { Window }; diff --git a/src/components/Chat/Window/index.ts b/src/components/Chat/Window/index.ts new file mode 100644 index 0000000..6ec26f3 --- /dev/null +++ b/src/components/Chat/Window/index.ts @@ -0,0 +1 @@ +export { Window } from './Window'; diff --git a/src/components/Chat/context.ts b/src/components/Chat/context.ts new file mode 100644 index 0000000..246853b --- /dev/null +++ b/src/components/Chat/context.ts @@ -0,0 +1,21 @@ +import React, { useContext, createContext } from 'react'; + +export type ChatContextType = { + search: string; + setSearch: React.Dispatch>; +}; + +export const ChatContext = createContext({ + search: '', + setSearch: () => undefined, +}); + +export const useChatContext = () => { + const context = useContext(ChatContext); + + if (!context) { + throw new Error('ChatContext was outside of its provider'); + } + + return context; +}; diff --git a/src/components/Chat/index.tsx b/src/components/Chat/index.tsx index 780ad16..c2ea525 100644 --- a/src/components/Chat/index.tsx +++ b/src/components/Chat/index.tsx @@ -2,17 +2,21 @@ import React from 'react'; import { Grid, GridItemProps, GridProps } from '@chakra-ui/react'; import { Sidebar } from './Sidebar'; import { Window } from './Window'; +import { ISidebarComposition } from './Sidebar/Sidebar'; +import { ChatContextProvider } from './ChatContextProvider'; interface IChatComposition { - Sidebar: React.FC; + Sidebar: React.FC & ISidebarComposition; Window: React.FC; } const Chat: React.FC & IChatComposition = props => { return ( - - {props.children} - + + + {props.children} + + ); }; diff --git a/src/components/UserProfile/Avatar/ChatButton.tsx b/src/components/UserProfile/Avatar/ChatButton.tsx index 92f0158..4186059 100644 --- a/src/components/UserProfile/Avatar/ChatButton.tsx +++ b/src/components/UserProfile/Avatar/ChatButton.tsx @@ -7,7 +7,7 @@ import type { IChatButtonProps } from '.'; const ChatButton: React.FC = props => { const { userid } = props; const history = useHistory(); - const handleChat = () => history.push(`/chat/${userid}`); + const handleChat = () => history.push(`/messages?chat_id=${userid}`); return ( - - - - {Array(20) - .fill(0) - .map((e, i) => i) - .map(n => ( - - - - - - - - - - - Sergey Yarkov
- - Sergey: hello, how you doing? - -
-
-
- - - 2h - - -
-
- ))} -
+ + - - - - - - Select a chat to start messaging - - - - +