Delete message / system messages (#367)

* initial

* system messages

* create subcomponents

* all variants

* integration into app

* fix incorrect naming

* fix icon color

* fix spacings, positionings, colors

* CR fixes

* fix wrong props

* fix imports

* fix PR
This commit is contained in:
Jakub Kotula 2023-04-11 20:54:56 +02:00 committed by GitHub
parent cf8d302ca6
commit c9ae7f8fe2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 521 additions and 83 deletions

View File

@ -121,9 +121,16 @@ export const Actions = (props: Props) => {
<DropdownMenu.Item
icon={<DeleteIcon />}
label="Delete message"
label="Delete for me"
danger
onSelect={() => console.log('delete')}
onSelect={() => console.log('delete for me')}
/>
<DropdownMenu.Item
icon={<DeleteIcon />}
label="Delete for everyone"
danger
onSelect={() => console.log('delete for everyone')}
/>
</DropdownMenu.Content>
</DropdownMenu>

View File

@ -2,7 +2,7 @@ import { Stack } from '@tamagui/core'
import { DividerDate, DividerNewMessages } from '../dividers'
import { MessageSkeleton } from '../skeleton'
import { PinAnnouncement } from '../system-messages'
import { SystemMessage } from '../system-message'
import { Message } from './message'
import type { ReactionsType } from './types'
@ -66,6 +66,11 @@ export const Messages = (props: Props) => {
reactions={{}}
id="1234-1237"
/>
<SystemMessage
type="deleted"
text="Messaged deleted for everyone"
timestamp="9:45"
/>
<Message
text="Morbi a metus. Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit. Nullam sapien sem, ornare ac, nonummy non, lobortis a, enim. Nunc tincidunt ante vitae massa. Duis ante orci, molestie vitae, vehicula venenatis, tincidunt ac, pede. Nulla accumsan, elit sit"
reactions={reactions}
@ -77,14 +82,22 @@ export const Messages = (props: Props) => {
pinned
id="1234-1239"
/>
<PinAnnouncement
name="Steve"
<SystemMessage
type="pinned"
state="pressed"
timestamp="9:45"
user={{
id: '123',
name: 'Steve',
src: 'https://images.unsplash.com/photo-1628196482365-8b8b2b2b2b1f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80',
}}
message={{
text: 'Morbi a metus. Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit.',
reactions: {},
reply: true,
pinned: true,
id: '1234-1235',
author: {
id: '123',
name: 'Alisher',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
},
}}
/>
<Message
@ -112,6 +125,12 @@ export const Messages = (props: Props) => {
reactions={reactions}
id="1234-1243"
/>
<SystemMessage
state="landed"
type="deleted"
text="Messaged deleted for you"
timestamp="10:12"
/>
<Message
images={[
{

View File

@ -0,0 +1,78 @@
import { AddUserIcon } from '@status-im/icons/20'
import { Stack } from 'tamagui'
import { Avatar, IconAvatar } from '../../avatar'
import { Text } from '../../text'
import type { SystemMessageState, User } from '../system-message'
type Props = {
timestamp: string
user: User
users: Array<User>
state: SystemMessageState
}
const AddedUsersMessageContent = (props: Props) => {
const { user, users, timestamp, state } = props
return (
<>
<IconAvatar
backgroundColor={state === 'landed' ? '$transparent' : '$blue-50-opa-5'}
color="$blue-50"
>
<AddUserIcon />
</IconAvatar>
<Stack flexDirection="row" space={2} flexBasis="max-content" flexGrow={1}>
<Stack flexDirection="row" space={4} alignItems="center" flexGrow={1}>
<Avatar size={16} src={user.src} />
<Text size={13} weight="semibold">
{user.name}
</Text>
<Text size={13}>added </Text>
{users.length === 1 && (
<Stack flexDirection="row" gap={4} alignItems="center">
<Avatar size={16} src={users[0].src} />
<Text size={13} weight="semibold">
{users[0].name}
</Text>
<Text size={11} color="$neutral-50">
{timestamp}
</Text>
</Stack>
)}
{users.length > 1 && (
<Stack
flexDirection="row"
flexGrow={1}
flexBasis="max-content"
justifyContent="space-between"
>
<Stack flexDirection="row" gap={4} alignItems="center">
{users.map((user, i) => {
return (
<>
<Text size={13}>
{users.length === i + 1 ? ' and ' : null}
</Text>
<Avatar size={16} src={user.src} />
<Text size={13} weight="semibold">
{user.name}
</Text>
</>
)
})}
</Stack>
<Text size={11} color="$neutral-50">
{timestamp}
</Text>
</Stack>
)}
</Stack>
</Stack>
</>
)
}
export { AddedUsersMessageContent }

View File

@ -0,0 +1,67 @@
import { TimeoutIcon } from '@status-im/icons/12'
import { DeleteIcon } from '@status-im/icons/20'
import { Stack } from 'tamagui'
import { IconAvatar } from '../../avatar'
import { Button } from '../../button'
import { Text } from '../../text'
import type { SystemMessageState } from '../system-message'
type Props = {
timestamp: string
text: string
action?: {
label: string
onPress: () => void
}
state: SystemMessageState
}
const DeletedMessageContent = (props: Props) => {
const { timestamp, text, action, state } = props
return (
<>
<IconAvatar
backgroundColor={state === 'landed' ? '$transparent' : '$red-50-opa-5'}
color="$neutral-100"
>
<DeleteIcon />
</IconAvatar>
<Stack
flexDirection="row"
space={2}
justifyContent="space-between"
flexBasis="max-content"
flexGrow={1}
>
<Stack
flexDirection="row"
alignItems="center"
justifyContent="space-between"
flexGrow={1}
>
<Stack flexDirection="row" space={8} alignItems="baseline">
<Text size={13}>{text}</Text>
<Text size={11} color="$neutral-50">
{timestamp}
</Text>
</Stack>
{action && (
<Button
onPress={action.onPress}
variant="darkGrey"
size={24}
icon={<TimeoutIcon />}
>
{action.label}
</Button>
)}
</Stack>
</Stack>
</>
)
}
export { DeletedMessageContent }

View File

@ -0,0 +1,76 @@
import { PinIcon } from '@status-im/icons/20'
import { Stack } from 'tamagui'
import { Avatar, IconAvatar } from '../../avatar'
import { Text } from '../../text'
import type { SystemMessageState, User } from '../system-message'
type Props = {
timestamp: string
user: User
message: {
author: User
text: string
images?: {
type: 'photo' | 'gif'
srcs: Array<string>
}
}
state: SystemMessageState
}
const PinnedMessageContent = (props: Props) => {
const { timestamp, user, message, state } = props
const { author, images, text } = message
return (
<>
<IconAvatar
backgroundColor={state === 'landed' ? '$transparent' : '$blue-50-opa-5'}
color="$neutral-100"
>
<PinIcon />
</IconAvatar>
<Stack
flexDirection="column"
space={2}
flexBasis="max-content"
flexGrow={1}
>
<Stack flexDirection="row" space={4} alignItems="baseline">
<Text size={13} weight="semibold">
{user.name}
</Text>
<Text size={13}>pinned a message</Text>
<Text size={11} color="$neutral-50">
{timestamp}
</Text>
</Stack>
<Stack
flexDirection="row"
space={4}
justifyContent="space-between"
flexGrow={1}
flexBasis="max-content"
>
<Stack flexDirection="row" space={4}>
<Avatar size={16} src={author.src} />
<Text size={11} weight="semibold">
{author.name}
</Text>
<Text size={11}>{images?.type === 'gif' ? 'GIF' : text}</Text>
</Stack>
{images?.type === 'photo' && images.srcs.length > 0 && (
<Text size={11} color="$neutral-50">
{images.srcs.length} photos
</Text>
)}
</Stack>
</Stack>
</>
)
}
export { PinnedMessageContent }

View File

@ -0,0 +1 @@
export { SystemMessage } from './system-message'

View File

@ -0,0 +1,142 @@
import { action } from '@storybook/addon-actions'
import { Stack } from '@tamagui/core'
import { SystemMessage } from './system-message'
import type { Meta, StoryObj } from '@storybook/react'
const meta: Meta<typeof SystemMessage> = {
component: SystemMessage,
argTypes: {
state: {
control: 'select',
options: ['default', 'pressed', 'landed'],
},
},
}
type Story = StoryObj<typeof SystemMessage>
export const AllVariants: Story = {
args: { state: 'default' },
render: ({ state }) => (
<Stack space flexDirection="row" width={800}>
<Stack space minWidth={800}>
<SystemMessage
type="deleted"
text="Message deleted"
timestamp="9:45"
state={state}
/>
<SystemMessage
timestamp="11:12"
type="deleted"
text="Message deleted for you"
action={{ label: 'Undo', onClick: action('undo') }}
state={state}
/>
<SystemMessage
type="pinned"
user={{ id: '123', name: 'Steve', src: '' }}
timestamp="9:45"
state={state}
message={{
text: 'This is a pinned message',
author: {
id: '321',
name: 'Alisher',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
},
}}
/>
<SystemMessage
type="pinned"
user={{ id: '123', name: 'Steve', src: '' }}
timestamp="9:45"
state={state}
message={{
text: 'This is an example of pinned message with photos',
images: {
type: 'photo',
srcs: ['random 1', 'random 2', 'random 3', 'random 4', 'random'],
},
author: {
id: '321',
name: 'Alisher',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
},
}}
/>
<SystemMessage
type="pinned"
user={{ id: '123', name: 'Steve', src: '' }}
timestamp="9:45"
state={state}
message={{
text: 'This is a pinned message',
images: {
type: 'gif',
srcs: ['random 1'],
},
author: {
id: '321',
name: 'Alisher',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
},
}}
/>
<SystemMessage
type="added"
timestamp="9:41"
user={{
id: '123',
name: 'Steve',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
}}
state={state}
users={[
{
id: '425',
name: 'Peter',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
},
]}
/>
<SystemMessage
type="added"
timestamp="9:41"
user={{
id: '123',
name: 'Brian',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
}}
state={state}
users={[
{
id: '425',
name: 'George',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
},
{
id: '426',
name: 'John',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
},
{
id: '427',
name: 'Paul',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
},
{
id: '428',
name: 'Ringo',
src: 'https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500',
},
]}
/>
</Stack>
</Stack>
),
}
export default meta

View File

@ -0,0 +1,121 @@
import { styled } from '@tamagui/core'
import { View } from 'react-native'
import { AddedUsersMessageContent } from './components/added-users-message-content'
import { DeletedMessageContent } from './components/deleted-message-content'
import { PinnedMessageContent } from './components/pinned-message-content'
type User = {
id: string
name: string
src: string
}
type SystemMessageState = 'default' | 'pressed' | 'landed'
type Props = {
state?: SystemMessageState
timestamp: string
} & (
| {
type: 'pinned'
user: User
message: {
author: User
text: string
images?: {
type: 'photo' | 'gif'
srcs: Array<string>
}
}
}
| {
type: 'deleted'
text: string
action?: {
label: string
onClick: () => void
}
}
| {
type: 'added'
user: User
users: Array<User>
}
)
const SystemMessage = (props: Props) => {
const { state = 'default', timestamp, type } = props
const renderMessage = (type: 'pinned' | 'deleted' | 'added') => {
switch (type) {
case 'pinned':
return (
<PinnedMessageContent
timestamp={timestamp}
user={props.user}
message={props.message}
state={state}
/>
)
case 'deleted':
return (
<DeletedMessageContent
timestamp={timestamp}
text={props.text}
action={props.action}
state={state}
/>
)
case 'added':
return (
<AddedUsersMessageContent
timestamp={timestamp}
user={props.user}
users={props.users}
state={state}
/>
)
}
}
return (
<Base
flexDirection="row"
space={8}
padding={8}
alignItems="center"
state={
type === 'deleted' && state === 'landed' ? 'landed_deleted' : state
}
>
{renderMessage(type)}
</Base>
)
}
export { SystemMessage }
export type { Props as SystemMessageProps, SystemMessageState, User }
const Base = styled(View, {
backgroundColor: '$white-50',
borderRadius: '$16',
padding: 8,
variants: {
state: {
default: {
backgroundColor: '$white-50',
},
pressed: {
backgroundColor: '$neutral-5',
},
landed: {
backgroundColor: '$blue-50-opa-5',
},
landed_deleted: {
backgroundColor: '$red-50-opa-5',
},
},
},
})

View File

@ -1 +0,0 @@
export { PinAnnouncement } from './pin-announcement'

View File

@ -1,25 +0,0 @@
import { PinAnnouncement } from './pin-announcement'
import type { Meta, StoryObj } from '@storybook/react'
const mockMessage = {
text: 'Morbi a metus. Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit.',
reactions: {},
pinned: true,
id: '1234-1234',
}
const meta: Meta<typeof PinAnnouncement> = {
component: PinAnnouncement,
}
type Story = StoryObj<typeof PinAnnouncement>
export const Primary: Story = {
args: {
name: 'Pavel',
message: mockMessage,
},
}
export default meta

View File

@ -1,48 +0,0 @@
import { PinIcon } from '@status-im/icons/16'
import { Stack } from '@tamagui/core'
import { Avatar, IconAvatar } from '../avatar'
import { Text } from '../text'
import type { MessageProps } from '../messages'
type Props = {
message: MessageProps
name: string
}
const PinAnnouncement = (props: Props) => {
const { message, name } = props
return (
<Stack flexDirection="row" space={8} padding={8}>
<IconAvatar backgroundColor="$turquoise-50-opa-5" color="$neutral-100">
<PinIcon />
</IconAvatar>
<Stack flexDirection="column" space={2}>
<Stack flexDirection="row" space={4} alignItems="center">
<Text size={13} weight="semibold">
{name}
</Text>
<Text size={13}>pinned a message</Text>
<Text size={11} color="$neutral-50">
09:30
</Text>
</Stack>
<Stack flexDirection="row" space={4}>
<Avatar
size={16}
src="https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500"
/>
<Text size={11} weight="semibold">
Alisher Yakupov
</Text>
<Text size={11}>{message.text}</Text>
</Stack>
</Stack>
</Stack>
)
}
export { PinAnnouncement }
export type { Props as PinAnnouncementProps }

View File

@ -180,6 +180,7 @@ export const tokens = createTokens({
xdai: 'hsla(179, 51%, 50%, 1)',
polygon: 'hsla(268, 84%, 70%, 1)',
unknown: 'hsla(206, 26%, 95%, 1)',
transparent: 'hsla(0, 0%, 0%, 0)',
},
size,
space,