Replies, buttons, add shadow (#353)

* improve primary button

* create shadow component

* add all avatar variants

* update reply

* update button prop

* add usePressableColors hook

* update icon button component

* update messages actions

* update composer

* add inverted variant to Shadow component

* update Message component

* fix prop name

* add user-select none to web app

* fix avatar shape

* fix button icon color

* fix icon button token name

* fix icon button selected prop name

* change accordion item prop name

* set default indicator variant

* fix button prop

* export helper types

* refactor accordion props

* buttons only extend PressableProps

* improve typing of variants in component props

* add tmp workaround for pressable props

* remove new line

* make GetVariants helper nonnullable

* fix image aspectRatio

* fix avatar indicator

* fix icon button props

* add todo
This commit is contained in:
Pavel 2023-03-22 10:39:42 +01:00 committed by GitHub
parent 461a9bfe60
commit 6474b39bac
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
27 changed files with 923 additions and 637 deletions

View File

@ -124,9 +124,9 @@ export default function App() {
>
<Heading color="$textPrimary">Rarible</Heading>
<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={48}
outline
/>
</View>
)}

View File

@ -3,6 +3,7 @@ body,
#root {
height: 100%;
overscroll-behavior: none;
user-select: none;
}
*::selection {

View File

@ -8,7 +8,7 @@ const meta: Meta<typeof Accordion> = {
component: Accordion,
argTypes: {},
args: {
numberOfNewMessages: 3,
unreadCount: 3,
title: 'Welcome',
},
parameters: {
@ -27,7 +27,7 @@ export const Default: Story = {
<>
<AccordionItem
key="welcome"
isSelected
selected
channel={CHANNEL_GROUPS[0].channels[0]}
/>
<AccordionItem key="general" channel={CHANNEL_GROUPS[0].channels[0]} />

View File

@ -6,24 +6,18 @@ import { AnimatePresence } from 'tamagui'
import { Label, Paragraph } from '../typography'
import type { GetProps } from '@tamagui/core'
type BaseProps = GetProps<typeof Stack>
type Props = {
children: React.ReactElement[] | React.ReactElement
initialExpanded: boolean
title: string
numberOfNewMessages?: number
} & BaseProps
unreadCount?: number
}
const Accordion = (props: Props) => {
const { children, initialExpanded, title, unreadCount } = props
const Accordion = ({
children,
initialExpanded,
title,
numberOfNewMessages,
}: Props) => {
const [isExpanded, setIsExpanded] = useState(initialExpanded)
return (
<Stack
accessibilityRole="button"
@ -32,6 +26,7 @@ const Accordion = ({
borderTopWidth={1}
borderTopColor="$neutral-10"
paddingHorizontal={8}
paddingBottom={8}
>
<Stack justifyContent="flex-start">
<Stack width="100%">
@ -68,7 +63,7 @@ const Accordion = ({
</Paragraph>
</Stack>
<AnimatePresence>
{!isExpanded && numberOfNewMessages && (
{!isExpanded && unreadCount !== 0 && (
<Stack
key={`notifications-${title}}`}
width={20}
@ -96,7 +91,7 @@ const Accordion = ({
alignItems="center"
>
<Label color="$white-100" weight="medium">
{numberOfNewMessages}
{unreadCount}
</Label>
</Stack>
</Stack>

View File

@ -6,10 +6,9 @@ import { Label, Paragraph } from '../typography'
import type { Channel } from '../sidebar/mock-data'
type Props = {
isSelected?: boolean
selected?: boolean
onPress?: () => void
channel: Channel
mb?: number
}
const textColor = {
@ -20,8 +19,9 @@ const textColor = {
}
const AccordionItem = (props: Props) => {
const { channel, isSelected, onPress, mb } = props
const { emoji, title, channelStatus = 'normal', numberOfMessages } = channel
const { channel, selected, onPress } = props
const { emoji, title, channelStatus = 'normal', unreadCount } = channel
return (
<Stack
@ -34,7 +34,7 @@ const AccordionItem = (props: Props) => {
},
},
]}
backgroundColor={isSelected ? '$primary-50-opa-10' : 'transparent'}
backgroundColor={selected ? '$primary-50-opa-10' : 'transparent'}
hoverStyle={{
backgroundColor: '$primary-50-opa-5',
}}
@ -51,7 +51,6 @@ const AccordionItem = (props: Props) => {
flexDirection="row"
cursor="pointer"
onPress={onPress}
mb={mb}
>
<Stack
justifyContent="flex-start"
@ -91,7 +90,7 @@ const AccordionItem = (props: Props) => {
alignItems="center"
>
<Label color="$white-100" weight="medium">
{numberOfMessages}
{unreadCount}
</Label>
</Stack>
</Stack>

View File

@ -18,12 +18,28 @@ export const Default: Story = {
src: 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80',
},
render: args => (
<Stack space>
<Avatar {...args} size={56} />
<Avatar {...args} size={52} />
<Avatar {...args} size={48} />
<Avatar {...args} size={32} />
<Avatar {...args} size={20} />
<Stack space flexDirection="row">
<Stack space>
<Avatar {...args} size={80} />
<Avatar {...args} size={56} />
<Avatar {...args} size={48} />
<Avatar {...args} size={32} />
<Avatar {...args} size={28} />
<Avatar {...args} size={24} />
<Avatar {...args} size={20} />
<Avatar {...args} size={16} />
</Stack>
<Stack space>
<Avatar {...args} size={80} indicator="online" />
<Avatar {...args} size={56} indicator="online" />
<Avatar {...args} size={48} indicator="online" />
<Avatar {...args} size={32} indicator="online" />
<Avatar {...args} size={28} indicator="online" />
<Avatar {...args} size={24} indicator="online" />
<Avatar {...args} size={20} indicator="online" />
<Avatar {...args} size={16} indicator="online" />
</Stack>
</Stack>
),
}
@ -31,14 +47,18 @@ export const Default: Story = {
export const Rounded: Story = {
args: {
src: 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80',
shape: 'rounded',
},
render: args => (
<Stack space>
<Avatar {...args} size={56} shape="rounded" />
<Avatar {...args} size={52} shape="rounded" />
<Avatar {...args} size={48} shape="rounded" />
<Avatar {...args} size={32} shape="rounded" />
<Avatar {...args} size={20} shape="rounded" />
<Avatar {...args} size={80} />
<Avatar {...args} size={56} />
<Avatar {...args} size={48} />
<Avatar {...args} size={32} />
<Avatar {...args} size={28} />
<Avatar {...args} size={24} />
<Avatar {...args} size={20} />
<Avatar {...args} size={16} />
</Stack>
),
}

View File

@ -4,160 +4,28 @@ import { Stack, styled, Text, Unspaced } from '@tamagui/core'
import { Image } from '../image'
import type { GetProps } from '@tamagui/core'
import type { GetStyledVariants } from '@tamagui/core'
// import { Button as RNButton } from 'react-native'
type Variants = GetStyledVariants<typeof Base>
// setupReactNative({ Button: RNButton })
// import type { GetProps} from '@tamagui/core';
const Base = styled(Stack, {
name: 'Avatar',
display: 'flex',
position: 'relative',
backgroundColor: '$white-100',
justifyContent: 'center',
alignItems: 'center',
variants: {
size: {
80: {
width: 80,
height: 80,
borderRadius: 80 / 2,
},
56: {
width: 56,
height: 56,
borderRadius: 56 / 2,
},
52: {
width: 52,
height: 52,
borderRadius: 52 / 2,
},
48: {
width: 48,
height: 48,
borderRadius: 48 / 2,
},
32: {
width: 32,
height: 32,
borderRadius: 32 / 2,
},
20: {
width: 20,
height: 20,
borderRadius: 20 / 2,
},
},
shape: {
circle: {},
rounded: {
borderRadius: 16,
},
},
withOutline: {
true: {
borderWidth: 2,
borderColor: '$white-100',
},
},
} as const,
})
const Indicator = styled(Stack, {
name: 'Indicator',
position: 'absolute',
bottom: 2,
right: 2,
zIndex: 2,
borderWidth: 2,
borderColor: '$white-100',
variants: {
size: {
80: {
width: 10,
height: 10,
borderRadius: 10 / 2,
},
56: {
width: 10,
height: 10,
borderRadius: 10 / 2,
},
// FIXME: use catch all variant
52: {
width: 12,
height: 12,
borderRadius: 12 / 2,
},
48: {
width: 10,
height: 10,
borderRadius: 10 / 2,
right: 0,
bottom: 0,
},
32: {
width: 10,
height: 10,
borderRadius: 10 / 2,
right: 0,
bottom: 0,
},
20: {
width: 10,
height: 10,
borderRadius: 10 / 2,
right: 0,
bottom: 0,
},
},
state: {
online: {
backgroundColor: '$success-50',
},
offline: {
backgroundColor: '$neutral-40',
},
},
shape: {
circle: {},
rounded: {
borderRadius: 16,
},
},
} as const,
})
const Fallback = styled(Text, {
name: 'AvatarFallback',
})
type BaseProps = GetProps<typeof Base>
interface Props {
type Props = {
src: string
size: NonNullable<BaseProps['size']>
indicator?: 'online' | 'offline'
shape?: 'circle' | 'rounded'
withOutline?: boolean
size: 80 | 56 | 48 | 32 | 28 | 24 | 20 | 16
shape?: Variants['shape']
outline?: Variants['outline']
indicator?: GetStyledVariants<typeof Indicator>['state']
}
type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'
const Avatar = (props: Props) => {
const { src, size, shape = 'circle', withOutline, indicator } = props
const {
src,
size,
shape = 'circle',
outline = false,
indicator = 'none',
} = props
const [status, setStatus] = useState<ImageLoadingStatus>('idle')
@ -166,36 +34,163 @@ const Avatar = (props: Props) => {
}, [src])
return (
<Base size={size} shape={shape} withOutline={withOutline}>
{indicator && (
<Base size={size} shape={shape} outline={outline}>
{indicator !== 'none' && (
<Unspaced>
<Indicator size={size} state={indicator} />
</Unspaced>
)}
<Shape shape={shape}>
<Image
src={src}
width="full"
aspectRatio={1}
onLoad={() => setStatus('loaded')}
onError={() => setStatus('error')}
/>
<Image
src={src}
width="full"
radius="full"
aspectRatio={1}
onLoad={() => setStatus('loaded')}
onError={() => setStatus('error')}
/>
{status === '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>
)}
</Shape>
</Base>
)
}
export { Avatar }
export type { Props as AvatarProps }
const Base = styled(Stack, {
name: 'Avatar',
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
variants: {
// defined in Avatar props
size: {
'...': (size: number) => {
return {
width: size,
height: size,
}
},
},
shape: {
circle: {
borderRadius: 80, // big enough to cover all sizes
},
rounded: {
borderRadius: 16,
},
},
outline: {
true: {
borderWidth: 2,
borderColor: '$white-100',
},
},
} as const,
})
const Shape = styled(Stack, {
name: 'AvatarShape',
width: '100%',
height: '100%',
backgroundColor: '$white-100',
overflow: 'hidden',
variants: {
shape: {
circle: {
borderRadius: 80, // big enough to cover all sizes
},
rounded: {
borderRadius: 16,
},
},
},
})
const Indicator = styled(Stack, {
name: 'AvatarIndicator',
position: 'absolute',
zIndex: 2,
borderWidth: 2,
borderColor: '$white-100',
borderRadius: 10,
variants: {
size: {
80: {
width: 16,
height: 16,
bottom: 4,
right: 4,
},
56: {
width: 12,
height: 12,
bottom: 2,
right: 2,
},
48: {
width: 12,
height: 12,
right: 0,
bottom: 0,
},
32: {
width: 12,
height: 12,
right: -2,
bottom: -2,
},
28: {
width: 12,
height: 12,
right: -2,
bottom: -2,
},
24: {
width: 12,
height: 12,
right: -2,
bottom: -2,
},
20: {
display: 'none',
},
16: {
display: 'none',
},
},
state: {
none: {},
online: {
backgroundColor: '$success-50',
},
offline: {
backgroundColor: '$neutral-40',
},
},
} as const,
})
const Fallback = styled(Text, {
name: 'AvatarFallback',
})

View File

@ -58,13 +58,6 @@ export const PrimaryDisabled: Story = {
},
}
export const PrimaryFullWidth: Story = {
args: {
children: 'Click me',
width: 'full',
},
}
export const Primary32: Story = {
name: 'Primary / 32',
args: {
@ -103,30 +96,38 @@ export const PrimaryIconOnly: Story = {
},
}
export const PrimaryIconOnlyCirlce: Story = {
name: 'Primary/Icon only/Circle',
args: {
icon,
shape: 'circle',
},
}
export const Success: Story = {
args: {
type: 'positive',
variant: 'positive',
children: 'Click me',
},
}
export const Outline: Story = {
args: {
type: 'outline',
variant: 'outline',
children: 'Click me',
},
}
export const Ghost: Story = {
args: {
type: 'ghost',
variant: 'ghost',
children: 'Click me',
},
}
export const Danger: Story = {
args: {
type: 'danger',
variant: 'danger',
children: 'Click me',
},
}

View File

@ -1,12 +1,73 @@
import { forwardRef } from 'react'
import { cloneElement, forwardRef } from 'react'
import { Stack, styled } from '@tamagui/core'
import { Paragraph } from '../typography'
import type { GetProps } from '@tamagui/core'
import type { GetVariants, MapVariant, PressableProps } from '../types'
import type { StackProps } from '@tamagui/core'
import type { Ref } from 'react'
type Variants = GetVariants<typeof Base>
type Props = PressableProps & {
variant?: Variants['variant']
size?: Variants['size']
shape?: 'default' | 'circle'
children?: string
icon?: React.ReactElement
iconAfter?: React.ReactElement
disabled?: boolean
}
const textColors: MapVariant<typeof Base, 'variant'> = {
primary: '$white-100',
positive: '$white-100',
grey: '$neutral-100',
darkGrey: '$neutral-100',
outline: '$neutral-100',
ghost: '$neutral-100',
danger: '$white-100',
}
const Button = (props: Props, ref: Ref<HTMLButtonElement>) => {
const {
variant = 'primary',
shape = 'default',
size = 40,
icon = null,
iconAfter = null,
children,
...buttonProps
} = props
// TODO: provider aria-label if button has only icon
const iconOnly = !children && Boolean(icon)
const textColor = textColors[variant]
return (
<Base
{...(buttonProps as unknown as StackProps)} // TODO: Tamagui has incorrect types for PressableProps
ref={ref}
variant={variant}
radius={shape === 'circle' ? 'full' : size}
size={size}
iconOnly={iconOnly}
>
{icon ? cloneElement(icon, { color: textColor }) : null}
<ButtonText color={textColor} size={size}>
{children}
</ButtonText>
{iconAfter ? cloneElement(iconAfter, { color: textColor }) : null}
</Base>
)
}
const _Button = forwardRef(Button)
export { _Button as Button }
export type { Props as ButtonProps }
const Base = styled(Stack, {
tag: 'button',
name: 'Button',
@ -16,26 +77,24 @@ const Base = styled(Stack, {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 16,
paddingTop: 7,
paddingBottom: 9,
cursor: 'pointer',
userSelect: 'none',
animation: 'fast',
borderWidth: 1,
borderColor: 'transparent',
animation: 'fast',
variants: {
type: {
variant: {
primary: {
backgroundColor: '$primary',
hoverStyle: { backgroundColor: '$primaryHover' },
pressStyle: { backgroundColor: '$primaryHover' },
backgroundColor: '$primary-50',
hoverStyle: { backgroundColor: '$primary-60' },
// TODO: update background color
pressStyle: { backgroundColor: '$primary-50' },
},
positive: {
backgroundColor: '$success-50',
hoverStyle: { backgroundColor: '$success-60' },
// TODO: update background color
pressStyle: { backgroundColor: '$success-50' },
},
grey: {
@ -51,7 +110,7 @@ const Base = styled(Stack, {
outline: {
borderWidth: 1,
borderColor: '$neutral-30',
hoverStyle: { borderColor: '$neutral-30' },
hoverStyle: { borderColor: '$neutral-40' },
pressStyle: { borderColor: '$neutral-50' },
},
ghost: {
@ -62,6 +121,7 @@ const Base = styled(Stack, {
danger: {
backgroundColor: '$danger',
hoverStyle: { backgroundColor: '$danger-60' },
// TODO: update background color
pressStyle: { backgroundColor: '$danger' },
},
},
@ -75,32 +135,41 @@ const Base = styled(Stack, {
size: {
40: {
minHeight: 40,
borderRadius: 12,
height: 40,
paddingHorizontal: 16,
paddingTop: 7,
paddingBottom: 9,
gap: 4,
},
32: {
minHeight: 32,
borderRadius: 10,
paddingHorizontal: 16,
paddingTop: 4,
paddingBottom: 6,
height: 32,
paddingHorizontal: 12,
gap: 4,
},
24: {
minHeight: 24,
borderRadius: 8,
height: 24,
paddingHorizontal: 8,
paddingTop: 2,
paddingBottom: 4,
},
},
radius: {
full: {
borderRadius: 40,
},
40: {
borderRadius: 12,
},
32: {
borderRadius: 10,
},
24: {
borderRadius: 8,
},
},
iconOnly: {
true: {
space: 0,
paddingHorizontal: 8,
gap: 0,
padding: 0,
aspectRatio: 1,
},
},
} as const,
@ -113,30 +182,6 @@ const ButtonText = styled(Paragraph, {
weight: 'medium',
variants: {
type: {
primary: {
color: '$white-100',
},
positive: {
color: '$white-100',
},
grey: {
color: '$neutral-100',
},
darkGrey: {
color: '$neutral-100',
},
outline: {
color: '$neutral-100',
},
ghost: {
color: '$neutral-100',
},
danger: {
color: '$white-100',
},
},
size: {
40: {
variant: 'normal',
@ -150,42 +195,3 @@ const ButtonText = styled(Paragraph, {
},
} as const,
})
type BaseProps = GetProps<typeof Base>
type Props = BaseProps & {
children?: string
type?: BaseProps['type']
size?: BaseProps['size']
disabled?: boolean
icon?: React.ReactNode
iconAfter?: React.ReactNode
}
const Button = (props: Props, ref: Ref<HTMLButtonElement>) => {
const {
type = 'primary',
size = 40,
children,
icon,
iconAfter,
...rest
} = props
const iconOnly = !children && Boolean(icon)
return (
<Base {...rest} ref={ref} type={type} size={size} iconOnly={iconOnly}>
<ButtonText type={type} size={size}>
{icon}
{children}
{iconAfter}
</ButtonText>
</Base>
)
}
const _Button = forwardRef(Button)
export { _Button as Button }
export type { Props as ButtonProps }

View File

@ -10,7 +10,7 @@ import {
ReactionIcon,
} from '@status-im/icons/20'
import { BlurView } from 'expo-blur'
import { AnimatePresence, Stack, XStack, YStack } from 'tamagui'
import { AnimatePresence, Stack, XStack } from 'tamagui'
import { Button } from '../button'
import { IconButton } from '../icon-button'
@ -18,6 +18,7 @@ import { Image } from '../image'
import { Input } from '../input'
import { useChatDispatch, useChatState } from '../provider'
import { Reply } from '../reply'
import { Shadow } from '../shadow'
interface Props {
blur?: boolean
@ -43,6 +44,8 @@ const Composer = (props: Props) => {
const chatState = useChatState()
const chatDispatch = useChatDispatch()
const showSendButton = text !== '' || imagesData.length > 0
return (
<BlurView
intensity={40}
@ -51,20 +54,16 @@ const Composer = (props: Props) => {
width: '100%',
}}
>
<YStack
<Shadow
variant={iconButtonBlurred ? 'none' : '$2'}
inverted
animation="fast"
backgroundColor={iconButtonBlurred ? '$blurBackground' : '$background'}
shadowColor={iconButtonBlurred ? 'none' : 'rgba(9, 16, 28, 0.08)'}
shadowOffset={{ width: 4, height: iconButtonBlurred ? 0 : 4 }}
shadowRadius={20}
borderTopLeftRadius={20}
borderTopRightRadius={20}
px={16}
width="100%"
py={12}
style={{
elevation: 10,
}}
>
{chatState?.type === 'reply' && (
<Stack paddingLeft={4} paddingBottom={4}>
@ -169,42 +168,38 @@ const Composer = (props: Props) => {
variant="outline"
icon={<ImageIcon />}
disabled={isImageUploadDisabled}
blurred={iconButtonBlurred}
blur={iconButtonBlurred}
/>
</label>
<IconButton
variant="outline"
icon={<ReactionIcon />}
blurred={iconButtonBlurred}
blur={iconButtonBlurred}
/>
<IconButton
variant="outline"
icon={<FormatIcon />}
disabled
blurred={iconButtonBlurred}
blur={iconButtonBlurred}
/>
</Stack>
{text || imagesData.length > 0 ? (
// TODO fix styles for circular button. Also the color is different from the design and we have layout shift because of the size.
{showSendButton ? (
<Button
variant="primary"
shape="circle"
icon={<ArrowUpIcon />}
height={32}
size={32}
width={32}
borderRadius={32}
justifyContent="center"
alignItems="center"
type="positive"
/>
) : (
<IconButton
<Button
variant="outline"
icon={<AudioIcon />}
blurred={iconButtonBlurred}
size={32}
// blurred={iconButtonBlurred}
/>
)}
</XStack>
</YStack>
</Shadow>
</BlurView>
)
}

View File

@ -0,0 +1,69 @@
import { useState } from 'react'
import type { PressableProps } from '../types'
import type { ColorTokens } from 'tamagui'
type Config = {
default: ColorTokens
hover: ColorTokens
press: ColorTokens
active: ColorTokens
}
type Return = {
color: ColorTokens
pressableProps: Pick<
PressableProps,
'onHoverIn' | 'onHoverOut' | 'onPressIn' | 'onPressOut'
>
}
export const usePressableColors = (
styles: Config,
props: Partial<PressableProps> & {
'aria-expanded'?: boolean
'aria-selected'?: boolean
selected?: boolean
}
): Return => {
const [hovered, setHovered] = useState(false)
const [pressed, setPressed] = useState(false)
/**
* Order of precedence:
* 1. active
* 2. press
* 3. hover
* 4. default
*/
const key =
props['aria-expanded'] || props['aria-selected']
? 'active'
: pressed
? 'press'
: hovered
? 'hover'
: 'default'
return {
color: styles[key],
pressableProps: {
onHoverIn: event => {
props.onHoverIn?.(event)
setHovered(true)
},
onHoverOut: event => {
props.onHoverOut?.(event)
setHovered(false)
},
onPressIn: event => {
props.onPressIn?.(event)
setPressed(true)
},
onPressOut: event => {
props.onPressOut?.(event)
setPressed(false)
},
} as const,
}
}

View File

@ -1,4 +1,5 @@
import { OptionsIcon } from '@status-im/icons/20'
import { Stack } from 'tamagui'
import { IconButton } from './icon-button'
@ -8,6 +9,12 @@ import type { Meta, StoryObj } from '@storybook/react'
const meta: Meta<typeof IconButton> = {
component: IconButton,
argTypes: {},
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=10466-128996&t=GxddSvW99WvZQY0A-11',
},
},
}
type Story = StoryObj<typeof IconButton>
@ -17,6 +24,29 @@ export const Default: Story = {
args: {
icon: <OptionsIcon />,
},
render: args => {
return (
<Stack space backgroundColor="" padding={40}>
<Stack flexDirection="row" gap={8}>
<IconButton {...args} variant="default" />
<IconButton {...args} variant="outline" selected />
<IconButton {...args} variant="ghost" />
<IconButton {...args} variant="default" aria-selected />
<IconButton {...args} variant="outline" aria-selected />
<IconButton {...args} variant="ghost" aria-selected />
</Stack>
<Stack flexDirection="row" gap={8}>
<IconButton {...args} variant="default" blur />
<IconButton {...args} variant="outline" blur />
<IconButton {...args} variant="ghost" blur />
<IconButton {...args} variant="default" blur aria-selected />
<IconButton {...args} variant="outline" blur aria-selected />
<IconButton {...args} variant="ghost" blur aria-selected />
</Stack>
</Stack>
)
},
}
export default meta

View File

@ -1,110 +1,20 @@
import { cloneElement, forwardRef } from 'react'
import { Stack, styled } from '@tamagui/core'
import { Stack, styled } from 'tamagui'
import { usePressableColors } from '../hooks/use-pressable-colors'
import type { GetVariants, PressableProps } from '../types'
import type { StackProps } from '@tamagui/core'
import type { Ref } from 'react'
const Base = styled(Stack, {
name: 'IconButton',
accessibilityRole: 'button',
type Variants = GetVariants<typeof Base>
cursor: 'pointer',
userSelect: 'none',
borderRadius: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
animation: 'fast',
width: 30,
height: 30,
borderWidth: 1,
padding: 4,
variants: {
variant: {
default: {
backgroundColor: '$iconButtonBackground',
borderColor: 'transparent',
hoverStyle: {
backgroundColor: '$iconButtonBackgroundHover',
},
pressStyle: {
backgroundColor: '$iconButtonBackgroundHover',
},
},
outline: {
backgroundColor: 'transparent',
borderColor: '$iconButtonOutlineBorder',
hoverStyle: {
borderColor: '$iconButtonOutlineBorderHover',
},
pressStyle: {
borderColor: '$iconButtonOutlineBorderHover',
},
},
},
blurred: {
default: {
backgroundColor: '$iconButtonBackgroundBlurred',
hoverStyle: {
backgroundColor: '$iconButtonBackgroundBlurredHover',
},
pressStyle: {
backgroundColor: 'iconButtonBackgroundBlurredHover',
},
},
outline: {
borderColor: '$iconButtonOutlineBorderBlurred',
hoverStyle: {
borderColor: '$iconButtonOutlineBorderBlurredHover',
},
pressStyle: {
borderColor: '$iconButtonOutlineBorderBlurredHover',
},
},
},
selected: {
default: {
backgroundColor: '$iconButtonBackgroundSelected',
borderColor: '$iconButtonBorderSelected',
},
defaultWithBlur: {
backgroundColor: '$iconButtonBackgroundBlurredSelected',
borderColor: '$iconButtonBorderBlurredSelected',
},
outline: {
backgroundColor: '$iconButtonOutlineBackgroundSelected',
borderColor: '$iconButtonOutlineBorderSelected',
},
outlineWithBlur: {
backgroundColor: '$iconButtonOutBackgroundBlurredSelected',
borderColor: '$iconButtonOutlineBorderBlurredSelected',
},
},
disabled: {
true: {
opacity: 0.3,
cursor: 'default',
},
},
} as const,
})
interface Props {
type Props = PressableProps & {
icon: React.ReactElement
onPress?: () => void
variant?: Variants['variant']
selected?: boolean
blurred?: boolean
variant?: 'default' | 'outline'
blur?: boolean
disabled?: boolean
// FIXME: enforce aria-label for accessibility
// 'aria-label'?: string
@ -113,80 +23,34 @@ interface Props {
'aria-selected'?: boolean
}
const iconColor = {
default: {
default: '$iconButtonColor',
defaultBlurred: '$iconButtonColorBlurred',
selected: '$iconButtonColorSelected',
selectedBlurred: '$iconButtonColorBlurred',
},
outline: {
default: '$iconButtonColorOutline',
defaultBlurred: '$iconButtonColorOutlineBlurred',
selected: '$iconButtonColorOutlineSelected',
selectedBlurred: '$iconButtonColorOutlineBlurred',
},
}
const getStateForIconColor = ({
blurred,
selected,
}: {
blurred?: boolean
selected?: boolean
}) => {
if (!selected && blurred) {
return 'defaultBlurred'
}
if (selected && blurred) {
return 'selectedBlurred'
}
if (selected && !blurred) {
return 'selected'
}
return 'default'
}
const getSelectedVariant = ({
selected,
blurred,
variant,
}: {
selected?: boolean
blurred?: boolean
variant?: 'default' | 'outline'
}) => {
if (!selected) {
return undefined
}
if (blurred && variant === 'default') {
return 'defaultWithBlur'
}
if (blurred && variant === 'outline') {
return 'outlineWithBlur'
}
return variant
}
const IconButton = (props: Props, ref: Ref<HTMLButtonElement>) => {
const { icon, blurred, variant = 'default', ...rest } = props
const { icon, blur, variant = 'default', ...buttonProps } = props
const { pressableProps, color } = usePressableColors(
{
default: blur ? '$neutral-80-opa-70' : '$neutral-50',
hover: blur ? '$neutral-80-opa-70' : '$neutral-50',
press: '$neutral-100',
active: '$neutral-100',
},
props
)
const selected =
props.selected || props['aria-expanded'] || props['aria-selected']
const state = getStateForIconColor({ blurred, selected })
const selectedVariant = getSelectedVariant({ selected, variant, blurred })
return (
<Base
{...rest}
{...(buttonProps as unknown as StackProps)} // TODO: Tamagui has incorrect types for PressableProps
{...(pressableProps as unknown as StackProps)} // TODO: Tamagui has incorrect types for PressableProps
ref={ref}
variant={variant}
selected={selectedVariant}
blurred={blurred ? variant : undefined}
variant={blur ? undefined : variant}
active={blur ? undefined : selected ? variant : undefined}
variantBlur={blur ? variant : undefined}
activeBlur={blur ? (selected ? variant : undefined) : undefined}
>
{cloneElement(icon, {
color: iconColor[variant][state],
color,
size: 20,
})}
</Base>
@ -197,3 +61,127 @@ const _IconButton = forwardRef(IconButton)
export { _IconButton as IconButton }
export type { Props as IconButtonProps }
const Base = styled(Stack, {
name: 'IconButton',
tag: 'button',
accessibilityRole: 'button',
cursor: 'pointer',
userSelect: 'none',
borderRadius: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: 4,
width: 32,
height: 32,
borderWidth: 1,
borderColor: 'transparent',
animation: 'fast',
variants: {
variant: {
default: {
backgroundColor: '$neutral-10',
borderColor: 'transparent',
hoverStyle: { backgroundColor: '$neutral-20' },
pressStyle: {
backgroundColor: '$neutral-20',
borderColor: '$neutral-30',
},
},
outline: {
backgroundColor: 'transparent',
borderColor: '$neutral-20',
hoverStyle: { borderColor: '$neutral-30' },
pressStyle: {
borderColor: '$neutral-20',
backgroundColor: '$neutral-10',
},
},
ghost: {
backgroundColor: 'transparent',
hoverStyle: { backgroundColor: '$neutral-10' },
pressStyle: {
backgroundColor: '$neutral-10',
borderColor: '$neutral-20',
},
},
},
active: {
default: {
backgroundColor: '$neutral-20',
borderColor: '$neutral-30',
},
outline: {
borderColor: '$neutral-20',
backgroundColor: '$neutral-10',
},
ghost: {
backgroundColor: '$neutral-10',
borderColor: '$neutral-20',
},
},
variantBlur: {
default: {
backgroundColor: '$neutral-80-opa-5',
borderColor: 'transparent',
hoverStyle: { backgroundColor: '$neutral-80-opa-10' },
pressStyle: {
backgroundColor: '$neutral-80-opa-10',
borderColor: '$neutral-80-opa-5',
},
},
outline: {
backgroundColor: 'transparent',
borderColor: '$neutral-80-opa-10',
hoverStyle: { borderColor: '$neutral-80-opa-20' },
pressStyle: {
borderColor: '$neutral-80-opa-10',
backgroundColor: '$neutral-80-opa-5',
},
},
ghost: {
backgroundColor: 'transparent',
hoverStyle: { backgroundColor: '$neutral-80-opa-5' },
pressStyle: {
backgroundColor: '$neutral-80-opa-5',
borderColor: '$neutral-80-opa-10',
},
},
},
activeBlur: {
default: {
backgroundColor: '$neutral-80-opa-10',
borderColor: '$neutral-80-opa-5',
},
outline: {
borderColor: '$neutral-80-opa-10',
backgroundColor: '$neutral-80-opa-5',
},
ghost: {
backgroundColor: '$neutral-80-opa-5',
borderColor: '$neutral-80-opa-10',
},
},
disabled: {
true: {
opacity: 0.3,
cursor: 'default',
},
},
} as const,
})

View File

@ -3,48 +3,24 @@ import { forwardRef } from 'react'
import { setupReactNative, styled } from '@tamagui/core'
import { Image as RNImage } from 'react-native'
import type { GetProps } from '@tamagui/core'
import type { GetProps, GetVariants } from '../types'
import type { Ref } from 'react'
import type { ImagePropsBase as RNImageProps } from 'react-native'
setupReactNative({
Image: RNImage,
})
const Base = styled(RNImage, {
name: 'Image',
position: 'relative',
zIndex: 1,
source: {
uri: '',
},
type Variants = GetVariants<typeof Base>
variants: {
radius: {
12: {
borderRadius: 12,
},
full: {
borderRadius: 9999,
},
},
},
})
type ImageProps = GetProps<typeof Base>
interface Props {
type Props = GetProps<typeof Base> & {
src: string
width: number | 'full'
height?: number
aspectRatio?: ImageProps['aspectRatio']
radius?: ImageProps['radius']
onLoad?: RNImageProps['onLoad']
onError?: RNImageProps['onError']
radius?: Variants['radius']
}
const Image = (props: Props, ref: Ref<HTMLImageElement>) => {
const { src, aspectRatio, radius, ...rest } = props
const { src, radius = 'none', aspectRatio, ...imageProps } = props
const width = props.width === 'full' ? '100%' : props.width
const height = aspectRatio ? undefined : props.height
@ -56,19 +32,39 @@ const Image = (props: Props, ref: Ref<HTMLImageElement>) => {
return (
<Base
{...rest}
{...imageProps}
ref={ref}
source={source}
width={width}
height={height}
radius={radius}
aspectRatio={aspectRatio}
radius={radius}
/>
)
}
// TODO?: this was used in @tamagui/image package. Why?
// focusableInputHOC(Image)
const _Image = Base.extractable(forwardRef(Image))
const _Image = forwardRef(Image)
export { _Image as Image }
export type { Props as ImageProps }
const Base = styled(RNImage, {
name: 'Image',
position: 'relative',
zIndex: 1,
source: {
uri: '',
},
variants: {
radius: {
none: {},
12: {
borderRadius: 12,
},
full: {
borderRadius: 9999,
},
},
},
})

View File

@ -11,10 +11,10 @@ import {
PinIcon,
ReplyIcon,
} from '@status-im/icons/20'
import { Stack } from 'tamagui'
import { DropdownMenu } from '../../dropdown-menu'
import { IconButton } from '../../icon-button'
import { Shadow } from '../../shadow'
import { ReactionPopover } from './reaction-popover'
import type { ReactionsType } from '../types'
@ -35,21 +35,19 @@ export const Actions = (props: Props) => {
}, [onOpenChange])
return (
<Stack
backgroundColor="$white-100"
borderWidth={1}
borderColor="$neutral-10"
borderRadius={12}
padding={2}
space={2}
<Shadow
variant="$1"
overflow="hidden"
position="absolute"
top={-16}
right={0}
top={-8}
right={8}
borderRadius={12}
borderWidth={1}
borderColor="$neutral-10"
backgroundColor="$white-100"
padding={2}
space={2}
flexDirection="row"
shadowRadius={20}
shadowOffset={{ width: 0, height: 4 }}
shadowColor="rgba(9, 16, 28, 0.08)"
zIndex={10}
>
{/* REACTION */}
@ -59,29 +57,25 @@ export const Actions = (props: Props) => {
sideOffset={6}
onOpenChange={onOpenChange}
>
<IconButton variant="outline" icon={<AddReactionIcon />} />
<IconButton variant="ghost" icon={<AddReactionIcon />} />
</ReactionPopover>
{/* REPLY */}
<IconButton
variant="outline"
icon={<ReplyIcon />}
onPress={onReplyPress}
/>
<IconButton variant="ghost" icon={<ReplyIcon />} onPress={onReplyPress} />
{/* EDIT */}
<IconButton variant="outline" icon={<EditIcon />} onPress={onEditPress} />
<IconButton variant="ghost" icon={<EditIcon />} onPress={onEditPress} />
{/* DELETE */}
{/* <IconButton
variant="outline"
variant="ghost"
icon={<DeleteIcon />}
onPress={onDeletePress}
/> */}
{/* OPTIONS MENU */}
<DropdownMenu modal={false} onOpenChange={onOpenChange}>
<IconButton variant="outline" icon={<OptionsIcon />} />
<IconButton variant="ghost" icon={<OptionsIcon />} />
<DropdownMenu.Content align="end" sideOffset={10}>
<DropdownMenu.Item
icon={<EditIcon />}
@ -124,6 +118,6 @@ export const Actions = (props: Props) => {
/>
</DropdownMenu.Content>
</DropdownMenu>
</Stack>
</Shadow>
)
}

View File

@ -1,10 +1,9 @@
import { useState } from 'react'
import { PinIcon } from '@status-im/icons/16'
import { View } from 'react-native'
import { Stack, styled, Unspaced, XStack, YStack } from 'tamagui'
import { Author } from '../author/author'
import { Author } from '../author'
import { Avatar } from '../avatar'
import { Image } from '../image'
import { useChatDispatch } from '../provider'
@ -23,7 +22,7 @@ interface Props {
pinned?: boolean
}
const Wrapper = styled(View, {
const Base = styled(Stack, {
position: 'relative',
paddingHorizontal: 8,
paddingVertical: 8,
@ -36,6 +35,7 @@ const Wrapper = styled(View, {
backgroundColor: '$neutral-5',
},
},
pinned: {
true: {
backgroundColor: '$blue-50-opa-5',
@ -48,15 +48,16 @@ const Message = (props: Props) => {
const { text, images, reactions, reply, pinned } = props
const [hovered, setHovered] = useState(false)
const [actionsOpen, setActionsOpen] = useState(false)
const [showActions, setShowActions] = useState(false)
const active = actionsOpen || hovered
const active = showActions || hovered
const hasReactions = Object.keys(reactions).length > 0
// <Sheet press="long">
const dispatch = useChatDispatch()
return (
<Wrapper
<Base
active={active}
onHoverIn={() => setHovered(true)}
onHoverOut={() => setHovered(false)}
@ -65,7 +66,7 @@ const Message = (props: Props) => {
<Unspaced>
<Actions
reactions={reactions}
onOpenChange={setActionsOpen}
onOpenChange={setShowActions}
onReplyPress={() => dispatch({ type: 'reply', messageId: '1' })}
onEditPress={() => dispatch({ type: 'edit', messageId: '1' })}
/>
@ -113,7 +114,12 @@ const Message = (props: Props) => {
/>
{text && (
<Paragraph flexGrow={0} weight="regular" color="$neutral-100">
<Paragraph
flexGrow={0}
weight="regular"
color="$neutral-100"
userSelect="text"
>
{text}
</Paragraph>
)}
@ -130,14 +136,14 @@ const Message = (props: Props) => {
</Stack>
))}
{reactions && (
{hasReactions && (
<Stack paddingTop={8}>
<Reactions reactions={reactions} />
</Stack>
)}
</YStack>
</XStack>
</Wrapper>
</Base>
)
}

View File

@ -23,7 +23,7 @@ export const Default: Story = {
args: {},
render: args => (
<Popover {...args}>
<Button type="primary">Trigger</Button>
<Button variant="primary">Trigger</Button>
<Popover.Content>some content</Popover.Content>
</Popover>
),

View File

@ -13,10 +13,72 @@ import { Stack, styled } from '@tamagui/core'
import { Paragraph } from '../typography'
import type { GetProps } from '@tamagui/core'
import type { GetVariants } from '../types'
import type { StackProps } from '@tamagui/core'
import type { Ref } from 'react'
import type { PressableProps } from 'react-native'
type Variants = GetVariants<typeof Button>
export const REACTIONS = {
love: LoveIcon,
laugh: LaughIcon,
'thumbs-up': ThumbsUpIcon,
'thumbs-down': ThumbsDownIcon,
sad: SadIcon,
angry: AngryIcon,
add: AddReactionIcon,
} as const
type Props = PressableProps & {
icon: keyof typeof REACTIONS
variant?: Variants['variant']
size?: Variants['size']
// FIXME: use aria-selected
selected?: boolean
count?: number
// FIXME: update to latest RN
'aria-expanded'?: boolean
'aria-selected'?: boolean
}
const ReactButton = (props: Props, ref: Ref<HTMLButtonElement>) => {
const {
icon,
variant = 'outline',
size = 40,
count,
...pressableProps
} = props
const Icon = REACTIONS[icon]
const selected =
props.selected || props['aria-expanded'] || props['aria-selected']
return (
<Button
{...(pressableProps as unknown as StackProps)} // TODO: Tamagui has incorrect types for PressableProps
ref={ref}
variant={variant}
size={size}
selected={selected}
>
<Icon color="$neutral-100" />
{count && (
<Paragraph weight="medium" variant="smaller" whiteSpace="nowrap">
{count}
</Paragraph>
)}
</Button>
)
}
const _ReactButton = forwardRef(ReactButton)
export { _ReactButton as ReactButton }
export type { Props as ReactButtonProps }
const Button = styled(Stack, {
name: 'ReactButton',
accessibilityRole: 'button',
@ -78,64 +140,3 @@ const Button = styled(Stack, {
},
} as const,
})
type ButtonProps = GetProps<typeof Button>
export const REACTIONS = {
love: LoveIcon,
laugh: LaughIcon,
'thumbs-up': ThumbsUpIcon,
'thumbs-down': ThumbsDownIcon,
sad: SadIcon,
angry: AngryIcon,
add: AddReactionIcon,
} as const
interface Props extends PressableProps {
icon: keyof typeof REACTIONS
variant?: ButtonProps['variant']
size?: ButtonProps['size']
// FIXME: use aria-selected
selected?: boolean
count?: number
// FIXME: update to latest RN
'aria-expanded'?: boolean
'aria-selected'?: boolean
}
const ReactButton = (props: Props, ref: Ref<HTMLButtonElement>) => {
const {
icon,
variant = 'outline',
size = 40,
count,
...pressableProps
} = props
const Icon = REACTIONS[icon]
const selected =
props.selected || props['aria-expanded'] || props['aria-selected']
return (
<Button
{...(pressableProps as any)}
ref={ref}
variant={variant}
size={size}
selected={selected}
>
<Icon color="$neutral-100" />
{count && (
<Paragraph weight="medium" variant="smaller" whiteSpace="nowrap">
{count}
</Paragraph>
)}
</Button>
)
}
const _ReactButton = forwardRef(ReactButton)
export { _ReactButton as ReactButton }
export type { Props as ReactButtonProps }

View File

@ -27,7 +27,7 @@ const Reply = (props: Props) => {
</Stack>
</Unspaced>
<Avatar size={20} src={src} />
<Avatar size={16} src={src} />
<Paragraph variant="smaller" weight="semibold" color="$neutral-100">
{name}
@ -64,12 +64,11 @@ const Reply = (props: Props) => {
>
{content}
{/* FIXME: This should be regular button with size 24 */}
{onClose && (
<Button
type="outline"
size={24}
icon={<CloseIcon />}
variant="outline"
size={24}
onPress={onClose}
/>
)}

View File

@ -0,0 +1 @@
export { Shadow, type ShadowProps } from './shadow'

View File

@ -0,0 +1,61 @@
import { Stack } from 'tamagui'
import { Shadow } from './shadow'
import type { Meta, StoryObj } from '@storybook/react'
const meta: Meta<typeof Shadow> = {
component: Shadow,
argTypes: {},
args: {},
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/v98g9ZiaSHYUdKWrbFg9eM/Foundations?node-id=624-752&t=ppNe6QC4ntgNciqw-11',
},
},
}
export default meta
type Story = StoryObj<typeof Shadow>
export const Default: Story = {
args: {},
render: () => (
<Stack gap={40} flexDirection="row">
<Shadow
variant="$1"
width={50}
height={50}
borderWidth={1}
borderColor="$neutral-20"
borderRadius={12}
/>
<Shadow
variant="$2"
width={50}
height={50}
borderWidth={1}
borderColor="$neutral-20"
borderRadius={12}
/>
<Shadow
variant="$3"
width={50}
height={50}
borderWidth={1}
borderColor="$neutral-20"
borderRadius={12}
/>
<Shadow
variant="$4"
width={50}
height={50}
borderWidth={1}
borderColor="$neutral-20"
borderRadius={12}
/>
</Stack>
),
}

View File

@ -0,0 +1,97 @@
import { forwardRef } from 'react'
import { Stack, styled } from 'tamagui'
import type { GetVariants } from '../types'
import type { StackProps } from '@tamagui/core'
import type { Ref } from 'react'
import type { View } from 'react-native'
type Variants = GetVariants<typeof Base>
type Props = StackProps & {
variant?: Variants['variant']
inverted?: boolean
}
const Shadow = (props: Props, ref: Ref<View>) => {
const { variant = '$1', inverted = false, ...stackProps } = props
return (
<Base
{...stackProps}
ref={ref}
variant={inverted ? undefined : variant}
inverted={inverted ? variant : undefined}
/>
)
}
const _Shadow = forwardRef(Shadow)
export { _Shadow as Shadow }
export type { Props as ShadowProps }
const Base = styled(Stack, {
variants: {
variant: {
$1: {
// box-shadow: 0px 2px 20px 0px hsla(218, 51%, 7%, 0.04);
shadowOffset: { width: 0, height: 2 },
shadowRadius: 20,
shadowColor: 'hsla(218, 51%, 7%, 0.04)',
},
$2: {
// box-shadow: 0px 4px 20px 0px hsla(218, 51%, 7%, 0.08);
shadowOffset: { width: 0, height: 4 },
shadowRadius: 20,
shadowColor: 'hsla(218, 51%, 7%, 0.08)',
},
$3: {
// box-shadow: 0px 8px 30px 0px hsla(218, 51%, 7%, 0.12);
shadowOffset: { width: 0, height: 8 },
shadowRadius: 30,
shadowColor: 'hsla(218, 51%, 7%, 0.12)',
},
$4: {
// box-shadow: 0px 12px 56px 0px hsla(218, 51%, 7%, 0.16);
shadowOffset: { width: 0, height: 12 },
shadowRadius: 56,
shadowColor: 'hsla(218, 51%, 7%, 0.16)',
},
none: {},
},
inverted: {
$1: {
// box-shadow: 0px -2px 20px 0px hsla(218, 51%, 7%, 0.04);
shadowOffset: { width: 0, height: -2 },
shadowRadius: 20,
shadowColor: 'hsla(218, 51%, 7%, 0.04)',
},
$2: {
// box-shadow: 0px -4px 20px 0px hsla(218, 51%, 7%, 0.08);
shadowOffset: { width: 0, height: -4 },
shadowRadius: 20,
shadowColor: 'hsla(218, 51%, 7%, 0.08)',
},
$3: {
// box-shadow: 0px -8px 30px 0px hsla(218, 51%, 7%, 0.12);
shadowOffset: { width: 0, height: -8 },
shadowRadius: 30,
shadowColor: 'hsla(218, 51%, 7%, 0.12)',
},
$4: {
// box-shadow: 0px 12px 56px 0px hsla(218, 51%, 7%, 0.16);
shadowOffset: { width: 0, height: 12 },
shadowRadius: 56,
shadowColor: 'hsla(218, 51%, 7%, 0.16)',
},
none: {},
},
} as const,
defaultVariants: {
variant: '$1',
},
})

View File

@ -4,7 +4,7 @@ export interface Channel {
description: string
emoji: string
channelStatus?: 'muted' | 'normal' | 'withMessages' | 'withMentions'
numberOfMessages?: number
unreadCount?: number
}
export interface ChannelGroup {
@ -49,7 +49,7 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
emoji: randomEmoji(),
description: 'Share random funny stuff with the community. Play nice.',
channelStatus: 'withMentions',
numberOfMessages: 3,
unreadCount: 3,
},
],
},
@ -68,7 +68,7 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
id: 'jobs',
title: '# jobs',
channelStatus: 'withMentions',
numberOfMessages: 3,
unreadCount: 3,
emoji: randomEmoji(),
description: 'Share random funny stuff with the community. Play nice.',
},
@ -76,7 +76,7 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
id: 'events',
title: '# events',
channelStatus: 'withMentions',
numberOfMessages: 2,
unreadCount: 2,
emoji: randomEmoji(),
description: 'Share random funny stuff with the community. Play nice.',
},

View File

@ -53,7 +53,7 @@ const Sidebar = (props: Props) => {
<Stack paddingHorizontal={16} paddingBottom={16}>
<Stack marginTop={-40} marginBottom={12}>
<Avatar
withOutline
outline
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={80}
/>
@ -72,18 +72,15 @@ const Sidebar = (props: Props) => {
key={group.id}
initialExpanded={group.id === 'welcome'}
title={group.title}
numberOfNewMessages={group.unreadCount}
unreadCount={group.unreadCount}
>
{group.channels.map((channel, index) => {
const isLastChannelOfTheList = index === group.channels.length - 1
{group.channels.map(channel => {
return (
<AccordionItem
key={channel.id}
channel={channel}
isSelected={selectedChannelId === channel.id}
selected={selectedChannelId === channel.id}
onPress={() => onChannelPress(channel.id)}
mb={isLastChannelOfTheList ? 8 : 0}
/>
)
})}

View File

@ -22,7 +22,7 @@ export const Default: Story = {
},
render: args => (
<Tooltip {...args}>
<Button type="outline">Trigger</Button>
<Button variant="outline">Trigger</Button>
</Tooltip>
),
}

View File

@ -51,7 +51,7 @@ const Topbar = (props: Props) => {
<IconButton
icon={<ArrowLeftIcon />}
onPress={() => goBack?.()}
blurred={blur}
blur={blur}
/>
</Stack>
@ -100,7 +100,7 @@ const Topbar = (props: Props) => {
icon={<MembersIcon />}
selected={membersVisisble}
onPress={onMembersPress}
blurred={blur}
blur={blur}
/>
</Stack>

View File

@ -0,0 +1,35 @@
import type {
ColorTokens,
GetBaseProps,
GetProps,
GetStyledVariants,
TamaguiComponent,
} from '@tamagui/core'
import type { PressableProps as NativePressableProps } from 'react-native'
type PressableProps = {
onHoverIn?: Exclude<NativePressableProps['onHoverIn'], null>
onHoverOut?: NativePressableProps['onHoverOut']
onPress?: NativePressableProps['onPress']
onPressIn?: NativePressableProps['onPressIn']
onPressOut?: NativePressableProps['onPressOut']
onLongPress?: NativePressableProps['onLongPress']
delayHoverIn?: NativePressableProps['delayHoverIn']
delayHoverOut?: NativePressableProps['delayHoverOut']
delayLongPress?: NativePressableProps['delayLongPress']
disabled?: NativePressableProps['disabled']
}
export type MapVariant<
C extends TamaguiComponent,
K extends keyof GetStyledVariants<C>,
V extends GetStyledVariants<C> = GetStyledVariants<C>
> = {
[key in V[K] & string]: ColorTokens
}
export type GetVariants<A extends TamaguiComponent> = Required<
GetStyledVariants<A>
>
export type { GetBaseProps, GetProps, PressableProps }