Add cross-platform sidebar (#331)

* feat: add icon example and solve some types

* fix: add accordion and emojis

* fix: fixes animation issues and adds overall minor improvements

* fix: more fixes and new emoji

* fix: id from channel
This commit is contained in:
marcelines 2023-01-18 13:15:51 +00:00 committed by GitHub
parent 0d56feb24b
commit 2bbaea1f36
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
29 changed files with 1119 additions and 117 deletions

View File

@ -15,7 +15,7 @@ import {
} from '@status-im/components'
import { Stack, TamaguiProvider } from '@tamagui/core'
import { useFonts } from 'expo-font'
import { SafeAreaView, TouchableOpacity } from 'react-native'
import { SafeAreaView, ScrollView, TouchableOpacity } from 'react-native'
import tamaguiConfig from './tamagui.config'
@ -37,51 +37,51 @@ export default function App() {
return (
<TamaguiProvider config={tamaguiConfig} defaultTheme={theme}>
<SafeAreaView>
<Image
source={{
uri: 'https://images.unsplash.com/photo-1673537074513-e66435b69012?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80',
height: 200,
width: '100%',
}}
/>
<Sidebar
name="Rarible"
description="Multichain community-centric NFT marketplace. Create, buy and sell your NFTs."
membersCount={123}
/>
<Stack
flexDirection="column"
justifyContent="center"
alignItems="center"
marginTop={20}
height="100%"
width="100%"
backgroundColor="$background"
>
<Heading weight="semibold" marginBottom={12}>
Communities
</Heading>
<Heading heading="h2" marginBottom={12}>
This is an Heading 2
</Heading>
<Paragraph weight="semibold" marginBottom={12} uppercase>
Paragraph uppercased and bolded
</Paragraph>
<Paragraph marginBottom={12} uppercase>
This is a paragraph
</Paragraph>
<Label marginBottom={12}>This is a label</Label>
<Code marginBottom={12}>This is a code line</Code>
<Paragraph fontWeight="400">0x213abc190 ... 121ah4a9e</Paragraph>
<Shape marginVertical={20} />
<Paragraph>Theme selected - {theme}</Paragraph>
<TouchableOpacity
onPress={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
<ScrollView>
<Image
source={{
uri: 'https://images.unsplash.com/photo-1673537074513-e66435b69012?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80',
height: 200,
width: '100%',
}}
/>
<Sidebar
name="Rarible"
description="Multichain community-centric NFT marketplace. Create, buy and sell your NFTs."
membersCount={123}
/>
<Stack
flexDirection="column"
justifyContent="center"
alignItems="center"
paddingTop={20}
width="100%"
backgroundColor="$background"
>
<Paragraph>Toogle theme</Paragraph>
</TouchableOpacity>
</Stack>
<Heading weight="semibold" marginBottom={12}>
Communities
</Heading>
<Heading heading="h2" marginBottom={12}>
This is an Heading 2
</Heading>
<Paragraph weight="semibold" marginBottom={12} uppercase>
Paragraph uppercased and bolded
</Paragraph>
<Paragraph marginBottom={12} uppercase>
This is a paragraph
</Paragraph>
<Label marginBottom={12}>This is a label</Label>
<Code marginBottom={12}>This is a code line</Code>
<Paragraph fontWeight="400">0x213abc190 ... 121ah4a9e</Paragraph>
<Shape marginVertical={20} />
<Paragraph>Theme selected - {theme}</Paragraph>
<TouchableOpacity
onPress={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
<Paragraph>Toogle theme</Paragraph>
</TouchableOpacity>
</Stack>
</ScrollView>
</SafeAreaView>
</TamaguiProvider>
)

View File

@ -16,8 +16,12 @@ body,
grid-template-rows: 56px 1fr 100px;
}
#sidebar,
#main,
#main > div {
border: 1px solid rgba(0, 0, 0, 0.1);
}
#sidebar {
overflow: auto;
height: 100vh;
}

View File

@ -43,6 +43,7 @@
"@tamagui/vite-plugin": "1.0.15",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-native-svg": "^13.7.0",
"react-native-web": "^0.18.0",
"storybook": "^7.0.0-beta.21",
"storybook-addon-designs": "^7.0.0-beta.2",

View File

@ -0,0 +1,111 @@
import React from 'react'
import { Stack } from '@tamagui/core'
import { AnimatePresence } from 'tamagui'
import { Chevron } from '../icon'
import { Label, Paragraph } from '../typography'
import type { GetProps } from '@tamagui/core'
type BaseProps = GetProps<typeof Stack>
type Props = {
children: React.ReactElement[] | React.ReactElement
isExpanded: boolean
onToggle?: () => void
title: string
numberOfNewMessages?: number
showNotifications?: boolean
} & BaseProps
const Accordion = ({
children,
isExpanded,
onToggle,
title,
numberOfNewMessages,
showNotifications,
}: Props) => {
return (
<Stack
width="100%"
borderRadius="$0"
borderTopWidth={1}
borderTopColor="$neutral-10"
paddingHorizontal={8}
>
<Stack justifyContent="flex-start">
<Stack width="100%">
<Stack
width="100%"
flexDirection="row"
justifyContent={'space-between'}
onPress={onToggle}
cursor="pointer"
py={8}
>
<Stack flexDirection="row" alignItems="center">
<Stack
animation="fast"
transform={[{ rotateZ: isExpanded ? '90deg' : '0deg' }]}
>
<Chevron color="$neutral-50" size={16} />
</Stack>
<Paragraph
marginLeft={4}
color="$neutral-50"
weight="medium"
variant="smaller"
>
{title}
</Paragraph>
</Stack>
<AnimatePresence>
{showNotifications && numberOfNewMessages && (
<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"
>
<Label color="$white-100" weight="medium">
{numberOfNewMessages}
</Label>
</Stack>
</Stack>
)}
</AnimatePresence>
</Stack>
<AnimatePresence>
{isExpanded && (
<React.Fragment key={title}>{children}</React.Fragment>
)}
</AnimatePresence>
</Stack>
</Stack>
</Stack>
)
}
export { Accordion }

View File

@ -0,0 +1,115 @@
import { Stack } from '@tamagui/core'
import { Muted } from '../icon'
import { Label, Paragraph } from '../typography'
import type { GetProps } from '@tamagui/core'
type BaseProps = GetProps<typeof Stack>
type Props = {
isSelected?: boolean
onToggle?: () => void
title: string
channelStatus?: 'muted' | 'normal' | 'withMessages' | 'withMentions'
icon?: React.ReactNode
numberOfMessages?: number
} & BaseProps
const textColor = {
muted: '$neutral-40',
normal: '$neutral-50',
withMessages: '$neutral-100',
withMentions: '$neutral-100',
}
const AccordionItem = ({
icon,
isSelected,
title,
channelStatus = 'normal',
numberOfMessages,
...rest
}: Props) => {
return (
<Stack
{...rest}
animation={[
'fast',
{
opacity: {
overshootClamping: true,
},
},
]}
backgroundColor={isSelected ? '$turquoise-50-opa-10' : 'transparent'}
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"
>
<Stack
justifyContent="flex-start"
alignItems="center"
flexDirection="row"
>
{icon && <>{icon}</>}
<Paragraph
color={textColor[channelStatus]}
weight="medium"
marginLeft={icon ? 8 : 0}
>
{title}
</Paragraph>
</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"
>
<Label color="$white-100" weight="medium">
{numberOfMessages}
</Label>
</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' && <Muted size={20} color="$neutral-40" />}
</Stack>
)}
</Stack>
)
}
export { AccordionItem }

View File

@ -0,0 +1,18 @@
import { createAnimations } from '@tamagui/animations-react-native'
export const animations = createAnimations({
fast: {
damping: 20,
mass: 1.2,
stiffness: 250,
},
medium: {
damping: 10,
mass: 0.9,
stiffness: 100,
},
slow: {
damping: 20,
stiffness: 60,
},
})

View File

@ -1,27 +1,6 @@
import { createAnimations as createAnimationsCSS } from '@tamagui/animations-css'
import { createAnimations } from '@tamagui/animations-react-native'
import { createAnimations } from '@tamagui/animations-css'
export const animations = createAnimations({
bouncy: {
type: 'spring',
damping: 10,
mass: 0.9,
stiffness: 100,
},
lazy: {
type: 'spring',
damping: 20,
stiffness: 60,
},
quick: {
type: 'spring',
damping: 20,
mass: 1.2,
stiffness: 250,
},
})
export const animationsCSS = createAnimationsCSS({
fast: 'ease-in 150ms',
medium: 'ease-in 300ms',
slow: 'ease-in 450ms',

View File

@ -18,7 +18,9 @@ const Base = styled(Stack, {
display: 'inline-flex',
position: 'relative',
overflow: 'hidden',
backgroundColor: 'rgb(255,255,255)',
backgroundColor: '$white-100',
justifyContent: 'center',
alignItems: 'center',
variants: {
size: {
@ -55,6 +57,12 @@ const Base = styled(Stack, {
borderRadius: 16,
},
},
withOutline: {
true: {
borderWidth: 2,
borderColor: '$white-100',
},
},
} as const,
})
@ -69,12 +77,13 @@ interface Props {
size: NonNullable<BaseProps['size']>
indicator?: 'online' | 'offline'
shape?: 'circle' | 'rounded'
withOutline?: boolean
}
type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'
const Avatar = (props: Props) => {
const { src, size, shape = 'circle' } = props
const { src, size, shape = 'circle', withOutline } = props
const [status, setStatus] = useState<ImageLoadingStatus>('idle')
@ -83,7 +92,7 @@ const Avatar = (props: Props) => {
}, [JSON.stringify(src)])
return (
<Base size={size} shape={shape}>
<Base size={size} shape={shape} withOutline={withOutline}>
<Image
src={src}
width={size}
@ -91,15 +100,17 @@ const Avatar = (props: Props) => {
onLoad={() => setStatus('loaded')}
onError={() => setStatus('error')}
/>
<Fallback
width={size}
height={size}
display="flex"
alignItems="center"
justifyContent="center"
>
PP
</Fallback>
{status === 'error' && (
<Fallback
width={size}
height={size}
display="flex"
alignItems="center"
justifyContent="center"
>
PP
</Fallback>
)}
</Base>
)
}

View File

@ -1,9 +1,8 @@
import { styled, Text } from '@tamagui/core'
import { ButtonFrame } from 'tamagui'
import { Stack, styled, Text } from '@tamagui/core'
import type { GetProps } from '@tamagui/core'
const Base = styled(ButtonFrame, {
const Base = styled(Stack, {
// tag: 'button',
cursor: 'pointer',
@ -31,24 +30,22 @@ const Base = styled(ButtonFrame, {
const ButtonText = styled(Text, {
fontFamily: '$inter',
textAlign: 'center',
color: '$white',
color: '$white-100',
})
type BaseProps = GetProps<typeof Base>
interface Props {
type Props = {
type?: BaseProps['type']
children: string
onPress?: () => void
}
} & Omit<BaseProps, 'type'>
const Button = (props: Props) => {
const { type = 'primary', children, onPress } = props
console.log(onPress)
const { type = 'primary', children, onPress, ...rest } = props
console.log('Button', type)
return (
<Base type={type} onPress={onPress}>
<Base {...rest} type={type} onPress={onPress}>
<ButtonText>{children}</ButtonText>
</Base>
)

View File

@ -0,0 +1,9 @@
import type { SizeTokens, StyleObject } from '@tamagui/core'
import type { SvgProps } from 'react-native-svg'
export type EmojiProps = SvgProps & {
size?: number | SizeTokens
style?: StyleObject
sizeBackground?: number
hasBackground?: boolean
}

View File

@ -0,0 +1,44 @@
import { memo } from 'react'
import { Defs, Image, Path, Pattern, Svg, Use } from 'react-native-svg'
import { themed } from '../themed'
import type { EmojiProps } from '../EmojiProps'
const Emoji = (props: EmojiProps) => {
const { size = 16, ...otherProps } = props
return (
<Svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none"
{...otherProps}
>
<Path fill="url(#basketball-a)" d="M1.333 1.333h13.333v13.333H1.333z" />
<Defs>
<Pattern
id="basketball-a"
patternContentUnits="objectBoundingBox"
width="1"
height="1"
>
<Use xlinkHref="#basketball-b" transform="scale(.01389)" />
</Pattern>
<Image
id="basketball-b"
width="72"
height="72"
xlinkHref="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAMAAABiM0N1AAAAY1BMVEVHcEz0kAz0kAz0kAz0kAz0kAz0kAwjHyD0kAz0kAz0kAz0kAz0kAz0kAz0kAzKeRAjHyCMWBZKNBzniQ0jHyDagg9kQhqmZhQwJh9xSRnNexCzbRI9LR5+UBeZXxXAdBFXOxt8YctEAAAAFXRSTlMAIGCPv9///1Cf70CvgM+fn////4DoPY5GAAAB20lEQVR4AdzSh46yQBQFYGlnYMACSBGxvP9T/t6JhuDxv4wbkk32pAP5uG2jJAijOAHMI0ASR2Gw+UHSLIHkBbkkWfqdEkQWAEESG/nXlRcACJpS5H7VZIAOAZlHVVuLZQh2u1TODliGJDu1qNTCF4JNlbYABaL8t709voOw/+wcTPkdVJrDZ8eYqvaH6sqYT9LRGJIUSBzJkfbVtCwRRE7bvO0usEDXy6uTH3SSp30H2Pk9uTscnHT2gc7OGdxl8gGhk9djvQzVozzs6JykMZeLvC+XoVKeXSCZNZfN/tTXS1DdzyrPXk4OydRcp0P8Vf6ECkyRkqolqJKCMKV4Toi6b3SooUkGDoroo6sOXelnkYMsld3qUEvtW3FS3myvQz3fSOp2P8vNPDJo0CAPbphFLiABL6nToI7XiMTtjHdbalDJFyJ7CwHQKFsNog8koSyfp33XoDvPWg4gBkBXMmrQSJcGIHaz5rVpEC/NTRvrQFgPMivlL0O391x/eWvrHeS/VuygAAAABkFgEPv39GUCL8QcwE5EHa2bETZsbGrR+Mt3RB4ke9kMIhTWMNBS6OdhdHicA48BsDOFUFKjNEuJn1RRL8de111AcEnDRRaYfVyIAmkMxDqQDwumcHXpGt5TKwAAAABJRU5ErkJggg=="
/>
</Defs>
</Svg>
)
}
Emoji.displayName = 'Basketball'
export const Basketball = memo<EmojiProps>(themed(Emoji))

View File

@ -0,0 +1,42 @@
import { memo } from 'react'
import { Path, Svg } from 'react-native-svg'
import { themed } from '../themed'
import type { EmojiProps } from '../EmojiProps'
const Emoji = (props: EmojiProps) => {
const { size = 16, ...otherProps } = props
return (
<Svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none"
{...otherProps}
>
<Path
d="M7.145 13.135a1.054 1.054 0 0 0 1.493-.27.383.383 0 0 0-.091-.527l-.859-.925 1.96 1.402a.689.689 0 0 0 .52.118.688.688 0 0 0 .45-.293.72.72 0 0 0-.172-.991L8.777 10.08l2.495 1.785a.687.687 0 0 0 1.08-.429.724.724 0 0 0-.282-.737L9.858 8.768l2.722 1.947a.689.689 0 0 0 .774.018.703.703 0 0 0 .305-.447.726.726 0 0 0-.28-.736L1.716 4.059.107 6.677a.7.7 0 0 0 .129.897l3.614 3.164c.164.144.336.28.514.408l2.781 1.99Z"
fill="#EF9645"
/>
<Path
d="M13.353 9.066 8.107 5.2l-.027-.038.024-.037.025-.026.396.223c.445.294 1.46.728 2.23.728.528 0 .843-.201.938-.596a.68.68 0 0 0-.112-.497.652.652 0 0 0-.423-.272 3.111 3.111 0 0 1-1.3-.518L9.6 3.99c-.29-.2-.618-.425-.86-.572a1.85 1.85 0 0 0-1.004-.277c-.555 0-1.119.2-1.664.394l-.588.206c-.253.088-.52.131-.787.129-.716 0-1.418-.278-2.096-.546l-.068-.028a.632.632 0 0 0-.435-.01.647.647 0 0 0-.342.276l-1.61 2.62a.655.655 0 0 0 .12.834l3.613 3.165c.164.143.335.279.51.404l2.862 2.047a.919.919 0 0 0 .694.157.917.917 0 0 0 .6-.39.388.388 0 0 0-.091-.529l-.851-.608a.2.2 0 0 1-.082-.138.206.206 0 0 1 .044-.155.193.193 0 0 1 .263-.036l1.847 1.321a.639.639 0 0 0 .905-.163.676.676 0 0 0-.16-.925L8.692 9.93a.2.2 0 0 1-.082-.138.205.205 0 0 1 .044-.155.195.195 0 0 1 .263-.036l2.382 1.704a.644.644 0 0 0 .722.017.658.658 0 0 0 .285-.417.678.678 0 0 0-.262-.688l-2.26-1.615a.197.197 0 0 1-.082-.138.203.203 0 0 1 .045-.156.172.172 0 0 1 .235-.031l2.625 1.878a.645.645 0 0 0 .486.11.642.642 0 0 0 .42-.274.673.673 0 0 0-.16-.925Z"
fill="#FFDC5D"
/>
<Path
d="M7.195 11.996a.716.716 0 0 1-.18.323l-.466.472a.69.69 0 0 1-.668.188.693.693 0 0 1-.314-.182.715.715 0 0 1-.183-.689.715.715 0 0 1 .18-.32l.466-.473a.69.69 0 0 1 .667-.187.7.7 0 0 1 .425.329c.093.162.12.356.073.54ZM2.301 9.662l.698-.71a.714.714 0 0 0 .192-.631.719.719 0 0 0-.398-.521.68.68 0 0 0-.778.147l-.697.71a.714.714 0 0 0-.193.631.719.719 0 0 0 .4.521.68.68 0 0 0 .776-.147Zm2.415.025a.711.711 0 0 0 .221-.508.725.725 0 0 0-.204-.516.695.695 0 0 0-.505-.208.684.684 0 0 0-.496.227l-1.394 1.422a.711.711 0 0 0-.22.508.725.725 0 0 0 .203.516.695.695 0 0 0 .505.207.684.684 0 0 0 .497-.226l1.393-1.422Zm1.045 1.423a.716.716 0 0 0 .16-.75.71.71 0 0 0-.233-.32.687.687 0 0 0-.66-.1.69.69 0 0 0-.25.167l-.93.946a.716.716 0 0 0-.162.75.71.71 0 0 0 .234.32.683.683 0 0 0 .91-.067l.93-.946Zm7.684-1.963.291-.257-3.209-5.146-5.695.942a.69.69 0 0 0-.454.29.722.722 0 0 0-.12.534c.306 1.278 2.787.328 3.879-.285l5.308 3.922Z"
fill="#EF9645"
/>
<Path
d="M7.195 11.522a.716.716 0 0 1-.18.323l-.466.472a.69.69 0 0 1-.668.188.692.692 0 0 1-.314-.183.715.715 0 0 1-.183-.688.716.716 0 0 1 .18-.32l.466-.473a.69.69 0 0 1 .667-.188.7.7 0 0 1 .425.33c.093.162.12.356.073.539ZM2.301 9.188l.698-.71a.714.714 0 0 0 .192-.632.719.719 0 0 0-.398-.52.681.681 0 0 0-.778.147l-.697.71a.715.715 0 0 0-.193.631.719.719 0 0 0 .4.52.68.68 0 0 0 .776-.146Zm2.415.024a.711.711 0 0 0 .221-.508.725.725 0 0 0-.204-.515.695.695 0 0 0-.505-.208.684.684 0 0 0-.496.227L2.338 9.63a.711.711 0 0 0-.22.508.725.725 0 0 0 .203.515.695.695 0 0 0 .505.208.682.682 0 0 0 .497-.227l1.393-1.422Zm1.045 1.423a.716.716 0 0 0 .16-.75.71.71 0 0 0-.233-.32.688.688 0 0 0-.66-.099.69.69 0 0 0-.25.166l-.93.946a.716.716 0 0 0-.162.75.71.71 0 0 0 .234.321.683.683 0 0 0 .91-.067l.93-.947ZM15.767 7.1a.7.7 0 0 0 .125-.894l-.002-.002-1.609-3.093a.693.693 0 0 0-.366-.295.677.677 0 0 0-.467.01c-.924.366-1.937.786-2.92.444l-.51-.178c-.904-.32-1.887-.696-2.785-.14-.322.194-.807.538-1.119.75a3.15 3.15 0 0 1-1.282.51.688.688 0 0 0-.454.29.72.72 0 0 0-.12.534c.285 1.191 2.46.43 3.639-.175a.441.441 0 0 1 .465.038l5.377 3.992 2.028-1.791Z"
fill="#FFCC4D"
/>
</Svg>
)
}
Emoji.displayName = 'Collaboration'
export const Collaboration = memo<EmojiProps>(themed(Emoji))

View File

@ -0,0 +1,43 @@
import { memo } from 'react'
import { Defs, Image, Path, Pattern, Svg, Use } from 'react-native-svg'
import { themed } from '../themed'
import type { EmojiProps } from '../EmojiProps'
const Emoji = (props: EmojiProps) => {
const { size = 16, ...otherProps } = props
return (
<Svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none"
{...otherProps}
>
<Path fill="url(#fire-a)" d="M1.333 1.333h13.333v13.333H1.333z" />
<Defs>
<Pattern
id="fire-a"
patternContentUnits="objectBoundingBox"
width="1"
height="1"
>
<Use xlinkHref="#fire-b" transform="scale(.01389)" />
</Pattern>
<Image
id="fire-b"
width="72"
height="72"
xlinkHref="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAMAAABiM0N1AAAAh1BMVEVHcEz0kAz0kAz0kAz0kAz0kAz0kAz0kAz0kAz0kAz0kAz0kAz0kAz0kAz0kAz0kAz/zE3/zE3/zE3/zE3/zE3/zE3/zE3/zE39wkL/zE3/zE3/zE3/zE30kAz/zE36ri33nxz+yEn+xUX5qij8vT32mxj9wUH3oyD1mBT6sjH1lBD7tjX8uTmQeifTAAAAHXRSTlMAMEBQv++PEM+A359gryBwzxDvv4CPUK9gIN9gMOUtXcUAAAKrSURBVHjarZfZkoIwEEWDLEkAcXfWDpu78//fN4OMqdAkJFCeJy3LI919OwJxJAtm5DUkENGXiGYAof8SUwwAERkPxV+K4I+AjvawoFdbAxtrYpAQBDiacB1c06SG+RiPBxpRAC2cuDMHgMQkgszZkz2a0W/bP4GzaAENuKsg8dwra0hJBx8kiXuIG2K8bJLQVaQbjwcKM3cRLo7KVlsTEGX9ti56Hnso/Vg3H5bSRsNDkFgCkIUJEklXwAATm0UBcPWNDaMn7UwimiyicUfkTxZxtIoxaLEnMkQ/YqstMHUIb6I/UcRw7Gegw7q1nkyxq2jR/b7X6UjiXBpeWj9SK4OQujcbzZz9h6i30jSEQRg+B2mnjtBzXZE5Pgd9mcbW1JbuMbDA8aw4/n3GOceao21oi2euLBdwqvLb4NDiZ9NgmFqIMscinDkX0VH8cToaSwtcRVA2pvJs2JCFDFYGFg7iQXtNOLteKEUULNxbUXlBlyQ9rqXlouWgFkcf18ABJohEAQoB5/MQpokOYEQfSJS/H/HkMrzFAW5uAR0qKaqHj14OHa7ijAIp+RneYh9UzkLkaPqSExjwSS9IlxI19SgUwMB/QBnu7E1p/Ela0Aeo1+iELkRDjj2aJBXK6+iZc+NeFYoHieqD5i6XqRfUUNa3pu93gcjV4Rb9/4JUM6GqFH2uarrumlvNUB49w1Sdc1Nza8JlZRZqNRRF/z+FxnI9nUy10rGYEoVUzsxGlefXSj0NUs0zVCVGUelulbKm32IkTaczgvCniXzSI5oiioiG+XjR3PCoP1ZkenSny3GeJSUGVm9jPG8rYmbn7tmRQfZrN816Tyx8bFw8mw9i5/3Tpvl8J06stuvBqraUOPO1NM78i4zje7fUWHbfZAKr/XazlI7Ndj8UnF+YwEpCshNBaAAAAABJRU5ErkJggg=="
/>
</Defs>
</Svg>
)
}
Emoji.displayName = 'Fire'
export const Fire = memo<EmojiProps>(themed(Emoji))

View File

@ -0,0 +1,57 @@
import { memo } from 'react'
import {
ClipPath,
Defs,
G,
Image,
Path,
Pattern,
Svg,
Use,
} from 'react-native-svg'
import { themed } from '../themed'
import type { EmojiProps } from '../EmojiProps'
const Emoji = (props: EmojiProps) => {
const { size = 16, ...otherProps } = props
return (
<Svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none"
{...otherProps}
>
<G clipPath="url(#peach-a)">
<Path fill="url(#peach-b)" d="M.667.667h14.667v14.667H.667z" />
</G>
<Defs>
<ClipPath id="peach-a">
<Path fill="#fff" d="M0 0h16v16H0z" />
</ClipPath>
<Pattern
id="peach-b"
patternContentUnits="objectBoundingBox"
width="1"
height="1"
>
<Use xlinkHref="#peach-c" transform="scale(.01389)" />
</Pattern>
<Image
id="peach-c"
width="72"
height="72"
xlinkHref="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAMAAABiM0N1AAAAwFBMVEVHcExlnER3slV3slV3slV3slVvqU53slV3slV3slV3slVnnkV3slVup0xckTtckTtckTtckTtckTtckTt3slVckTtyrFBwkEGPjkqyjVXYimDpjmf/iGyZjk2Ar1aujVT/iGz/iGz/iGyhpVyOq1nvXFndLkTsVVb/iGz/iGz/iGz3i2vCm2KipVzqTVL/iGzRl2TyZl3/iGz/iGzfNEf7e2biO0r/iGz9gmr/iGz/iGz3cmLmRU7/iGz/iGz/iGyyzkZtAAAAQHRSTlMAQIC/n2YwUK//3xDvIIi//8+vY8/t////+///////QJ8wEM//////71Bg/////4D//3DP////3/8gj///r0C/62GdZgAAAmhJREFUeAGc0FWChTAMBdDg1Ghwt/0vcnyeNEXPd/QC4bjeDz8I4b4oZi+4K+CWgDODdOFJJRq/pFke7l/DmUXx3yQyfCpzARuUx+zk76SwxDdlDlaBZGxvkirRpBUQImY7pALQSJUOGELOdnngoJXxXiTZgSBDK/0eDzvEkaJ5++xQVTdt23VdPwxD33XtWCNiIsCgjkZN/WDq2hkoOorOoZb1s9uysLIbiKHo9BEteD6bmaH/rmItw9P3KJy9BdwzAssPq7QeVlmo2t1Cz549h+PxBEznCxD5FIT7z54ooJXjSkyABHiYNPpQVkgPHEXTPfIw/KxXDT+HOR2Z07bJO9Mr38LodrVEKWueOByZjAC5eUNBIm/fVFYE8F49CW0Ts6kmQGPNE/ZM27Q1mw5XR5eQC1XJxbVonbon0ZmcOIg70JsHLuTIQ3Go32drmHtyZBC75CsqY0buEgEm9njkTCZuJdeWkzul1O5ZbJG836W0SgsxinYPQpPOpKAU5lasItIwSk3Sini7R1mkXAAssqRh+AMi80VF51XU/ArRwp+IXoRvZK8X4cuW/4qF7OBh038iherUMicsylXHnxlxlrCvSUTxRzqgRKKsrcUXsnsS3eumX4kRqVMNrRR+/cy9/tLipNVpWnSAO/REotjrSiyMKZwrq1F+eMUjByoQbXuQs91iZCWEY8XkQP4vrHEyAdGAPTrTgWcPPMCkYgIeRcdBnwFeQ67k5iq2JyfOs9lidnnUYo0D+Zk2SIwbNrn6qsIz7vgFCZwTo8Pr4bOmzqgBrmI2P0jn378W2fjm55jzZFk2NN8BIrOmWnrNWn4AAAAASUVORK5CYII="
/>
</Defs>
</Svg>
)
}
Emoji.displayName = 'Peach'
export const Peach = memo<EmojiProps>(themed(Emoji))

View File

@ -0,0 +1,46 @@
import { memo } from 'react'
import { Path, Svg } from 'react-native-svg'
import { themed } from '../themed'
import type { EmojiProps } from '../EmojiProps'
const Emoji = (props: EmojiProps) => {
const { size = 16, ...otherProps } = props
return (
<Svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none"
{...otherProps}
>
<Path
d="M1.667 4.91v.061c0-.019.004-.037.006-.056l-.006-.005Zm5.88 4.99a.875.875 0 0 1-.186-.11L1.922 5.468a.637.637 0 0 1-.255-.497v5.484c0 .915.494 1.113.494 1.113l5.236 4.077c.093.072.17.116.239.143a.487.487 0 0 1-.089-.271V9.9Zm-3.968.163c-.45 0-.92-.451-1.05-1.008-.13-.557.13-1.008.58-1.008.45 0 .92.451 1.049 1.008.13.557-.13 1.008-.58 1.008Zm2.525 3.36c-.45 0-.92-.451-1.049-1.008-.13-.557.13-1.008.58-1.008.45 0 .92.451 1.049 1.008.13.557-.13 1.009-.58 1.009Z"
fill="#A0041E"
/>
<Path
d="M14.078 5.43 8.602 9.79a.852.852 0 0 1-.15.095v5.632a.492.492 0 0 1-.09.275.945.945 0 0 0 .248-.147l5.242-4.077s.481-.198.481-1.113V4.934c0 .18-.085.36-.255.496Zm-3.374 7.497c-.11.51-.506.922-.886.922-.379 0-.598-.413-.488-.922.11-.51.506-.922.885-.922.38 0 .598.412.49.922Zm1.6-2.362c-.114.525-.532.95-.932.95s-.63-.425-.515-.95c.116-.525.534-.95.933-.95.4 0 .63.425.515.95Zm1.386-2.287c-.113.5-.526.903-.92.903-.395 0-.622-.404-.508-.903.113-.5.525-.904.92-.904s.622.405.508.904Zm.64-3.366c.001.007.003.014.003.022V4.91l-.002.002Z"
fill="#DD2E44"
/>
<Path
d="M14.078 4.437 8.583.205c-.34-.273-.9-.273-1.24 0l-5.42 4.27a.644.644 0 0 0-.25.44c-.002.02-.006.037-.006.056 0 .18.085.36.255.497l5.44 4.322c.056.045.119.08.185.11v5.617a.49.49 0 0 0 .088.27c.082.126.211.213.365.213.152 0 .28-.085.362-.208a.49.49 0 0 0 .09-.275V9.885a.852.852 0 0 0 .15-.095l5.476-4.36a.637.637 0 0 0 .255-.496l-.002-.022a.638.638 0 0 0-.253-.475Zm-5.852-.572c.728 0 1.32.467 1.32 1.045 0 .577-.592 1.045-1.32 1.045-.729 0-1.32-.468-1.32-1.045 0-.578.591-1.045 1.32-1.045Z"
fill="#EA596E"
/>
<Path
d="M8.226 5.955c.728 0 1.319-.468 1.319-1.045s-.59-1.045-1.32-1.045c-.728 0-1.318.468-1.318 1.045s.59 1.045 1.319 1.045ZM13.181 7.374c-.394 0-.806.405-.92.904-.114.5.114.904.508.904.395 0 .807-.405.92-.904.115-.499-.113-.904-.508-.904Zm-1.391 2.24c-.4 0-.818.426-.933.951-.115.525.116.95.515.95.4 0 .818-.425.933-.95.115-.525-.116-.95-.515-.95Zm-1.576 2.39c-.38 0-.776.414-.885.923-.11.51.11.922.489.922.38 0 .776-.412.885-.922.11-.51-.11-.922-.489-.922Z"
fill="#fff"
/>
<Path
d="M3.109 8.047c-.45 0-.71.451-.58 1.008.13.557.6 1.008 1.05 1.008.45 0 .709-.451.579-1.008-.13-.557-.6-1.008-1.05-1.008Zm2.525 3.36c-.45 0-.709.451-.58 1.008.13.557.6 1.008 1.05 1.008.45 0 .71-.451.58-1.008-.13-.557-.6-1.008-1.05-1.008Z"
fill="#E1E8ED"
/>
</Svg>
)
}
Emoji.displayName = 'Play'
export const Play = memo<EmojiProps>(themed(Emoji))

View File

@ -0,0 +1,57 @@
import { memo } from 'react'
import {
ClipPath,
Defs,
G,
Image,
Path,
Pattern,
Svg,
Use,
} from 'react-native-svg'
import { themed } from '../themed'
import type { EmojiProps } from '../EmojiProps'
const Emoji = (props: EmojiProps) => {
const { size = 16, ...otherProps } = props
return (
<Svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none"
{...otherProps}
>
<G clipPath="url(#unicorn-a)">
<Path fill="url(#unicorn-b)" d="M.667.667h14.667v14.667H.667z" />
</G>
<Defs>
<ClipPath id="unicorn-a">
<Path fill="#fff" d="M0 0h16v16H0z" />
</ClipPath>
<Pattern
id="unicorn-b"
patternContentUnits="objectBoundingBox"
width="1"
height="1"
>
<Use xlinkHref="#unicorn-c" transform="scale(.01389)" />
</Pattern>
<Image
id="unicorn-c"
width="72"
height="72"
xlinkHref="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAMAAABiM0N1AAAAwFBMVEVHcEx6QYTBzdXBzdXBzdVgN5pgN5rBzdW2n6fENRJgN5rBzdVgN5rBzdXBzdVgN5rBzdXufA7seA5gN5rBzdXBzdXBzdXBzdXufA7MQhHaWhDteQ5gN5rWUxDHOhF1V6fufA7XVhDNgFzBzdW8xs9gN5rufA6prMSyvMbncRCImKORgrhTYmyCbK9pRaDIPBOfmMF2iJYqMDSaqLFga3N0VqbboGV6g4nbYBTlhi6ESHc8Q0ewXkvMuaPHsKSuipHrwtT2AAAAI3RSTlMAIO8wfdN9QBD+W983v1zvn2bpqq+PIM+WpzDDj0DP36/Pz9/s0Q4AAAK6SURBVHhe7ZbXUuMwFIblFsuOU0ghlLCweyTXkkpnd9//rVbGFMtYsgwZrvYbbpjJfHP+o19K0MnVEToIR74/OYzqxD+U6spn/Lz4ugif+37kOKfaIdKtnJU/OcFfNp06zl2xqmo+Qzd4geb2SlxNYmIexqqSz4b+q8nrWdZS8+gb7D9xCxh3jlNUwThGDAMAxjZC7pQyRnTkjmiFqSYyTfyo2JR/fjaAebEtgBBgcEnf4ER05AlMZxPmiXzGLwL63EAzstkRgISKcEWmH6XHX63+AMBiAMEmZqa12CQpVLmp6IbAOicQM5NkJpbOW2LRdYlWRRXu/1KasJE2MQAEzyRZXjdZGFmW1nx40eumnmgespE2AbwTJDXXEk0FW79wWDC/jPiQQViMxLHmVVqPJcTNK2eekigECOMd1OCmmnpFQvHKS26gkeC6YvLKhDU09teznlpEQLJKBUod4umVwR9e0v0GEcl7NkqbwlkvpntJMkalpVZzN3FpyggzsVIK4VvauG93ao0uCbST8SbURB9UuG4VDUEJkreJBqDGukWEgUNxTdJkZL/dE2k4qWgMr9xut9tbxXDSM+NF8pOTrih83D6GICOQiGyosGstk1hkQhcCZZHqSII6qrMWihbQCZKLRNCRTCA6ho4EApENXcmbRSZ0JWkWzaArYbNIBw7lKnV4HfvSKnVIZkurpH74fXFVs4+iMQgZI1NWJfU22siQVUm5RHPZ911SE2FdEowxk1RJ7ez1YcvAGVV5imbD1nu45kUAQUzqNRwqPTE5f/HDNN3zpgVW+3WR8KI4TdMdNw9WfIbJR1FcXbKhXA++juE+TcOKyFS/0vzktZ/VOlZ/PqWfM5G6SHZndfxpEV4INt1VhPBc5lEXMexZqRlj1F3Eg20GL1AXyTm8yPw+0X/R+FCiwTdE+weFN6s3Jd7QlgAAAABJRU5ErkJggg=="
/>
</Defs>
</Svg>
)
}
Emoji.displayName = 'Unicorn'
export const Unicorn = memo<EmojiProps>(themed(Emoji))

View File

@ -0,0 +1,6 @@
export { Basketball } from './emojis/basketball'
export { Collaboration } from './emojis/collaboration'
export { Fire } from './emojis/fire'
export { Peach } from './emojis/peach'
export { Play } from './emojis/play'
export { Unicorn } from './emojis/unicorn'

View File

@ -0,0 +1,27 @@
import { Stack } from '@tamagui/core'
import type { EmojiProps } from './EmojiProps'
import type React from 'react'
export function themed(Component: React.ElementType) {
const useWrapped = (props: EmojiProps) => {
const { size, hasBackground, sizeBackground = 24, ...rest } = props
if (hasBackground) {
return (
<Stack
width={sizeBackground}
height={sizeBackground}
borderRadius="50%"
backgroundColor="$turquoise-50-opa-10"
justifyContent="center"
alignItems="center"
>
<Component {...rest} size={size} />
</Stack>
)
}
return <Component {...rest} size={size} />
}
return useWrapped
}

View File

@ -0,0 +1,18 @@
import type {
SizeTokens,
StyleObject,
ThemeParsed,
Tokens,
} from '@tamagui/core'
import type { SvgProps } from 'react-native-svg'
type GetTokenString<A> = A extends string ? `$${A}` : `$${string}`
export type ColorTokens =
| GetTokenString<keyof Tokens['color']>
| GetTokenString<keyof ThemeParsed>
export type IconProps = SvgProps & {
size?: number | SizeTokens
color?: ColorTokens
style?: StyleObject
}

View File

@ -0,0 +1,27 @@
import { memo } from 'react'
import { Path, Svg } from 'react-native-svg'
import { themed } from '../themed'
import type { IconProps } from '../IconProps'
const Icon = (props: IconProps) => {
const { color, size = 16, ...otherProps } = props
return (
<Svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none"
{...otherProps}
>
<Path d="M8 14L12 10L8 6" stroke={`${color}`} strokeWidth="1.3" />
</Svg>
)
}
Icon.displayName = 'Chevron'
export const Chevron = memo<IconProps>(themed(Icon))

View File

@ -0,0 +1,39 @@
import { memo } from 'react'
import { Path, Svg } from 'react-native-svg'
import { themed } from '../themed'
import type { IconProps } from '../IconProps'
const Icon = (props: IconProps) => {
const { color, size = 16, ...otherProps } = props
return (
<Svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...otherProps}
>
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M4.6 5a1.9 1.9 0 1 1 3.8 0 1.9 1.9 0 0 1-3.8 0Zm1.9-3.1a3.1 3.1 0 1 0 0 6.2 3.1 3.1 0 0 0 0-6.2ZM5 8.9a3.6 3.6 0 0 0-3.6 3.6A1.6 1.6 0 0 0 3 14.1h6a1.6 1.6 0 0 0 1.6-1.6A3.6 3.6 0 0 0 7 8.9H5Zm-2.4 3.6A2.4 2.4 0 0 1 5 10.1h2a2.4 2.4 0 0 1 2.4 2.4.4.4 0 0 1-.4.4H3a.4.4 0 0 1-.4-.4Z"
fill={`${color}`}
/>
<Path
d="M10 3.1a1.9 1.9 0 0 1 0 3.8v1.2a3.1 3.1 0 0 0 0-6.2v1.2ZM11 10.1h.5a2.4 2.4 0 0 1 2.4 2.4.4.4 0 0 1-.4.4h-2v1.2h2a1.6 1.6 0 0 0 1.6-1.6 3.6 3.6 0 0 0-3.6-3.6H11v1.2Z"
fill={`${color}`}
/>
</Svg>
)
}
Icon.displayName = 'Group'
export const Group = memo<IconProps>(themed(Icon))

View File

@ -0,0 +1,32 @@
import { memo } from 'react'
import { Path, Svg } from 'react-native-svg'
import { themed } from '../themed'
import type { IconProps } from '../IconProps'
const Icon = (props: IconProps) => {
const { color, size = 16, ...otherProps } = props
return (
<Svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none"
{...otherProps}
>
<Path
d="M5.655 4.735A5.15 5.15 0 0 1 15.15 7.5v.75a3.1 3.1 0 0 0 .62 1.86l.45.6a2.15 2.15 0 0 1-1.212 3.38l2.523 2.523-.92.919-3.38-3.382H5.5a2.15 2.15 0 0 1-1.72-3.44l.45-.6a3.1 3.1 0 0 0 .62-1.86V7.5c0-.524.079-1.03.224-1.507L2.471 3.39l.919-.92 2.265 2.265Zm.517 2.356a3.894 3.894 0 0 0-.022.409v.75a4.4 4.4 0 0 1-.88 2.64l-.45.6a.85.85 0 0 0 .68 1.36h6.431l-5.76-5.76Zm7.598 5.759L6.604 5.685A3.85 3.85 0 0 1 13.85 7.5v.75a4.4 4.4 0 0 0 .88 2.64l.45.6a.85.85 0 0 1-.68 1.36h-.73Zm-2.373 4.522a3.649 3.649 0 0 1-3.978-.791l.92-.92a2.35 2.35 0 0 0 3.323 0l.919.92a3.65 3.65 0 0 1-1.184.791Z"
fill={`${color}`}
fillRule="evenodd"
clipRule="evenodd"
/>
</Svg>
)
}
Icon.displayName = 'Muted'
export const Muted = memo<IconProps>(themed(Icon))

View File

@ -0,0 +1,3 @@
export { Chevron } from './icons/chevron'
export { Group } from './icons/group'
export { Muted } from './icons/muted'

View File

@ -0,0 +1,15 @@
import { useCurrentColor } from 'tamagui'
import type { IconProps } from './IconProps'
import type React from 'react'
export function themed(Component: React.ElementType) {
const useWrapped = (props: IconProps) => {
const { size, color: colorToken = '$neutral-100', ...rest } = props
const color = useCurrentColor(colorToken)
return <Component {...rest} color={color} size={size} />
}
return useWrapped
}

View File

@ -1,4 +1,6 @@
import { setupReactNative, Stack, styled } from '@tamagui/core'
// eslint-disable-next-line eslint-comments/disable-enable-pair
/* eslint-disable import/namespace */
import { setupReactNative, styled } from '@tamagui/core'
// import { focusableInputHOC } from '@tamagui/focusable'
import { TextInput } from 'react-native'
@ -43,7 +45,7 @@ export const InputFrame = styled(
// },/
// focusStyle: {
// // borderColor: '$borderColorFocus',
// borderWidth: 2,
// marginHorizontal: -1,
// },

View File

@ -1,7 +1,13 @@
import { useState } from 'react'
import { Stack } from '@tamagui/core'
import { Accordion } from '../accordion/accordion'
import { AccordionItem } from '../accordion/accordionItem'
import { Avatar } from '../avatar'
import { Button } from '../button'
// import { Button } from '../button'
import { Basketball, Collaboration, Fire, Peach, Play, Unicorn } from '../emoji'
import { Group } from '../icon'
import { Image } from '../image'
import { Heading, Paragraph } from '../typography'
@ -11,35 +17,259 @@ interface Props {
membersCount: number
}
interface Channels {
id: string
title: string
icon?: React.ReactNode
channelStatus?: 'muted' | 'normal' | 'withMessages' | 'withMentions'
numberOfMessages?: number
}
interface CommunitiesProps {
id: string
title: string
numberOfNewMessages?: number
channels: Channels[]
}
// MOCK DATA
const COMMUNITIES: CommunitiesProps[] = [
{
id: 'welcome',
title: 'Welcome',
numberOfNewMessages: 3,
channels: [
{
id: 'welcome',
title: '# welcome',
icon: <Collaboration hasBackground />,
},
{
id: 'general-welcome',
title: '# general',
icon: <Play hasBackground />,
},
{
id: 'random',
title: '# random',
icon: <Fire hasBackground />,
},
{
id: 'onboarding',
title: '# onboarding',
icon: <Unicorn hasBackground />,
channelStatus: 'withMentions',
numberOfMessages: 3,
},
],
},
{
id: 'community',
title: 'Community',
numberOfNewMessages: 5,
channels: [
{
id: 'announcements',
title: '# announcements',
icon: <Peach hasBackground />,
},
{
id: 'jobs',
title: '# jobs',
channelStatus: 'withMentions',
numberOfMessages: 3,
icon: <Play hasBackground />,
},
{
id: 'events',
title: '# events',
channelStatus: 'withMentions',
numberOfMessages: 2,
icon: <Fire hasBackground />,
},
{
id: 'meetups',
title: '# meetups',
icon: <Unicorn hasBackground />,
},
],
},
{
id: 'Design',
title: 'Design',
channels: [
{
id: 'design',
title: '# design',
icon: <Collaboration hasBackground />,
},
{
id: 'ux',
title: '# ux',
icon: <Play hasBackground />,
},
{
id: 'ui',
title: '# ui',
icon: <Fire hasBackground />,
},
{
id: 'figma',
title: '# figma',
icon: <Unicorn hasBackground />,
},
],
},
{
id: 'General',
title: 'General',
channels: [
{
id: 'general',
title: '# general',
icon: <Collaboration hasBackground />,
},
{
id: 'people-ops',
title: '# people-ops',
icon: <Basketball hasBackground />,
},
],
},
{
id: 'Frontend',
title: 'Frontend',
channels: [
{
id: 'react',
title: '# react',
icon: <Collaboration hasBackground />,
channelStatus: 'withMessages',
},
{
id: 'vue',
title: '# vue',
icon: <Play hasBackground />,
},
{
id: 'angular',
title: '# angular',
channelStatus: 'muted',
icon: <Fire hasBackground />,
},
{
id: 'svelte',
title: '# svelte',
icon: <Unicorn hasBackground />,
},
],
},
{
id: 'Backend',
title: 'Backend',
channels: [
{
id: 'node',
title: '# node',
icon: <Collaboration hasBackground />,
},
{
id: 'python',
title: '# python',
icon: <Play hasBackground />,
},
{
id: 'ruby',
title: '# ruby',
icon: <Fire hasBackground />,
},
{
id: 'php',
title: '# php',
channelStatus: 'muted',
icon: <Unicorn hasBackground />,
},
],
},
]
const COMMUNITIES_EXPAND_CONTROL = COMMUNITIES.reduce(
(o, key) => ({ ...o, [key.id]: false }),
{} as Record<string, boolean>[]
)
const _Sidebar = (props: Props) => {
const { name, description, membersCount } = props
const [isExpanded, setIsExpanded] = useState({
...COMMUNITIES_EXPAND_CONTROL,
welcome: true,
})
const [selectedChannel, setSelectedChannel] = useState('welcome')
const handleToggle = (id: string) => {
setIsExpanded(prev => ({
...prev,
[id]: !prev[id as keyof typeof isExpanded],
}))
}
return (
<Stack backgroundColor="$background" minHeight={'calc(100vh - 2px)'}>
<Stack backgroundColor="$background">
<Image
src="https://images.unsplash.com/photo-1584475784921-d9dbfd9d17ca?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80"
width={350}
width={352}
height={136}
/>
<Stack
paddingHorizontal={16}
paddingBottom={16}
marginTop={-16}
backgroundColor="$background"
borderTopLeftRadius={16}
borderTopRightRadius={16}
borderTopLeftRadius={20}
borderTopRightRadius={20}
zIndex={10}
>
<Stack marginTop={-32} marginBottom={12}>
<Avatar
src="https://images.unsplash.com/photo-1553835973-dec43bfddbeb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80"
size={56}
/>
<Stack paddingHorizontal={16}>
<Stack marginTop={-32} marginBottom={12}>
<Avatar
withOutline
src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fi.seadn.io%2Fgae%2FFG0QJ00fN3c_FWuPeUr9-T__iQl63j9hn5d6svW8UqOmia5zp3lKHPkJuHcvhZ0f_Pd6P2COo9tt9zVUvdPxG_9BBw%3Fw%3D500%26auto%3Dformat&f=1&nofb=1&ipt=c177cd71d8d0114080cfc6efd3f9e098ddaeb1b347919bd3089bf0aacb003b3e&ipo=images"
size={56}
/>
</Stack>
<Heading marginBottom={16}>{name}</Heading>
<Paragraph marginBottom={12}>{description}</Paragraph>
<Stack flexDirection="row" alignItems="center" mb={12}>
<Group color="$neutral-100" size={16} />
<Paragraph ml={8}>{membersCount}</Paragraph>
</Stack>
</Stack>
<Heading marginBottom={16}>{name}</Heading>
<Paragraph marginBottom={12}>{description}</Paragraph>
<Paragraph marginBottom={12}>{membersCount}</Paragraph>
<Button>Request to join community</Button>
{COMMUNITIES.map(community => (
<Accordion
key={community.id}
isExpanded={!!isExpanded[community.id as keyof typeof isExpanded]}
onToggle={() => handleToggle(community.id)}
title={community.title}
numberOfNewMessages={community.numberOfNewMessages}
showNotifications={
!isExpanded[community.id as keyof typeof isExpanded]
}
>
{community.channels.map(channel => (
<AccordionItem
key={channel.id}
icon={channel.icon}
title={channel.title}
channelStatus={channel.channelStatus}
numberOfMessages={channel.numberOfMessages}
isSelected={selectedChannel === channel.id}
onPress={() => setSelectedChannel(channel.id)}
/>
))}
</Accordion>
))}
<Stack borderBottomColor="$neutral-10" borderBottomWidth={1} />
{/* <Button mt={20}>Request to join community</Button> */}
</Stack>
</Stack>
)

View File

@ -3,7 +3,7 @@ import { createInterFont } from '@tamagui/font-inter'
import { createMedia } from '@tamagui/react-native-media-driver'
import { shorthands } from '@tamagui/shorthands'
import { animations, animationsCSS } from './animations'
import { animations } from './animations'
import { themes } from './themes'
import { tokens } from './tokens'
@ -106,8 +106,5 @@ export const config = createTamagui({
pointerCoarse: { pointer: 'coarse' },
}),
shorthands,
animations: {
...animations,
...animationsCSS,
},
animations,
})

View File

@ -35,7 +35,10 @@ const light = createTheme({
beigeHover: tokens.color['beige-60'],
})
const dark = createTheme({
// note: we set up a single consistent base type to validate the rest:
type BaseTheme = typeof light
const dark: BaseTheme = createTheme({
background: tokens.color['neutral-95'],
textPrimary: tokens.color['white-100'],
primary: tokens.color['primary-60'],
@ -68,7 +71,15 @@ const dark = createTheme({
beigeHover: tokens.color['beige-50'],
})
export const themes = {
const allThemes = {
light,
dark,
}
type ThemeName = keyof typeof allThemes
type Themes = {
[key in ThemeName]: BaseTheme
}
export const themes: Themes = allThemes

View File

@ -8488,7 +8488,26 @@ css-select@^4.1.3, css-select@^4.2.0:
domutils "^2.8.0"
nth-check "^2.0.1"
css-what@^6.0.1:
css-select@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
dependencies:
boolbase "^1.0.0"
css-what "^6.1.0"
domhandler "^5.0.2"
domutils "^3.0.1"
nth-check "^2.0.1"
css-tree@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
dependencies:
mdn-data "2.0.14"
source-map "^0.6.1"
css-what@^6.0.1, css-what@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
@ -8880,12 +8899,21 @@ dom-serializer@^1.0.1:
domhandler "^4.2.0"
entities "^2.0.0"
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.2"
entities "^4.2.0"
dom-walk@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
domelementtype@^2.0.1, domelementtype@^2.2.0:
domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
@ -8897,6 +8925,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
dependencies:
domelementtype "^2.2.0"
domhandler@^5.0.1, domhandler@^5.0.2:
version "5.0.3"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
domelementtype "^2.3.0"
domutils@^2.5.2, domutils@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
@ -8906,6 +8941,15 @@ domutils@^2.5.2, domutils@^2.8.0:
domelementtype "^2.2.0"
domhandler "^4.2.0"
domutils@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==
dependencies:
dom-serializer "^2.0.0"
domelementtype "^2.3.0"
domhandler "^5.0.1"
dot-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
@ -9001,6 +9045,11 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@^4.2.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
env-editor@^0.4.1:
version "0.4.2"
resolved "https://registry.yarnpkg.com/env-editor/-/env-editor-0.4.2.tgz#4e76568d0bd8f5c2b6d314a9412c8fe9aa3ae861"
@ -13300,6 +13349,11 @@ mdast-util-to-string@^1.0.0:
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527"
integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==
mdn-data@2.0.14:
version "2.0.14"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@ -14079,7 +14133,6 @@ node-fetch-native@^1.0.1:
node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7, node-fetch@^2.x.x:
version "2.6.7"
uid "1b5d62978f2ed07b99444f64f0df39f960a6d34d"
resolved "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz#1b5d62978f2ed07b99444f64f0df39f960a6d34d"
node-forge@^1.1.0, node-forge@^1.2.1, node-forge@^1.3.1:
@ -15374,6 +15427,14 @@ react-native-gradle-plugin@^0.70.3:
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz#cbcf0619cbfbddaa9128701aa2d7b4145f9c4fc8"
integrity sha512-oOanj84fJEXUg9FoEAQomA8ISG+DVIrTZ3qF7m69VQUJyOGYyDZmPqKcjvRku4KXlEH6hWO9i4ACLzNBh8gC0A==
react-native-svg@^13.7.0:
version "13.7.0"
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.7.0.tgz#be2ffb935e996762543dd7376bdc910722f7a43c"
integrity sha512-WR5CIURvee5cAfvMhmdoeOjh1SC8KdLq5u5eFsz4pbYzCtIFClGSkLnNgkMSDMVV5LV0qQa4jeIk75ieIBzaDA==
dependencies:
css-select "^5.1.0"
css-tree "^1.1.3"
react-native-web-internals@^1.0.15:
version "1.0.15"
resolved "https://registry.yarnpkg.com/react-native-web-internals/-/react-native-web-internals-1.0.15.tgz#8c4367e2461edf5bab4abab71471b351a08cb19f"