Update community sidebars, topbar & author (#370)
* add ChannelAvatar component * add Channel component * add expandable state to DividerLabel * change counter story name * Finalize SidebarCommunity component * complete Author component * update UserList component * finalize SidebarMembers component * Finalize Topbar component * fix Banner truncate * update dropdown menu props * update app styles * make mono font work * render author part optionally * rename css IDs * remove console.log * imageUrl -> imageSrc * upgrade expo-blur * add loading to messages * fix ¯\_(ツ)_/¯ comment
This commit is contained in:
parent
a0cde7d4a6
commit
75929703da
|
@ -5,7 +5,7 @@ import {
|
|||
CHANNEL_GROUPS,
|
||||
Composer,
|
||||
Messages,
|
||||
Sidebar,
|
||||
SidebarCommunity,
|
||||
SidebarMembers,
|
||||
Topbar,
|
||||
useAppDispatch,
|
||||
|
@ -20,8 +20,8 @@ const COMMUNITY = {
|
|||
description:
|
||||
'Multichain community-centric NFT marketplace. Create, buy and sell your NFTs.',
|
||||
membersCount: 123,
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1574786527860-f2e274867c91?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1764&q=80',
|
||||
imageSrc:
|
||||
'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2264&q=80',
|
||||
}
|
||||
|
||||
const updateProperty = (property: string, value: number) => {
|
||||
|
@ -29,8 +29,17 @@ const updateProperty = (property: string, value: number) => {
|
|||
}
|
||||
|
||||
function App() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [showMembers, setShowMembers] = useState(false)
|
||||
|
||||
// TODO: Use it to simulate loading
|
||||
// useEffect(() => {
|
||||
// setLoading(true)
|
||||
// setTimeout(() => {
|
||||
// setLoading(false)
|
||||
// }, 2000)
|
||||
// }, [])
|
||||
|
||||
const appState = useAppState()
|
||||
const appDispatch = useAppDispatch()
|
||||
|
||||
|
@ -73,13 +82,14 @@ function App() {
|
|||
|
||||
return (
|
||||
<div id="app">
|
||||
<div id="sidebar" style={{ zIndex: 200 }}>
|
||||
<Sidebar
|
||||
<div id="sidebar-community" style={{ zIndex: 200 }}>
|
||||
<SidebarCommunity
|
||||
community={COMMUNITY}
|
||||
selectedChannelId={appState.channelId}
|
||||
onChannelPress={channelId =>
|
||||
appDispatch({ type: 'set-channel', channelId })
|
||||
}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -90,15 +100,31 @@ function App() {
|
|||
channel={selectedChannel}
|
||||
showMembers={showMembers}
|
||||
onMembersPress={() => setShowMembers(show => !show)}
|
||||
pinnedMessages={[
|
||||
{
|
||||
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',
|
||||
},
|
||||
]}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="content" ref={contentRef}>
|
||||
<div id="messages">
|
||||
<Messages />
|
||||
<Messages loading={loading} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading === false && (
|
||||
<div id="composer" ref={composerRef}>
|
||||
{scrollPosition !== 'bottom' && (
|
||||
<div id="anchor-actions">
|
||||
|
@ -107,10 +133,11 @@ function App() {
|
|||
)}
|
||||
<Composer blur={scrollPosition !== 'bottom'} />
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{showMembers && (
|
||||
<div id="members">
|
||||
<div id="sidebar-members">
|
||||
<SidebarMembers />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -18,19 +18,25 @@ body,
|
|||
|
||||
#app {
|
||||
isolation: isolate;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: 352px 1fr auto;
|
||||
}
|
||||
|
||||
#main {
|
||||
position: relative;
|
||||
#sidebar-community {
|
||||
overflow: auto;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
#sidebar-members {
|
||||
width: 352px;
|
||||
overflow: auto;
|
||||
height: 100vh;
|
||||
background-color: #fff;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#main {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#topbar {
|
||||
|
@ -45,6 +51,7 @@ body,
|
|||
padding-top: var(--topbar-height);
|
||||
padding-bottom: var(--composer-height);
|
||||
height: 100vh;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
#messages {
|
||||
|
@ -60,12 +67,7 @@ body,
|
|||
#composer {
|
||||
position: absolute;
|
||||
inset: auto 0 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#members {
|
||||
width: 352px;
|
||||
overflow: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
|
|
|
@ -92,3 +92,9 @@ h6 {
|
|||
#__next {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
/* Temporary testing purposes of keyboard navigation */
|
||||
button:focus-visible {
|
||||
outline: 2px solid crimson;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"react-native-web": "^0.18.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.1.1",
|
||||
"@radix-ui/react-dialog": "^1.0.3",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||
"@radix-ui/react-popover": "^1.0.5",
|
||||
|
@ -43,7 +44,7 @@
|
|||
"@tamagui/react-native-media-driver": "1.7.7",
|
||||
"@tamagui/shorthands": "1.7.7",
|
||||
"@tamagui/theme-base": "1.7.7",
|
||||
"expo-blur": "~12.0.1",
|
||||
"expo-blur": "^12.2.2",
|
||||
"expo-linear-gradient": "^12.1.2",
|
||||
"tamagui": "1.7.7",
|
||||
"zustand": "^4.3.6"
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
import { CHANNEL_GROUPS } from '../sidebar/mock-data'
|
||||
import { Accordion } from './accordion'
|
||||
import { AccordionItem } from './accordionItem'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
const meta: Meta<typeof Accordion> = {
|
||||
component: Accordion,
|
||||
argTypes: {},
|
||||
args: {
|
||||
unreadCount: 3,
|
||||
title: 'Welcome',
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'figma',
|
||||
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Web?node-id=14849%3A172544&t=4BeIzudVkio0c6Px-4',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof Accordion>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<AccordionItem
|
||||
key="welcome"
|
||||
selected
|
||||
channel={CHANNEL_GROUPS[0].channels[0]}
|
||||
/>
|
||||
<AccordionItem key="general" channel={CHANNEL_GROUPS[0].channels[0]} />
|
||||
<AccordionItem key="lounge" channel={CHANNEL_GROUPS[0].channels[0]} />
|
||||
<AccordionItem key="random" channel={CHANNEL_GROUPS[0].channels[0]} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
|
@ -1,105 +0,0 @@
|
|||
import { Fragment, useState } from 'react'
|
||||
|
||||
import { ChevronRightIcon } from '@status-im/icons/20'
|
||||
import { Stack } from '@tamagui/core'
|
||||
import { AnimatePresence } from 'tamagui'
|
||||
|
||||
import { Text } from '../text'
|
||||
|
||||
type Props = {
|
||||
children: React.ReactElement[] | React.ReactElement
|
||||
initialExpanded: boolean
|
||||
title: string
|
||||
unreadCount?: number
|
||||
}
|
||||
|
||||
const Accordion = (props: Props) => {
|
||||
const { children, initialExpanded, title, unreadCount } = props
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(initialExpanded)
|
||||
|
||||
return (
|
||||
<Stack
|
||||
accessibilityRole="button"
|
||||
width="100%"
|
||||
borderRadius="$0"
|
||||
borderTopWidth={1}
|
||||
borderTopColor="$neutral-10"
|
||||
paddingHorizontal={8}
|
||||
paddingBottom={8}
|
||||
>
|
||||
<Stack justifyContent="flex-start">
|
||||
<Stack width="100%">
|
||||
<Stack
|
||||
width="100%"
|
||||
flexDirection="row"
|
||||
justifyContent={'space-between'}
|
||||
onPress={() => setIsExpanded(prev => !prev)}
|
||||
cursor="pointer"
|
||||
py={8}
|
||||
>
|
||||
<Stack flexDirection="row" alignItems="center" gap={4}>
|
||||
<Stack
|
||||
animation="fast"
|
||||
justifyContent="center"
|
||||
transform={[
|
||||
{
|
||||
rotateZ: isExpanded ? '90deg' : '0deg',
|
||||
},
|
||||
{
|
||||
translateY: isExpanded ? -4 : 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ChevronRightIcon color="$neutral-50" />
|
||||
</Stack>
|
||||
<Text size={13} color="$neutral-50" weight="medium">
|
||||
{title}
|
||||
</Text>
|
||||
</Stack>
|
||||
<AnimatePresence>
|
||||
{!isExpanded && unreadCount !== 0 && (
|
||||
<Stack
|
||||
key={`notifications-${title}}`}
|
||||
width={20}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
mr={8}
|
||||
animation={[
|
||||
'fast',
|
||||
{
|
||||
opacity: {
|
||||
overshootClamping: true,
|
||||
},
|
||||
},
|
||||
]}
|
||||
enterStyle={{ opacity: 0 }}
|
||||
exitStyle={{ opacity: 0 }}
|
||||
opacity={1}
|
||||
>
|
||||
<Stack
|
||||
backgroundColor="$turquoise-50"
|
||||
borderRadius="$4"
|
||||
width={16}
|
||||
height={16}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text size={11} color="$white-100" weight="medium">
|
||||
{unreadCount}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Stack>
|
||||
<AnimatePresence>
|
||||
{isExpanded && <Fragment key={title}>{children}</Fragment>}
|
||||
</AnimatePresence>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export { Accordion }
|
|
@ -1,120 +0,0 @@
|
|||
import { MutedIcon } from '@status-im/icons/20'
|
||||
import { Stack, Text as RNText } from '@tamagui/core'
|
||||
|
||||
import { Text } from '../text'
|
||||
|
||||
import type { Channel } from '../sidebar/mock-data'
|
||||
import type { ColorTokens } from '@tamagui/core'
|
||||
|
||||
type Props = {
|
||||
selected?: boolean
|
||||
onPress?: () => void
|
||||
channel: Channel
|
||||
}
|
||||
|
||||
const textColors: Record<NonNullable<Channel['channelStatus']>, ColorTokens> = {
|
||||
muted: '$neutral-40',
|
||||
normal: '$neutral-50',
|
||||
withMessages: '$neutral-100',
|
||||
withMentions: '$neutral-100',
|
||||
}
|
||||
|
||||
const AccordionItem = (props: Props) => {
|
||||
const { channel, selected, onPress } = props
|
||||
|
||||
const { emoji, title, channelStatus = 'normal', unreadCount } = channel
|
||||
|
||||
return (
|
||||
<Stack
|
||||
accessibilityRole="button"
|
||||
animation={[
|
||||
'fast',
|
||||
{
|
||||
opacity: {
|
||||
overshootClamping: true,
|
||||
},
|
||||
},
|
||||
]}
|
||||
backgroundColor={selected ? '$primary-50-opa-10' : 'transparent'}
|
||||
hoverStyle={{
|
||||
backgroundColor: '$primary-50-opa-5',
|
||||
}}
|
||||
borderRadius="$4"
|
||||
padding={8}
|
||||
width="100%"
|
||||
enterStyle={{ opacity: 0 }}
|
||||
exitStyle={{ opacity: 0 }}
|
||||
opacity={1}
|
||||
justifyContent={
|
||||
channelStatus === 'normal' ? 'flex-start' : 'space-between'
|
||||
}
|
||||
alignItems="center"
|
||||
flexDirection="row"
|
||||
cursor="pointer"
|
||||
onPress={onPress}
|
||||
>
|
||||
<Stack
|
||||
justifyContent="flex-start"
|
||||
alignItems="center"
|
||||
flexDirection="row"
|
||||
>
|
||||
{emoji && (
|
||||
<Stack
|
||||
width={24}
|
||||
height={24}
|
||||
borderRadius={24 / 2}
|
||||
backgroundColor="$turquoise-50-opa-10"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
marginRight={8}
|
||||
>
|
||||
<RNText>{emoji}</RNText>
|
||||
</Stack>
|
||||
)}
|
||||
<Text size={15} color={textColors[channelStatus]} weight="medium">
|
||||
{title}
|
||||
</Text>
|
||||
</Stack>
|
||||
{channelStatus !== 'normal' && (
|
||||
<Stack>
|
||||
{channelStatus === 'withMentions' && (
|
||||
<Stack width={20} justifyContent="center" alignItems="center">
|
||||
<Stack
|
||||
backgroundColor="$turquoise-50"
|
||||
borderRadius="$4"
|
||||
width={16}
|
||||
height={16}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text size={11} color="$white-100" weight="medium">
|
||||
{unreadCount}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
{channelStatus === 'withMessages' && (
|
||||
<Stack
|
||||
width={20}
|
||||
height={20}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Stack
|
||||
backgroundColor="$neutral-40"
|
||||
borderRadius="$4"
|
||||
width={8}
|
||||
height={8}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{channelStatus === 'muted' && <MutedIcon color="$neutral-40" />}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export { AccordionItem }
|
|
@ -1,3 +1,5 @@
|
|||
import { Stack } from 'tamagui'
|
||||
|
||||
import { Author } from './author'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
@ -14,30 +16,125 @@ const meta: Meta<typeof Author> = {
|
|||
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Web?node-id=3155%3A49848&t=87Ziud3PyYYSvsRg-4',
|
||||
},
|
||||
},
|
||||
|
||||
render: args => (
|
||||
<Stack gap={8}>
|
||||
<Author {...args} />
|
||||
<Author {...args} status="verified" />
|
||||
<Author {...args} status="untrustworthy" />
|
||||
<Author {...args} status="contact" />
|
||||
</Stack>
|
||||
),
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof Author>
|
||||
|
||||
export const Default: Story = {
|
||||
export const AllVariants: Story = {
|
||||
args: {},
|
||||
}
|
||||
render: args => (
|
||||
<Stack gap={20}>
|
||||
<Stack gap={8}>
|
||||
<Author {...args} />
|
||||
<Author {...args} status="verified" />
|
||||
<Author {...args} status="untrustworthy" />
|
||||
<Author {...args} status="contact" />
|
||||
</Stack>
|
||||
|
||||
export const Contact: Story = {
|
||||
args: {
|
||||
status: 'contact',
|
||||
},
|
||||
}
|
||||
<Stack gap={8}>
|
||||
<Author {...args} address="zQ3...9d4Gs0" />
|
||||
<Author {...args} status="verified" address="zQ3...9d4Gs0" />
|
||||
<Author {...args} status="untrustworthy" address="zQ3...9d4Gs0" />
|
||||
<Author {...args} status="contact" address="zQ3...9d4Gs0" />
|
||||
</Stack>
|
||||
|
||||
export const Verified: Story = {
|
||||
args: {
|
||||
status: 'verified',
|
||||
},
|
||||
}
|
||||
<Stack gap={8}>
|
||||
<Author {...args} address="zQ3...9d4Gs0" time="09:30" />
|
||||
<Author
|
||||
{...args}
|
||||
status="verified"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
<Author
|
||||
{...args}
|
||||
status="untrustworthy"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
<Author
|
||||
{...args}
|
||||
status="contact"
|
||||
nickname="alisher.eth"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
export const Untrustworthy: Story = {
|
||||
args: {
|
||||
status: 'untrustworthy',
|
||||
},
|
||||
<Stack gap={8}>
|
||||
<Author
|
||||
{...args}
|
||||
nickname="alisher.eth"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
<Author
|
||||
{...args}
|
||||
status="verified"
|
||||
nickname="alisher.eth"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
<Author
|
||||
{...args}
|
||||
status="untrustworthy"
|
||||
nickname="alisher.eth"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
<Author
|
||||
{...args}
|
||||
status="contact"
|
||||
nickname="alisher.eth"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack gap={8}>
|
||||
<Author
|
||||
{...args}
|
||||
size={15}
|
||||
nickname="alisher.eth"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
<Author
|
||||
{...args}
|
||||
status="verified"
|
||||
size={15}
|
||||
nickname="alisher.eth"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
<Author
|
||||
{...args}
|
||||
status="untrustworthy"
|
||||
size={15}
|
||||
nickname="alisher.eth"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
<Author
|
||||
{...args}
|
||||
status="contact"
|
||||
size={15}
|
||||
nickname="alisher.eth"
|
||||
address="zQ3...9d4Gs0"
|
||||
time="09:30"
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
),
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
|
|
@ -7,35 +7,51 @@ import { XStack } from 'tamagui'
|
|||
|
||||
import { Text } from '../text'
|
||||
|
||||
interface Props {
|
||||
import type { TextProps } from '../text'
|
||||
|
||||
type Props = {
|
||||
name: string
|
||||
size?: Extract<TextProps['size'], 13 | 15>
|
||||
nickname?: string
|
||||
address?: string
|
||||
status?: 'verified' | 'untrustworthy' | 'contact'
|
||||
address?: string
|
||||
time?: string
|
||||
orientation?: 'horizontal' | 'vertical'
|
||||
}
|
||||
|
||||
const Author = (props: Props) => {
|
||||
const { name, status, address, time } = props
|
||||
const { name, size = 13, nickname, status, address, time } = props
|
||||
|
||||
return (
|
||||
<XStack space={8} alignItems="center">
|
||||
<XStack space={4} alignItems="center">
|
||||
<Text size={13} weight="semibold">
|
||||
<XStack gap={4} alignItems="center">
|
||||
<Text size={size} weight="semibold">
|
||||
{name}
|
||||
</Text>
|
||||
|
||||
{nickname && (
|
||||
<Text size={11} color="$neutral-60">
|
||||
· {nickname}
|
||||
</Text>
|
||||
)}
|
||||
{status === 'contact' && <ContactIcon />}
|
||||
{status === 'verified' && <VerifiedIcon />}
|
||||
{status === 'untrustworthy' && <UntrustworthyIcon />}
|
||||
</XStack>
|
||||
|
||||
{(address || time) && (
|
||||
<XStack gap={4} alignItems="center">
|
||||
{address && (
|
||||
<Text size={11} color="$neutral-50">
|
||||
<Text size={11} color="$neutral-50" type="monospace">
|
||||
{address}
|
||||
{time && ` · ${time}`}
|
||||
</Text>
|
||||
)}
|
||||
{time && (
|
||||
<Text size={11} color="$neutral-50">
|
||||
· {time}
|
||||
</Text>
|
||||
)}
|
||||
</XStack>
|
||||
)}
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import { LockedIcon, UnlockedIcon } from '@status-im/icons/12'
|
||||
import { type ColorTokens, Stack, styled, Text } from '@tamagui/core'
|
||||
|
||||
type Props = {
|
||||
emoji: string
|
||||
color?: ColorTokens
|
||||
background?: ColorTokens
|
||||
size: 32 | 24 | 20
|
||||
lock?: 'locked' | 'unlocked' | 'none'
|
||||
}
|
||||
|
||||
const emojiSizes: Record<Props['size'], number> = {
|
||||
32: 14,
|
||||
24: 13,
|
||||
20: 11,
|
||||
}
|
||||
|
||||
// https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=399-20709&t=kX5LC5OYFnSF8BiZ-11
|
||||
const ChannelAvatar = (props: Props) => {
|
||||
const { emoji, background = '$blue-50-opa-20', size, lock = 'none' } = props
|
||||
|
||||
return (
|
||||
<Base size={size} backgroundColor={background}>
|
||||
{lock !== 'none' && (
|
||||
<LockBase variant={size}>
|
||||
{lock === 'locked' ? <LockedIcon /> : <UnlockedIcon />}
|
||||
</LockBase>
|
||||
)}
|
||||
|
||||
<Text fontSize={emojiSizes[size]}>{emoji}</Text>
|
||||
</Base>
|
||||
)
|
||||
}
|
||||
|
||||
export { ChannelAvatar }
|
||||
export type { Props as ChannelAvatarProps }
|
||||
|
||||
const Base = styled(Stack, {
|
||||
position: 'relative',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
|
||||
variants: {
|
||||
size: {
|
||||
'...': (size: number) => {
|
||||
return {
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const LockBase = styled(Stack, {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 16,
|
||||
height: 16,
|
||||
backgroundColor: '$white-100',
|
||||
position: 'absolute',
|
||||
borderRadius: 16,
|
||||
|
||||
variants: {
|
||||
variant: {
|
||||
32: {
|
||||
right: -4,
|
||||
bottom: -4,
|
||||
},
|
||||
24: {
|
||||
right: -4,
|
||||
bottom: -4,
|
||||
},
|
||||
20: {
|
||||
right: -6,
|
||||
bottom: -6,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
|
@ -1,2 +1,3 @@
|
|||
export * from './avatar'
|
||||
export * from './channel-avatar'
|
||||
export * from './icon-avatar'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { styled } from '@tamagui/core'
|
||||
import { Stack, styled } from '@tamagui/core'
|
||||
import { View } from 'react-native'
|
||||
|
||||
import { Counter } from '../counter'
|
||||
|
@ -15,7 +15,7 @@ type Props = {
|
|||
|
||||
const Banner = (props: Props) => {
|
||||
const {
|
||||
icon,
|
||||
icon = null,
|
||||
children,
|
||||
count,
|
||||
backgroundColor = '$primary-50-opa-20',
|
||||
|
@ -25,9 +25,11 @@ const Banner = (props: Props) => {
|
|||
<Base backgroundColor={backgroundColor}>
|
||||
<Content>
|
||||
{icon}
|
||||
<Text size={13} color="$textPrimary">
|
||||
<Stack flexGrow={1} flexShrink={1}>
|
||||
<Text size={13} color="$textPrimary" truncate>
|
||||
{children}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Content>
|
||||
{count ? <Counter value={count} /> : null}
|
||||
</Base>
|
||||
|
@ -43,10 +45,12 @@ const Base = styled(View, {
|
|||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
maxHeight: '40px',
|
||||
gap: 10,
|
||||
})
|
||||
|
||||
const Content = styled(View, {
|
||||
flexDirection: 'row',
|
||||
gap: 10,
|
||||
alignItems: 'center',
|
||||
width: '90%', // truncate does not work without this ¯\_(ツ)_/¯
|
||||
})
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import { Stack } from '@tamagui/core'
|
||||
|
||||
import { Channel } from './channel'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||
const meta: Meta<typeof Channel> = {
|
||||
component: Channel,
|
||||
args: {
|
||||
emoji: '🍑',
|
||||
children: 'channel',
|
||||
},
|
||||
|
||||
argTypes: {},
|
||||
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'figma',
|
||||
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=411-18564&t=kX5LC5OYFnSF8BiZ-11',
|
||||
},
|
||||
},
|
||||
|
||||
render: args => (
|
||||
<Stack space flexDirection="row">
|
||||
<Stack space width={336}>
|
||||
<Channel {...args} type="default" />
|
||||
<Channel {...args} type="default" selected />
|
||||
|
||||
<Channel {...args} type="notification" />
|
||||
<Channel {...args} type="notification" selected />
|
||||
|
||||
<Channel {...args} type="mention" mentionCount={10} />
|
||||
<Channel {...args} type="mention" mentionCount={10} selected />
|
||||
|
||||
<Channel {...args} type="muted" />
|
||||
<Channel {...args} type="muted" selected />
|
||||
</Stack>
|
||||
</Stack>
|
||||
),
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof Channel>
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
lock: 'none',
|
||||
},
|
||||
}
|
||||
|
||||
export const Locked: Story = {
|
||||
args: {
|
||||
lock: 'locked',
|
||||
},
|
||||
}
|
||||
|
||||
export const Unlocked: Story = {
|
||||
args: {
|
||||
lock: 'unlocked',
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
|
@ -0,0 +1,130 @@
|
|||
import { useState } from 'react'
|
||||
|
||||
import { MutedIcon, NotificationIcon, OptionsIcon } from '@status-im/icons/20'
|
||||
import { Stack, styled } from 'tamagui'
|
||||
|
||||
import { ChannelAvatar } from '../avatar'
|
||||
import { Counter } from '../counter'
|
||||
import { DropdownMenu } from '../dropdown-menu'
|
||||
import { Text } from '../text'
|
||||
|
||||
import type { ChannelAvatarProps } from '../avatar'
|
||||
import type { ColorTokens } from 'tamagui'
|
||||
|
||||
type Props = {
|
||||
children: string
|
||||
selected: boolean
|
||||
emoji: ChannelAvatarProps['emoji']
|
||||
lock?: ChannelAvatarProps['lock']
|
||||
mentionCount?: number
|
||||
} & (
|
||||
| {
|
||||
type: 'default' | 'notification' | 'muted'
|
||||
}
|
||||
| {
|
||||
type: 'mention'
|
||||
mentionCount: number
|
||||
}
|
||||
)
|
||||
|
||||
const textColors: Record<Props['type'], ColorTokens> = {
|
||||
default: '$neutral-50',
|
||||
notification: '$neutral-100',
|
||||
mention: '$neutral-100',
|
||||
muted: '$neutral-40',
|
||||
}
|
||||
|
||||
const Channel = (props: Props) => {
|
||||
const { type, children, selected, emoji, lock } = props
|
||||
|
||||
const [hovered, setHovered] = useState(false)
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
|
||||
const active = hovered || menuOpen
|
||||
|
||||
const renderContent = () => {
|
||||
if (active) {
|
||||
return (
|
||||
<DropdownMenu onOpenChange={setMenuOpen}>
|
||||
<Stack tag="button" width={20} height={20}>
|
||||
<OptionsIcon color="$neutral-50" />
|
||||
</Stack>
|
||||
|
||||
{/* TODO: Find all options */}
|
||||
<DropdownMenu.Content align="start">
|
||||
<DropdownMenu.Item
|
||||
icon={<MutedIcon />}
|
||||
label="Mute channel"
|
||||
onSelect={() => {
|
||||
console.log('Mute channel')
|
||||
}}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
icon={<MutedIcon />}
|
||||
label="Mark as read"
|
||||
onSelect={() => {
|
||||
console.log('Mark as read')
|
||||
}}
|
||||
/>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'default':
|
||||
return null
|
||||
case 'mention': {
|
||||
const { mentionCount } = props
|
||||
return <Counter value={mentionCount} />
|
||||
}
|
||||
case 'notification':
|
||||
return <NotificationIcon color="$neutral-40" />
|
||||
case 'muted':
|
||||
return <MutedIcon color="$neutral-40" />
|
||||
}
|
||||
}
|
||||
|
||||
const textColor: ColorTokens =
|
||||
selected || active ? '$neutral-100' : textColors[type]
|
||||
|
||||
return (
|
||||
<Base
|
||||
onHoverIn={() => setHovered(true)}
|
||||
onHoverOut={() => setHovered(false)}
|
||||
state={active ? 'active' : selected ? 'selected' : undefined}
|
||||
>
|
||||
<Stack flexDirection="row" gap={8} alignItems="center">
|
||||
<ChannelAvatar emoji={emoji} size={24} lock={lock} />
|
||||
<Text size={15} weight="medium" color={textColor}>
|
||||
# {children}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
{renderContent()}
|
||||
</Base>
|
||||
)
|
||||
}
|
||||
|
||||
export { Channel }
|
||||
export type { Props as ChannelProps }
|
||||
|
||||
const Base = styled(Stack, {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: 8,
|
||||
borderRadius: 12,
|
||||
userSelect: 'none',
|
||||
|
||||
variants: {
|
||||
state: {
|
||||
active: {
|
||||
backgroundColor: '$primary-50-opa-5',
|
||||
},
|
||||
selected: {
|
||||
backgroundColor: '$primary-50-opa-10',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
|
@ -0,0 +1 @@
|
|||
export { Channel, type ChannelProps } from './channel'
|
|
@ -0,0 +1,4 @@
|
|||
export { CHANNEL_GROUPS } from './mock-data'
|
||||
export { SidebarCommunity } from './sidebar-community'
|
||||
export { SidebarMembers } from './sidebar-members'
|
||||
export { Topbar } from './topbar'
|
|
@ -1,17 +1,17 @@
|
|||
export interface Channel {
|
||||
export interface ChannelType {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
emoji: string
|
||||
channelStatus?: 'muted' | 'normal' | 'withMessages' | 'withMentions'
|
||||
unreadCount?: number
|
||||
channelStatus?: 'default' | 'notification' | 'muted'
|
||||
mentionCount?: number
|
||||
}
|
||||
|
||||
export interface ChannelGroup {
|
||||
export interface ChannelGroupType {
|
||||
id: string
|
||||
title: string
|
||||
unreadCount?: number
|
||||
channels: Channel[]
|
||||
channels: ChannelType[]
|
||||
}
|
||||
|
||||
const emojis = ['👋', '🔥', '🦄', '🍑', '🤫', '🫣', '🏀', '🤝']
|
||||
|
@ -19,7 +19,7 @@ const emojis = ['👋', '🔥', '🦄', '🍑', '🤫', '🫣', '🏀', '🤝']
|
|||
const randomEmoji = () => emojis[Math.floor(Math.random() * emojis.length)]
|
||||
|
||||
// MOCK DATA
|
||||
export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||
export const CHANNEL_GROUPS: ChannelGroupType[] = [
|
||||
{
|
||||
id: 'welcome',
|
||||
title: 'Welcome',
|
||||
|
@ -27,29 +27,28 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
|||
channels: [
|
||||
{
|
||||
id: 'welcome',
|
||||
title: '# welcome',
|
||||
title: 'welcome',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'general-welcome',
|
||||
title: '# general',
|
||||
title: 'general',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'random',
|
||||
title: '# random',
|
||||
title: 'random',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'onboarding',
|
||||
title: '# onboarding',
|
||||
title: 'onboarding',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
channelStatus: 'withMentions',
|
||||
unreadCount: 3,
|
||||
mentionCount: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -60,29 +59,27 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
|||
channels: [
|
||||
{
|
||||
id: 'announcements',
|
||||
title: '# announcements',
|
||||
title: 'announcements',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'jobs',
|
||||
title: '# jobs',
|
||||
channelStatus: 'withMentions',
|
||||
unreadCount: 3,
|
||||
title: 'jobs',
|
||||
mentionCount: 3,
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'events',
|
||||
title: '# events',
|
||||
channelStatus: 'withMentions',
|
||||
unreadCount: 2,
|
||||
title: 'events',
|
||||
mentionCount: 2,
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'meetups',
|
||||
title: '# meetups',
|
||||
title: 'meetups',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
|
@ -94,25 +91,25 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
|||
channels: [
|
||||
{
|
||||
id: 'design',
|
||||
title: '# design',
|
||||
title: 'design',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'ux',
|
||||
title: '# ux',
|
||||
title: 'ux',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'ui',
|
||||
title: '# ui',
|
||||
title: 'ui',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'figma',
|
||||
title: '# figma',
|
||||
title: 'figma',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
|
@ -124,13 +121,13 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
|||
channels: [
|
||||
{
|
||||
id: 'general',
|
||||
title: '# general',
|
||||
title: 'general',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'people-ops',
|
||||
title: '# people-ops',
|
||||
title: 'people-ops',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
|
@ -142,27 +139,27 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
|||
channels: [
|
||||
{
|
||||
id: 'react',
|
||||
title: '# react',
|
||||
title: 'react',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
channelStatus: 'withMessages',
|
||||
channelStatus: 'notification',
|
||||
},
|
||||
{
|
||||
id: 'vue',
|
||||
title: '# vue',
|
||||
title: 'vue',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'angular',
|
||||
title: '# angular',
|
||||
title: 'angular',
|
||||
channelStatus: 'muted',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'svelte',
|
||||
title: '# svelte',
|
||||
title: 'svelte',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
|
@ -174,25 +171,25 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
|||
channels: [
|
||||
{
|
||||
id: 'node',
|
||||
title: '# node',
|
||||
title: 'node',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'python',
|
||||
title: '# python',
|
||||
title: 'python',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'ruby',
|
||||
title: '# ruby',
|
||||
title: 'ruby',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
||||
},
|
||||
{
|
||||
id: 'php',
|
||||
title: '# php',
|
||||
title: 'php',
|
||||
channelStatus: 'muted',
|
||||
emoji: randomEmoji(),
|
||||
description: 'Share random funny stuff with the community. Play nice.',
|
|
@ -0,0 +1,74 @@
|
|||
import * as Accordion from '@radix-ui/react-accordion'
|
||||
import { Stack } from 'tamagui'
|
||||
|
||||
import { Channel } from '../../../channel'
|
||||
import { DividerLabel } from '../../../dividers'
|
||||
|
||||
import type { ChannelType } from '../../mock-data'
|
||||
|
||||
type Props = {
|
||||
name: string
|
||||
channels: ChannelType[]
|
||||
unreadCount?: number
|
||||
selectedChannelId?: string
|
||||
expanded: boolean
|
||||
}
|
||||
|
||||
const ChannelGroup = (props: Props) => {
|
||||
const { name, channels, selectedChannelId, expanded } = props
|
||||
|
||||
const totalMentionsCount = channels.reduce(
|
||||
(acc, channel) => acc + (channel.mentionCount || 0),
|
||||
0
|
||||
)
|
||||
|
||||
return (
|
||||
<Accordion.Item value={name}>
|
||||
<Stack>
|
||||
<Accordion.Trigger>
|
||||
<DividerLabel
|
||||
type="expandable"
|
||||
expanded={expanded}
|
||||
label={name}
|
||||
counterType="default"
|
||||
count={
|
||||
totalMentionsCount > 0 && expanded === false
|
||||
? totalMentionsCount
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Accordion.Trigger>
|
||||
|
||||
<Stack paddingHorizontal={8} paddingBottom={expanded ? 8 : 0}>
|
||||
{channels.map(channel => {
|
||||
const {
|
||||
emoji,
|
||||
title,
|
||||
//This will work differently with the live data
|
||||
channelStatus: type = 'default',
|
||||
mentionCount = 0,
|
||||
} = channel
|
||||
|
||||
const selected = selectedChannelId === channel.id
|
||||
|
||||
return (
|
||||
<Accordion.Content key={channel.title}>
|
||||
<Channel
|
||||
emoji={emoji}
|
||||
selected={!!selected}
|
||||
{...(mentionCount > 0
|
||||
? { type: 'mention', mentionCount }
|
||||
: { type })}
|
||||
>
|
||||
{title}
|
||||
</Channel>
|
||||
</Accordion.Content>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Accordion.Item>
|
||||
)
|
||||
}
|
||||
|
||||
export { ChannelGroup }
|
|
@ -0,0 +1 @@
|
|||
export { SidebarCommunity } from './sidebar-community'
|
|
@ -0,0 +1,47 @@
|
|||
import { CHANNEL_GROUPS } from '../mock-data'
|
||||
import { SidebarCommunity } from './sidebar-community'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||
const meta: Meta<typeof SidebarCommunity> = {
|
||||
title: 'Community/Community Sidebar',
|
||||
component: SidebarCommunity,
|
||||
args: {
|
||||
community: {
|
||||
name: 'Rarible',
|
||||
description:
|
||||
'Multichain community-centric NFT marketplace. Create, buy and sell your NFTs.',
|
||||
membersCount: 123,
|
||||
imageSrc:
|
||||
'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2264&q=80',
|
||||
},
|
||||
channels: CHANNEL_GROUPS,
|
||||
},
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'figma',
|
||||
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Web?node-id=14692%3A148489&t=NfQkS7CPSrZknAGF-4',
|
||||
},
|
||||
},
|
||||
render: args => (
|
||||
<div style={{ maxWidth: 360 }}>
|
||||
<SidebarCommunity {...args} />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof SidebarCommunity>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
loading:true
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
|
@ -1,42 +1,47 @@
|
|||
import { useState } from 'react'
|
||||
|
||||
import * as Accordion from '@radix-ui/react-accordion'
|
||||
import { GroupIcon } from '@status-im/icons/16'
|
||||
import { CommunitiesIcon } from '@status-im/icons/20'
|
||||
import { Stack } from '@tamagui/core'
|
||||
|
||||
import { Accordion } from '../accordion/accordion'
|
||||
import { AccordionItem } from '../accordion/accordionItem'
|
||||
import { Avatar } from '../avatar'
|
||||
import { Button } from '../button'
|
||||
import { Image } from '../image'
|
||||
import { SidebarSkeleton } from '../skeleton/sidebar-skeleton'
|
||||
import { Text } from '../text'
|
||||
import { CHANNEL_GROUPS } from './mock-data'
|
||||
import { Avatar } from '../../avatar'
|
||||
import { Button } from '../../button'
|
||||
import { Image } from '../../image'
|
||||
import { SidebarSkeleton } from '../../skeleton/sidebar-skeleton'
|
||||
import { Text } from '../../text'
|
||||
import { CHANNEL_GROUPS } from '../mock-data'
|
||||
import { ChannelGroup } from './components/channel-group'
|
||||
|
||||
import type { ChannelGroup } from './mock-data'
|
||||
import type { ChannelGroupType } from '../mock-data'
|
||||
|
||||
export type SidebarProps = {
|
||||
type Props = {
|
||||
community: {
|
||||
name: string
|
||||
description: string
|
||||
membersCount: number
|
||||
imageUrl: string
|
||||
imageSrc: string
|
||||
}
|
||||
channels?: ChannelGroup[]
|
||||
channels?: ChannelGroupType[]
|
||||
selectedChannelId?: string
|
||||
onChannelPress: (channelId: string) => void
|
||||
isLoading?: boolean
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const Sidebar = (props: SidebarProps) => {
|
||||
const SidebarCommunity = (props: Props) => {
|
||||
const {
|
||||
community,
|
||||
channels = CHANNEL_GROUPS,
|
||||
selectedChannelId,
|
||||
onChannelPress,
|
||||
isLoading,
|
||||
loading,
|
||||
// onChannelPress,
|
||||
} = props
|
||||
|
||||
const { name, description, membersCount, imageUrl } = community
|
||||
const { name, description, membersCount, imageSrc } = community
|
||||
|
||||
if (isLoading) {
|
||||
const [value, setValue] = useState(['Welcome'])
|
||||
|
||||
if (loading) {
|
||||
return <SidebarSkeleton />
|
||||
}
|
||||
|
||||
|
@ -48,7 +53,7 @@ const Sidebar = (props: SidebarProps) => {
|
|||
height="100%"
|
||||
overflow="scroll"
|
||||
>
|
||||
<Image src={imageUrl} width="full" aspectRatio={2.6} />
|
||||
<Image src={imageSrc} width="full" aspectRatio={2.6} />
|
||||
<Stack
|
||||
paddingBottom={16}
|
||||
marginTop={-16}
|
||||
|
@ -76,31 +81,26 @@ const Sidebar = (props: SidebarProps) => {
|
|||
<Text size={15}>{membersCount}</Text>
|
||||
</Stack>
|
||||
|
||||
<Button>Join community</Button>
|
||||
<Button icon={<CommunitiesIcon />}>Request to join community</Button>
|
||||
</Stack>
|
||||
{channels.map(group => (
|
||||
<Accordion
|
||||
key={group.id}
|
||||
initialExpanded={group.id === 'welcome'}
|
||||
title={group.title}
|
||||
unreadCount={group.unreadCount}
|
||||
>
|
||||
{group.channels.map(channel => {
|
||||
|
||||
<Accordion.Root type="multiple" value={value} onValueChange={setValue}>
|
||||
{channels.map(group => {
|
||||
return (
|
||||
<AccordionItem
|
||||
key={channel.id}
|
||||
channel={channel}
|
||||
selected={selectedChannelId === channel.id}
|
||||
onPress={() => onChannelPress(channel.id)}
|
||||
<ChannelGroup
|
||||
key={group.id}
|
||||
name={group.title}
|
||||
unreadCount={group.unreadCount}
|
||||
channels={group.channels}
|
||||
expanded={value.includes(group.title)}
|
||||
selectedChannelId={selectedChannelId}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Accordion>
|
||||
))}
|
||||
<Stack borderBottomColor="$neutral-10" borderBottomWidth={1} />
|
||||
</Accordion.Root>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export { Sidebar }
|
||||
export { SidebarCommunity }
|
|
@ -4,9 +4,15 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||
const meta: Meta<typeof SidebarMembers> = {
|
||||
title: 'Sidebar/Members',
|
||||
title: 'Community/Members Sidebar',
|
||||
component: SidebarMembers,
|
||||
args: {},
|
||||
argTypes: {},
|
||||
render: () => (
|
||||
<div style={{ maxWidth: 360 }}>
|
||||
<SidebarMembers />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof SidebarMembers>
|
|
@ -1,32 +1,18 @@
|
|||
import { Stack } from '@tamagui/core'
|
||||
|
||||
import { DividerLabel } from '../dividers'
|
||||
import { UserList } from '../user-list'
|
||||
import { DividerLabel } from '../../dividers'
|
||||
import { UserList } from '../../user-list'
|
||||
|
||||
import type { UserListProps } from '../user-list'
|
||||
import type { UserListProps } from '../../user-list'
|
||||
|
||||
type GroupProps = {
|
||||
label: string
|
||||
users: UserListProps['users']
|
||||
}
|
||||
|
||||
const Group = (props: GroupProps) => {
|
||||
const { label, users } = props
|
||||
|
||||
return (
|
||||
<Stack paddingBottom={8}>
|
||||
<DividerLabel label={label} tight={false} />
|
||||
<Stack paddingHorizontal={8}>
|
||||
<UserList users={users} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
// type Props = {
|
||||
// users: []
|
||||
// }
|
||||
|
||||
const SidebarMembers = () => {
|
||||
return (
|
||||
<Stack
|
||||
backgroundColor="$background"
|
||||
backgroundColor="$white-100"
|
||||
borderLeftWidth={1}
|
||||
borderColor="$neutral-10"
|
||||
overflow="scroll"
|
||||
|
@ -51,9 +37,11 @@ const SidebarMembers = () => {
|
|||
src: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80',
|
||||
address: 'zQ3...9d4Gs0',
|
||||
indicator: 'online',
|
||||
status: 'untrustworthy',
|
||||
},
|
||||
{
|
||||
name: 'Pedro',
|
||||
nickname: 'pedro',
|
||||
src: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80',
|
||||
address: 'zQ3...9d4Gs0',
|
||||
indicator: 'online',
|
||||
|
@ -63,12 +51,14 @@ const SidebarMembers = () => {
|
|||
src: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80',
|
||||
address: 'zQ3...9d4Gs0',
|
||||
indicator: 'online',
|
||||
status: 'contact',
|
||||
},
|
||||
{
|
||||
name: 'Pedro',
|
||||
src: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80',
|
||||
address: 'zQ3...9d4Gs0',
|
||||
indicator: 'online',
|
||||
status: 'verified',
|
||||
},
|
||||
{
|
||||
name: 'Pedro',
|
||||
|
@ -92,6 +82,7 @@ const SidebarMembers = () => {
|
|||
src: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80',
|
||||
address: 'zQ3...9d4Gs0',
|
||||
indicator: 'offline',
|
||||
status: 'verified',
|
||||
},
|
||||
{
|
||||
name: 'Pedro',
|
||||
|
@ -110,6 +101,7 @@ const SidebarMembers = () => {
|
|||
src: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80',
|
||||
address: 'zQ3...9d4Gs0',
|
||||
indicator: 'offline',
|
||||
status: 'verified',
|
||||
},
|
||||
{
|
||||
name: 'Pedro',
|
||||
|
@ -122,6 +114,7 @@ const SidebarMembers = () => {
|
|||
src: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80',
|
||||
address: 'zQ3...9d4Gs0',
|
||||
indicator: 'offline',
|
||||
status: 'contact',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
@ -130,3 +123,21 @@ const SidebarMembers = () => {
|
|||
}
|
||||
|
||||
export { SidebarMembers }
|
||||
|
||||
type GroupProps = {
|
||||
label: string
|
||||
users: UserListProps['users']
|
||||
}
|
||||
|
||||
const Group = (props: GroupProps) => {
|
||||
const { label, users } = props
|
||||
|
||||
return (
|
||||
<Stack paddingBottom={8}>
|
||||
<DividerLabel label={label} tight={false} />
|
||||
<Stack paddingHorizontal={8}>
|
||||
<UserList users={users} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
|
@ -6,13 +6,13 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||
const meta: Meta<typeof Topbar> = {
|
||||
title: 'Navigation/Topbar',
|
||||
title: 'Community/Topbar',
|
||||
component: Topbar,
|
||||
args: {
|
||||
channel: {
|
||||
id: '1',
|
||||
emoji: '👋',
|
||||
title: '# channel',
|
||||
title: 'channel',
|
||||
description: 'This is a channel description',
|
||||
},
|
||||
},
|
||||
|
@ -35,16 +35,34 @@ export const Default: Story = {
|
|||
args: {},
|
||||
}
|
||||
|
||||
export const isLoading: Story = {
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
isLoading: true,
|
||||
loading: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const WithPinnedMessages: Story = {
|
||||
args: {
|
||||
pinnedMessages: [
|
||||
{
|
||||
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',
|
||||
},
|
||||
],
|
||||
showMembers: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const WithMembersSelected: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
showMembers: true,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
import {
|
||||
ArrowLeftIcon,
|
||||
CommunitiesIcon,
|
||||
DeleteIcon,
|
||||
DownloadIcon,
|
||||
LockedIcon,
|
||||
MembersIcon,
|
||||
MutedIcon,
|
||||
OptionsIcon,
|
||||
ShareIcon,
|
||||
UpToDateIcon,
|
||||
} from '@status-im/icons/20'
|
||||
import { Stack, Text as RNText } from '@tamagui/core'
|
||||
import { BlurView } from 'expo-blur'
|
||||
|
||||
import { DropdownMenu } from '../../dropdown-menu'
|
||||
import { IconButton } from '../../icon-button'
|
||||
import { PinnedMessage } from '../../pinned-message'
|
||||
import { TopbarSkeleton } from '../../skeleton/topbar-skeleton'
|
||||
import { Text } from '../../text'
|
||||
|
||||
import type { MessageProps } from '../../messages'
|
||||
import type { ChannelType } from '../mock-data'
|
||||
|
||||
type Props = {
|
||||
showMembers: boolean
|
||||
onMembersPress: () => void
|
||||
goBack?: () => void
|
||||
channel: ChannelType
|
||||
blur?: boolean
|
||||
pinnedMessages?: MessageProps[]
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const Topbar = (props: Props) => {
|
||||
const {
|
||||
showMembers,
|
||||
onMembersPress,
|
||||
goBack,
|
||||
blur,
|
||||
channel,
|
||||
pinnedMessages,
|
||||
loading,
|
||||
} = props
|
||||
|
||||
if (loading) {
|
||||
return <TopbarSkeleton />
|
||||
}
|
||||
|
||||
const { title, description, emoji } = channel
|
||||
|
||||
return (
|
||||
<BlurView intensity={40} style={{ zIndex: 100 }}>
|
||||
<Stack
|
||||
flexDirection="row"
|
||||
height={56}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
padding={16}
|
||||
backgroundColor={'$blurBackground'}
|
||||
borderBottomWidth={1}
|
||||
borderColor={blur ? 'transparent' : '$neutral-80-opa-10'}
|
||||
>
|
||||
<Stack flexDirection="row" alignItems="center" flexWrap="wrap">
|
||||
<Stack marginRight={12} $gtSm={{ display: 'none' }}>
|
||||
<IconButton
|
||||
icon={<ArrowLeftIcon />}
|
||||
onPress={() => goBack?.()}
|
||||
blur={blur}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack marginRight={12}>
|
||||
<RNText>{emoji}</RNText>
|
||||
</Stack>
|
||||
<Text size={15} weight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
<Stack marginLeft={4}>
|
||||
<LockedIcon color="$neutral-80-opa-40" />
|
||||
</Stack>
|
||||
<Stack
|
||||
backgroundColor="$neutral-80-opa-10"
|
||||
marginHorizontal={12}
|
||||
height={16}
|
||||
width={1}
|
||||
$sm={{ display: 'none' }}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack flexGrow={1} flexShrink={1} $sm={{ display: 'none' }}>
|
||||
<Text size={13} weight="medium" color="$neutral-80-opa-50" truncate>
|
||||
{description}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
justifyContent="flex-end"
|
||||
gap={12}
|
||||
>
|
||||
<Stack $sm={{ display: 'none' }}>
|
||||
<IconButton
|
||||
icon={<MembersIcon />}
|
||||
selected={showMembers}
|
||||
onPress={onMembersPress}
|
||||
blur={blur}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<DropdownMenu>
|
||||
<IconButton icon={<OptionsIcon />} />
|
||||
|
||||
<DropdownMenu.Content align="end" sideOffset={4}>
|
||||
<DropdownMenu.Item
|
||||
icon={<CommunitiesIcon />}
|
||||
label="View channel details"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
icon={<MutedIcon />}
|
||||
label="Mute channel"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
icon={<UpToDateIcon />}
|
||||
label="Mark as read"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
icon={<DownloadIcon />}
|
||||
label="Fetch messages"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
icon={<ShareIcon />}
|
||||
label="Share link to the channel"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
<DropdownMenu.Item
|
||||
icon={<DeleteIcon />}
|
||||
label="Clear history"
|
||||
onSelect={() => console.log('click')}
|
||||
danger
|
||||
/>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{pinnedMessages && pinnedMessages.length > 0 && (
|
||||
<PinnedMessage messages={pinnedMessages} />
|
||||
)}
|
||||
</BlurView>
|
||||
)
|
||||
}
|
||||
|
||||
export { Topbar }
|
|
@ -5,6 +5,7 @@ import { Counter } from './counter'
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
const meta: Meta<typeof Counter> = {
|
||||
title: 'Components/Counter',
|
||||
component: Counter,
|
||||
argTypes: {
|
||||
value: {
|
||||
|
|
|
@ -1,26 +1,59 @@
|
|||
import { ChevronRightIcon } from '@status-im/icons/20'
|
||||
import { Stack } from '@tamagui/core'
|
||||
|
||||
import { Counter } from '../../counter'
|
||||
import { Text } from '../../text'
|
||||
import { DividerLine } from '../divider-line'
|
||||
|
||||
import type { CounterProps } from '../../counter'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
tight?: boolean
|
||||
count?: number
|
||||
count?: CounterProps['value']
|
||||
counterType?: CounterProps['type']
|
||||
} & (
|
||||
| {
|
||||
type?: 'default'
|
||||
}
|
||||
| {
|
||||
type?: 'expandable'
|
||||
expanded: boolean
|
||||
// ?chevronPosition?: 'left' | 'right'
|
||||
}
|
||||
)
|
||||
|
||||
// TODO: Add counter after PR #355 lands
|
||||
const DividerLabel = (props: Props) => {
|
||||
const { label, tight = true } = props
|
||||
const { label, tight = true, counterType = 'secondary', count } = props
|
||||
|
||||
return (
|
||||
<Stack paddingBottom={8} gap={tight ? 8 : 16}>
|
||||
<DividerLine />
|
||||
<Stack paddingHorizontal={16}>
|
||||
<Stack
|
||||
paddingHorizontal={16}
|
||||
flexDirection="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Stack flexDirection="row" alignItems="center" gap={2}>
|
||||
{props.type === 'expandable' && (
|
||||
<Stack
|
||||
marginLeft={-2}
|
||||
transform={[
|
||||
{
|
||||
rotate: props.expanded ? '90deg' : '0deg',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ChevronRightIcon color="$neutral-50" />
|
||||
</Stack>
|
||||
)}
|
||||
<Text size={13} color="$neutral-50" weight="medium">
|
||||
{label}
|
||||
</Text>
|
||||
</Stack>
|
||||
{count && count > 0 && <Counter type={counterType} value={count} />}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -106,4 +106,4 @@ DropdownMenu.Item = MenuItem
|
|||
DropdownMenu.Separator = Separator
|
||||
|
||||
export { DropdownMenu }
|
||||
export type { Props as DropdownMenuProps }
|
||||
export type DropdownMenuProps = Omit<Props, 'children'>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export * from './anchor-actions'
|
||||
export * from './avatar'
|
||||
export * from './button'
|
||||
export * from './community'
|
||||
export * from './composer'
|
||||
export * from './dividers'
|
||||
export * from './dynamic-button'
|
||||
|
@ -11,16 +13,10 @@ export * from './input'
|
|||
export * from './messages'
|
||||
export * from './pinned-message'
|
||||
export * from './provider'
|
||||
export * from './sidebar'
|
||||
export * from './sidebar-members'
|
||||
export * from './skeleton'
|
||||
export * from './text'
|
||||
export * from './toast'
|
||||
export * from './topbar'
|
||||
export * from './user-list'
|
||||
|
||||
// MOCK DATA
|
||||
export { CHANNEL_GROUPS } from './sidebar/mock-data'
|
||||
|
||||
// eslint-disable-next-line simple-import-sort/exports
|
||||
export { config } from './tamagui.config'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Stack } from '@tamagui/core'
|
||||
|
||||
import { DividerDate, DividerNewMessages } from '../dividers'
|
||||
import { MessageSkeleton } from '../skeleton'
|
||||
import { PinAnnouncement } from '../system-messages'
|
||||
import { Message } from './message'
|
||||
|
||||
|
@ -14,7 +15,29 @@ const reactions: ReactionsType = {
|
|||
'thumbs-down': new Set(['me', '1', '2', '3']),
|
||||
}
|
||||
|
||||
export const Messages = () => {
|
||||
type Props = {
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export const Messages = (props: Props) => {
|
||||
const { loading } = props
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<>
|
||||
<MessageSkeleton size="large" />
|
||||
<MessageSkeleton size="small" />
|
||||
<MessageSkeleton size="medium" />
|
||||
<MessageSkeleton size="large" />
|
||||
<MessageSkeleton size="medium" />
|
||||
<MessageSkeleton size="small" />
|
||||
<MessageSkeleton size="large" />
|
||||
<MessageSkeleton size="small" />
|
||||
<MessageSkeleton size="medium" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Message
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export { Sidebar } from './sidebar'
|
|
@ -1,51 +0,0 @@
|
|||
import { Stack } from '@tamagui/core'
|
||||
|
||||
import { CHANNEL_GROUPS } from './mock-data'
|
||||
import { Sidebar } from './sidebar'
|
||||
|
||||
import type { SidebarProps } from './sidebar'
|
||||
import type { Meta } from '@storybook/react'
|
||||
|
||||
const COMMUNITY = {
|
||||
name: 'Rarible',
|
||||
description:
|
||||
'Multichain community-centric NFT marketplace. Create, buy and sell your NFTs.',
|
||||
membersCount: 123,
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1574786527860-f2e274867c91?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1764&q=80',
|
||||
}
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||
const meta: Meta<typeof Sidebar> = {
|
||||
title: 'Sidebar/Community',
|
||||
component: Sidebar,
|
||||
args: {
|
||||
channels: CHANNEL_GROUPS,
|
||||
community: COMMUNITY,
|
||||
},
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'figma',
|
||||
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Web?node-id=14692%3A148489&t=NfQkS7CPSrZknAGF-4',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const Default = {
|
||||
render: (args: SidebarProps) => (
|
||||
<Stack width={352} height="100vh">
|
||||
<Sidebar {...args} />
|
||||
</Stack>
|
||||
),
|
||||
}
|
||||
|
||||
export const LoadingSidebar = {
|
||||
render: (args: SidebarProps) => (
|
||||
<Stack width={352} height="100vh">
|
||||
<Sidebar {...args} isLoading />
|
||||
</Stack>
|
||||
),
|
||||
}
|
||||
|
||||
export default meta
|
|
@ -33,7 +33,8 @@ export const config = createTamagui({
|
|||
},
|
||||
}),
|
||||
mono: createFont({
|
||||
family: 'UbuntuMono',
|
||||
family:
|
||||
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;',
|
||||
weight: {},
|
||||
letterSpacing: {},
|
||||
size: {},
|
||||
|
|
|
@ -1,181 +0,0 @@
|
|||
import {
|
||||
ArrowLeftIcon,
|
||||
CommunitiesIcon,
|
||||
DeleteIcon,
|
||||
DownloadIcon,
|
||||
LockedIcon,
|
||||
MembersIcon,
|
||||
MutedIcon,
|
||||
OptionsIcon,
|
||||
ShareIcon,
|
||||
UpToDateIcon,
|
||||
} from '@status-im/icons/20'
|
||||
import { Stack, Text as RNText } from '@tamagui/core'
|
||||
import { BlurView } from 'expo-blur'
|
||||
|
||||
import { DropdownMenu } from '../dropdown-menu'
|
||||
import { IconButton } from '../icon-button'
|
||||
import { PinnedMessage } from '../pinned-message'
|
||||
import { TopbarSkeleton } from '../skeleton/topbar-skeleton'
|
||||
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
|
||||
goBack?: () => void
|
||||
channel: Channel
|
||||
blur?: boolean
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
const Topbar = (props: Props) => {
|
||||
const { showMembers, onMembersPress, goBack, blur, channel, isLoading } =
|
||||
props
|
||||
|
||||
const { title, description, emoji } = channel
|
||||
|
||||
if (isLoading) {
|
||||
return <TopbarSkeleton />
|
||||
}
|
||||
|
||||
return (
|
||||
<BlurView intensity={40} style={{ zIndex: 100 }}>
|
||||
<Stack flexDirection="column" width="100%" height={96}>
|
||||
<Stack
|
||||
flexDirection="row"
|
||||
height={56}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
padding={16}
|
||||
backgroundColor={'$blurBackground'}
|
||||
borderBottomWidth={1}
|
||||
borderColor={blur ? 'transparent' : '$neutral-80-opa-10'}
|
||||
width="100%"
|
||||
>
|
||||
<Stack flexDirection="row" alignItems="center" flexWrap="wrap">
|
||||
<Stack mr={12} $gtSm={{ display: 'none' }}>
|
||||
<IconButton
|
||||
icon={<ArrowLeftIcon />}
|
||||
onPress={() => goBack?.()}
|
||||
blur={blur}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{emoji && (
|
||||
<Stack marginRight={12}>
|
||||
<RNText>{emoji}</RNText>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{title && (
|
||||
<Text size={15} weight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<LockedIcon color="$neutral-80-opa-40" />
|
||||
<Stack
|
||||
backgroundColor="$neutral-80-opa-10"
|
||||
marginHorizontal={12}
|
||||
height={16}
|
||||
width={1}
|
||||
$sm={{ display: 'none' }}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
space={12}
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
justifyContent={description ? 'space-between' : 'flex-end'}
|
||||
flexGrow={1}
|
||||
flexShrink={1}
|
||||
$sm={{ justifyContent: 'flex-end' }}
|
||||
>
|
||||
{description && (
|
||||
<Stack flexGrow={1} flexShrink={1} $sm={{ display: 'none' }}>
|
||||
<Text
|
||||
weight="medium"
|
||||
color="$neutral-80-opa-50"
|
||||
size={13}
|
||||
truncate
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
<Stack $sm={{ display: 'none' }}>
|
||||
<IconButton
|
||||
icon={<MembersIcon />}
|
||||
selected={showMembers}
|
||||
onPress={onMembersPress}
|
||||
blur={blur}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<DropdownMenu>
|
||||
<IconButton icon={<OptionsIcon />} />
|
||||
|
||||
<DropdownMenu.Content align="end" sideOffset={4}>
|
||||
<DropdownMenu.Item
|
||||
icon={<CommunitiesIcon />}
|
||||
label="View channel details"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
icon={<MutedIcon />}
|
||||
label="Mute channel"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
icon={<UpToDateIcon />}
|
||||
label="Mark as read"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
icon={<DownloadIcon />}
|
||||
label="Fetch messages"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
<DropdownMenu.Item
|
||||
icon={<ShareIcon />}
|
||||
label="Share link to the channel"
|
||||
onSelect={() => console.log('click')}
|
||||
/>
|
||||
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
<DropdownMenu.Item
|
||||
icon={<DeleteIcon />}
|
||||
label="Clear history"
|
||||
onSelect={() => console.log('click')}
|
||||
danger
|
||||
/>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<PinnedMessage messages={mockMessages} />
|
||||
</Stack>
|
||||
</BlurView>
|
||||
)
|
||||
}
|
||||
|
||||
export { Topbar }
|
|
@ -17,11 +17,13 @@ const UserList = (props: Props) => {
|
|||
return (
|
||||
<YStack>
|
||||
{users.map((user, index) => {
|
||||
const { src, indicator, ...authorProps } = user
|
||||
|
||||
return (
|
||||
<XStack
|
||||
key={user.address! + index}
|
||||
padding={8}
|
||||
space={8}
|
||||
gap={8}
|
||||
borderRadius={12}
|
||||
alignItems="center"
|
||||
cursor="pointer"
|
||||
|
@ -29,15 +31,10 @@ const UserList = (props: Props) => {
|
|||
backgroundColor: '$primary-50-opa-5',
|
||||
}}
|
||||
>
|
||||
<Avatar size={32} src={user.src} indicator={user.indicator} />
|
||||
<Avatar size={32} src={src} indicator={indicator} />
|
||||
<YStack>
|
||||
<Author
|
||||
name={user.name}
|
||||
nickname={user.nickname}
|
||||
status={user.status}
|
||||
orientation="vertical"
|
||||
/>
|
||||
<Text size={13} color="$neutral-50">
|
||||
<Author {...authorProps} />
|
||||
<Text size={13} color="$neutral-50" type="monospace">
|
||||
{user.address}
|
||||
</Text>
|
||||
</YStack>
|
||||
|
|
|
@ -18,7 +18,7 @@ const SvgNotificationIcon = (props: IconProps) => {
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<Circle cx={10} cy={10} r={4} fill="#4360DF" />
|
||||
<Circle cx={10} cy={10} r={4} fill={color} />
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
|
43
yarn.lock
43
yarn.lock
|
@ -3321,6 +3321,22 @@
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-accordion@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-accordion/-/react-accordion-1.1.1.tgz#fa1ab1b5c6a29aa75aefaf306a9e72fe3a482dbc"
|
||||
integrity sha512-TQtyyRubYe8DD6DYCovNLTjd2D+TFrNCpr99T5M3cYUbR7BsRxWsxfInjbQ1nHsdy2uPTcnJS5npyXPVfP0piw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.0"
|
||||
"@radix-ui/react-collapsible" "1.0.2"
|
||||
"@radix-ui/react-collection" "1.0.2"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-direction" "1.0.0"
|
||||
"@radix-ui/react-id" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.2"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.0"
|
||||
|
||||
"@radix-ui/react-arrow@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.2.tgz#93b0ff95f65e2264a05b14ef1031ec798243dd6f"
|
||||
|
@ -3329,6 +3345,21 @@
|
|||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.2"
|
||||
|
||||
"@radix-ui/react-collapsible@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.0.2.tgz#0583470c7caa8cd1ab6f606416288d19b3baf777"
|
||||
integrity sha512-QNiDT6Au8jUU0K1WV+HEd4loH7C5CKQjeXxskwqyiyAkyCmW7qlQM5vSSJCIoQC+OVPyhgafSmGudRP8Qm1/gA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.0"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-id" "1.0.0"
|
||||
"@radix-ui/react-presence" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.2"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-collection@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.2.tgz#d50da00bfa2ac14585319efdbbb081d4c5a29a97"
|
||||
|
@ -6383,7 +6414,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/react-dom@18.0.11", "@types/react-dom@^18.0.11":
|
||||
"@types/react-dom@^18.0.11":
|
||||
version "18.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33"
|
||||
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
|
||||
|
@ -6397,7 +6428,7 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@18.0.28", "@types/react@>=16", "@types/react@^18.0.28":
|
||||
"@types/react@*", "@types/react@>=16", "@types/react@^18.0.28":
|
||||
version "18.0.28"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065"
|
||||
integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==
|
||||
|
@ -9504,10 +9535,10 @@ expo-asset@~8.7.0:
|
|||
path-browserify "^1.0.0"
|
||||
url-parse "^1.5.9"
|
||||
|
||||
expo-blur@~12.0.1:
|
||||
version "12.0.1"
|
||||
resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-12.0.1.tgz#7aa4186620359acfa976dda84360070b634ffe3d"
|
||||
integrity sha512-7oF/xRIFJukM4/qL6ejZ4Z/4YcVExvBPsBrz7rGYz6PtgAkWwYFR62+ExZOzTEG4hgoPPmlnt1ncimsk/MYUgQ==
|
||||
expo-blur@^12.2.2:
|
||||
version "12.2.2"
|
||||
resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-12.2.2.tgz#b7f94499255afbd3468302d02f3c4e39a0e562d5"
|
||||
integrity sha512-SvGbEZbB0VFNGqCW7FcqzWOEb3lrRgBnQKGrsKo49KwhMyHTYjYVYWnmrk9l8Tr7lIaNnd55QD6dPAzcXjZYMg==
|
||||
|
||||
expo-constants@^14.0.2, expo-constants@~14.0.0, expo-constants@~14.0.2:
|
||||
version "14.0.2"
|
||||
|
|
Loading…
Reference in New Issue