feat(react): add MainSidebar component
This commit is contained in:
parent
59f0198d81
commit
c1fba5e633
|
@ -0,0 +1,64 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import * as Collapsible from '@radix-ui/react-collapsible'
|
||||||
|
|
||||||
|
import { BellIcon } from '~/src/icons/bell-icon'
|
||||||
|
import { ChevronDownIcon } from '~/src/icons/chevron-down-icon'
|
||||||
|
import { styled } from '~/src/styles/config'
|
||||||
|
import { ContextMenu, ContextMenuTrigger } from '~/src/system/context-menu'
|
||||||
|
import { Text } from '~/src/system/text'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChannelGroup = (props: Props) => {
|
||||||
|
const { name, children } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenuTrigger>
|
||||||
|
<Collapsible.Root defaultOpen>
|
||||||
|
<CollapsibleTrigger>
|
||||||
|
<Text size="15" weight="500" color="black-70">
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent>{children}</CollapsibleContent>
|
||||||
|
</Collapsible.Root>
|
||||||
|
<ContextMenu>
|
||||||
|
<ContextMenu.TriggerItem label="Mute Chat" icon={<BellIcon />}>
|
||||||
|
<ContextMenu.Item>For 15 min</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>For 1 hour</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>For 8 hours</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>For 24 hours</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>Until I turn it back on</ContextMenu.Item>
|
||||||
|
</ContextMenu.TriggerItem>
|
||||||
|
<ContextMenu.Item icon={<BellIcon />}>Mark as Read</ContextMenu.Item>
|
||||||
|
</ContextMenu>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollapsibleTrigger = styled(Collapsible.Trigger, {
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: 8,
|
||||||
|
borderRadius: 8,
|
||||||
|
height: 34,
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
background: '#E9EDF1',
|
||||||
|
},
|
||||||
|
|
||||||
|
'&[aria-expanded="true"] svg': {
|
||||||
|
transform: 'rotate(180deg)',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const CollapsibleContent = styled(Collapsible.Content, {
|
||||||
|
overflow: 'hidden',
|
||||||
|
})
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { BellIcon } from '~/src/icons/bell-icon'
|
||||||
|
import { ContextMenu, ContextMenuTrigger } from '~/src/system/context-menu'
|
||||||
|
|
||||||
|
import { SidebarItem } from './sidebar-item'
|
||||||
|
|
||||||
|
import type { SidebarItemProps } from './sidebar-item'
|
||||||
|
|
||||||
|
interface Props extends SidebarItemProps {
|
||||||
|
children: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChannelItem = (props: Props) => {
|
||||||
|
const { children, ...sidebarItemProps } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenuTrigger>
|
||||||
|
<SidebarItem {...sidebarItemProps}>#{children}</SidebarItem>
|
||||||
|
<ContextMenu>
|
||||||
|
<ContextMenu.Item icon={<BellIcon />}>View Profile</ContextMenu.Item>
|
||||||
|
<ContextMenu.Separator />
|
||||||
|
<ContextMenu.TriggerItem label="Mute Chat">
|
||||||
|
<ContextMenu.Item>For 15 min</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>For 1 hour</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>For 8 hours</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>For 24 hours</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>Until I turn it back on</ContextMenu.Item>
|
||||||
|
</ContextMenu.TriggerItem>
|
||||||
|
<ContextMenu.Separator />
|
||||||
|
<ContextMenu.Item icon={<BellIcon />} danger>
|
||||||
|
Delete
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { BellIcon } from '~/src/icons/bell-icon'
|
||||||
|
import { ContextMenu, ContextMenuTrigger } from '~/src/system'
|
||||||
|
|
||||||
|
import { SidebarItem } from './sidebar-item'
|
||||||
|
|
||||||
|
import type { SidebarItemProps } from './sidebar-item'
|
||||||
|
|
||||||
|
interface Props extends SidebarItemProps {
|
||||||
|
children: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChatItem = (props: Props) => {
|
||||||
|
const { children, ...sidebarItemProps } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenuTrigger>
|
||||||
|
<SidebarItem {...sidebarItemProps}>{children}</SidebarItem>
|
||||||
|
<ContextMenu>
|
||||||
|
<ContextMenu.Item icon={<BellIcon />}>View Profile</ContextMenu.Item>
|
||||||
|
<ContextMenu.Separator />
|
||||||
|
<ContextMenu.TriggerItem label="Mute Chat" icon={<BellIcon />}>
|
||||||
|
<ContextMenu.Item>For 15 min</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>For 1 hour</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>For 8 hours</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>For 24 hours</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item>Until I turn it back on</ContextMenu.Item>
|
||||||
|
</ContextMenu.TriggerItem>
|
||||||
|
<ContextMenu.Separator />
|
||||||
|
<ContextMenu.Item icon={<BellIcon />} danger>
|
||||||
|
Delete
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { useAppState } from '~/src/contexts/app-context'
|
||||||
|
import { EditIcon } from '~/src/icons/edit-icon'
|
||||||
|
import { styled } from '~/src/styles/config'
|
||||||
|
import { Box, Button } from '~/src/system'
|
||||||
|
import { Avatar } from '~/src/system/avatar'
|
||||||
|
import { Dialog, DialogTrigger } from '~/src/system/dialog'
|
||||||
|
import { Grid } from '~/src/system/grid'
|
||||||
|
import { Heading } from '~/src/system/heading'
|
||||||
|
import { IconButton } from '~/src/system/icon-button'
|
||||||
|
import { Text } from '~/src/system/text'
|
||||||
|
import { TextInput } from '~/src/system/text-input'
|
||||||
|
|
||||||
|
import { ChannelGroup } from './channel-group'
|
||||||
|
import { ChannelItem } from './channel-item'
|
||||||
|
import { ChatItem } from './chat-item'
|
||||||
|
|
||||||
|
const CHANNELS = {
|
||||||
|
Public: ['welcome', 'general', 'random'],
|
||||||
|
Other: ['random', 'general', 'welcome'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHATS = ['vitalik.eth', 'pvl.eth', 'Climate Change']
|
||||||
|
|
||||||
|
export const MainSidebar = () => {
|
||||||
|
const { dispatch } = useAppState()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<DialogTrigger>
|
||||||
|
<IdentityWrapper>
|
||||||
|
<Avatar size={36} />
|
||||||
|
<div>
|
||||||
|
<Text>CryptoKitties</Text>
|
||||||
|
<Text color="gray" size={12}>
|
||||||
|
186 members
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</IdentityWrapper>
|
||||||
|
<Dialog title="Crypto Kitties" actionLabel="Save">
|
||||||
|
<Text>A community of cat lovers, meow!</Text>
|
||||||
|
<TextInput placeholder="meow" />
|
||||||
|
<Text>
|
||||||
|
To access this community, paste community public key in Status
|
||||||
|
desktop or mobile app
|
||||||
|
</Text>
|
||||||
|
<Button>Download Status for Mac</Button>
|
||||||
|
</Dialog>
|
||||||
|
</DialogTrigger>
|
||||||
|
|
||||||
|
{Object.entries(CHANNELS).map(([group, channels]) => (
|
||||||
|
<ChannelGroup key={group} name={group}>
|
||||||
|
{channels.map(channel => (
|
||||||
|
<ChannelItem
|
||||||
|
key={group + channel}
|
||||||
|
unread={channel === 'general'}
|
||||||
|
muted={channel === 'random'}
|
||||||
|
active={false}
|
||||||
|
>
|
||||||
|
{channel}
|
||||||
|
</ChannelItem>
|
||||||
|
))}
|
||||||
|
</ChannelGroup>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Grid
|
||||||
|
flow="column"
|
||||||
|
align="center"
|
||||||
|
justify="between"
|
||||||
|
css={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
<Heading>Messages</Heading>
|
||||||
|
<IconButton
|
||||||
|
label="New Chat"
|
||||||
|
onClick={() => dispatch({ type: 'NEW_CHAT' })}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
{CHATS.map(chat => (
|
||||||
|
<ChatItem key={chat} unread={false} muted={false} active={false}>
|
||||||
|
{chat}
|
||||||
|
</ChatItem>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = styled('div', {
|
||||||
|
width: 304,
|
||||||
|
flexShrink: 0,
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: '10px 16px',
|
||||||
|
display: 'none',
|
||||||
|
backgroundColor: '#F6F8FA',
|
||||||
|
overflowY: 'scroll',
|
||||||
|
|
||||||
|
'@medium': {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Separator = styled('div', {
|
||||||
|
margin: '16px 0',
|
||||||
|
height: 1,
|
||||||
|
background: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
})
|
||||||
|
|
||||||
|
const IdentityWrapper = styled('button', {
|
||||||
|
padding: '4px 6px',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
gap: '$2',
|
||||||
|
borderRadius: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
background: '#E9EDF1',
|
||||||
|
},
|
||||||
|
})
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React, { forwardRef } from 'react'
|
||||||
|
|
||||||
|
import { styled } from '~/src/styles/config'
|
||||||
|
import { Avatar } from '~/src/system'
|
||||||
|
|
||||||
|
import type { Ref } from 'react'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
muted: boolean
|
||||||
|
unread: boolean
|
||||||
|
active: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarItem = (props: Props, ref: Ref<HTMLButtonElement>) => {
|
||||||
|
const { muted, unread, active, children, ...buttonProps } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
state={muted ? 'muted' : unread ? 'unread' : undefined}
|
||||||
|
active={active}
|
||||||
|
{...buttonProps}
|
||||||
|
>
|
||||||
|
<Avatar size={24} />
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const _SidebarItem = forwardRef(SidebarItem)
|
||||||
|
|
||||||
|
export { _SidebarItem as SidebarItem }
|
||||||
|
export type SidebarItemProps = Omit<Props, 'children'>
|
||||||
|
|
||||||
|
const Button = styled('button', {
|
||||||
|
position: 'relative',
|
||||||
|
fontFamily: '$sans',
|
||||||
|
fontWeight: '$500',
|
||||||
|
fontSize: 15,
|
||||||
|
display: 'inline-flex',
|
||||||
|
color: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
gap: '$2',
|
||||||
|
borderRadius: 8,
|
||||||
|
padding: 8,
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
background: '#E9EDF1',
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
active: {
|
||||||
|
true: {
|
||||||
|
background: 'rgba(233, 237, 241, 1)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
muted: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
},
|
||||||
|
unread: {
|
||||||
|
fontWeight: '$600',
|
||||||
|
'&::after': {
|
||||||
|
content: '"1"',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 8,
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
background: '#4360DF',
|
||||||
|
borderRadius: '$full',
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
Loading…
Reference in New Issue