From f309ba291bfd9f83ea9d1957b7d72fbf89821c9a Mon Sep 17 00:00:00 2001 From: Pavel Prichodko <14926950+prichodko@users.noreply.github.com> Date: Wed, 13 Apr 2022 14:24:48 +0200 Subject: [PATCH] feat(react): add all chat input variants --- .../src/routes/chat/chat-input.tsx | 146 ------------------ .../chat/components/chat-input/index.tsx | 96 ++++++++++++ .../components/chat-input/input-reply.tsx | 85 ++++++++++ .../status-react/src/routes/chat/index.tsx | 63 +++++--- 4 files changed, 224 insertions(+), 166 deletions(-) delete mode 100644 packages/status-react/src/routes/chat/chat-input.tsx create mode 100644 packages/status-react/src/routes/chat/components/chat-input/index.tsx create mode 100644 packages/status-react/src/routes/chat/components/chat-input/input-reply.tsx diff --git a/packages/status-react/src/routes/chat/chat-input.tsx b/packages/status-react/src/routes/chat/chat-input.tsx deleted file mode 100644 index a9b873f7..00000000 --- a/packages/status-react/src/routes/chat/chat-input.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React, { useEffect, useRef } from 'react' - -import { useChatState } from '~/src/contexts/chat-context' -import { CrossIcon } from '~/src/icons/cross-icon' -import { EmojiIcon } from '~/src/icons/emoji-icon' -import { GifIcon } from '~/src/icons/gif-icon' -import { ImageIcon } from '~/src/icons/image-icon' -import { ReplyIcon } from '~/src/icons/reply-icon' -import { StickerIcon } from '~/src/icons/sticker-icon' -import { styled } from '~/src/styles/config' -import { Flex, Icon, IconButton, Image, Text } from '~/src/system' - -import type { Message } from '~/src/contexts/chat-context' - -interface Props { - value?: string -} - -export const ChatInput = (props: Props) => { - const { value } = props - const { state } = useChatState() - - const inputRef = useRef(null) - - useEffect(() => { - if (state.message) { - inputRef.current?.focus() - } - }, [state.message]) - - return ( - - - - - - {state.message && } - - - - - - - - - - - - - - - - - - ) -} - -interface InputReplyProps { - reply: Message -} - -const InputReply = ({ reply }: InputReplyProps) => { - const { dispatch } = useChatState() - return ( - - - - - - vitalik.eth - - - - dispatch({ type: 'CANCEL_REPLY' })} - > - - - - {reply.type === 'text' && ( - - - This a very very very very very very very very very very very very - very very very very very very very very very very very very very - very very very very long message that is going to be truncated. - - - )} - {reply.type === 'image' && ( - message - )} - - ) -} - -const Wrapper = styled('div', { - display: 'flex', - overflow: 'hidden', - alignItems: 'flex-end', - padding: '12px 8px 12px 10px', - gap: 4, -}) - -const Bubble = styled('div', { - width: '100%', - background: '#EEF2F5', - borderRadius: '16px 16px 4px 16px;', - padding: 2, - overflow: 'hidden', -}) - -const InputWrapper = styled('div', { - display: 'flex', - height: 40, - width: '100%', - alignItems: 'center', - background: '#EEF2F5', - padding: '0 0 0 12px', -}) - -const Input = styled('input', { - display: 'flex', - background: 'none', - alignItems: 'center', - width: '100%', -}) - -const Reply = styled('div', { - display: 'flex', - flexDirection: 'column', - width: '100%', - overflow: 'hidden', - padding: '6px 12px', - background: 'rgba(0, 0, 0, 0.1)', - borderRadius: '14px 14px 4px 14px;', -}) diff --git a/packages/status-react/src/routes/chat/components/chat-input/index.tsx b/packages/status-react/src/routes/chat/components/chat-input/index.tsx new file mode 100644 index 00000000..4985e943 --- /dev/null +++ b/packages/status-react/src/routes/chat/components/chat-input/index.tsx @@ -0,0 +1,96 @@ +import React, { useEffect, useRef, useState } from 'react' + +import { useChatContext } from '~/src/contexts/chat-context' +import { EmojiIcon } from '~/src/icons/emoji-icon' +import { GifIcon } from '~/src/icons/gif-icon' +import { ImageIcon } from '~/src/icons/image-icon' +import { StickerIcon } from '~/src/icons/sticker-icon' +import { styled } from '~/src/styles/config' +import { Flex, IconButton } from '~/src/system' + +import { InputReply } from './input-reply' + +interface Props { + mode?: 'normal' | 'editing' + value?: string + editing?: boolean +} + +export const ChatInput = (props: Props) => { + const { value, editing } = props + + const [inputValue, setInputValue] = useState(value ?? '') + const { state } = useChatContext() + + const inputRef = useRef(null) + + useEffect(() => { + state.message && !editing && inputRef.current?.focus() + }, [state.message, editing]) + + const handleChange = (event: React.ChangeEvent) => { + setInputValue(event.target.value) + } + + return ( + + + + + + {state.message && } + + + + + + + + + + + + + + + + + ) +} + +const Wrapper = styled('div', { + display: 'flex', + overflow: 'hidden', + alignItems: 'flex-end', + padding: '12px 8px 12px 10px', + gap: 4, +}) + +const Bubble = styled('div', { + width: '100%', + background: '#EEF2F5', + borderRadius: '16px 16px 4px 16px;', + padding: 2, + overflow: 'hidden', +}) + +const InputWrapper = styled('div', { + display: 'flex', + height: 40, + width: '100%', + alignItems: 'center', + background: '#EEF2F5', + padding: '0 0 0 12px', +}) + +const Input = styled('input', { + display: 'flex', + background: 'none', + alignItems: 'center', + width: '100%', +}) diff --git a/packages/status-react/src/routes/chat/components/chat-input/input-reply.tsx b/packages/status-react/src/routes/chat/components/chat-input/input-reply.tsx new file mode 100644 index 00000000..47fc353d --- /dev/null +++ b/packages/status-react/src/routes/chat/components/chat-input/input-reply.tsx @@ -0,0 +1,85 @@ +import React from 'react' + +import { useChatContext } from '~/src/contexts/chat-context' +import { CrossIcon } from '~/src/icons/cross-icon' +import { ReplyIcon } from '~/src/icons/reply-icon' +import { styled } from '~/src/styles/config' +import { Box, Flex, Icon, IconButton, Image, Text } from '~/src/system' + +import type { Message } from '~/src/protocol/use-messages' + +interface Props { + message: Message +} + +export const InputReply = (props: Props) => { + const { dispatch } = useChatContext() + + const { message } = props + + return ( + + + + + + {message.contact.name} + + + + dispatch({ type: 'CANCEL_REPLY' })} + > + + + + {message.type === 'text' && ( + + + {message.text} + + + )} + {message.type === 'image' && ( + message + )} + {message.type === 'image-text' && ( + + + + {message.text} + + + message + + )} + + ) +} + +const Wrapper = styled('div', { + display: 'flex', + flexDirection: 'column', + width: '100%', + overflow: 'hidden', + padding: '6px 12px', + background: 'rgba(0, 0, 0, 0.1)', + borderRadius: '14px 14px 4px 14px;', +}) diff --git a/packages/status-react/src/routes/chat/index.tsx b/packages/status-react/src/routes/chat/index.tsx index 341dc128..ef39483a 100644 --- a/packages/status-react/src/routes/chat/index.tsx +++ b/packages/status-react/src/routes/chat/index.tsx @@ -1,19 +1,28 @@ -import React from 'react' +import React, { useEffect, useRef } from 'react' + +import { useMatch } from 'react-router-dom' import { MemberSidebar } from '~/src/components/member-sidebar' import { useAppState } from '~/src/contexts/app-context' import { ChatProvider } from '~/src/contexts/chat-context' +import { useChat } from '~/src/protocol/use-chat' +import { useMessages } from '~/src/protocol/use-messages' import { styled } from '~/src/styles/config' import { Avatar, Flex, Heading, Text } from '~/src/system' -import { ChatInput } from './chat-input' -import { ChatMessage } from './chat-message' +import { ChatInput } from './components/chat-input' +import { ChatMessage } from './components/chat-message' import { Navbar } from './components/navbar' -const EmptyChat = () => { +const ChatStart = () => { + // TODO: unify this with the useChat hook + const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion + + const chat = useChat(params.id!) + return ( - + general Welcome to the beginning of the #general channel! @@ -21,22 +30,22 @@ const EmptyChat = () => { } const Content = () => { + const contentRef = useRef(null) + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + contentRef.current!.scrollTop = contentRef.current!.scrollHeight ?? 0 + }, []) + + const messages = useMessages() + return ( -
- - - - - - - - - - - - - -
+ + + {messages.map(message => ( + + ))} + ) } @@ -71,6 +80,20 @@ const Wrapper = styled('div', { background: '#fff', }) +const ContentWrapper = styled('div', { + flex: 1, + overflowY: 'auto', + overflowX: 'hidden', + WebkitOverflowScrolling: 'touch', + overscrollBehavior: 'contain', + // scrollSnapType: 'y proximity', + + // '& > div:last-child': { + // scrollSnapAlign: 'end', + // scrollMarginBlockEnd: '1px', + // }, +}) + const Main = styled('div', { flex: 1, display: 'flex',