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