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,
|
CHANNEL_GROUPS,
|
||||||
Composer,
|
Composer,
|
||||||
Messages,
|
Messages,
|
||||||
Sidebar,
|
SidebarCommunity,
|
||||||
SidebarMembers,
|
SidebarMembers,
|
||||||
Topbar,
|
Topbar,
|
||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
|
@ -20,8 +20,8 @@ const COMMUNITY = {
|
||||||
description:
|
description:
|
||||||
'Multichain community-centric NFT marketplace. Create, buy and sell your NFTs.',
|
'Multichain community-centric NFT marketplace. Create, buy and sell your NFTs.',
|
||||||
membersCount: 123,
|
membersCount: 123,
|
||||||
imageUrl:
|
imageSrc:
|
||||||
'https://images.unsplash.com/photo-1574786527860-f2e274867c91?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1764&q=80',
|
'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) => {
|
const updateProperty = (property: string, value: number) => {
|
||||||
|
@ -29,8 +29,17 @@ const updateProperty = (property: string, value: number) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
const [showMembers, setShowMembers] = useState(false)
|
const [showMembers, setShowMembers] = useState(false)
|
||||||
|
|
||||||
|
// TODO: Use it to simulate loading
|
||||||
|
// useEffect(() => {
|
||||||
|
// setLoading(true)
|
||||||
|
// setTimeout(() => {
|
||||||
|
// setLoading(false)
|
||||||
|
// }, 2000)
|
||||||
|
// }, [])
|
||||||
|
|
||||||
const appState = useAppState()
|
const appState = useAppState()
|
||||||
const appDispatch = useAppDispatch()
|
const appDispatch = useAppDispatch()
|
||||||
|
|
||||||
|
@ -73,13 +82,14 @@ function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div id="sidebar" style={{ zIndex: 200 }}>
|
<div id="sidebar-community" style={{ zIndex: 200 }}>
|
||||||
<Sidebar
|
<SidebarCommunity
|
||||||
community={COMMUNITY}
|
community={COMMUNITY}
|
||||||
selectedChannelId={appState.channelId}
|
selectedChannelId={appState.channelId}
|
||||||
onChannelPress={channelId =>
|
onChannelPress={channelId =>
|
||||||
appDispatch({ type: 'set-channel', channelId })
|
appDispatch({ type: 'set-channel', channelId })
|
||||||
}
|
}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -90,27 +100,44 @@ function App() {
|
||||||
channel={selectedChannel}
|
channel={selectedChannel}
|
||||||
showMembers={showMembers}
|
showMembers={showMembers}
|
||||||
onMembersPress={() => setShowMembers(show => !show)}
|
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>
|
||||||
|
|
||||||
<div id="content" ref={contentRef}>
|
<div id="content" ref={contentRef}>
|
||||||
<div id="messages">
|
<div id="messages">
|
||||||
<Messages />
|
<Messages loading={loading} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="composer" ref={composerRef}>
|
{loading === false && (
|
||||||
{scrollPosition !== 'bottom' && (
|
<div id="composer" ref={composerRef}>
|
||||||
<div id="anchor-actions">
|
{scrollPosition !== 'bottom' && (
|
||||||
<AnchorActions />
|
<div id="anchor-actions">
|
||||||
</div>
|
<AnchorActions />
|
||||||
)}
|
</div>
|
||||||
<Composer blur={scrollPosition !== 'bottom'} />
|
)}
|
||||||
</div>
|
<Composer blur={scrollPosition !== 'bottom'} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{showMembers && (
|
{showMembers && (
|
||||||
<div id="members">
|
<div id="sidebar-members">
|
||||||
<SidebarMembers />
|
<SidebarMembers />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -18,19 +18,25 @@ body,
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 352px 1fr auto;
|
grid-template-columns: 352px 1fr auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main {
|
#sidebar-community {
|
||||||
position: relative;
|
overflow: auto;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar-members {
|
||||||
|
width: 352px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 100vh;
|
background-color: #fff;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main {
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#topbar {
|
#topbar {
|
||||||
|
@ -45,6 +51,7 @@ body,
|
||||||
padding-top: var(--topbar-height);
|
padding-top: var(--topbar-height);
|
||||||
padding-bottom: var(--composer-height);
|
padding-bottom: var(--composer-height);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
isolation: isolate;
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages {
|
#messages {
|
||||||
|
@ -60,12 +67,7 @@ body,
|
||||||
#composer {
|
#composer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: auto 0 0;
|
inset: auto 0 0;
|
||||||
z-index: 100;
|
z-index: 1;
|
||||||
}
|
|
||||||
|
|
||||||
#members {
|
|
||||||
width: 352px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
|
|
|
@ -92,3 +92,9 @@ h6 {
|
||||||
#__next {
|
#__next {
|
||||||
isolation: isolate;
|
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"
|
"react-native-web": "^0.18.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-accordion": "^1.1.1",
|
||||||
"@radix-ui/react-dialog": "^1.0.3",
|
"@radix-ui/react-dialog": "^1.0.3",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||||
"@radix-ui/react-popover": "^1.0.5",
|
"@radix-ui/react-popover": "^1.0.5",
|
||||||
|
@ -43,7 +44,7 @@
|
||||||
"@tamagui/react-native-media-driver": "1.7.7",
|
"@tamagui/react-native-media-driver": "1.7.7",
|
||||||
"@tamagui/shorthands": "1.7.7",
|
"@tamagui/shorthands": "1.7.7",
|
||||||
"@tamagui/theme-base": "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",
|
"expo-linear-gradient": "^12.1.2",
|
||||||
"tamagui": "1.7.7",
|
"tamagui": "1.7.7",
|
||||||
"zustand": "^4.3.6"
|
"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 { Author } from './author'
|
||||||
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react'
|
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',
|
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>
|
type Story = StoryObj<typeof Author>
|
||||||
|
|
||||||
export const Default: Story = {
|
export const AllVariants: Story = {
|
||||||
args: {},
|
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 = {
|
<Stack gap={8}>
|
||||||
args: {
|
<Author {...args} address="zQ3...9d4Gs0" />
|
||||||
status: 'contact',
|
<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 = {
|
<Stack gap={8}>
|
||||||
args: {
|
<Author {...args} address="zQ3...9d4Gs0" time="09:30" />
|
||||||
status: 'verified',
|
<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 = {
|
<Stack gap={8}>
|
||||||
args: {
|
<Author
|
||||||
status: 'untrustworthy',
|
{...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
|
export default meta
|
||||||
|
|
|
@ -7,34 +7,50 @@ import { XStack } from 'tamagui'
|
||||||
|
|
||||||
import { Text } from '../text'
|
import { Text } from '../text'
|
||||||
|
|
||||||
interface Props {
|
import type { TextProps } from '../text'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
name: string
|
name: string
|
||||||
|
size?: Extract<TextProps['size'], 13 | 15>
|
||||||
nickname?: string
|
nickname?: string
|
||||||
address?: string
|
|
||||||
status?: 'verified' | 'untrustworthy' | 'contact'
|
status?: 'verified' | 'untrustworthy' | 'contact'
|
||||||
|
address?: string
|
||||||
time?: string
|
time?: string
|
||||||
orientation?: 'horizontal' | 'vertical'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Author = (props: Props) => {
|
const Author = (props: Props) => {
|
||||||
const { name, status, address, time } = props
|
const { name, size = 13, nickname, status, address, time } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<XStack space={8} alignItems="center">
|
<XStack space={8} alignItems="center">
|
||||||
<XStack space={4} alignItems="center">
|
<XStack gap={4} alignItems="center">
|
||||||
<Text size={13} weight="semibold">
|
<Text size={size} weight="semibold">
|
||||||
{name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{nickname && (
|
||||||
|
<Text size={11} color="$neutral-60">
|
||||||
|
· {nickname}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
{status === 'contact' && <ContactIcon />}
|
{status === 'contact' && <ContactIcon />}
|
||||||
{status === 'verified' && <VerifiedIcon />}
|
{status === 'verified' && <VerifiedIcon />}
|
||||||
{status === 'untrustworthy' && <UntrustworthyIcon />}
|
{status === 'untrustworthy' && <UntrustworthyIcon />}
|
||||||
</XStack>
|
</XStack>
|
||||||
|
|
||||||
{address && (
|
{(address || time) && (
|
||||||
<Text size={11} color="$neutral-50">
|
<XStack gap={4} alignItems="center">
|
||||||
{address}
|
{address && (
|
||||||
{time && ` · ${time}`}
|
<Text size={11} color="$neutral-50" type="monospace">
|
||||||
</Text>
|
{address}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{time && (
|
||||||
|
<Text size={11} color="$neutral-50">
|
||||||
|
· {time}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</XStack>
|
||||||
)}
|
)}
|
||||||
</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 './avatar'
|
||||||
|
export * from './channel-avatar'
|
||||||
export * from './icon-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 { View } from 'react-native'
|
||||||
|
|
||||||
import { Counter } from '../counter'
|
import { Counter } from '../counter'
|
||||||
|
@ -15,7 +15,7 @@ type Props = {
|
||||||
|
|
||||||
const Banner = (props: Props) => {
|
const Banner = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
icon,
|
icon = null,
|
||||||
children,
|
children,
|
||||||
count,
|
count,
|
||||||
backgroundColor = '$primary-50-opa-20',
|
backgroundColor = '$primary-50-opa-20',
|
||||||
|
@ -25,9 +25,11 @@ const Banner = (props: Props) => {
|
||||||
<Base backgroundColor={backgroundColor}>
|
<Base backgroundColor={backgroundColor}>
|
||||||
<Content>
|
<Content>
|
||||||
{icon}
|
{icon}
|
||||||
<Text size={13} color="$textPrimary">
|
<Stack flexGrow={1} flexShrink={1}>
|
||||||
{children}
|
<Text size={13} color="$textPrimary" truncate>
|
||||||
</Text>
|
{children}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
</Content>
|
</Content>
|
||||||
{count ? <Counter value={count} /> : null}
|
{count ? <Counter value={count} /> : null}
|
||||||
</Base>
|
</Base>
|
||||||
|
@ -43,10 +45,12 @@ const Base = styled(View, {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
maxHeight: '40px',
|
maxHeight: '40px',
|
||||||
|
gap: 10,
|
||||||
})
|
})
|
||||||
|
|
||||||
const Content = styled(View, {
|
const Content = styled(View, {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: 10,
|
gap: 10,
|
||||||
alignItems: 'center',
|
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
|
id: string
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
emoji: string
|
emoji: string
|
||||||
channelStatus?: 'muted' | 'normal' | 'withMessages' | 'withMentions'
|
channelStatus?: 'default' | 'notification' | 'muted'
|
||||||
unreadCount?: number
|
mentionCount?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelGroup {
|
export interface ChannelGroupType {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
unreadCount?: number
|
unreadCount?: number
|
||||||
channels: Channel[]
|
channels: ChannelType[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = ['👋', '🔥', '🦄', '🍑', '🤫', '🫣', '🏀', '🤝']
|
const emojis = ['👋', '🔥', '🦄', '🍑', '🤫', '🫣', '🏀', '🤝']
|
||||||
|
@ -19,7 +19,7 @@ const emojis = ['👋', '🔥', '🦄', '🍑', '🤫', '🫣', '🏀', '🤝']
|
||||||
const randomEmoji = () => emojis[Math.floor(Math.random() * emojis.length)]
|
const randomEmoji = () => emojis[Math.floor(Math.random() * emojis.length)]
|
||||||
|
|
||||||
// MOCK DATA
|
// MOCK DATA
|
||||||
export const CHANNEL_GROUPS: ChannelGroup[] = [
|
export const CHANNEL_GROUPS: ChannelGroupType[] = [
|
||||||
{
|
{
|
||||||
id: 'welcome',
|
id: 'welcome',
|
||||||
title: 'Welcome',
|
title: 'Welcome',
|
||||||
|
@ -27,29 +27,28 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||||
channels: [
|
channels: [
|
||||||
{
|
{
|
||||||
id: 'welcome',
|
id: 'welcome',
|
||||||
title: '# welcome',
|
title: 'welcome',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'general-welcome',
|
id: 'general-welcome',
|
||||||
title: '# general',
|
title: 'general',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'random',
|
id: 'random',
|
||||||
title: '# random',
|
title: 'random',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'onboarding',
|
id: 'onboarding',
|
||||||
title: '# onboarding',
|
title: 'onboarding',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
channelStatus: 'withMentions',
|
mentionCount: 3,
|
||||||
unreadCount: 3,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -60,29 +59,27 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||||
channels: [
|
channels: [
|
||||||
{
|
{
|
||||||
id: 'announcements',
|
id: 'announcements',
|
||||||
title: '# announcements',
|
title: 'announcements',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'jobs',
|
id: 'jobs',
|
||||||
title: '# jobs',
|
title: 'jobs',
|
||||||
channelStatus: 'withMentions',
|
mentionCount: 3,
|
||||||
unreadCount: 3,
|
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'events',
|
id: 'events',
|
||||||
title: '# events',
|
title: 'events',
|
||||||
channelStatus: 'withMentions',
|
mentionCount: 2,
|
||||||
unreadCount: 2,
|
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'meetups',
|
id: 'meetups',
|
||||||
title: '# meetups',
|
title: 'meetups',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
|
@ -94,25 +91,25 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||||
channels: [
|
channels: [
|
||||||
{
|
{
|
||||||
id: 'design',
|
id: 'design',
|
||||||
title: '# design',
|
title: 'design',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ux',
|
id: 'ux',
|
||||||
title: '# ux',
|
title: 'ux',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ui',
|
id: 'ui',
|
||||||
title: '# ui',
|
title: 'ui',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'figma',
|
id: 'figma',
|
||||||
title: '# figma',
|
title: 'figma',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
|
@ -124,13 +121,13 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||||
channels: [
|
channels: [
|
||||||
{
|
{
|
||||||
id: 'general',
|
id: 'general',
|
||||||
title: '# general',
|
title: 'general',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'people-ops',
|
id: 'people-ops',
|
||||||
title: '# people-ops',
|
title: 'people-ops',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
|
@ -142,27 +139,27 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||||
channels: [
|
channels: [
|
||||||
{
|
{
|
||||||
id: 'react',
|
id: 'react',
|
||||||
title: '# react',
|
title: 'react',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
channelStatus: 'withMessages',
|
channelStatus: 'notification',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'vue',
|
id: 'vue',
|
||||||
title: '# vue',
|
title: 'vue',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'angular',
|
id: 'angular',
|
||||||
title: '# angular',
|
title: 'angular',
|
||||||
channelStatus: 'muted',
|
channelStatus: 'muted',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'svelte',
|
id: 'svelte',
|
||||||
title: '# svelte',
|
title: 'svelte',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
|
@ -174,25 +171,25 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||||
channels: [
|
channels: [
|
||||||
{
|
{
|
||||||
id: 'node',
|
id: 'node',
|
||||||
title: '# node',
|
title: 'node',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'python',
|
id: 'python',
|
||||||
title: '# python',
|
title: 'python',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ruby',
|
id: 'ruby',
|
||||||
title: '# ruby',
|
title: 'ruby',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'php',
|
id: 'php',
|
||||||
title: '# php',
|
title: 'php',
|
||||||
channelStatus: 'muted',
|
channelStatus: 'muted',
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
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 { GroupIcon } from '@status-im/icons/16'
|
||||||
|
import { CommunitiesIcon } from '@status-im/icons/20'
|
||||||
import { Stack } from '@tamagui/core'
|
import { Stack } from '@tamagui/core'
|
||||||
|
|
||||||
import { Accordion } from '../accordion/accordion'
|
import { Avatar } from '../../avatar'
|
||||||
import { AccordionItem } from '../accordion/accordionItem'
|
import { Button } from '../../button'
|
||||||
import { Avatar } from '../avatar'
|
import { Image } from '../../image'
|
||||||
import { Button } from '../button'
|
import { SidebarSkeleton } from '../../skeleton/sidebar-skeleton'
|
||||||
import { Image } from '../image'
|
import { Text } from '../../text'
|
||||||
import { SidebarSkeleton } from '../skeleton/sidebar-skeleton'
|
import { CHANNEL_GROUPS } from '../mock-data'
|
||||||
import { Text } from '../text'
|
import { ChannelGroup } from './components/channel-group'
|
||||||
import { CHANNEL_GROUPS } from './mock-data'
|
|
||||||
|
|
||||||
import type { ChannelGroup } from './mock-data'
|
import type { ChannelGroupType } from '../mock-data'
|
||||||
|
|
||||||
export type SidebarProps = {
|
type Props = {
|
||||||
community: {
|
community: {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
membersCount: number
|
membersCount: number
|
||||||
imageUrl: string
|
imageSrc: string
|
||||||
}
|
}
|
||||||
channels?: ChannelGroup[]
|
channels?: ChannelGroupType[]
|
||||||
selectedChannelId?: string
|
selectedChannelId?: string
|
||||||
onChannelPress: (channelId: string) => void
|
onChannelPress: (channelId: string) => void
|
||||||
isLoading?: boolean
|
loading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Sidebar = (props: SidebarProps) => {
|
const SidebarCommunity = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
community,
|
community,
|
||||||
channels = CHANNEL_GROUPS,
|
channels = CHANNEL_GROUPS,
|
||||||
selectedChannelId,
|
selectedChannelId,
|
||||||
onChannelPress,
|
loading,
|
||||||
isLoading,
|
// onChannelPress,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const { name, description, membersCount, imageUrl } = community
|
const { name, description, membersCount, imageSrc } = community
|
||||||
|
|
||||||
if (isLoading) {
|
const [value, setValue] = useState(['Welcome'])
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
return <SidebarSkeleton />
|
return <SidebarSkeleton />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +53,7 @@ const Sidebar = (props: SidebarProps) => {
|
||||||
height="100%"
|
height="100%"
|
||||||
overflow="scroll"
|
overflow="scroll"
|
||||||
>
|
>
|
||||||
<Image src={imageUrl} width="full" aspectRatio={2.6} />
|
<Image src={imageSrc} width="full" aspectRatio={2.6} />
|
||||||
<Stack
|
<Stack
|
||||||
paddingBottom={16}
|
paddingBottom={16}
|
||||||
marginTop={-16}
|
marginTop={-16}
|
||||||
|
@ -76,31 +81,26 @@ const Sidebar = (props: SidebarProps) => {
|
||||||
<Text size={15}>{membersCount}</Text>
|
<Text size={15}>{membersCount}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Button>Join community</Button>
|
<Button icon={<CommunitiesIcon />}>Request to join community</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
{channels.map(group => (
|
|
||||||
<Accordion
|
<Accordion.Root type="multiple" value={value} onValueChange={setValue}>
|
||||||
key={group.id}
|
{channels.map(group => {
|
||||||
initialExpanded={group.id === 'welcome'}
|
return (
|
||||||
title={group.title}
|
<ChannelGroup
|
||||||
unreadCount={group.unreadCount}
|
key={group.id}
|
||||||
>
|
name={group.title}
|
||||||
{group.channels.map(channel => {
|
unreadCount={group.unreadCount}
|
||||||
return (
|
channels={group.channels}
|
||||||
<AccordionItem
|
expanded={value.includes(group.title)}
|
||||||
key={channel.id}
|
selectedChannelId={selectedChannelId}
|
||||||
channel={channel}
|
/>
|
||||||
selected={selectedChannelId === channel.id}
|
)
|
||||||
onPress={() => onChannelPress(channel.id)}
|
})}
|
||||||
/>
|
</Accordion.Root>
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Accordion>
|
|
||||||
))}
|
|
||||||
<Stack borderBottomColor="$neutral-10" borderBottomWidth={1} />
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</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
|
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||||
const meta: Meta<typeof SidebarMembers> = {
|
const meta: Meta<typeof SidebarMembers> = {
|
||||||
title: 'Sidebar/Members',
|
title: 'Community/Members Sidebar',
|
||||||
component: SidebarMembers,
|
component: SidebarMembers,
|
||||||
|
args: {},
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
|
render: () => (
|
||||||
|
<div style={{ maxWidth: 360 }}>
|
||||||
|
<SidebarMembers />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
type Story = StoryObj<typeof SidebarMembers>
|
type Story = StoryObj<typeof SidebarMembers>
|
|
@ -1,32 +1,18 @@
|
||||||
import { Stack } from '@tamagui/core'
|
import { Stack } from '@tamagui/core'
|
||||||
|
|
||||||
import { DividerLabel } from '../dividers'
|
import { DividerLabel } from '../../dividers'
|
||||||
import { UserList } from '../user-list'
|
import { UserList } from '../../user-list'
|
||||||
|
|
||||||
import type { UserListProps } from '../user-list'
|
import type { UserListProps } from '../../user-list'
|
||||||
|
|
||||||
type GroupProps = {
|
// type Props = {
|
||||||
label: string
|
// users: []
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const SidebarMembers = () => {
|
const SidebarMembers = () => {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
backgroundColor="$background"
|
backgroundColor="$white-100"
|
||||||
borderLeftWidth={1}
|
borderLeftWidth={1}
|
||||||
borderColor="$neutral-10"
|
borderColor="$neutral-10"
|
||||||
overflow="scroll"
|
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',
|
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',
|
address: 'zQ3...9d4Gs0',
|
||||||
indicator: 'online',
|
indicator: 'online',
|
||||||
|
status: 'untrustworthy',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Pedro',
|
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',
|
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',
|
address: 'zQ3...9d4Gs0',
|
||||||
indicator: 'online',
|
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',
|
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',
|
address: 'zQ3...9d4Gs0',
|
||||||
indicator: 'online',
|
indicator: 'online',
|
||||||
|
status: 'contact',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Pedro',
|
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',
|
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',
|
address: 'zQ3...9d4Gs0',
|
||||||
indicator: 'online',
|
indicator: 'online',
|
||||||
|
status: 'verified',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Pedro',
|
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',
|
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',
|
address: 'zQ3...9d4Gs0',
|
||||||
indicator: 'offline',
|
indicator: 'offline',
|
||||||
|
status: 'verified',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Pedro',
|
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',
|
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',
|
address: 'zQ3...9d4Gs0',
|
||||||
indicator: 'offline',
|
indicator: 'offline',
|
||||||
|
status: 'verified',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Pedro',
|
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',
|
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',
|
address: 'zQ3...9d4Gs0',
|
||||||
indicator: 'offline',
|
indicator: 'offline',
|
||||||
|
status: 'contact',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -130,3 +123,21 @@ const SidebarMembers = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export { 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
|
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||||
const meta: Meta<typeof Topbar> = {
|
const meta: Meta<typeof Topbar> = {
|
||||||
title: 'Navigation/Topbar',
|
title: 'Community/Topbar',
|
||||||
component: Topbar,
|
component: Topbar,
|
||||||
args: {
|
args: {
|
||||||
channel: {
|
channel: {
|
||||||
id: '1',
|
id: '1',
|
||||||
emoji: '👋',
|
emoji: '👋',
|
||||||
title: '# channel',
|
title: 'channel',
|
||||||
description: 'This is a channel description',
|
description: 'This is a channel description',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -35,16 +35,34 @@ export const Default: Story = {
|
||||||
args: {},
|
args: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isLoading: Story = {
|
export const Loading: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
loading: true,
|
||||||
isLoading: 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 = {
|
export const WithMembersSelected: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
|
||||||
showMembers: true,
|
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'
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
const meta: Meta<typeof Counter> = {
|
const meta: Meta<typeof Counter> = {
|
||||||
|
title: 'Components/Counter',
|
||||||
component: Counter,
|
component: Counter,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
value: {
|
value: {
|
||||||
|
|
|
@ -1,25 +1,58 @@
|
||||||
|
import { ChevronRightIcon } from '@status-im/icons/20'
|
||||||
import { Stack } from '@tamagui/core'
|
import { Stack } from '@tamagui/core'
|
||||||
|
|
||||||
|
import { Counter } from '../../counter'
|
||||||
import { Text } from '../../text'
|
import { Text } from '../../text'
|
||||||
import { DividerLine } from '../divider-line'
|
import { DividerLine } from '../divider-line'
|
||||||
|
|
||||||
|
import type { CounterProps } from '../../counter'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label: string
|
label: string
|
||||||
tight?: boolean
|
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 DividerLabel = (props: Props) => {
|
||||||
const { label, tight = true } = props
|
const { label, tight = true, counterType = 'secondary', count } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack paddingBottom={8} gap={tight ? 8 : 16}>
|
<Stack paddingBottom={8} gap={tight ? 8 : 16}>
|
||||||
<DividerLine />
|
<DividerLine />
|
||||||
<Stack paddingHorizontal={16}>
|
<Stack
|
||||||
<Text size={13} color="$neutral-50" weight="medium">
|
paddingHorizontal={16}
|
||||||
{label}
|
flexDirection="row"
|
||||||
</Text>
|
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>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
|
|
|
@ -106,4 +106,4 @@ DropdownMenu.Item = MenuItem
|
||||||
DropdownMenu.Separator = Separator
|
DropdownMenu.Separator = Separator
|
||||||
|
|
||||||
export { DropdownMenu }
|
export { DropdownMenu }
|
||||||
export type { Props as DropdownMenuProps }
|
export type DropdownMenuProps = Omit<Props, 'children'>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
export * from './anchor-actions'
|
export * from './anchor-actions'
|
||||||
|
export * from './avatar'
|
||||||
export * from './button'
|
export * from './button'
|
||||||
|
export * from './community'
|
||||||
export * from './composer'
|
export * from './composer'
|
||||||
export * from './dividers'
|
export * from './dividers'
|
||||||
export * from './dynamic-button'
|
export * from './dynamic-button'
|
||||||
|
@ -11,16 +13,10 @@ export * from './input'
|
||||||
export * from './messages'
|
export * from './messages'
|
||||||
export * from './pinned-message'
|
export * from './pinned-message'
|
||||||
export * from './provider'
|
export * from './provider'
|
||||||
export * from './sidebar'
|
|
||||||
export * from './sidebar-members'
|
|
||||||
export * from './skeleton'
|
export * from './skeleton'
|
||||||
export * from './text'
|
export * from './text'
|
||||||
export * from './toast'
|
export * from './toast'
|
||||||
export * from './topbar'
|
|
||||||
export * from './user-list'
|
export * from './user-list'
|
||||||
|
|
||||||
// MOCK DATA
|
|
||||||
export { CHANNEL_GROUPS } from './sidebar/mock-data'
|
|
||||||
|
|
||||||
// eslint-disable-next-line simple-import-sort/exports
|
// eslint-disable-next-line simple-import-sort/exports
|
||||||
export { config } from './tamagui.config'
|
export { config } from './tamagui.config'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Stack } from '@tamagui/core'
|
import { Stack } from '@tamagui/core'
|
||||||
|
|
||||||
import { DividerDate, DividerNewMessages } from '../dividers'
|
import { DividerDate, DividerNewMessages } from '../dividers'
|
||||||
|
import { MessageSkeleton } from '../skeleton'
|
||||||
import { PinAnnouncement } from '../system-messages'
|
import { PinAnnouncement } from '../system-messages'
|
||||||
import { Message } from './message'
|
import { Message } from './message'
|
||||||
|
|
||||||
|
@ -14,7 +15,29 @@ const reactions: ReactionsType = {
|
||||||
'thumbs-down': new Set(['me', '1', '2', '3']),
|
'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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Message
|
<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({
|
mono: createFont({
|
||||||
family: 'UbuntuMono',
|
family:
|
||||||
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;',
|
||||||
weight: {},
|
weight: {},
|
||||||
letterSpacing: {},
|
letterSpacing: {},
|
||||||
size: {},
|
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 (
|
return (
|
||||||
<YStack>
|
<YStack>
|
||||||
{users.map((user, index) => {
|
{users.map((user, index) => {
|
||||||
|
const { src, indicator, ...authorProps } = user
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<XStack
|
<XStack
|
||||||
key={user.address! + index}
|
key={user.address! + index}
|
||||||
padding={8}
|
padding={8}
|
||||||
space={8}
|
gap={8}
|
||||||
borderRadius={12}
|
borderRadius={12}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
|
@ -29,15 +31,10 @@ const UserList = (props: Props) => {
|
||||||
backgroundColor: '$primary-50-opa-5',
|
backgroundColor: '$primary-50-opa-5',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar size={32} src={user.src} indicator={user.indicator} />
|
<Avatar size={32} src={src} indicator={indicator} />
|
||||||
<YStack>
|
<YStack>
|
||||||
<Author
|
<Author {...authorProps} />
|
||||||
name={user.name}
|
<Text size={13} color="$neutral-50" type="monospace">
|
||||||
nickname={user.nickname}
|
|
||||||
status={user.status}
|
|
||||||
orientation="vertical"
|
|
||||||
/>
|
|
||||||
<Text size={13} color="$neutral-50">
|
|
||||||
{user.address}
|
{user.address}
|
||||||
</Text>
|
</Text>
|
||||||
</YStack>
|
</YStack>
|
||||||
|
|
|
@ -18,7 +18,7 @@ const SvgNotificationIcon = (props: IconProps) => {
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<Circle cx={10} cy={10} r={4} fill="#4360DF" />
|
<Circle cx={10} cy={10} r={4} fill={color} />
|
||||||
</Svg>
|
</Svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
43
yarn.lock
43
yarn.lock
|
@ -3321,6 +3321,22 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@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":
|
"@radix-ui/react-arrow@1.0.2":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.2.tgz#93b0ff95f65e2264a05b14ef1031ec798243dd6f"
|
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"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-primitive" "1.0.2"
|
"@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":
|
"@radix-ui/react-collection@1.0.2":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.2.tgz#d50da00bfa2ac14585319efdbbb081d4c5a29a97"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
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"
|
version "18.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33"
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33"
|
||||||
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
|
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
|
||||||
|
@ -6397,7 +6428,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@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"
|
version "18.0.28"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065"
|
||||||
integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==
|
integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==
|
||||||
|
@ -9504,10 +9535,10 @@ expo-asset@~8.7.0:
|
||||||
path-browserify "^1.0.0"
|
path-browserify "^1.0.0"
|
||||||
url-parse "^1.5.9"
|
url-parse "^1.5.9"
|
||||||
|
|
||||||
expo-blur@~12.0.1:
|
expo-blur@^12.2.2:
|
||||||
version "12.0.1"
|
version "12.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-12.0.1.tgz#7aa4186620359acfa976dda84360070b634ffe3d"
|
resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-12.2.2.tgz#b7f94499255afbd3468302d02f3c4e39a0e562d5"
|
||||||
integrity sha512-7oF/xRIFJukM4/qL6ejZ4Z/4YcVExvBPsBrz7rGYz6PtgAkWwYFR62+ExZOzTEG4hgoPPmlnt1ncimsk/MYUgQ==
|
integrity sha512-SvGbEZbB0VFNGqCW7FcqzWOEb3lrRgBnQKGrsKo49KwhMyHTYjYVYWnmrk9l8Tr7lIaNnd55QD6dPAzcXjZYMg==
|
||||||
|
|
||||||
expo-constants@^14.0.2, expo-constants@~14.0.0, expo-constants@~14.0.2:
|
expo-constants@^14.0.2, expo-constants@~14.0.0, expo-constants@~14.0.2:
|
||||||
version "14.0.2"
|
version "14.0.2"
|
||||||
|
|
Loading…
Reference in New Issue