From 234cf44ebc7d19226ab04fdd4bc6c8bbcdeef118 Mon Sep 17 00:00:00 2001 From: Pavel Prichodko <14926950+prichodko@users.noreply.github.com> Date: Wed, 13 Apr 2022 14:24:37 +0200 Subject: [PATCH] feat(react): add all chat message variants --- .../src/routes/chat/chat-message.tsx | 359 ------------------ .../chat/components/chat-message/actions.tsx | 129 +++++++ .../chat/components/chat-message/index.tsx | 279 ++++++++++++++ .../components/chat-message/message-reply.tsx | 105 +++++ .../components/chat-message/reactions.tsx | 115 ++++++ 5 files changed, 628 insertions(+), 359 deletions(-) delete mode 100644 packages/status-react/src/routes/chat/chat-message.tsx create mode 100644 packages/status-react/src/routes/chat/components/chat-message/actions.tsx create mode 100644 packages/status-react/src/routes/chat/components/chat-message/index.tsx create mode 100644 packages/status-react/src/routes/chat/components/chat-message/message-reply.tsx create mode 100644 packages/status-react/src/routes/chat/components/chat-message/reactions.tsx diff --git a/packages/status-react/src/routes/chat/chat-message.tsx b/packages/status-react/src/routes/chat/chat-message.tsx deleted file mode 100644 index 0492b9e..0000000 --- a/packages/status-react/src/routes/chat/chat-message.tsx +++ /dev/null @@ -1,359 +0,0 @@ -import React from 'react' - -import { useChatState } from '~/src/contexts/chat-context' -import { BellIcon } from '~/src/icons/bell-icon' -import { PencilIcon } from '~/src/icons/pencil-icon' -import { PinIcon } from '~/src/icons/pin-icon' -import { ReactionIcon } from '~/src/icons/reaction-icon' -import { ReplyIcon } from '~/src/icons/reply-icon' -import { TrashIcon } from '~/src/icons/trash-icon' -import { styled } from '~/src/styles/config' -import { - AlertDialog, - AlertDialogTrigger, - Avatar, - Box, - Button, - ContextMenu, - ContextMenuTrigger, - DropdownMenu, - DropdownMenuTrigger, - EmojiHash, - Flex, - IconButton, - Image, - Text, - Tooltip, -} from '~/src/system' - -interface Props { - reply?: 'text' | 'image' | 'image-text' - image?: boolean - mention?: boolean - pinned?: boolean -} - -const MessageLink = (props: React.AnchorHTMLAttributes) => { - const { onClick } = props - - return ( - { - onClick?.(e) - e.preventDefault() - }} - > - https://specs.status.im/spec/ - - ) -} - -export const ChatMessage = (props: Props) => { - const { reply, image, mention, pinned } = props - - const { dispatch } = useChatState() - - return ( - <> - {reply && } - - -
- - - -
- - simon.eth -
- - }> - View Profile - - }> - Send Message - - }> - Verify Identity - - }> - Send Contact Request - - - } danger> - Mark as Untrustworthy - -
-
-
- -
- - - carmen - - - 10:00 - - - - - My first hoya{' '} - - - https://specs.status.im/spec - - - {' '} - bloom has started to develop alongside my first aphid issue 😩 - - - {image && ( - - message - - )} -
- - - - - - - - - - dispatch({ - type: 'SET_REPLY', - message: { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - type: reply!, - text: 'bloom has started to develop alongside my first aphid issue 😩', - }, - }) - } - > - - - - - - - - - {pinned ? ( - - - - - - ) : ( - - - - - - )} - - - - - - -
- - Reply - Pin - -
- - ) -} - -const MessageReply = ({ - reply, -}: { - reply: 'text' | 'image' | 'image-text' -}) => { - return ( - - - - - vitalik.eth - - - {reply === '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 === 'image' && ( - message - )} - {reply === 'image-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. - - message - - )} - - ) -} - -const Wrapper = styled('div', { - position: 'relative', - padding: '10px 16px', - display: 'flex', - gap: '$2', - - transitionProperty: 'background-color, border-color, color, fill, stroke', - transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', - transitionDuration: '100ms', - - '&:hover, &[data-open="true"]': { - background: '#EEF2F5', - // [`& ${Actions}`]: { - // marginLeft: '5px', - // }, - }, - - a: { - textDecoration: 'underline', - }, - - variants: { - mention: { - true: { - background: '$mention-4', - - '&:hover, &[data-open="true"]': { - background: '$mention-3', - // [`& ${Actions}`]: { - // marginLeft: '5px', - // }, - }, - - '&::before': { - content: '""', - position: 'absolute', - top: 0, - left: 0, - bottom: 0, - width: 3, - background: '$mention-1', - }, - }, - }, - pinned: { - true: { - background: '$pin-3', - - '&:hover, &[data-open="true"]': { - background: '$pin-2', - }, - - '&::before': { - content: '""', - position: 'absolute', - top: 0, - left: 0, - bottom: 0, - width: 3, - background: '$pin-1', - }, - }, - }, - }, -}) - -const Reply = styled('div', { - position: 'relative', - // height: 40, - marginLeft: 68, - display: 'flex', - flexDirection: 'column', - gap: '$1', - - '&::before, &::after': { - content: '""', - position: 'absolute', - '--background-accent': 'rgba(147, 155, 161, 0.4)', - '--avatar-size': '44px', - '--gutter': '8px', - '--width': '2px', - }, - - '&::before': { - display: 'block', - position: 'absolute', - top: 10, - right: 'calc(100% + 10px)', - bottom: '0', - left: 'calc(var(--avatar-size)/2*-1 + var(--gutter)*-1)', - marginRight: 'var(--reply-spacing)', - marginTop: 'calc(var(--width)*-1/2)', - marginLeft: 'calc(var(--width)*-1/2)', - marginBottom: 'calc(0.125rem - 4px)', - borderLeft: 'var(--width) solid var(--background-accent)', - borderBottom: '0 solid var(--background-accent)', - borderRight: '0 solid var(--background-accent)', - borderTop: 'var(--width) solid var(--background-accent)', - borderTopLeftRadius: '10px', - }, -}) - -const Actions = styled('div', { - position: 'absolute', - top: -18, - right: 16, - padding: 2, - boxShadow: '0px 4px 12px rgba(0, 34, 51, 0.08)', - background: '#fff', - borderRadius: 8, - display: 'none', - - ':hover > &': { - display: 'flex', - }, -}) diff --git a/packages/status-react/src/routes/chat/components/chat-message/actions.tsx b/packages/status-react/src/routes/chat/components/chat-message/actions.tsx new file mode 100644 index 0000000..00154ed --- /dev/null +++ b/packages/status-react/src/routes/chat/components/chat-message/actions.tsx @@ -0,0 +1,129 @@ +import React, { useState } from 'react' + +import { ReactionPopover } from '~/src/components/reaction-popover' +import { PencilIcon } from '~/src/icons/pencil-icon' +import { PinIcon } from '~/src/icons/pin-icon' +import { ReactionIcon } from '~/src/icons/reaction-icon' +import { ReplyIcon } from '~/src/icons/reply-icon' +import { TrashIcon } from '~/src/icons/trash-icon' +import { UnpinIcon } from '~/src/icons/unpin-icon' +import { styled } from '~/src/styles/config' +import { + AlertDialog, + AlertDialogTrigger, + IconButton, + Tooltip, +} from '~/src/system' + +interface Props { + owner: boolean + pinned: boolean + onReplyClick: () => void + onEditClick: () => void + reacting: boolean + onReactingChange: (reacting: boolean) => void +} + +export const Actions = (props: Props) => { + const { + owner, + pinned, + onReplyClick, + onEditClick, + reacting, + onReactingChange, + } = props + + return ( + + { + console.log(emoji) + onReactingChange(false) + }} + > + + + + + + + + + + + + {owner && ( + + + + + + )} + {pinned ? ( + + + + + + ) : ( + + + + + + )} + {owner && ( + + + + + + + + + )} + + ) +} + +const Wrapper = styled('div', { + position: 'absolute', + top: -18, + right: 16, + padding: 2, + boxShadow: '0px 4px 12px rgba(0, 34, 51, 0.08)', + background: '#fff', + borderRadius: 8, + display: 'none', + + ':hover > &': { + display: 'flex', + }, + + variants: { + open: { + true: { + display: 'flex', + }, + }, + }, +}) diff --git a/packages/status-react/src/routes/chat/components/chat-message/index.tsx b/packages/status-react/src/routes/chat/components/chat-message/index.tsx new file mode 100644 index 0000000..b8a7088 --- /dev/null +++ b/packages/status-react/src/routes/chat/components/chat-message/index.tsx @@ -0,0 +1,279 @@ +import React, { useState } from 'react' + +import { useChatContext } from '~/src/contexts/chat-context' +import { BellIcon } from '~/src/icons/bell-icon' +import { PinIcon } from '~/src/icons/pin-icon' +import { styled } from '~/src/styles/config' +import { + Avatar, + Box, + Button, + ContextMenu, + ContextMenuTrigger, + DropdownMenu, + DropdownMenuTrigger, + EmojiHash, + Flex, + Image, + Text, +} from '~/src/system' + +import { ChatInput } from '../chat-input' +import { Actions } from './actions' +import { MessageReply } from './message-reply' +import { Reactions } from './reactions' + +import type { Message } from '~/src/protocol/use-messages' + +interface Props { + message: Message +} + +// const MessageLink = forwardRef(function MessageLink( +// props: React.AnchorHTMLAttributes, +// ref: Ref +// ) { +// const { onClick } = props + +// return ( +// { +// onClick?.(e) +// e.preventDefault() +// }} +// > +// https://specs.status.im/spec/ +// +// ) +// }) + +export const ChatMessage = (props: Props) => { + const { message } = props + + const { type, contact, owner, mention, pinned, reply } = message + + const [editing, setEditing] = useState(false) + const [reacting, setReacting] = useState(false) + + const { dispatch } = useChatContext() + + if (editing) { + return ( + + + + + + + + + + + ) + } + + const handleReplyClick = () => { + dispatch({ + type: 'SET_REPLY', + message, + }) + } + + const handleReaction = (reaction: string) => { + console.log(reaction) + } + + const renderMessage = () => { + switch (type) { + case 'text': { + // + // + // https://specs.status.im/spec + // + // + // {' '} + return {message.text} + } + case 'image': { + return ( + + message + + ) + } + case 'image-text': { + const { text, imageUrl } = message + + return ( + <> + {text} + + message + + + ) + } + } + } + + return ( + <> + + + {reply && } + + + + + +
+ + {contact.name} + +
+ + }> + View Profile + + }> + Send Message + + }> + Verify Identity + + }> + Send Contact Request + + + } danger> + Mark as Untrustworthy + +
+
+
+ + + {pinned && ( + + + Pinned by carmen.eth + + )} + + + + {contact.name} + + + 10:00 AM + + + + {renderMessage()} + + + +
+ + setEditing(true)} + onReplyClick={handleReplyClick} + reacting={reacting} + onReactingChange={setReacting} + /> +
+ + Reply + Pin + +
+ + ) +} + +// TODO: Use compound variants https://stitches.dev/docs/variants#compound-variants +const Wrapper = styled('div', { + position: 'relative', + padding: '10px 16px', + gap: '$2', + + transitionProperty: 'background-color, border-color, color, fill, stroke', + transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', + transitionDuration: '100ms', + + '&:hover, &[data-open="true"], &[data-active="true"]': { + background: '#EEF2F5', + }, + + a: { + textDecoration: 'underline', + }, + + variants: { + mention: { + true: { + background: '$mention-4', + '&:hover, &[data-open="true"], &[data-active="true"]': { + background: '$mention-3', + }, + + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + width: 3, + background: '$mention-1', + }, + }, + }, + pinned: { + true: { + background: '$pin-3', + '&:hover, &[data-open="true"], &[data-active="true"]': { + background: '$pin-2', + }, + + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + width: 3, + background: '$pin-1', + }, + }, + }, + }, +}) diff --git a/packages/status-react/src/routes/chat/components/chat-message/message-reply.tsx b/packages/status-react/src/routes/chat/components/chat-message/message-reply.tsx new file mode 100644 index 0000000..2436e88 --- /dev/null +++ b/packages/status-react/src/routes/chat/components/chat-message/message-reply.tsx @@ -0,0 +1,105 @@ +import React from 'react' + +import { styled } from '~/src/styles/config' +import { Avatar, Box, Flex, Image, Text } from '~/src/system' + +import type { Reply } from '~/src/protocol/use-messages' + +interface Props { + reply: Reply +} + +export const MessageReply = (props: Props) => { + const { reply } = props + + const { contact } = reply + + return ( + + + + + {contact.name} + + + {reply.type === 'text' && ( + + + {reply.text} + + + )} + {reply.type === 'image' && ( + + message + + )} + {reply.type === 'image-text' && ( + + + {reply.text} + + message + + )} + + ) +} + +const Wrapper = styled('div', { + position: 'relative', + // height: 40, + marginLeft: 52, + display: 'flex', + flexDirection: 'column', + // gap: '$1', + paddingBottom: 8, + + '&::before, &::after': { + content: '""', + position: 'absolute', + '--background-accent': 'rgba(147, 155, 161, 0.4)', + '--avatar-size': '44px', + '--gutter': '8px', + '--width': '2px', + }, + + '&::before': { + display: 'block', + position: 'absolute', + top: 10, + right: 'calc(100% + 10px)', + bottom: 10, + left: 'calc(var(--avatar-size)/2*-1 + var(--gutter)*-1)', + marginRight: 'var(--reply-spacing)', + marginTop: 'calc(var(--width)*-1/2)', + marginLeft: 'calc(var(--width)*-1/2)', + marginBottom: 'calc(0.125rem - 4px)', + borderLeft: 'var(--width) solid var(--background-accent)', + borderBottom: '0 solid var(--background-accent)', + borderRight: '0 solid var(--background-accent)', + borderTop: 'var(--width) solid var(--background-accent)', + borderTopLeftRadius: '10px', + }, +}) diff --git a/packages/status-react/src/routes/chat/components/chat-message/reactions.tsx b/packages/status-react/src/routes/chat/components/chat-message/reactions.tsx new file mode 100644 index 0000000..3d5e9a7 --- /dev/null +++ b/packages/status-react/src/routes/chat/components/chat-message/reactions.tsx @@ -0,0 +1,115 @@ +import React from 'react' + +import { ReactionPopover } from '~/src/components/reaction-popover' +import { ReactionIcon } from '~/src/icons/reaction-icon' +import { styled } from '~/src/styles/config' +import { Flex, Image } from '~/src/system' + +interface Props { + onClick: (reaction: string) => void +} + +export const Reactions = (props: Props) => { + const { onClick } = props + + return ( + + + + + + + + { + console.log(emoji) + }} + > + + + + ) +} + +const Button = styled('button', { + padding: 2, + boxShadow: '0px 4px 12px rgba(0, 34, 51, 0.08)', + background: '$accent-8', + borderRadius: '2px 10px 10px 10px', + minWidth: 36, + height: 20, + display: 'inline-flex', + alignItems: 'center', + + variants: { + active: { + true: {}, + }, + }, +})