From c338bf7aae0b1d376c10ce0253fc46af705ba1e7 Mon Sep 17 00:00:00 2001
From: Jakub Kotula <520927+jkbktl@users.noreply.github.com>
Date: Thu, 30 Mar 2023 13:32:26 +0200
Subject: [PATCH] Add pinned messages (#355)
* initial
* add to app
* add to app
* context tag
* rebase and fix changes
* update context-tag, update dialog
* update mocks
* fix dialog show
* clean up stories
* fix ids
* unify component definition
* pr fixes
* fix blur view
* fix blur view
* fix composer position
* context tag
* add icon avatar + pin announcement
* fix spacing
* fixes
* blue background for pin
---------
Co-authored-by: Pavel Prichodko <14926950+prichodko@users.noreply.github.com>
---
apps/web/styles/app.css | 4 +-
packages/components/hooks/use-blur.ts | 2 +-
.../components/src/avatar/icon-avatar.tsx | 44 ++++
packages/components/src/avatar/index.tsx | 1 +
.../components/src/banner/banner.stories.tsx | 54 +++++
packages/components/src/banner/banner.tsx | 45 ++++
packages/components/src/banner/index.tsx | 1 +
.../src/context-tag/context-tag.stories.tsx | 61 +++++
.../src/context-tag/context-tag.tsx | 147 ++++++++++++
packages/components/src/context-tag/index.tsx | 1 +
.../src/counter/counter.stories.tsx | 80 +++++++
packages/components/src/counter/counter.tsx | 68 ++++++
packages/components/src/counter/index.tsx | 1 +
packages/components/src/dialog/dialog.tsx | 29 ++-
packages/components/src/index.tsx | 1 +
.../src/messages/components/actions.tsx | 23 +-
packages/components/src/messages/index.tsx | 23 ++
packages/components/src/messages/message.tsx | 7 +-
.../components/src/pinned-message/index.tsx | 1 +
.../pinned-message/pinned-message.stories.tsx | 36 +++
.../src/pinned-message/pinned-message.tsx | 93 ++++++++
.../components/src/system-messages/index.ts | 1 +
.../pin-announcement.stories.tsx | 25 +++
.../src/system-messages/pin-announcement.tsx | 48 ++++
packages/components/src/topbar/topbar.tsx | 211 ++++++++++--------
25 files changed, 890 insertions(+), 117 deletions(-)
create mode 100644 packages/components/src/avatar/icon-avatar.tsx
create mode 100644 packages/components/src/banner/banner.stories.tsx
create mode 100644 packages/components/src/banner/banner.tsx
create mode 100644 packages/components/src/banner/index.tsx
create mode 100644 packages/components/src/context-tag/context-tag.stories.tsx
create mode 100644 packages/components/src/context-tag/context-tag.tsx
create mode 100644 packages/components/src/context-tag/index.tsx
create mode 100644 packages/components/src/counter/counter.stories.tsx
create mode 100644 packages/components/src/counter/counter.tsx
create mode 100644 packages/components/src/counter/index.tsx
create mode 100644 packages/components/src/pinned-message/index.tsx
create mode 100644 packages/components/src/pinned-message/pinned-message.stories.tsx
create mode 100644 packages/components/src/pinned-message/pinned-message.tsx
create mode 100644 packages/components/src/system-messages/index.ts
create mode 100644 packages/components/src/system-messages/pin-announcement.stories.tsx
create mode 100644 packages/components/src/system-messages/pin-announcement.tsx
diff --git a/apps/web/styles/app.css b/apps/web/styles/app.css
index 2d2d2aa7..9b545c6d 100644
--- a/apps/web/styles/app.css
+++ b/apps/web/styles/app.css
@@ -20,7 +20,7 @@ body,
#main {
position: relative;
display: grid;
- grid-template-rows: 56px 1fr 100px;
+ grid-template-rows: 96px 1fr 100px; /* 56px 1fr 100px without pinned messages */
height: 100vh;
}
@@ -45,7 +45,7 @@ body,
overflow: auto;
padding: 40px 0px 0px 0px;
height: 100vh;
- margin-top: -56px;
+ margin-top: -96px; /* -56px without pinned messages */
}
#messages {
diff --git a/packages/components/hooks/use-blur.ts b/packages/components/hooks/use-blur.ts
index 0905ae93..197ce512 100644
--- a/packages/components/hooks/use-blur.ts
+++ b/packages/components/hooks/use-blur.ts
@@ -17,7 +17,7 @@ type UseBlurProps = {
const useBlur = (props: UseBlurProps): UseBlurReturn => {
const {
marginBlurBottom = 32,
- heightTop = 56,
+ heightTop = 96,
throttle = 100,
ref,
} = props || {}
diff --git a/packages/components/src/avatar/icon-avatar.tsx b/packages/components/src/avatar/icon-avatar.tsx
new file mode 100644
index 00000000..945617d2
--- /dev/null
+++ b/packages/components/src/avatar/icon-avatar.tsx
@@ -0,0 +1,44 @@
+import { cloneElement } from 'react'
+
+import { type ColorTokens, Stack, styled } from '@tamagui/core'
+
+type Props = {
+ children: React.ReactElement
+ backgroundColor?: ColorTokens
+ color?: ColorTokens
+ size?: 20 | 32 | 48
+}
+
+const IconAvatar = (props: Props) => {
+ const {
+ children,
+ color = '$blue-50',
+ backgroundColor = '$blue-50-opa-20',
+ size = 32,
+ } = props
+ return (
+
+ {cloneElement(children, { color })}
+
+ )
+}
+
+const Base = styled(Stack, {
+ borderRadius: 80,
+ justifyContent: 'center',
+ alignItems: 'center',
+
+ variants: {
+ size: {
+ '...': (size: number) => {
+ return {
+ width: size,
+ height: size,
+ }
+ },
+ },
+ },
+})
+
+export { IconAvatar }
+export type { Props as IconAvatarProps }
diff --git a/packages/components/src/avatar/index.tsx b/packages/components/src/avatar/index.tsx
index 886c6ec3..0dac2863 100644
--- a/packages/components/src/avatar/index.tsx
+++ b/packages/components/src/avatar/index.tsx
@@ -1 +1,2 @@
export * from './avatar'
+export * from './icon-avatar'
diff --git a/packages/components/src/banner/banner.stories.tsx b/packages/components/src/banner/banner.stories.tsx
new file mode 100644
index 00000000..3a78021f
--- /dev/null
+++ b/packages/components/src/banner/banner.stories.tsx
@@ -0,0 +1,54 @@
+import { PinIcon } from '@status-im/icons/20'
+import { Stack } from '@tamagui/core'
+
+import { Banner } from './banner'
+
+import type { Meta, StoryObj } from '@storybook/react'
+
+const meta: Meta = {
+ component: Banner,
+ argTypes: {
+ children: {
+ control: 'text',
+ },
+ },
+}
+
+type Story = StoryObj
+
+export const Full: Story = {
+ args: {
+ icon: ,
+ children: 'Banner message',
+ count: 5,
+ },
+}
+
+export const NoIcon: Story = {
+ args: {
+ children: 'Banner message',
+ count: 5,
+ },
+}
+
+export const NoCount: Story = {
+ args: {
+ icon: ,
+ children: 'Banner message',
+ },
+}
+
+export const AllVariants: Story = {
+ args: {},
+ render: () => (
+
+ } count={5}>
+ Banner message
+
+ Banner message
+ }>Banner message
+
+ ),
+}
+
+export default meta
diff --git a/packages/components/src/banner/banner.tsx b/packages/components/src/banner/banner.tsx
new file mode 100644
index 00000000..0b1de159
--- /dev/null
+++ b/packages/components/src/banner/banner.tsx
@@ -0,0 +1,45 @@
+import { styled } from '@tamagui/core'
+import { View } from 'react-native'
+
+import { Counter } from '../counter'
+import { Text } from '../text'
+
+type Props = {
+ children: React.ReactNode
+ icon?: React.ReactNode
+ count?: number
+}
+
+const Banner = (props: Props) => {
+ const { icon, children, count } = props
+
+ return (
+
+
+ {icon}
+
+ {children}
+
+
+ {count ? : null}
+
+ )
+}
+
+export { Banner }
+export type { Props as BannerProps }
+
+const Base = styled(View, {
+ backgroundColor: '$primary-50-opa-20',
+ padding: 12,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ maxHeight: '40px',
+})
+
+const Content = styled(View, {
+ flexDirection: 'row',
+ gap: 10,
+ alignItems: 'center',
+})
diff --git a/packages/components/src/banner/index.tsx b/packages/components/src/banner/index.tsx
new file mode 100644
index 00000000..b7074b39
--- /dev/null
+++ b/packages/components/src/banner/index.tsx
@@ -0,0 +1 @@
+export { Banner, type BannerProps } from './banner'
diff --git a/packages/components/src/context-tag/context-tag.stories.tsx b/packages/components/src/context-tag/context-tag.stories.tsx
new file mode 100644
index 00000000..19c948f1
--- /dev/null
+++ b/packages/components/src/context-tag/context-tag.stories.tsx
@@ -0,0 +1,61 @@
+import { PendingIcon } from '@status-im/icons/12'
+import { Stack } from '@tamagui/core'
+
+import { ContextTag } from './context-tag'
+
+import type { Meta, StoryObj } from '@storybook/react'
+
+const meta: Meta = {
+ component: ContextTag,
+ argTypes: {
+ label: {
+ control: ['Rarible', '# channel-name'],
+ },
+ size: [24, 32],
+ outline: [true, false],
+ blur: [true, false],
+ },
+}
+
+type Story = StoryObj
+
+export const Base: Story = {
+ args: { label: 'Name', size: 24, outline: false, blur: false },
+}
+
+export const AllVariants: Story = {
+ args: {},
+ render: () => (
+
+
+
+
+
+
+
+
+
+
+
+ }
+ type="icon"
+ label="Context"
+ outline
+ />
+
+
+
+ ),
+}
+
+export default meta
diff --git a/packages/components/src/context-tag/context-tag.tsx b/packages/components/src/context-tag/context-tag.tsx
new file mode 100644
index 00000000..5f4711ee
--- /dev/null
+++ b/packages/components/src/context-tag/context-tag.tsx
@@ -0,0 +1,147 @@
+import { cloneElement, Fragment } from 'react'
+
+import { ChevronRightIcon } from '@status-im/icons/16'
+import { styled } from '@tamagui/core'
+import { View } from 'react-native'
+
+import { Avatar } from '../avatar'
+import { Text } from '../text'
+
+import type { AvatarProps } from '../avatar'
+import type { TextProps } from '../text'
+
+type ContextTagType =
+ | 'default'
+ | 'group'
+ | 'channel'
+ | 'community'
+ | 'token'
+ | 'network'
+ | 'account'
+ | 'collectible'
+ | 'address'
+ | 'icon'
+ | 'audio'
+
+type Props = {
+ children?: React.ReactNode
+ src?: string
+ icon?: React.ReactElement
+ label: string | [string, string]
+ type?: ContextTagType
+ size?: 24 | 32
+ blur?: boolean
+ outline?: boolean
+}
+
+const textSizes: Record, TextProps['size']> = {
+ '32': 15,
+ '24': 13,
+}
+
+const avatarSizes: Record, AvatarProps['size']> = {
+ '32': 28,
+ '24': 20,
+}
+
+const Label = ({ children, size }: { children: string; size: 24 | 32 }) => (
+
+ {children}
+
+)
+
+const ContextTag = (props: Props) => {
+ const {
+ src,
+ icon,
+ label,
+ type = 'default',
+ size = 24,
+ blur = false,
+ outline,
+ } = props
+
+ const hasImg = Boolean(src || icon)
+
+ return (
+
+ {src && }
+ {icon && cloneElement(icon, { color: '$neutral-50' })}
+
+ {Array.isArray(label) ? (
+ label.map((item, i) => {
+ if (i !== 0) {
+ return (
+
+
+
+
+ )
+ } else {
+ return (
+
+ )
+ }
+ })
+ ) : (
+
+ )}
+
+ )
+}
+
+export { ContextTag }
+export type { Props as ContextTagProps }
+
+const Base = styled(View, {
+ backgroundColor: '$neutral-10',
+ paddingVertical: 1,
+ borderRadius: 20,
+ display: 'inline-flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ borderWidth: '1px',
+ borderStyle: 'solid',
+ borderColor: 'transparent',
+
+ variants: {
+ outline: {
+ true: {
+ borderColor: '$primary-50',
+ },
+ false: {
+ borderColor: 'transparent',
+ },
+ },
+ blur: {
+ true: {
+ backgroundColor: '$neutral-80-opa-5',
+ },
+ false: {
+ backgroundColor: '$neutral-10',
+ },
+ },
+ size: {
+ 24: props => {
+ // there is only first param which is "size" and hasImg doesn't exist here
+ return {
+ space: 4,
+ paddingLeft: props.hasImg ? 8 : 2,
+ paddingRight: 8,
+ }
+ },
+ 32: ({ hasImg }) => ({
+ // this therefore doesn't work as well
+ space: 8,
+ paddingLeft: hasImg ? 12 : 2,
+ paddingRight: 12,
+ }),
+ },
+ hasImg: {
+ true: {},
+ false: {},
+ }, // to correctly infer the type of the variant
+ } as const,
+})
diff --git a/packages/components/src/context-tag/index.tsx b/packages/components/src/context-tag/index.tsx
new file mode 100644
index 00000000..43417b05
--- /dev/null
+++ b/packages/components/src/context-tag/index.tsx
@@ -0,0 +1 @@
+export { ContextTag, type ContextTagProps } from './context-tag'
diff --git a/packages/components/src/counter/counter.stories.tsx b/packages/components/src/counter/counter.stories.tsx
new file mode 100644
index 00000000..1f56fc7d
--- /dev/null
+++ b/packages/components/src/counter/counter.stories.tsx
@@ -0,0 +1,80 @@
+import { Stack } from '@tamagui/core'
+
+import { Counter } from './counter'
+
+import type { Meta, StoryObj } from '@storybook/react'
+
+const meta: Meta = {
+ component: Counter,
+ argTypes: {
+ value: {
+ control: {
+ type: 'number',
+ min: 0,
+ max: 1000,
+ },
+ },
+ type: {
+ control: 'select',
+ options: ['default', 'secondary', 'grey', 'outline'],
+ },
+ },
+}
+
+type Story = StoryObj
+
+export const Default: Story = {
+ args: {
+ value: 5,
+ type: 'default',
+ },
+}
+
+export const Secondary: Story = {
+ args: {
+ value: 5,
+ type: 'secondary',
+ },
+}
+
+export const Grey: Story = {
+ args: {
+ value: 5,
+ type: 'grey',
+ },
+}
+
+export const Outline: Story = {
+ args: {
+ value: 5,
+ type: 'outline',
+ },
+}
+
+export const AllVariants: Story = {
+ args: {},
+ render: () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+}
+
+export default meta
diff --git a/packages/components/src/counter/counter.tsx b/packages/components/src/counter/counter.tsx
new file mode 100644
index 00000000..89c4d514
--- /dev/null
+++ b/packages/components/src/counter/counter.tsx
@@ -0,0 +1,68 @@
+import { styled } from '@tamagui/core'
+import { View } from 'react-native'
+
+import { Text } from '../text'
+
+import type { ColorTokens } from '@tamagui/core'
+
+export type CounterVariants = 'default' | 'grey' | 'secondary' | 'outline'
+
+type Props = {
+ value: number
+ type?: CounterVariants
+}
+
+const Counter = (props: Props) => {
+ const { value, type = 'default' } = props
+
+ return (
+
+
+ {value > 99 ? '99+' : value}
+
+
+ )
+}
+
+export { Counter }
+export type { Props as CounterProps }
+
+const Base = styled(View, {
+ backgroundColor: '$primary-50',
+ paddingHorizontal: 3,
+ paddingVertical: 0,
+ borderRadius: '6px', // TODO: use tokens when fixed its definition
+ height: 16,
+ minWidth: 16,
+ maxWidth: 28,
+ display: 'inline-flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ flexBasis: 'fit-content',
+
+ variants: {
+ type: {
+ default: {
+ backgroundColor: '$primary-50',
+ },
+ secondary: {
+ backgroundColor: '$neutral-80-opa-5',
+ },
+ grey: {
+ backgroundColor: '$neutral-10',
+ },
+ outline: {
+ backgroundColor: 'transparent',
+ borderColor: '$neutral-20',
+ borderWidth: '1px',
+ },
+ },
+ },
+})
+
+const textColor: Record, ColorTokens> = {
+ default: '$white-100',
+ secondary: '$neutral-100',
+ outline: '$neutral-100',
+ grey: '$neutral-100',
+}
diff --git a/packages/components/src/counter/index.tsx b/packages/components/src/counter/index.tsx
new file mode 100644
index 00000000..5793f948
--- /dev/null
+++ b/packages/components/src/counter/index.tsx
@@ -0,0 +1 @@
+export { Counter, type CounterProps } from './counter'
diff --git a/packages/components/src/dialog/dialog.tsx b/packages/components/src/dialog/dialog.tsx
index 2f73427b..fa7b6ba9 100644
--- a/packages/components/src/dialog/dialog.tsx
+++ b/packages/components/src/dialog/dialog.tsx
@@ -1,7 +1,7 @@
import { forwardRef } from 'react'
import { Content, Overlay, Portal, Root, Trigger } from '@radix-ui/react-dialog'
-import { useMedia } from 'tamagui'
+import { Stack, styled, useMedia } from 'tamagui'
import { Sheet } from '../sheet'
@@ -15,6 +15,15 @@ interface Props {
press?: 'normal' | 'long'
}
+const Wrapper = styled(Stack, {
+ position: 'absolute',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '100vw',
+ height: '100vh',
+})
+
// const DialogTrigger = (
// props: DialogTriggerProps & {
// press: Props['press']
@@ -52,14 +61,16 @@ const Dialog = (props: Props) => {
{/* CONTENT */}
-
- {content}
+
+
+ {content}
+
)
diff --git a/packages/components/src/index.tsx b/packages/components/src/index.tsx
index b878f49c..6d047f78 100644
--- a/packages/components/src/index.tsx
+++ b/packages/components/src/index.tsx
@@ -8,6 +8,7 @@ export * from './icon-button'
export * from './image'
export * from './input'
export * from './messages'
+export * from './pinned-message'
export * from './provider'
export * from './sidebar'
export * from './sidebar-members'
diff --git a/packages/components/src/messages/components/actions.tsx b/packages/components/src/messages/components/actions.tsx
index a5cb8342..e79086aa 100644
--- a/packages/components/src/messages/components/actions.tsx
+++ b/packages/components/src/messages/components/actions.tsx
@@ -25,10 +25,11 @@ interface Props {
onReplyPress: VoidFunction
onEditPress: VoidFunction
// onDeletePress: VoidFunction
+ pinned?: boolean
}
export const Actions = (props: Props) => {
- const { reactions, onOpenChange, onReplyPress, onEditPress } = props
+ const { reactions, onOpenChange, onReplyPress, onEditPress, pinned } = props
useEffect(() => {
return () => onOpenChange(false)
@@ -76,7 +77,7 @@ export const Actions = (props: Props) => {
{/* OPTIONS MENU */}
} />
-
+
}
label="Edit message"
@@ -92,11 +93,19 @@ export const Actions = (props: Props) => {
label="Copy text"
onSelect={() => console.log('copy')}
/>
- }
- label="Pin to the channel"
- onSelect={() => console.log('pin')}
- />
+ {pinned ? (
+ }
+ label="Unpin message"
+ onSelect={() => console.log('unpin')}
+ />
+ ) : (
+ }
+ label="Pin to the channel"
+ onSelect={() => console.log('pin')}
+ />
+ )}
}
label="Forward"
diff --git a/packages/components/src/messages/index.tsx b/packages/components/src/messages/index.tsx
index a08371bf..f0e44e07 100644
--- a/packages/components/src/messages/index.tsx
+++ b/packages/components/src/messages/index.tsx
@@ -1,3 +1,4 @@
+import { PinAnnouncement } from '../system-messages'
import { Message } from './message'
import type { ReactionsType } from './types'
@@ -16,16 +17,19 @@ export const Messages = () => {
{
},
]}
reactions={{}}
+ id="1234-1237"
/>
+
{
},
]}
reactions={{}}
+ id="1234-1244"
/>
{
},
]}
reactions={{}}
+ id="1234-1245"
/>
>
)
diff --git a/packages/components/src/messages/message.tsx b/packages/components/src/messages/message.tsx
index f098c271..beb6f09f 100644
--- a/packages/components/src/messages/message.tsx
+++ b/packages/components/src/messages/message.tsx
@@ -14,7 +14,8 @@ import { Reactions } from './components/reactions'
import type { ReactionsType } from './types'
-interface Props {
+export interface MessageProps {
+ id: string
text?: React.ReactNode
images?: Array<{ url: string }>
reactions: ReactionsType
@@ -44,7 +45,7 @@ const Base = styled(Stack, {
} as const,
})
-const Message = (props: Props) => {
+const Message = (props: MessageProps) => {
const { text, images, reactions, reply, pinned } = props
const [hovered, setHovered] = useState(false)
@@ -59,6 +60,7 @@ const Message = (props: Props) => {
return (
setHovered(true)}
onHoverOut={() => setHovered(false)}
>
@@ -69,6 +71,7 @@ const Message = (props: Props) => {
onOpenChange={setShowActions}
onReplyPress={() => dispatch({ type: 'reply', messageId: '1' })}
onEditPress={() => dispatch({ type: 'edit', messageId: '1' })}
+ pinned={pinned}
/>
)}
diff --git a/packages/components/src/pinned-message/index.tsx b/packages/components/src/pinned-message/index.tsx
new file mode 100644
index 00000000..69270e89
--- /dev/null
+++ b/packages/components/src/pinned-message/index.tsx
@@ -0,0 +1 @@
+export { PinnedMessage, type PinnedMessageProps } from './pinned-message'
diff --git a/packages/components/src/pinned-message/pinned-message.stories.tsx b/packages/components/src/pinned-message/pinned-message.stories.tsx
new file mode 100644
index 00000000..e9b11ac9
--- /dev/null
+++ b/packages/components/src/pinned-message/pinned-message.stories.tsx
@@ -0,0 +1,36 @@
+import { PinnedMessage } from './pinned-message'
+
+import type { Meta, StoryObj } from '@storybook/react'
+
+const mockMessages = [
+ {
+ text: 'Morbi a metus. Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit.',
+ reactions: {},
+ pinned: true,
+ id: '1234-1234',
+ },
+ {
+ text: 'Morbi a metus. Phasellus enim erat, vestibulum vel, aliquam. message',
+ reactions: {},
+ pinned: true,
+ id: '4321-4321',
+ },
+]
+
+const meta: Meta = {
+ component: PinnedMessage,
+ argTypes: {
+ messages: mockMessages,
+ },
+}
+
+type Story = StoryObj
+
+export const Primary: Story = {
+ args: {
+ messages: mockMessages,
+ // children: 'Click me',
+ },
+}
+
+export default meta
diff --git a/packages/components/src/pinned-message/pinned-message.tsx b/packages/components/src/pinned-message/pinned-message.tsx
new file mode 100644
index 00000000..010cf897
--- /dev/null
+++ b/packages/components/src/pinned-message/pinned-message.tsx
@@ -0,0 +1,93 @@
+import { useState } from 'react'
+
+import { PinIcon } from '@status-im/icons/20'
+import { styled } from '@tamagui/core'
+import { Pressable, View } from 'react-native'
+
+import { Banner } from '../banner'
+import { Button } from '../button'
+import { ContextTag } from '../context-tag'
+import { Dialog } from '../dialog'
+import { Message } from '../messages'
+import { Text } from '../text'
+
+import type { MessageProps } from '../messages'
+
+type Props = {
+ messages: MessageProps[]
+}
+
+const PinnedMessage = (props: Props) => {
+ const { messages } = props
+ const [isDetailVisible, setIsDetailVisible] = useState(false)
+
+ return messages.length > 0 ? (
+
+ ) : null
+}
+
+export { PinnedMessage }
+export type { Props as PinnedMessageProps }
+
+const Base = styled(View, {
+ position: 'relative',
+ paddingHorizontal: 16,
+ paddingVertical: 16,
+ borderRadius: 16,
+ alignSelf: 'center',
+ alignItems: 'flex-start',
+ maxWidth: 480,
+ backgroundColor: '$neutral-5',
+ zIndex: 100,
+
+ variants: {
+ active: {
+ true: {
+ backgroundColor: '$neutral-5',
+ },
+ },
+ pinned: {
+ true: {
+ backgroundColor: '$blue-50-opa-5',
+ },
+ },
+ } as const,
+})
+
+const DialogHeader = styled(View, {
+ alignItems: 'flex-start',
+ justifyContent: 'flex-start',
+ paddingVertical: 16,
+ space: 11,
+})
+
+const DialogContent = styled(View, {
+ alignItems: 'stretch',
+ justifyContent: 'flex-start',
+})
diff --git a/packages/components/src/system-messages/index.ts b/packages/components/src/system-messages/index.ts
new file mode 100644
index 00000000..63e89270
--- /dev/null
+++ b/packages/components/src/system-messages/index.ts
@@ -0,0 +1 @@
+export { PinAnnouncement } from './pin-announcement'
diff --git a/packages/components/src/system-messages/pin-announcement.stories.tsx b/packages/components/src/system-messages/pin-announcement.stories.tsx
new file mode 100644
index 00000000..c5d659eb
--- /dev/null
+++ b/packages/components/src/system-messages/pin-announcement.stories.tsx
@@ -0,0 +1,25 @@
+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 = {
+ component: PinAnnouncement,
+}
+
+type Story = StoryObj
+
+export const Primary: Story = {
+ args: {
+ name: 'Pavel',
+ message: mockMessage,
+ },
+}
+
+export default meta
diff --git a/packages/components/src/system-messages/pin-announcement.tsx b/packages/components/src/system-messages/pin-announcement.tsx
new file mode 100644
index 00000000..fab4f65e
--- /dev/null
+++ b/packages/components/src/system-messages/pin-announcement.tsx
@@ -0,0 +1,48 @@
+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 (
+
+
+
+
+
+
+
+ {name}
+
+ pinned a message
+
+ 09:30
+
+
+
+
+
+ Alisher Yakupov
+
+ {message.text}
+
+
+
+ )
+}
+
+export { PinAnnouncement }
+export type { Props as PinAnnouncementProps }
diff --git a/packages/components/src/topbar/topbar.tsx b/packages/components/src/topbar/topbar.tsx
index 51adc21f..f6fe642c 100644
--- a/packages/components/src/topbar/topbar.tsx
+++ b/packages/components/src/topbar/topbar.tsx
@@ -16,10 +16,26 @@ import { BlurView } from 'expo-blur'
import { Divider } from '../divider'
import { DropdownMenu } from '../dropdown-menu'
import { IconButton } from '../icon-button'
+import { PinnedMessage } from '../pinned-message'
import { Text } from '../text'
import type { Channel } from '../sidebar/mock-data'
+const mockMessages = [
+ {
+ text: 'Morbi a metus. Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit.',
+ reactions: {},
+ pinned: true,
+ id: '1234-1234',
+ },
+ {
+ text: 'Morbi a metus. Phasellus enim erat, vestibulum vel, aliquam.',
+ reactions: {},
+ pinned: true,
+ id: '4321-4321',
+ },
+]
+
type Props = {
showMembers: boolean
onMembersPress: () => void
@@ -35,113 +51,116 @@ const Topbar = (props: Props) => {
return (
-
-
-
- }
- onPress={() => goBack?.()}
- blur={blur}
- />
-
-
- {emoji && (
-
- {emoji}
-
- )}
-
- {title && (
-
- {title}
-
- )}
-
-
-
-
-
+
- {description && (
-
-
- {description}
-
+
+
+ }
+ onPress={() => goBack?.()}
+ blur={blur}
+ />
- )}
-
- }
- selected={showMembers}
- onPress={onMembersPress}
- blur={blur}
- />
+
+ {emoji && (
+
+ {emoji}
+
+ )}
+
+ {title && (
+
+ {title}
+
+ )}
+
+
+
-
- } />
+
+ {description && (
+
+
+ {description}
+
+
+ )}
+
+ }
+ selected={showMembers}
+ onPress={onMembersPress}
+ blur={blur}
+ />
+
-
- }
- label="View channel members and details"
- onSelect={() => console.log('click')}
- />
- }
- label="Mute channel"
- onSelect={() => console.log('click')}
- />
- }
- label="Mark as read"
- onSelect={() => console.log('click')}
- />
- }
- label="Fetch messages"
- onSelect={() => console.log('click')}
- />
- }
- label="Share link to the channel"
- onSelect={() => console.log('click')}
- />
+
+ } />
-
+
+ }
+ label="View channel members and details"
+ onSelect={() => console.log('click')}
+ />
+ }
+ label="Mute channel"
+ onSelect={() => console.log('click')}
+ />
+ }
+ label="Mark as read"
+ onSelect={() => console.log('click')}
+ />
+ }
+ label="Fetch messages"
+ onSelect={() => console.log('click')}
+ />
+ }
+ label="Share link to the channel"
+ onSelect={() => console.log('click')}
+ />
- }
- label="Clear history"
- onSelect={() => console.log('click')}
- danger
- />
-
-
+
+
+ }
+ label="Clear history"
+ onSelect={() => console.log('click')}
+ danger
+ />
+
+
+
+
)