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:
parent
461a9bfe60
commit
6474b39bac
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -3,6 +3,7 @@ body,
|
|||
#root {
|
||||
height: 100%;
|
||||
overscroll-behavior: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
*::selection {
|
||||
|
|
|
@ -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]} />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
),
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
})
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
),
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export { Shadow, type ShadowProps } from './shadow'
|
|
@ -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>
|
||||
),
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
})
|
|
@ -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.',
|
||||
},
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -22,7 +22,7 @@ export const Default: Story = {
|
|||
},
|
||||
render: args => (
|
||||
<Tooltip {...args}>
|
||||
<Button type="outline">Trigger</Button>
|
||||
<Button variant="outline">Trigger</Button>
|
||||
</Tooltip>
|
||||
),
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 }
|
Loading…
Reference in New Issue