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>
|
<Heading color="$textPrimary">Rarible</Heading>
|
||||||
<Avatar
|
<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"
|
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}
|
size={48}
|
||||||
|
outline
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,6 +3,7 @@ body,
|
||||||
#root {
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
*::selection {
|
*::selection {
|
||||||
|
|
|
@ -8,7 +8,7 @@ const meta: Meta<typeof Accordion> = {
|
||||||
component: Accordion,
|
component: Accordion,
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
args: {
|
args: {
|
||||||
numberOfNewMessages: 3,
|
unreadCount: 3,
|
||||||
title: 'Welcome',
|
title: 'Welcome',
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
|
@ -27,7 +27,7 @@ export const Default: Story = {
|
||||||
<>
|
<>
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
key="welcome"
|
key="welcome"
|
||||||
isSelected
|
selected
|
||||||
channel={CHANNEL_GROUPS[0].channels[0]}
|
channel={CHANNEL_GROUPS[0].channels[0]}
|
||||||
/>
|
/>
|
||||||
<AccordionItem key="general" 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 { Label, Paragraph } from '../typography'
|
||||||
|
|
||||||
import type { GetProps } from '@tamagui/core'
|
|
||||||
|
|
||||||
type BaseProps = GetProps<typeof Stack>
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactElement[] | React.ReactElement
|
children: React.ReactElement[] | React.ReactElement
|
||||||
initialExpanded: boolean
|
initialExpanded: boolean
|
||||||
title: string
|
title: string
|
||||||
numberOfNewMessages?: number
|
unreadCount?: number
|
||||||
} & BaseProps
|
}
|
||||||
|
|
||||||
|
const Accordion = (props: Props) => {
|
||||||
|
const { children, initialExpanded, title, unreadCount } = props
|
||||||
|
|
||||||
const Accordion = ({
|
|
||||||
children,
|
|
||||||
initialExpanded,
|
|
||||||
title,
|
|
||||||
numberOfNewMessages,
|
|
||||||
}: Props) => {
|
|
||||||
const [isExpanded, setIsExpanded] = useState(initialExpanded)
|
const [isExpanded, setIsExpanded] = useState(initialExpanded)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
|
@ -32,6 +26,7 @@ const Accordion = ({
|
||||||
borderTopWidth={1}
|
borderTopWidth={1}
|
||||||
borderTopColor="$neutral-10"
|
borderTopColor="$neutral-10"
|
||||||
paddingHorizontal={8}
|
paddingHorizontal={8}
|
||||||
|
paddingBottom={8}
|
||||||
>
|
>
|
||||||
<Stack justifyContent="flex-start">
|
<Stack justifyContent="flex-start">
|
||||||
<Stack width="100%">
|
<Stack width="100%">
|
||||||
|
@ -68,7 +63,7 @@ const Accordion = ({
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Stack>
|
</Stack>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{!isExpanded && numberOfNewMessages && (
|
{!isExpanded && unreadCount !== 0 && (
|
||||||
<Stack
|
<Stack
|
||||||
key={`notifications-${title}}`}
|
key={`notifications-${title}}`}
|
||||||
width={20}
|
width={20}
|
||||||
|
@ -96,7 +91,7 @@ const Accordion = ({
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Label color="$white-100" weight="medium">
|
<Label color="$white-100" weight="medium">
|
||||||
{numberOfNewMessages}
|
{unreadCount}
|
||||||
</Label>
|
</Label>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
@ -6,10 +6,9 @@ import { Label, Paragraph } from '../typography'
|
||||||
import type { Channel } from '../sidebar/mock-data'
|
import type { Channel } from '../sidebar/mock-data'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isSelected?: boolean
|
selected?: boolean
|
||||||
onPress?: () => void
|
onPress?: () => void
|
||||||
channel: Channel
|
channel: Channel
|
||||||
mb?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const textColor = {
|
const textColor = {
|
||||||
|
@ -20,8 +19,9 @@ const textColor = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccordionItem = (props: Props) => {
|
const AccordionItem = (props: Props) => {
|
||||||
const { channel, isSelected, onPress, mb } = props
|
const { channel, selected, onPress } = props
|
||||||
const { emoji, title, channelStatus = 'normal', numberOfMessages } = channel
|
|
||||||
|
const { emoji, title, channelStatus = 'normal', unreadCount } = channel
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
|
@ -34,7 +34,7 @@ const AccordionItem = (props: Props) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
backgroundColor={isSelected ? '$primary-50-opa-10' : 'transparent'}
|
backgroundColor={selected ? '$primary-50-opa-10' : 'transparent'}
|
||||||
hoverStyle={{
|
hoverStyle={{
|
||||||
backgroundColor: '$primary-50-opa-5',
|
backgroundColor: '$primary-50-opa-5',
|
||||||
}}
|
}}
|
||||||
|
@ -51,7 +51,6 @@ const AccordionItem = (props: Props) => {
|
||||||
flexDirection="row"
|
flexDirection="row"
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
mb={mb}
|
|
||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
justifyContent="flex-start"
|
justifyContent="flex-start"
|
||||||
|
@ -91,7 +90,7 @@ const AccordionItem = (props: Props) => {
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Label color="$white-100" weight="medium">
|
<Label color="$white-100" weight="medium">
|
||||||
{numberOfMessages}
|
{unreadCount}
|
||||||
</Label>
|
</Label>
|
||||||
</Stack>
|
</Stack>
|
||||||
</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',
|
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 => (
|
render: args => (
|
||||||
<Stack space>
|
<Stack space flexDirection="row">
|
||||||
<Avatar {...args} size={56} />
|
<Stack space>
|
||||||
<Avatar {...args} size={52} />
|
<Avatar {...args} size={80} />
|
||||||
<Avatar {...args} size={48} />
|
<Avatar {...args} size={56} />
|
||||||
<Avatar {...args} size={32} />
|
<Avatar {...args} size={48} />
|
||||||
<Avatar {...args} size={20} />
|
<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>
|
</Stack>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -31,14 +47,18 @@ export const Default: Story = {
|
||||||
export const Rounded: Story = {
|
export const Rounded: Story = {
|
||||||
args: {
|
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',
|
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 => (
|
render: args => (
|
||||||
<Stack space>
|
<Stack space>
|
||||||
<Avatar {...args} size={56} shape="rounded" />
|
<Avatar {...args} size={80} />
|
||||||
<Avatar {...args} size={52} shape="rounded" />
|
<Avatar {...args} size={56} />
|
||||||
<Avatar {...args} size={48} shape="rounded" />
|
<Avatar {...args} size={48} />
|
||||||
<Avatar {...args} size={32} shape="rounded" />
|
<Avatar {...args} size={32} />
|
||||||
<Avatar {...args} size={20} shape="rounded" />
|
<Avatar {...args} size={28} />
|
||||||
|
<Avatar {...args} size={24} />
|
||||||
|
<Avatar {...args} size={20} />
|
||||||
|
<Avatar {...args} size={16} />
|
||||||
</Stack>
|
</Stack>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,160 +4,28 @@ import { Stack, styled, Text, Unspaced } from '@tamagui/core'
|
||||||
|
|
||||||
import { Image } from '../image'
|
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 })
|
type Props = {
|
||||||
|
|
||||||
// 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 {
|
|
||||||
src: string
|
src: string
|
||||||
size: NonNullable<BaseProps['size']>
|
size: 80 | 56 | 48 | 32 | 28 | 24 | 20 | 16
|
||||||
indicator?: 'online' | 'offline'
|
shape?: Variants['shape']
|
||||||
shape?: 'circle' | 'rounded'
|
outline?: Variants['outline']
|
||||||
withOutline?: boolean
|
indicator?: GetStyledVariants<typeof Indicator>['state']
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'
|
type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'
|
||||||
|
|
||||||
const Avatar = (props: Props) => {
|
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')
|
const [status, setStatus] = useState<ImageLoadingStatus>('idle')
|
||||||
|
|
||||||
|
@ -166,36 +34,163 @@ const Avatar = (props: Props) => {
|
||||||
}, [src])
|
}, [src])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base size={size} shape={shape} withOutline={withOutline}>
|
<Base size={size} shape={shape} outline={outline}>
|
||||||
{indicator && (
|
{indicator !== 'none' && (
|
||||||
<Unspaced>
|
<Unspaced>
|
||||||
<Indicator size={size} state={indicator} />
|
<Indicator size={size} state={indicator} />
|
||||||
</Unspaced>
|
</Unspaced>
|
||||||
)}
|
)}
|
||||||
|
<Shape shape={shape}>
|
||||||
|
<Image
|
||||||
|
src={src}
|
||||||
|
width="full"
|
||||||
|
aspectRatio={1}
|
||||||
|
onLoad={() => setStatus('loaded')}
|
||||||
|
onError={() => setStatus('error')}
|
||||||
|
/>
|
||||||
|
|
||||||
<Image
|
{status === 'error' && (
|
||||||
src={src}
|
<Fallback
|
||||||
width="full"
|
width={size}
|
||||||
radius="full"
|
height={size}
|
||||||
aspectRatio={1}
|
display="flex"
|
||||||
onLoad={() => setStatus('loaded')}
|
alignItems="center"
|
||||||
onError={() => setStatus('error')}
|
justifyContent="center"
|
||||||
/>
|
>
|
||||||
|
PP
|
||||||
{status === 'error' && (
|
</Fallback>
|
||||||
<Fallback
|
)}
|
||||||
width={size}
|
</Shape>
|
||||||
height={size}
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
>
|
|
||||||
PP
|
|
||||||
</Fallback>
|
|
||||||
)}
|
|
||||||
</Base>
|
</Base>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Avatar }
|
export { Avatar }
|
||||||
export type { Props as AvatarProps }
|
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 = {
|
export const Primary32: Story = {
|
||||||
name: 'Primary / 32',
|
name: 'Primary / 32',
|
||||||
args: {
|
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 = {
|
export const Success: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'positive',
|
variant: 'positive',
|
||||||
children: 'Click me',
|
children: 'Click me',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Outline: Story = {
|
export const Outline: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'outline',
|
variant: 'outline',
|
||||||
children: 'Click me',
|
children: 'Click me',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Ghost: Story = {
|
export const Ghost: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'ghost',
|
variant: 'ghost',
|
||||||
children: 'Click me',
|
children: 'Click me',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Danger: Story = {
|
export const Danger: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'danger',
|
variant: 'danger',
|
||||||
children: 'Click me',
|
children: 'Click me',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,73 @@
|
||||||
import { forwardRef } from 'react'
|
import { cloneElement, forwardRef } from 'react'
|
||||||
|
|
||||||
import { Stack, styled } from '@tamagui/core'
|
import { Stack, styled } from '@tamagui/core'
|
||||||
|
|
||||||
import { Paragraph } from '../typography'
|
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'
|
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, {
|
const Base = styled(Stack, {
|
||||||
tag: 'button',
|
tag: 'button',
|
||||||
name: 'Button',
|
name: 'Button',
|
||||||
|
@ -16,26 +77,24 @@ const Base = styled(Stack, {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingHorizontal: 16,
|
|
||||||
paddingTop: 7,
|
|
||||||
paddingBottom: 9,
|
|
||||||
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
animation: 'fast',
|
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
|
animation: 'fast',
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
type: {
|
variant: {
|
||||||
primary: {
|
primary: {
|
||||||
backgroundColor: '$primary',
|
backgroundColor: '$primary-50',
|
||||||
hoverStyle: { backgroundColor: '$primaryHover' },
|
hoverStyle: { backgroundColor: '$primary-60' },
|
||||||
pressStyle: { backgroundColor: '$primaryHover' },
|
// TODO: update background color
|
||||||
|
pressStyle: { backgroundColor: '$primary-50' },
|
||||||
},
|
},
|
||||||
positive: {
|
positive: {
|
||||||
backgroundColor: '$success-50',
|
backgroundColor: '$success-50',
|
||||||
hoverStyle: { backgroundColor: '$success-60' },
|
hoverStyle: { backgroundColor: '$success-60' },
|
||||||
|
// TODO: update background color
|
||||||
pressStyle: { backgroundColor: '$success-50' },
|
pressStyle: { backgroundColor: '$success-50' },
|
||||||
},
|
},
|
||||||
grey: {
|
grey: {
|
||||||
|
@ -51,7 +110,7 @@ const Base = styled(Stack, {
|
||||||
outline: {
|
outline: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '$neutral-30',
|
borderColor: '$neutral-30',
|
||||||
hoverStyle: { borderColor: '$neutral-30' },
|
hoverStyle: { borderColor: '$neutral-40' },
|
||||||
pressStyle: { borderColor: '$neutral-50' },
|
pressStyle: { borderColor: '$neutral-50' },
|
||||||
},
|
},
|
||||||
ghost: {
|
ghost: {
|
||||||
|
@ -62,6 +121,7 @@ const Base = styled(Stack, {
|
||||||
danger: {
|
danger: {
|
||||||
backgroundColor: '$danger',
|
backgroundColor: '$danger',
|
||||||
hoverStyle: { backgroundColor: '$danger-60' },
|
hoverStyle: { backgroundColor: '$danger-60' },
|
||||||
|
// TODO: update background color
|
||||||
pressStyle: { backgroundColor: '$danger' },
|
pressStyle: { backgroundColor: '$danger' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -75,32 +135,41 @@ const Base = styled(Stack, {
|
||||||
|
|
||||||
size: {
|
size: {
|
||||||
40: {
|
40: {
|
||||||
minHeight: 40,
|
height: 40,
|
||||||
borderRadius: 12,
|
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
paddingTop: 7,
|
gap: 4,
|
||||||
paddingBottom: 9,
|
|
||||||
},
|
},
|
||||||
32: {
|
32: {
|
||||||
minHeight: 32,
|
height: 32,
|
||||||
borderRadius: 10,
|
paddingHorizontal: 12,
|
||||||
paddingHorizontal: 16,
|
gap: 4,
|
||||||
paddingTop: 4,
|
|
||||||
paddingBottom: 6,
|
|
||||||
},
|
},
|
||||||
24: {
|
24: {
|
||||||
minHeight: 24,
|
height: 24,
|
||||||
borderRadius: 8,
|
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
paddingTop: 2,
|
},
|
||||||
paddingBottom: 4,
|
},
|
||||||
|
|
||||||
|
radius: {
|
||||||
|
full: {
|
||||||
|
borderRadius: 40,
|
||||||
|
},
|
||||||
|
40: {
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
32: {
|
||||||
|
borderRadius: 10,
|
||||||
|
},
|
||||||
|
24: {
|
||||||
|
borderRadius: 8,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
iconOnly: {
|
iconOnly: {
|
||||||
true: {
|
true: {
|
||||||
space: 0,
|
gap: 0,
|
||||||
paddingHorizontal: 8,
|
padding: 0,
|
||||||
|
aspectRatio: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const,
|
} as const,
|
||||||
|
@ -113,30 +182,6 @@ const ButtonText = styled(Paragraph, {
|
||||||
weight: 'medium',
|
weight: 'medium',
|
||||||
|
|
||||||
variants: {
|
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: {
|
size: {
|
||||||
40: {
|
40: {
|
||||||
variant: 'normal',
|
variant: 'normal',
|
||||||
|
@ -150,42 +195,3 @@ const ButtonText = styled(Paragraph, {
|
||||||
},
|
},
|
||||||
} as const,
|
} 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,
|
ReactionIcon,
|
||||||
} from '@status-im/icons/20'
|
} from '@status-im/icons/20'
|
||||||
import { BlurView } from 'expo-blur'
|
import { BlurView } from 'expo-blur'
|
||||||
import { AnimatePresence, Stack, XStack, YStack } from 'tamagui'
|
import { AnimatePresence, Stack, XStack } from 'tamagui'
|
||||||
|
|
||||||
import { Button } from '../button'
|
import { Button } from '../button'
|
||||||
import { IconButton } from '../icon-button'
|
import { IconButton } from '../icon-button'
|
||||||
|
@ -18,6 +18,7 @@ import { Image } from '../image'
|
||||||
import { Input } from '../input'
|
import { Input } from '../input'
|
||||||
import { useChatDispatch, useChatState } from '../provider'
|
import { useChatDispatch, useChatState } from '../provider'
|
||||||
import { Reply } from '../reply'
|
import { Reply } from '../reply'
|
||||||
|
import { Shadow } from '../shadow'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
blur?: boolean
|
blur?: boolean
|
||||||
|
@ -43,6 +44,8 @@ const Composer = (props: Props) => {
|
||||||
const chatState = useChatState()
|
const chatState = useChatState()
|
||||||
const chatDispatch = useChatDispatch()
|
const chatDispatch = useChatDispatch()
|
||||||
|
|
||||||
|
const showSendButton = text !== '' || imagesData.length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlurView
|
<BlurView
|
||||||
intensity={40}
|
intensity={40}
|
||||||
|
@ -51,20 +54,16 @@ const Composer = (props: Props) => {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<YStack
|
<Shadow
|
||||||
|
variant={iconButtonBlurred ? 'none' : '$2'}
|
||||||
|
inverted
|
||||||
animation="fast"
|
animation="fast"
|
||||||
backgroundColor={iconButtonBlurred ? '$blurBackground' : '$background'}
|
backgroundColor={iconButtonBlurred ? '$blurBackground' : '$background'}
|
||||||
shadowColor={iconButtonBlurred ? 'none' : 'rgba(9, 16, 28, 0.08)'}
|
|
||||||
shadowOffset={{ width: 4, height: iconButtonBlurred ? 0 : 4 }}
|
|
||||||
shadowRadius={20}
|
|
||||||
borderTopLeftRadius={20}
|
borderTopLeftRadius={20}
|
||||||
borderTopRightRadius={20}
|
borderTopRightRadius={20}
|
||||||
px={16}
|
px={16}
|
||||||
width="100%"
|
width="100%"
|
||||||
py={12}
|
py={12}
|
||||||
style={{
|
|
||||||
elevation: 10,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{chatState?.type === 'reply' && (
|
{chatState?.type === 'reply' && (
|
||||||
<Stack paddingLeft={4} paddingBottom={4}>
|
<Stack paddingLeft={4} paddingBottom={4}>
|
||||||
|
@ -169,42 +168,38 @@ const Composer = (props: Props) => {
|
||||||
variant="outline"
|
variant="outline"
|
||||||
icon={<ImageIcon />}
|
icon={<ImageIcon />}
|
||||||
disabled={isImageUploadDisabled}
|
disabled={isImageUploadDisabled}
|
||||||
blurred={iconButtonBlurred}
|
blur={iconButtonBlurred}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
icon={<ReactionIcon />}
|
icon={<ReactionIcon />}
|
||||||
blurred={iconButtonBlurred}
|
blur={iconButtonBlurred}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
icon={<FormatIcon />}
|
icon={<FormatIcon />}
|
||||||
disabled
|
disabled
|
||||||
blurred={iconButtonBlurred}
|
blur={iconButtonBlurred}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
{text || imagesData.length > 0 ? (
|
{showSendButton ? (
|
||||||
// TODO fix styles for circular button. Also the color is different from the design and we have layout shift because of the size.
|
|
||||||
<Button
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
shape="circle"
|
||||||
icon={<ArrowUpIcon />}
|
icon={<ArrowUpIcon />}
|
||||||
height={32}
|
|
||||||
size={32}
|
size={32}
|
||||||
width={32}
|
|
||||||
borderRadius={32}
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
type="positive"
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<IconButton
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
icon={<AudioIcon />}
|
icon={<AudioIcon />}
|
||||||
blurred={iconButtonBlurred}
|
size={32}
|
||||||
|
// blurred={iconButtonBlurred}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</XStack>
|
</XStack>
|
||||||
</YStack>
|
</Shadow>
|
||||||
</BlurView>
|
</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 { OptionsIcon } from '@status-im/icons/20'
|
||||||
|
import { Stack } from 'tamagui'
|
||||||
|
|
||||||
import { IconButton } from './icon-button'
|
import { IconButton } from './icon-button'
|
||||||
|
|
||||||
|
@ -8,6 +9,12 @@ import type { Meta, StoryObj } from '@storybook/react'
|
||||||
const meta: Meta<typeof IconButton> = {
|
const meta: Meta<typeof IconButton> = {
|
||||||
component: IconButton,
|
component: IconButton,
|
||||||
argTypes: {},
|
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>
|
type Story = StoryObj<typeof IconButton>
|
||||||
|
@ -17,6 +24,29 @@ export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
icon: <OptionsIcon />,
|
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
|
export default meta
|
||||||
|
|
|
@ -1,110 +1,20 @@
|
||||||
import { cloneElement, forwardRef } from 'react'
|
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'
|
import type { Ref } from 'react'
|
||||||
|
|
||||||
const Base = styled(Stack, {
|
type Variants = GetVariants<typeof Base>
|
||||||
name: 'IconButton',
|
|
||||||
accessibilityRole: 'button',
|
|
||||||
|
|
||||||
cursor: 'pointer',
|
type Props = PressableProps & {
|
||||||
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 {
|
|
||||||
icon: React.ReactElement
|
icon: React.ReactElement
|
||||||
onPress?: () => void
|
variant?: Variants['variant']
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
blurred?: boolean
|
blur?: boolean
|
||||||
variant?: 'default' | 'outline'
|
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
// FIXME: enforce aria-label for accessibility
|
// FIXME: enforce aria-label for accessibility
|
||||||
// 'aria-label'?: string
|
// 'aria-label'?: string
|
||||||
|
@ -113,80 +23,34 @@ interface Props {
|
||||||
'aria-selected'?: boolean
|
'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 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 =
|
const selected =
|
||||||
props.selected || props['aria-expanded'] || props['aria-selected']
|
props.selected || props['aria-expanded'] || props['aria-selected']
|
||||||
|
|
||||||
const state = getStateForIconColor({ blurred, selected })
|
|
||||||
const selectedVariant = getSelectedVariant({ selected, variant, blurred })
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base
|
<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}
|
ref={ref}
|
||||||
variant={variant}
|
variant={blur ? undefined : variant}
|
||||||
selected={selectedVariant}
|
active={blur ? undefined : selected ? variant : undefined}
|
||||||
blurred={blurred ? variant : undefined}
|
variantBlur={blur ? variant : undefined}
|
||||||
|
activeBlur={blur ? (selected ? variant : undefined) : undefined}
|
||||||
>
|
>
|
||||||
{cloneElement(icon, {
|
{cloneElement(icon, {
|
||||||
color: iconColor[variant][state],
|
color,
|
||||||
size: 20,
|
size: 20,
|
||||||
})}
|
})}
|
||||||
</Base>
|
</Base>
|
||||||
|
@ -197,3 +61,127 @@ const _IconButton = forwardRef(IconButton)
|
||||||
|
|
||||||
export { _IconButton as IconButton }
|
export { _IconButton as IconButton }
|
||||||
export type { Props as IconButtonProps }
|
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 { setupReactNative, styled } from '@tamagui/core'
|
||||||
import { Image as RNImage } from 'react-native'
|
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 { Ref } from 'react'
|
||||||
import type { ImagePropsBase as RNImageProps } from 'react-native'
|
|
||||||
|
|
||||||
setupReactNative({
|
setupReactNative({
|
||||||
Image: RNImage,
|
Image: RNImage,
|
||||||
})
|
})
|
||||||
|
|
||||||
const Base = styled(RNImage, {
|
type Variants = GetVariants<typeof Base>
|
||||||
name: 'Image',
|
|
||||||
position: 'relative',
|
|
||||||
zIndex: 1,
|
|
||||||
source: {
|
|
||||||
uri: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
variants: {
|
type Props = GetProps<typeof Base> & {
|
||||||
radius: {
|
|
||||||
12: {
|
|
||||||
borderRadius: 12,
|
|
||||||
},
|
|
||||||
full: {
|
|
||||||
borderRadius: 9999,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
type ImageProps = GetProps<typeof Base>
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
src: string
|
src: string
|
||||||
width: number | 'full'
|
width: number | 'full'
|
||||||
height?: number
|
height?: number
|
||||||
aspectRatio?: ImageProps['aspectRatio']
|
radius?: Variants['radius']
|
||||||
radius?: ImageProps['radius']
|
|
||||||
onLoad?: RNImageProps['onLoad']
|
|
||||||
onError?: RNImageProps['onError']
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Image = (props: Props, ref: Ref<HTMLImageElement>) => {
|
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 width = props.width === 'full' ? '100%' : props.width
|
||||||
const height = aspectRatio ? undefined : props.height
|
const height = aspectRatio ? undefined : props.height
|
||||||
|
@ -56,19 +32,39 @@ const Image = (props: Props, ref: Ref<HTMLImageElement>) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base
|
<Base
|
||||||
{...rest}
|
{...imageProps}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
source={source}
|
source={source}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
radius={radius}
|
|
||||||
aspectRatio={aspectRatio}
|
aspectRatio={aspectRatio}
|
||||||
|
radius={radius}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO?: this was used in @tamagui/image package. Why?
|
const _Image = forwardRef(Image)
|
||||||
// focusableInputHOC(Image)
|
|
||||||
const _Image = Base.extractable(forwardRef(Image))
|
|
||||||
|
|
||||||
export { _Image as 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,
|
PinIcon,
|
||||||
ReplyIcon,
|
ReplyIcon,
|
||||||
} from '@status-im/icons/20'
|
} from '@status-im/icons/20'
|
||||||
import { Stack } from 'tamagui'
|
|
||||||
|
|
||||||
import { DropdownMenu } from '../../dropdown-menu'
|
import { DropdownMenu } from '../../dropdown-menu'
|
||||||
import { IconButton } from '../../icon-button'
|
import { IconButton } from '../../icon-button'
|
||||||
|
import { Shadow } from '../../shadow'
|
||||||
import { ReactionPopover } from './reaction-popover'
|
import { ReactionPopover } from './reaction-popover'
|
||||||
|
|
||||||
import type { ReactionsType } from '../types'
|
import type { ReactionsType } from '../types'
|
||||||
|
@ -35,21 +35,19 @@ export const Actions = (props: Props) => {
|
||||||
}, [onOpenChange])
|
}, [onOpenChange])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Shadow
|
||||||
backgroundColor="$white-100"
|
variant="$1"
|
||||||
borderWidth={1}
|
|
||||||
borderColor="$neutral-10"
|
|
||||||
borderRadius={12}
|
|
||||||
padding={2}
|
|
||||||
space={2}
|
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top={-16}
|
top={-8}
|
||||||
right={0}
|
right={8}
|
||||||
|
borderRadius={12}
|
||||||
|
borderWidth={1}
|
||||||
|
borderColor="$neutral-10"
|
||||||
|
backgroundColor="$white-100"
|
||||||
|
padding={2}
|
||||||
|
space={2}
|
||||||
flexDirection="row"
|
flexDirection="row"
|
||||||
shadowRadius={20}
|
|
||||||
shadowOffset={{ width: 0, height: 4 }}
|
|
||||||
shadowColor="rgba(9, 16, 28, 0.08)"
|
|
||||||
zIndex={10}
|
zIndex={10}
|
||||||
>
|
>
|
||||||
{/* REACTION */}
|
{/* REACTION */}
|
||||||
|
@ -59,29 +57,25 @@ export const Actions = (props: Props) => {
|
||||||
sideOffset={6}
|
sideOffset={6}
|
||||||
onOpenChange={onOpenChange}
|
onOpenChange={onOpenChange}
|
||||||
>
|
>
|
||||||
<IconButton variant="outline" icon={<AddReactionIcon />} />
|
<IconButton variant="ghost" icon={<AddReactionIcon />} />
|
||||||
</ReactionPopover>
|
</ReactionPopover>
|
||||||
|
|
||||||
{/* REPLY */}
|
{/* REPLY */}
|
||||||
<IconButton
|
<IconButton variant="ghost" icon={<ReplyIcon />} onPress={onReplyPress} />
|
||||||
variant="outline"
|
|
||||||
icon={<ReplyIcon />}
|
|
||||||
onPress={onReplyPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* EDIT */}
|
{/* EDIT */}
|
||||||
<IconButton variant="outline" icon={<EditIcon />} onPress={onEditPress} />
|
<IconButton variant="ghost" icon={<EditIcon />} onPress={onEditPress} />
|
||||||
|
|
||||||
{/* DELETE */}
|
{/* DELETE */}
|
||||||
{/* <IconButton
|
{/* <IconButton
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
icon={<DeleteIcon />}
|
icon={<DeleteIcon />}
|
||||||
onPress={onDeletePress}
|
onPress={onDeletePress}
|
||||||
/> */}
|
/> */}
|
||||||
|
|
||||||
{/* OPTIONS MENU */}
|
{/* OPTIONS MENU */}
|
||||||
<DropdownMenu modal={false} onOpenChange={onOpenChange}>
|
<DropdownMenu modal={false} onOpenChange={onOpenChange}>
|
||||||
<IconButton variant="outline" icon={<OptionsIcon />} />
|
<IconButton variant="ghost" icon={<OptionsIcon />} />
|
||||||
<DropdownMenu.Content align="end" sideOffset={10}>
|
<DropdownMenu.Content align="end" sideOffset={10}>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<EditIcon />}
|
icon={<EditIcon />}
|
||||||
|
@ -124,6 +118,6 @@ export const Actions = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Stack>
|
</Shadow>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { PinIcon } from '@status-im/icons/16'
|
import { PinIcon } from '@status-im/icons/16'
|
||||||
import { View } from 'react-native'
|
|
||||||
import { Stack, styled, Unspaced, XStack, YStack } from 'tamagui'
|
import { Stack, styled, Unspaced, XStack, YStack } from 'tamagui'
|
||||||
|
|
||||||
import { Author } from '../author/author'
|
import { Author } from '../author'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { Image } from '../image'
|
import { Image } from '../image'
|
||||||
import { useChatDispatch } from '../provider'
|
import { useChatDispatch } from '../provider'
|
||||||
|
@ -23,7 +22,7 @@ interface Props {
|
||||||
pinned?: boolean
|
pinned?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled(View, {
|
const Base = styled(Stack, {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
|
@ -36,6 +35,7 @@ const Wrapper = styled(View, {
|
||||||
backgroundColor: '$neutral-5',
|
backgroundColor: '$neutral-5',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
pinned: {
|
pinned: {
|
||||||
true: {
|
true: {
|
||||||
backgroundColor: '$blue-50-opa-5',
|
backgroundColor: '$blue-50-opa-5',
|
||||||
|
@ -48,15 +48,16 @@ const Message = (props: Props) => {
|
||||||
const { text, images, reactions, reply, pinned } = props
|
const { text, images, reactions, reply, pinned } = props
|
||||||
|
|
||||||
const [hovered, setHovered] = useState(false)
|
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">
|
// <Sheet press="long">
|
||||||
|
|
||||||
const dispatch = useChatDispatch()
|
const dispatch = useChatDispatch()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Base
|
||||||
active={active}
|
active={active}
|
||||||
onHoverIn={() => setHovered(true)}
|
onHoverIn={() => setHovered(true)}
|
||||||
onHoverOut={() => setHovered(false)}
|
onHoverOut={() => setHovered(false)}
|
||||||
|
@ -65,7 +66,7 @@ const Message = (props: Props) => {
|
||||||
<Unspaced>
|
<Unspaced>
|
||||||
<Actions
|
<Actions
|
||||||
reactions={reactions}
|
reactions={reactions}
|
||||||
onOpenChange={setActionsOpen}
|
onOpenChange={setShowActions}
|
||||||
onReplyPress={() => dispatch({ type: 'reply', messageId: '1' })}
|
onReplyPress={() => dispatch({ type: 'reply', messageId: '1' })}
|
||||||
onEditPress={() => dispatch({ type: 'edit', messageId: '1' })}
|
onEditPress={() => dispatch({ type: 'edit', messageId: '1' })}
|
||||||
/>
|
/>
|
||||||
|
@ -113,7 +114,12 @@ const Message = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{text && (
|
{text && (
|
||||||
<Paragraph flexGrow={0} weight="regular" color="$neutral-100">
|
<Paragraph
|
||||||
|
flexGrow={0}
|
||||||
|
weight="regular"
|
||||||
|
color="$neutral-100"
|
||||||
|
userSelect="text"
|
||||||
|
>
|
||||||
{text}
|
{text}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
)}
|
)}
|
||||||
|
@ -130,14 +136,14 @@ const Message = (props: Props) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{reactions && (
|
{hasReactions && (
|
||||||
<Stack paddingTop={8}>
|
<Stack paddingTop={8}>
|
||||||
<Reactions reactions={reactions} />
|
<Reactions reactions={reactions} />
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</YStack>
|
</YStack>
|
||||||
</XStack>
|
</XStack>
|
||||||
</Wrapper>
|
</Base>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const Default: Story = {
|
||||||
args: {},
|
args: {},
|
||||||
render: args => (
|
render: args => (
|
||||||
<Popover {...args}>
|
<Popover {...args}>
|
||||||
<Button type="primary">Trigger</Button>
|
<Button variant="primary">Trigger</Button>
|
||||||
<Popover.Content>some content</Popover.Content>
|
<Popover.Content>some content</Popover.Content>
|
||||||
</Popover>
|
</Popover>
|
||||||
),
|
),
|
||||||
|
|
|
@ -13,10 +13,72 @@ import { Stack, styled } from '@tamagui/core'
|
||||||
|
|
||||||
import { Paragraph } from '../typography'
|
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 { Ref } from 'react'
|
||||||
import type { PressableProps } from 'react-native'
|
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, {
|
const Button = styled(Stack, {
|
||||||
name: 'ReactButton',
|
name: 'ReactButton',
|
||||||
accessibilityRole: 'button',
|
accessibilityRole: 'button',
|
||||||
|
@ -78,64 +140,3 @@ const Button = styled(Stack, {
|
||||||
},
|
},
|
||||||
} as const,
|
} 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>
|
</Stack>
|
||||||
</Unspaced>
|
</Unspaced>
|
||||||
|
|
||||||
<Avatar size={20} src={src} />
|
<Avatar size={16} src={src} />
|
||||||
|
|
||||||
<Paragraph variant="smaller" weight="semibold" color="$neutral-100">
|
<Paragraph variant="smaller" weight="semibold" color="$neutral-100">
|
||||||
{name}
|
{name}
|
||||||
|
@ -64,12 +64,11 @@ const Reply = (props: Props) => {
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
|
||||||
{/* FIXME: This should be regular button with size 24 */}
|
|
||||||
{onClose && (
|
{onClose && (
|
||||||
<Button
|
<Button
|
||||||
type="outline"
|
|
||||||
size={24}
|
|
||||||
icon={<CloseIcon />}
|
icon={<CloseIcon />}
|
||||||
|
variant="outline"
|
||||||
|
size={24}
|
||||||
onPress={onClose}
|
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
|
description: string
|
||||||
emoji: string
|
emoji: string
|
||||||
channelStatus?: 'muted' | 'normal' | 'withMessages' | 'withMentions'
|
channelStatus?: 'muted' | 'normal' | 'withMessages' | 'withMentions'
|
||||||
numberOfMessages?: number
|
unreadCount?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelGroup {
|
export interface ChannelGroup {
|
||||||
|
@ -49,7 +49,7 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
channelStatus: 'withMentions',
|
channelStatus: 'withMentions',
|
||||||
numberOfMessages: 3,
|
unreadCount: 3,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -68,7 +68,7 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||||
id: 'jobs',
|
id: 'jobs',
|
||||||
title: '# jobs',
|
title: '# jobs',
|
||||||
channelStatus: 'withMentions',
|
channelStatus: 'withMentions',
|
||||||
numberOfMessages: 3,
|
unreadCount: 3,
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
|
@ -76,7 +76,7 @@ export const CHANNEL_GROUPS: ChannelGroup[] = [
|
||||||
id: 'events',
|
id: 'events',
|
||||||
title: '# events',
|
title: '# events',
|
||||||
channelStatus: 'withMentions',
|
channelStatus: 'withMentions',
|
||||||
numberOfMessages: 2,
|
unreadCount: 2,
|
||||||
emoji: randomEmoji(),
|
emoji: randomEmoji(),
|
||||||
description: 'Share random funny stuff with the community. Play nice.',
|
description: 'Share random funny stuff with the community. Play nice.',
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,7 +53,7 @@ const Sidebar = (props: Props) => {
|
||||||
<Stack paddingHorizontal={16} paddingBottom={16}>
|
<Stack paddingHorizontal={16} paddingBottom={16}>
|
||||||
<Stack marginTop={-40} marginBottom={12}>
|
<Stack marginTop={-40} marginBottom={12}>
|
||||||
<Avatar
|
<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"
|
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}
|
size={80}
|
||||||
/>
|
/>
|
||||||
|
@ -72,18 +72,15 @@ const Sidebar = (props: Props) => {
|
||||||
key={group.id}
|
key={group.id}
|
||||||
initialExpanded={group.id === 'welcome'}
|
initialExpanded={group.id === 'welcome'}
|
||||||
title={group.title}
|
title={group.title}
|
||||||
numberOfNewMessages={group.unreadCount}
|
unreadCount={group.unreadCount}
|
||||||
>
|
>
|
||||||
{group.channels.map((channel, index) => {
|
{group.channels.map(channel => {
|
||||||
const isLastChannelOfTheList = index === group.channels.length - 1
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
key={channel.id}
|
key={channel.id}
|
||||||
channel={channel}
|
channel={channel}
|
||||||
isSelected={selectedChannelId === channel.id}
|
selected={selectedChannelId === channel.id}
|
||||||
onPress={() => onChannelPress(channel.id)}
|
onPress={() => onChannelPress(channel.id)}
|
||||||
mb={isLastChannelOfTheList ? 8 : 0}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const Default: Story = {
|
||||||
},
|
},
|
||||||
render: args => (
|
render: args => (
|
||||||
<Tooltip {...args}>
|
<Tooltip {...args}>
|
||||||
<Button type="outline">Trigger</Button>
|
<Button variant="outline">Trigger</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ const Topbar = (props: Props) => {
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<ArrowLeftIcon />}
|
icon={<ArrowLeftIcon />}
|
||||||
onPress={() => goBack?.()}
|
onPress={() => goBack?.()}
|
||||||
blurred={blur}
|
blur={blur}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ const Topbar = (props: Props) => {
|
||||||
icon={<MembersIcon />}
|
icon={<MembersIcon />}
|
||||||
selected={membersVisisble}
|
selected={membersVisisble}
|
||||||
onPress={onMembersPress}
|
onPress={onMembersPress}
|
||||||
blurred={blur}
|
blur={blur}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</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