feat(react): add all chat message variants
This commit is contained in:
parent
1832ee9eae
commit
f69a37c041
|
@ -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<HTMLAnchorElement>) => {
|
|
||||||
const { onClick } = props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
{...props}
|
|
||||||
href="https://specs.status.im/spec/"
|
|
||||||
onClick={e => {
|
|
||||||
onClick?.(e)
|
|
||||||
e.preventDefault()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
https://specs.status.im/spec/
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatMessage = (props: Props) => {
|
|
||||||
const { reply, image, mention, pinned } = props
|
|
||||||
|
|
||||||
const { dispatch } = useChatState()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{reply && <MessageReply reply={reply} />}
|
|
||||||
<ContextMenuTrigger>
|
|
||||||
<Wrapper mention={mention} pinned={pinned}>
|
|
||||||
<div>
|
|
||||||
<DropdownMenuTrigger>
|
|
||||||
<button type="button">
|
|
||||||
<Avatar size={44} />
|
|
||||||
</button>
|
|
||||||
<DropdownMenu>
|
|
||||||
<div>
|
|
||||||
<Avatar size={36} />
|
|
||||||
<Text>simon.eth</Text>
|
|
||||||
</div>
|
|
||||||
<DropdownMenu.Separator />
|
|
||||||
<DropdownMenu.Item icon={<BellIcon />}>
|
|
||||||
View Profile
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item icon={<BellIcon />}>
|
|
||||||
Send Message
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item icon={<BellIcon />}>
|
|
||||||
Verify Identity
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item icon={<BellIcon />}>
|
|
||||||
Send Contact Request
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Separator />
|
|
||||||
<DropdownMenu.Item icon={<BellIcon />} danger>
|
|
||||||
Mark as Untrustworthy
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</DropdownMenu>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<Flex>
|
|
||||||
<Text color="primary" weight="500">
|
|
||||||
carmen
|
|
||||||
</Text>
|
|
||||||
<Text size="10" color="gray">
|
|
||||||
10:00
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Text>
|
|
||||||
My first hoya{' '}
|
|
||||||
<AlertDialogTrigger>
|
|
||||||
<MessageLink href="https://specs.status.im/spec">
|
|
||||||
https://specs.status.im/spec
|
|
||||||
</MessageLink>
|
|
||||||
<AlertDialog
|
|
||||||
title="Are you sure you want to visit this website?"
|
|
||||||
description="https://specs.status.im/spec"
|
|
||||||
actionLabel="Yes, take me there"
|
|
||||||
/>
|
|
||||||
</AlertDialogTrigger>{' '}
|
|
||||||
bloom has started to develop alongside my first aphid issue 😩
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{image && (
|
|
||||||
<Flex gap={1} css={{ paddingTop: '$1' }}>
|
|
||||||
<Image
|
|
||||||
width={147}
|
|
||||||
alt="message"
|
|
||||||
height={196}
|
|
||||||
src="https://images.unsplash.com/photo-1648492678772-8b52fe36cebc?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3570&q=80"
|
|
||||||
radius="bubble"
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Actions>
|
|
||||||
<Tooltip label="React">
|
|
||||||
<IconButton label="Pick reaction" intent="info" color="gray">
|
|
||||||
<ReactionIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label="Reply">
|
|
||||||
<IconButton
|
|
||||||
label="Reply to message"
|
|
||||||
intent="info"
|
|
||||||
color="gray"
|
|
||||||
onClick={() =>
|
|
||||||
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 😩',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ReplyIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label="Edit">
|
|
||||||
<IconButton label="Edit message" intent="info" color="gray">
|
|
||||||
<PencilIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
{pinned ? (
|
|
||||||
<Tooltip label="Unpin">
|
|
||||||
<IconButton label="Unpin message" intent="info" color="gray">
|
|
||||||
<UnpinIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
|
||||||
<Tooltip label="Pin">
|
|
||||||
<IconButton label="Pin message" intent="info" color="gray">
|
|
||||||
<PinIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<Tooltip label="Delete">
|
|
||||||
<IconButton label="Delete message" intent="danger" color="gray">
|
|
||||||
<TrashIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Actions>
|
|
||||||
</Wrapper>
|
|
||||||
<ContextMenu>
|
|
||||||
<ContextMenu.Item>Reply</ContextMenu.Item>
|
|
||||||
<ContextMenu.Item>Pin</ContextMenu.Item>
|
|
||||||
</ContextMenu>
|
|
||||||
</ContextMenuTrigger>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const MessageReply = ({
|
|
||||||
reply,
|
|
||||||
}: {
|
|
||||||
reply: 'text' | 'image' | 'image-text'
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Reply>
|
|
||||||
<Flex gap="1" align="center">
|
|
||||||
<Avatar size={20} />
|
|
||||||
<Text color="gray" size="13" weight="500">
|
|
||||||
vitalik.eth
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
{reply === 'text' && (
|
|
||||||
<Flex>
|
|
||||||
<Text color="gray" size="13" truncate={false}>
|
|
||||||
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.
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
{reply === 'image' && (
|
|
||||||
<Image
|
|
||||||
src="https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80"
|
|
||||||
width={56}
|
|
||||||
height={56}
|
|
||||||
fit="cover"
|
|
||||||
radius="1"
|
|
||||||
alt="message"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{reply === 'image-text' && (
|
|
||||||
<Box>
|
|
||||||
<Text color="gray" size="13" truncate={false}>
|
|
||||||
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.
|
|
||||||
</Text>
|
|
||||||
<Image
|
|
||||||
src="https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80"
|
|
||||||
width={56}
|
|
||||||
height={56}
|
|
||||||
fit="cover"
|
|
||||||
radius="1"
|
|
||||||
alt="message"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Reply>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -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 (
|
||||||
|
<Wrapper open={reacting}>
|
||||||
|
<ReactionPopover
|
||||||
|
open={reacting}
|
||||||
|
onOpenChange={onReactingChange}
|
||||||
|
onClick={emoji => {
|
||||||
|
console.log(emoji)
|
||||||
|
onReactingChange(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip label="React">
|
||||||
|
<IconButton label="Pick reaction" intent="info" color="gray">
|
||||||
|
<ReactionIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ReactionPopover>
|
||||||
|
<Tooltip label="Reply">
|
||||||
|
<IconButton
|
||||||
|
label="Reply to message"
|
||||||
|
intent="info"
|
||||||
|
color="gray"
|
||||||
|
onClick={onReplyClick}
|
||||||
|
>
|
||||||
|
<ReplyIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
{owner && (
|
||||||
|
<Tooltip label="Edit">
|
||||||
|
<IconButton
|
||||||
|
label="Edit message"
|
||||||
|
intent="info"
|
||||||
|
color="gray"
|
||||||
|
onClick={onEditClick}
|
||||||
|
>
|
||||||
|
<PencilIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{pinned ? (
|
||||||
|
<Tooltip label="Unpin">
|
||||||
|
<IconButton label="Unpin message" intent="info" color="gray">
|
||||||
|
<UnpinIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Tooltip label="Pin">
|
||||||
|
<IconButton label="Pin message" intent="info" color="gray">
|
||||||
|
<PinIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{owner && (
|
||||||
|
<AlertDialogTrigger>
|
||||||
|
<Tooltip label="Delete">
|
||||||
|
<IconButton label="Delete message" intent="danger" color="gray">
|
||||||
|
<TrashIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<AlertDialog
|
||||||
|
title="Delete Message"
|
||||||
|
description="Are you sure you want to delete this message?"
|
||||||
|
actionLabel="Delete"
|
||||||
|
actionVariant="danger"
|
||||||
|
cancelLabel="Cancel"
|
||||||
|
/>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
)}
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -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<HTMLAnchorElement>,
|
||||||
|
// ref: Ref<HTMLAnchorElement>
|
||||||
|
// ) {
|
||||||
|
// const { onClick } = props
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <a
|
||||||
|
// {...props}
|
||||||
|
// ref={ref}
|
||||||
|
// href="https://specs.status.im/spec/"
|
||||||
|
// onClick={e => {
|
||||||
|
// onClick?.(e)
|
||||||
|
// e.preventDefault()
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// https://specs.status.im/spec/
|
||||||
|
// </a>
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Wrapper>
|
||||||
|
<Avatar size={44} />
|
||||||
|
<Box>
|
||||||
|
<ChatInput value={text} />
|
||||||
|
<Flex gap={2}>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setEditing(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button size="small">Save</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReplyClick = () => {
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_REPLY',
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReaction = (reaction: string) => {
|
||||||
|
console.log(reaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderMessage = () => {
|
||||||
|
switch (type) {
|
||||||
|
case 'text': {
|
||||||
|
// <AlertDialogTrigger>
|
||||||
|
// <MessageLink href="https://specs.status.im/spec">
|
||||||
|
// https://specs.status.im/spec
|
||||||
|
// </MessageLink>
|
||||||
|
// <AlertDialog
|
||||||
|
// title="Are you sure you want to visit this website?"
|
||||||
|
// description="https://specs.status.im/spec"
|
||||||
|
// actionLabel="Yes, take me there"
|
||||||
|
// />
|
||||||
|
// </AlertDialogTrigger>{' '}
|
||||||
|
return <Text>{message.text}</Text>
|
||||||
|
}
|
||||||
|
case 'image': {
|
||||||
|
return (
|
||||||
|
<Flex gap={1} css={{ paddingTop: '$2' }}>
|
||||||
|
<Image
|
||||||
|
width={147}
|
||||||
|
alt="message"
|
||||||
|
height={196}
|
||||||
|
src={message.imageUrl}
|
||||||
|
radius="bubble"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 'image-text': {
|
||||||
|
const { text, imageUrl } = message
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text>{text}</Text>
|
||||||
|
<Flex gap={1} css={{ paddingTop: '$1' }}>
|
||||||
|
<Image
|
||||||
|
width={147}
|
||||||
|
alt="message"
|
||||||
|
height={196}
|
||||||
|
src={imageUrl}
|
||||||
|
radius="bubble"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContextMenuTrigger>
|
||||||
|
<Wrapper mention={mention} pinned={pinned} data-active={reacting}>
|
||||||
|
{reply && <MessageReply reply={reply} />}
|
||||||
|
<Flex gap={2}>
|
||||||
|
<Box>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<button type="button">
|
||||||
|
<Avatar size={44} src={contact.imageUrl} />
|
||||||
|
</button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<div>
|
||||||
|
<Avatar size="36" src={contact.imageUrl} />
|
||||||
|
<Text>{contact.name}</Text>
|
||||||
|
<EmojiHash />
|
||||||
|
</div>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
|
<DropdownMenu.Item icon={<BellIcon />}>
|
||||||
|
View Profile
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item icon={<BellIcon />}>
|
||||||
|
Send Message
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item icon={<BellIcon />}>
|
||||||
|
Verify Identity
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item icon={<BellIcon />}>
|
||||||
|
Send Contact Request
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
|
<DropdownMenu.Item icon={<BellIcon />} danger>
|
||||||
|
Mark as Untrustworthy
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box css={{ flex: 1 }}>
|
||||||
|
{pinned && (
|
||||||
|
<Flex gap={1}>
|
||||||
|
<PinIcon width={8} />
|
||||||
|
<Text size="13">Pinned by carmen.eth</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Flex gap="1" align="center">
|
||||||
|
<Text color="primary" weight="500" size="15">
|
||||||
|
{contact.name}
|
||||||
|
</Text>
|
||||||
|
<Text size="10" color="gray">
|
||||||
|
10:00 AM
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{renderMessage()}
|
||||||
|
|
||||||
|
<Reactions onClick={handleReaction} />
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Actions
|
||||||
|
owner={owner}
|
||||||
|
pinned={pinned}
|
||||||
|
onEditClick={() => setEditing(true)}
|
||||||
|
onReplyClick={handleReplyClick}
|
||||||
|
reacting={reacting}
|
||||||
|
onReactingChange={setReacting}
|
||||||
|
/>
|
||||||
|
</Wrapper>
|
||||||
|
<ContextMenu>
|
||||||
|
<ContextMenu.Item>Reply</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>Pin</ContextMenu.Item>
|
||||||
|
</ContextMenu>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -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 (
|
||||||
|
<Wrapper>
|
||||||
|
<Flex gap="1" align="center">
|
||||||
|
<Avatar size={20} src={contact.imageUrl} />
|
||||||
|
<Text color="gray" size="13" weight="500">
|
||||||
|
{contact.name}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
{reply.type === 'text' && (
|
||||||
|
<Flex>
|
||||||
|
<Text
|
||||||
|
color="gray"
|
||||||
|
size="13"
|
||||||
|
truncate={false}
|
||||||
|
css={{
|
||||||
|
lineClamp: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{reply.text}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{reply.type === 'image' && (
|
||||||
|
<Box css={{ paddingTop: '$1' }}>
|
||||||
|
<Image
|
||||||
|
src={reply.imageUrl}
|
||||||
|
width={56}
|
||||||
|
height={56}
|
||||||
|
fit="cover"
|
||||||
|
radius="1"
|
||||||
|
alt="message"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{reply.type === 'image-text' && (
|
||||||
|
<Flex direction="column" gap={1}>
|
||||||
|
<Text color="gray" size="13" truncate={false}>
|
||||||
|
{reply.text}
|
||||||
|
</Text>
|
||||||
|
<Image
|
||||||
|
src={reply.imageUrl}
|
||||||
|
width={56}
|
||||||
|
height={56}
|
||||||
|
fit="cover"
|
||||||
|
radius="1"
|
||||||
|
alt="message"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
})
|
|
@ -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 (
|
||||||
|
<Flex css={{ paddingTop: 6 }} gap={1}>
|
||||||
|
<Button
|
||||||
|
onClick={() => onClick('')}
|
||||||
|
active={false}
|
||||||
|
aria-label="❤️, 1 reaction, press to react"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
width={16}
|
||||||
|
src="https://twemoji.maxcdn.com/v/latest/svg/2764.svg"
|
||||||
|
alt="❤️"
|
||||||
|
/>
|
||||||
|
1
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => onClick('')}
|
||||||
|
active={true}
|
||||||
|
aria-label="👍️, 1 reaction, press to react"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
width={16}
|
||||||
|
src="https://twemoji.maxcdn.com/v/latest/svg/1f44d.svg"
|
||||||
|
alt="👍️"
|
||||||
|
/>
|
||||||
|
2
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => onClick('')}
|
||||||
|
active={true}
|
||||||
|
aria-label="👎️, 1 reaction, press to react"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
width={16}
|
||||||
|
src="https://twemoji.maxcdn.com/v/latest/svg/1f44e.svg"
|
||||||
|
alt="👎️"
|
||||||
|
/>
|
||||||
|
3
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => onClick('')}
|
||||||
|
active={false}
|
||||||
|
aria-label="😆, 1 reaction, press to react"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
width={16}
|
||||||
|
src="https://twemoji.maxcdn.com/v/latest/svg/1f606.svg"
|
||||||
|
alt="😆"
|
||||||
|
/>
|
||||||
|
1
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => onClick('')}
|
||||||
|
active={false}
|
||||||
|
aria-label="😭, 1 reaction, press to react"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
width={16}
|
||||||
|
src="https://twemoji.maxcdn.com/v/latest/svg/1f62d.svg"
|
||||||
|
alt="😭"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => onClick('')}
|
||||||
|
active={false}
|
||||||
|
aria-label="😡, 1 reaction, press to react"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
width={16}
|
||||||
|
src="https://twemoji.maxcdn.com/v/latest/svg/1f621.svg"
|
||||||
|
alt="😡"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<ReactionPopover
|
||||||
|
onClick={emoji => {
|
||||||
|
console.log(emoji)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button>
|
||||||
|
<ReactionIcon />
|
||||||
|
</Button>
|
||||||
|
</ReactionPopover>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
Loading…
Reference in New Issue