Extend Avatar (#371)
* add color hash to Avatar * remove compoundVariants * remove outline prop * remove vars * ref figma * remove example * add background color to Image * extend radius variants in Image * use union type * add channel avatar to stories * add channel avatar as Avatar type * resolve typecheck errors * add name prop * add icon avatar as Avatar type * add community avatar * move fallback * set default icon color * add group avatar * add wallet avatar * join user type * join channel type * remove 32 text variant * assert LockBase variant * remove fn type guards * fix icon import * set icon sizes based on props * set default variants and use render fns * uses raidus tokens * add outline * remove outline * fix overlapping background on loaded image * fix indicator position * fix background color
This commit is contained in:
parent
def47472e6
commit
811fa081a8
|
@ -1,64 +1,382 @@
|
||||||
|
import { PlaceholderIcon } from '@status-im/icons'
|
||||||
import { Stack } from '@tamagui/core'
|
import { Stack } from '@tamagui/core'
|
||||||
|
|
||||||
import { Avatar } from './avatar'
|
import { Avatar } from './avatar'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
AccountAvatarProps,
|
||||||
|
ChannelAvatarProps,
|
||||||
|
CommunityAvatarProps,
|
||||||
|
GroupAvatarProps,
|
||||||
|
IconAvatarProps,
|
||||||
|
UserAvatarProps,
|
||||||
|
WalletAvatarProps,
|
||||||
|
} from './avatar'
|
||||||
import type { Meta, StoryObj } from '@storybook/react'
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||||
const meta: Meta<typeof Avatar> = {
|
const meta: Meta<typeof Avatar> = {
|
||||||
component: Avatar,
|
component: Avatar,
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=102-5246&t=i4haPXGOeNtaLaEz-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type Story = StoryObj<typeof Avatar>
|
type UserArgs = Pick<UserAvatarProps, 'type' | 'src' | 'name'>
|
||||||
|
|
||||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||||
export const Default: Story = {
|
export const User: StoryObj<UserArgs> = {
|
||||||
|
// todo?: https://github.com/storybookjs/storybook/issues/13747
|
||||||
args: {
|
args: {
|
||||||
|
type: 'user',
|
||||||
|
name: 'John Doe',
|
||||||
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',
|
||||||
|
} as UserArgs,
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=115-6787&t=kcsW0DN5ochMPO1u-4',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
render: args => (
|
render: args => (
|
||||||
<Stack space flexDirection="row">
|
<Stack space flexDirection="row">
|
||||||
<Stack space>
|
<Stack space flexDirection="column">
|
||||||
<Avatar {...args} size={80} />
|
<Stack space alignItems="flex-start">
|
||||||
<Avatar {...args} size={56} />
|
<Avatar
|
||||||
<Avatar {...args} size={48} />
|
{...args}
|
||||||
<Avatar {...args} size={32} />
|
size={80}
|
||||||
<Avatar {...args} size={28} />
|
indicator="online"
|
||||||
<Avatar {...args} size={24} />
|
colorHash={[
|
||||||
<Avatar {...args} size={20} />
|
[3, 30],
|
||||||
<Avatar {...args} size={16} />
|
[2, 10],
|
||||||
|
[5, 5],
|
||||||
|
[3, 14],
|
||||||
|
[5, 4],
|
||||||
|
[4, 19],
|
||||||
|
[3, 16],
|
||||||
|
[4, 0],
|
||||||
|
[5, 28],
|
||||||
|
[4, 13],
|
||||||
|
[4, 15],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Avatar
|
||||||
|
{...args}
|
||||||
|
size={56}
|
||||||
|
indicator="online"
|
||||||
|
colorHash={[
|
||||||
|
[3, 30],
|
||||||
|
[2, 10],
|
||||||
|
[5, 5],
|
||||||
|
[3, 14],
|
||||||
|
[5, 4],
|
||||||
|
[4, 19],
|
||||||
|
[3, 16],
|
||||||
|
[4, 0],
|
||||||
|
[5, 28],
|
||||||
|
[4, 13],
|
||||||
|
[4, 15],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Avatar
|
||||||
|
{...args}
|
||||||
|
size={48}
|
||||||
|
indicator="online"
|
||||||
|
colorHash={[
|
||||||
|
[3, 30],
|
||||||
|
[2, 10],
|
||||||
|
[5, 5],
|
||||||
|
[3, 14],
|
||||||
|
[5, 4],
|
||||||
|
[4, 19],
|
||||||
|
[3, 16],
|
||||||
|
[4, 0],
|
||||||
|
[5, 28],
|
||||||
|
[4, 13],
|
||||||
|
[4, 15],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Avatar
|
||||||
|
{...args}
|
||||||
|
size={32}
|
||||||
|
indicator="online"
|
||||||
|
colorHash={[
|
||||||
|
[3, 30],
|
||||||
|
[2, 10],
|
||||||
|
[5, 5],
|
||||||
|
[3, 14],
|
||||||
|
[5, 4],
|
||||||
|
[4, 19],
|
||||||
|
[3, 16],
|
||||||
|
[4, 0],
|
||||||
|
[5, 28],
|
||||||
|
[4, 13],
|
||||||
|
[4, 15],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack space alignItems="flex-start">
|
||||||
|
<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" />
|
||||||
|
</Stack>
|
||||||
|
<Stack space alignItems="flex-start">
|
||||||
|
<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>
|
</Stack>
|
||||||
|
<Stack space flexDirection="column">
|
||||||
<Stack space>
|
<Stack space alignItems="flex-start">
|
||||||
<Avatar {...args} size={80} indicator="online" />
|
<Avatar
|
||||||
<Avatar {...args} size={56} indicator="online" />
|
{...args}
|
||||||
<Avatar {...args} size={48} indicator="online" />
|
src={undefined}
|
||||||
<Avatar {...args} size={32} indicator="online" />
|
size={80}
|
||||||
<Avatar {...args} size={28} indicator="online" />
|
indicator="online"
|
||||||
<Avatar {...args} size={24} indicator="online" />
|
colorHash={[
|
||||||
<Avatar {...args} size={20} indicator="online" />
|
[3, 30],
|
||||||
<Avatar {...args} size={16} indicator="online" />
|
[2, 10],
|
||||||
|
[5, 5],
|
||||||
|
[3, 14],
|
||||||
|
[5, 4],
|
||||||
|
[4, 19],
|
||||||
|
[3, 16],
|
||||||
|
[4, 0],
|
||||||
|
[5, 28],
|
||||||
|
[4, 13],
|
||||||
|
[4, 15],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Avatar
|
||||||
|
{...args}
|
||||||
|
src={undefined}
|
||||||
|
size={56}
|
||||||
|
indicator="online"
|
||||||
|
colorHash={[
|
||||||
|
[3, 30],
|
||||||
|
[2, 10],
|
||||||
|
[5, 5],
|
||||||
|
[3, 14],
|
||||||
|
[5, 4],
|
||||||
|
[4, 19],
|
||||||
|
[3, 16],
|
||||||
|
[4, 0],
|
||||||
|
[5, 28],
|
||||||
|
[4, 13],
|
||||||
|
[4, 15],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Avatar
|
||||||
|
{...args}
|
||||||
|
src={undefined}
|
||||||
|
size={48}
|
||||||
|
indicator="online"
|
||||||
|
colorHash={[
|
||||||
|
[3, 30],
|
||||||
|
[2, 10],
|
||||||
|
[5, 5],
|
||||||
|
[3, 14],
|
||||||
|
[5, 4],
|
||||||
|
[4, 19],
|
||||||
|
[3, 16],
|
||||||
|
[4, 0],
|
||||||
|
[5, 28],
|
||||||
|
[4, 13],
|
||||||
|
[4, 15],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Avatar
|
||||||
|
{...args}
|
||||||
|
src={undefined}
|
||||||
|
size={32}
|
||||||
|
indicator="online"
|
||||||
|
colorHash={[
|
||||||
|
[3, 30],
|
||||||
|
[2, 10],
|
||||||
|
[5, 5],
|
||||||
|
[3, 14],
|
||||||
|
[5, 4],
|
||||||
|
[4, 19],
|
||||||
|
[3, 16],
|
||||||
|
[4, 0],
|
||||||
|
[5, 28],
|
||||||
|
[4, 13],
|
||||||
|
[4, 15],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Rounded: Story = {
|
export const Group: StoryObj<GroupAvatarProps> = {
|
||||||
args: {
|
args: {
|
||||||
|
type: 'group',
|
||||||
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',
|
},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=14584-169312&t=kcsW0DN5ochMPO1u-4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render: args => (
|
||||||
|
<Stack space flexDirection="row">
|
||||||
|
<Stack space flexDirection="column">
|
||||||
|
<Avatar {...args} size={80} />
|
||||||
|
<Avatar {...args} size={48} />
|
||||||
|
<Avatar {...args} size={32} />
|
||||||
|
<Avatar {...args} size={28} />
|
||||||
|
<Avatar {...args} size={20} />
|
||||||
|
</Stack>
|
||||||
|
<Stack space flexDirection="column">
|
||||||
|
<Avatar {...args} src={undefined} size={80} />
|
||||||
|
<Avatar {...args} src={undefined} size={48} />
|
||||||
|
<Avatar {...args} src={undefined} size={32} />
|
||||||
|
<Avatar {...args} src={undefined} size={28} />
|
||||||
|
<Avatar {...args} src={undefined} size={20} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Wallet: StoryObj<WalletAvatarProps> = {
|
||||||
|
args: {
|
||||||
|
type: 'wallet',
|
||||||
|
name: 'Wallet 1',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=114-7646&t=kcsW0DN5ochMPO1u-4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render: args => (
|
||||||
|
<Stack space flexDirection="row">
|
||||||
|
<Stack space flexDirection="column">
|
||||||
|
<Avatar {...args} size={80} />
|
||||||
|
<Avatar {...args} size={48} />
|
||||||
|
<Avatar {...args} size={32} />
|
||||||
|
<Avatar {...args} size={28} />
|
||||||
|
<Avatar {...args} size={20} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Account: StoryObj<AccountAvatarProps> = {
|
||||||
|
args: {
|
||||||
|
type: 'account',
|
||||||
|
name: 'My Account',
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=483-19401&t=kcsW0DN5ochMPO1u-4',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
render: args => (
|
render: args => (
|
||||||
<Stack space>
|
<Stack space>
|
||||||
<Avatar {...args} size={80} />
|
<Avatar {...args} size={80} />
|
||||||
<Avatar {...args} size={56} />
|
|
||||||
<Avatar {...args} size={48} />
|
<Avatar {...args} size={48} />
|
||||||
<Avatar {...args} size={32} />
|
<Avatar {...args} size={32} />
|
||||||
<Avatar {...args} size={28} />
|
<Avatar {...args} size={28} />
|
||||||
<Avatar {...args} size={24} />
|
<Avatar {...args} size={24} />
|
||||||
<Avatar {...args} size={20} />
|
<Avatar {...args} size={20} />
|
||||||
<Avatar {...args} size={16} />
|
</Stack>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const community: StoryObj<CommunityAvatarProps> = {
|
||||||
|
args: {
|
||||||
|
type: 'community',
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=8824-149725&t=kcsW0DN5ochMPO1u-4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render: args => (
|
||||||
|
<Stack space flexDirection="row">
|
||||||
|
<Stack space alignItems="flex-start">
|
||||||
|
<Avatar {...args} size={32} />
|
||||||
|
<Avatar {...args} size={24} />
|
||||||
|
<Avatar {...args} size={20} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelArgs = Pick<ChannelAvatarProps, 'type' | 'emoji'>
|
||||||
|
|
||||||
|
export const Channel: StoryObj<ChannelArgs> = {
|
||||||
|
args: {
|
||||||
|
type: 'channel',
|
||||||
|
emoji: '🍑',
|
||||||
|
} as ChannelArgs,
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=399-20709&t=kcsW0DN5ochMPO1u-4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render: args => (
|
||||||
|
<Stack space flexDirection="row">
|
||||||
|
<Stack space alignItems="flex-start">
|
||||||
|
<Avatar {...args} size={80} />
|
||||||
|
<Avatar {...args} size={32} />
|
||||||
|
<Avatar {...args} size={24} />
|
||||||
|
<Avatar {...args} size={20} />
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack space alignItems="flex-start">
|
||||||
|
<Avatar {...args} size={32} lock="locked" />
|
||||||
|
<Avatar {...args} size={24} lock="locked" />
|
||||||
|
<Avatar {...args} size={20} lock="locked" />
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack space alignItems="flex-start">
|
||||||
|
<Avatar {...args} size={32} lock="unlocked" />
|
||||||
|
<Avatar {...args} size={24} lock="unlocked" />
|
||||||
|
<Avatar {...args} size={20} lock="unlocked" />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Icon: StoryObj<IconAvatarProps> = {
|
||||||
|
args: {
|
||||||
|
type: 'icon',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=2931-44944&t=kcsW0DN5ochMPO1u-4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render: args => (
|
||||||
|
<Stack space flexDirection="row">
|
||||||
|
<Stack space alignItems="flex-start">
|
||||||
|
<Avatar {...args} size={48} icon={<PlaceholderIcon size={20} />} />
|
||||||
|
<Avatar {...args} size={32} icon={<PlaceholderIcon size={16} />} />
|
||||||
|
<Avatar {...args} size={20} icon={<PlaceholderIcon size={12} />} />
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +1,354 @@
|
||||||
import { useEffect, useState } from 'react'
|
import { cloneElement, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import { Stack, styled, Text, Unspaced } from '@tamagui/core'
|
import { LockedIcon, MembersIcon, UnlockedIcon } from '@status-im/icons'
|
||||||
|
import { Stack, styled, Unspaced } from '@tamagui/core'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
|
|
||||||
import { Image } from '../image'
|
import { Image } from '../image'
|
||||||
|
import { Text } from '../text'
|
||||||
|
import { tokens } from '../tokens'
|
||||||
|
import { generateIdenticonRing } from './utils'
|
||||||
|
|
||||||
import type { GetStyledVariants } from '@tamagui/core'
|
import type { TextProps } from '../text'
|
||||||
|
import type { RadiusTokens } from '../tokens'
|
||||||
|
import type { IconProps } from '@status-im/icons'
|
||||||
|
import type { ColorTokens, GetStyledVariants } from '@tamagui/core'
|
||||||
|
|
||||||
type Variants = GetStyledVariants<typeof Base>
|
type UserAvatarProps = {
|
||||||
|
type: 'user'
|
||||||
type Props = {
|
|
||||||
src: string
|
|
||||||
size: 80 | 56 | 48 | 32 | 28 | 24 | 20 | 16
|
size: 80 | 56 | 48 | 32 | 28 | 24 | 20 | 16
|
||||||
shape?: Variants['shape']
|
name: string
|
||||||
outline?: Variants['outline']
|
src?: string
|
||||||
|
backgroundColor?: ColorTokens
|
||||||
indicator?: GetStyledVariants<typeof Indicator>['state']
|
indicator?: GetStyledVariants<typeof Indicator>['state']
|
||||||
|
colorHash?: number[][]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'
|
type GroupAvatarProps = {
|
||||||
|
type: 'group'
|
||||||
|
size: 80 | 48 | 32 | 28 | 20
|
||||||
|
name: string
|
||||||
|
src?: string
|
||||||
|
backgroundColor?: ColorTokens
|
||||||
|
}
|
||||||
|
|
||||||
const Avatar = (props: Props) => {
|
type WalletAvatarProps = {
|
||||||
const {
|
type: 'wallet'
|
||||||
src,
|
size: 80 | 48 | 32 | 28 | 20
|
||||||
size,
|
name: string
|
||||||
shape = 'circle',
|
backgroundColor?: ColorTokens
|
||||||
outline = false,
|
}
|
||||||
indicator = 'none',
|
|
||||||
} = props
|
|
||||||
|
|
||||||
const [status, setStatus] = useState<ImageLoadingStatus>('idle')
|
type ChannelAvatarProps = {
|
||||||
|
type: 'channel'
|
||||||
|
size: 80 | 32 | 24 | 20
|
||||||
|
emoji: string
|
||||||
|
backgroundColor?: ColorTokens
|
||||||
|
background?: ColorTokens
|
||||||
|
lock?: 'locked' | 'unlocked'
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
type CommunityAvatarProps = {
|
||||||
setStatus('idle')
|
type: 'community'
|
||||||
}, [src])
|
size: 80 | 32 | 24 | 20
|
||||||
|
name: string
|
||||||
|
src?: string
|
||||||
|
backgroundColor?: ColorTokens
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountAvatarProps = {
|
||||||
|
type: 'account'
|
||||||
|
size: 80 | 48 | 32 | 28 | 24 | 20
|
||||||
|
name: string
|
||||||
|
src?: string
|
||||||
|
backgroundColor?: ColorTokens
|
||||||
|
}
|
||||||
|
|
||||||
|
type IconAvatarProps = {
|
||||||
|
type: 'icon'
|
||||||
|
size: 48 | 32 | 20
|
||||||
|
icon: React.ReactElement
|
||||||
|
backgroundColor?: ColorTokens
|
||||||
|
color?: ColorTokens
|
||||||
|
}
|
||||||
|
|
||||||
|
type AvatarProps =
|
||||||
|
| UserAvatarProps
|
||||||
|
| GroupAvatarProps
|
||||||
|
| WalletAvatarProps
|
||||||
|
| ChannelAvatarProps
|
||||||
|
| CommunityAvatarProps
|
||||||
|
| AccountAvatarProps
|
||||||
|
| IconAvatarProps
|
||||||
|
|
||||||
|
type ImageLoadingStatus = 'loading' | 'loaded' | 'error'
|
||||||
|
|
||||||
|
const userPaddingSizes: Record<UserAvatarProps['size'], number> = {
|
||||||
|
'80': 4,
|
||||||
|
'56': 2,
|
||||||
|
'48': 2,
|
||||||
|
'32': 2,
|
||||||
|
'28': 0,
|
||||||
|
'24': 0,
|
||||||
|
'20': 0,
|
||||||
|
'16': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountRadiusSizes: Record<AccountAvatarProps['size'], RadiusTokens> = {
|
||||||
|
'80': '$16',
|
||||||
|
'48': '$12',
|
||||||
|
'32': '$10',
|
||||||
|
'28': '$8',
|
||||||
|
'24': '$8',
|
||||||
|
'20': '$6',
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelEmojiSizes: Record<ChannelAvatarProps['size'], TextProps['size']> =
|
||||||
|
{
|
||||||
|
// todo: design review
|
||||||
|
'80': 27,
|
||||||
|
'32': 15,
|
||||||
|
'24': 13,
|
||||||
|
'20': 11,
|
||||||
|
}
|
||||||
|
|
||||||
|
const textSizes: Record<NonNullable<AvatarProps['size']>, TextProps['size']> = {
|
||||||
|
'80': 27,
|
||||||
|
'56': 19,
|
||||||
|
'48': 19,
|
||||||
|
'32': 15,
|
||||||
|
'28': 13,
|
||||||
|
'24': 13,
|
||||||
|
'20': 11,
|
||||||
|
'16': 11,
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupMembersIconSizes: Record<
|
||||||
|
GroupAvatarProps['size'],
|
||||||
|
IconProps['size'] | number // to scales SVG
|
||||||
|
> = {
|
||||||
|
// todo: design review
|
||||||
|
'80': 36,
|
||||||
|
'48': 20,
|
||||||
|
'32': 16,
|
||||||
|
'28': 16,
|
||||||
|
'20': 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelLockIconVariants: Record<
|
||||||
|
ChannelAvatarProps['size'],
|
||||||
|
{
|
||||||
|
baseVariant: GetStyledVariants<typeof LockBase>['variant']
|
||||||
|
iconSize: IconProps['size'] | number // to scales SVG
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
// todo: design review
|
||||||
|
'80': { baseVariant: 80, iconSize: 40 },
|
||||||
|
'32': { baseVariant: 24, iconSize: 12 },
|
||||||
|
'24': { baseVariant: 24, iconSize: 12 },
|
||||||
|
'20': { baseVariant: 20, iconSize: 12 },
|
||||||
|
}
|
||||||
|
|
||||||
|
const Avatar = (props: AvatarProps) => {
|
||||||
|
const colorHash = 'colorHash' in props ? props.colorHash : undefined
|
||||||
|
const identiconRing = useMemo(() => {
|
||||||
|
if (colorHash) {
|
||||||
|
const gradient = generateIdenticonRing(colorHash)
|
||||||
|
return `conic-gradient(from 90deg, ${gradient})`
|
||||||
|
}
|
||||||
|
}, [colorHash])
|
||||||
|
|
||||||
|
const [status, setStatus] = useState<ImageLoadingStatus>()
|
||||||
|
|
||||||
|
const padding =
|
||||||
|
props.type === 'user' && identiconRing ? userPaddingSizes[props.size] : 0
|
||||||
|
const radius: RadiusTokens =
|
||||||
|
props.type === 'account' ? accountRadiusSizes[props.size] : '$full'
|
||||||
|
const backgroundColor = getBackgroundColor()
|
||||||
|
|
||||||
|
function getBackgroundColor(): ColorTokens {
|
||||||
|
if ('src' in props && props.src) {
|
||||||
|
switch (status) {
|
||||||
|
case 'error':
|
||||||
|
break
|
||||||
|
case 'loaded':
|
||||||
|
return '$transparent'
|
||||||
|
case 'loading':
|
||||||
|
default:
|
||||||
|
return '$white-100'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.backgroundColor) {
|
||||||
|
return props.backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.type === 'channel') {
|
||||||
|
return '$blue-50-opa-20'
|
||||||
|
}
|
||||||
|
|
||||||
|
return '$neutral-95'
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'user':
|
||||||
|
case 'account':
|
||||||
|
case 'group':
|
||||||
|
case 'community': {
|
||||||
|
if (!props.src) {
|
||||||
|
return (
|
||||||
|
<Fallback borderRadius={radius} backgroundColor={backgroundColor}>
|
||||||
|
{/* todo?: contrasting color to background */}
|
||||||
|
{props.type === 'group' ? (
|
||||||
|
cloneElement(
|
||||||
|
<MembersIcon
|
||||||
|
size={
|
||||||
|
groupMembersIconSizes[props.size] as IconProps['size']
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
color: '$white-100',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Text
|
||||||
|
size={textSizes[props.size]}
|
||||||
|
weight="medium"
|
||||||
|
color="$white-100"
|
||||||
|
>
|
||||||
|
{props.name.slice(0, 2).toUpperCase()}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Fallback>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Image
|
||||||
|
src={props.src}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
// todo: use tamagui image with token support
|
||||||
|
borderRadius={
|
||||||
|
tokens.radius[
|
||||||
|
radius
|
||||||
|
.toString()
|
||||||
|
.replace('$', '') as keyof typeof tokens.radius
|
||||||
|
].val
|
||||||
|
}
|
||||||
|
width="full"
|
||||||
|
aspectRatio={1}
|
||||||
|
onLoadStart={() => {
|
||||||
|
if (status) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus('loading')
|
||||||
|
}}
|
||||||
|
onLoad={() => setStatus('loaded')}
|
||||||
|
onError={() => setStatus('error')}
|
||||||
|
/>
|
||||||
|
{/* todo?: add fallback to Image */}
|
||||||
|
{status === 'error' && (
|
||||||
|
<Fallback
|
||||||
|
borderRadius={radius}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 'wallet':
|
||||||
|
return (
|
||||||
|
<Fallback borderRadius={radius} backgroundColor={backgroundColor}>
|
||||||
|
<Text
|
||||||
|
size={textSizes[props.size]}
|
||||||
|
weight="medium"
|
||||||
|
color="$white-100"
|
||||||
|
>
|
||||||
|
{props.name.slice(0, 2).toUpperCase()}
|
||||||
|
</Text>
|
||||||
|
</Fallback>
|
||||||
|
)
|
||||||
|
case 'channel':
|
||||||
|
return <Text size={channelEmojiSizes[props.size]}>{props.emoji}</Text>
|
||||||
|
case 'icon':
|
||||||
|
return cloneElement(props.icon, { color: props.color ?? '$white-100' })
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderBadge = () => {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'user': {
|
||||||
|
if (!props.indicator) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Unspaced>
|
||||||
|
<Indicator size={props.size} state={props.indicator} />
|
||||||
|
</Unspaced>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 'channel': {
|
||||||
|
if (!props.lock) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconVariant = channelLockIconVariants[props.size]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LockBase variant={iconVariant.baseVariant}>
|
||||||
|
{props.lock === 'locked' ? (
|
||||||
|
<LockedIcon size={iconVariant.iconSize as IconProps['size']} />
|
||||||
|
) : (
|
||||||
|
<UnlockedIcon size={iconVariant.iconSize as IconProps['size']} />
|
||||||
|
)}
|
||||||
|
</LockBase>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base size={size} shape={shape} outline={outline}>
|
<Stack style={{ position: 'relative', height: 'fit-content' }}>
|
||||||
{indicator !== 'none' && (
|
<Base
|
||||||
<Unspaced>
|
borderRadius={radius}
|
||||||
<Indicator size={size} state={indicator} />
|
padding={padding}
|
||||||
</Unspaced>
|
size={props.size}
|
||||||
)}
|
backgroundColor={backgroundColor}
|
||||||
<Shape shape={shape}>
|
// todo?: https://reactnative.dev/docs/images.html#background-image-via-nesting or svg instead
|
||||||
<Image
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
src={src}
|
// @ts-ignore
|
||||||
width="full"
|
style={{
|
||||||
aspectRatio={1}
|
...(Platform.OS === 'web' && {
|
||||||
onLoad={() => setStatus('loaded')}
|
background: identiconRing,
|
||||||
onError={() => setStatus('error')}
|
}),
|
||||||
/>
|
}}
|
||||||
|
>
|
||||||
{status === 'error' && (
|
{renderContent()}
|
||||||
<Fallback
|
</Base>
|
||||||
width={size}
|
{renderBadge()}
|
||||||
height={size}
|
</Stack>
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
>
|
|
||||||
PP
|
|
||||||
</Fallback>
|
|
||||||
)}
|
|
||||||
</Shape>
|
|
||||||
</Base>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Avatar }
|
export { Avatar }
|
||||||
export type { Props as AvatarProps }
|
export type {
|
||||||
|
AccountAvatarProps,
|
||||||
|
AvatarProps,
|
||||||
|
ChannelAvatarProps,
|
||||||
|
CommunityAvatarProps,
|
||||||
|
GroupAvatarProps,
|
||||||
|
IconAvatarProps,
|
||||||
|
UserAvatarProps,
|
||||||
|
WalletAvatarProps,
|
||||||
|
}
|
||||||
|
|
||||||
const Base = styled(Stack, {
|
const Base = styled(Stack, {
|
||||||
name: 'Avatar',
|
name: 'Avatar',
|
||||||
|
@ -74,54 +356,54 @@ const Base = styled(Stack, {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
overflow: 'hidden',
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
// defined in Avatar props
|
|
||||||
size: {
|
size: {
|
||||||
'...': (size: number) => {
|
80: {
|
||||||
return {
|
width: 80,
|
||||||
width: size,
|
height: 80,
|
||||||
height: size,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
56: {
|
||||||
|
width: 56,
|
||||||
shape: {
|
height: 56,
|
||||||
circle: {
|
|
||||||
borderRadius: '$full',
|
|
||||||
},
|
},
|
||||||
rounded: {
|
48: {
|
||||||
borderRadius: '$16',
|
width: 48,
|
||||||
|
height: 48,
|
||||||
},
|
},
|
||||||
},
|
32: {
|
||||||
|
width: 32,
|
||||||
outline: {
|
height: 32,
|
||||||
true: {
|
},
|
||||||
borderWidth: 2,
|
28: {
|
||||||
borderColor: '$white-100',
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
},
|
||||||
|
24: {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
},
|
||||||
|
20: {
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
},
|
||||||
|
16: {
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const,
|
} as const,
|
||||||
})
|
})
|
||||||
|
|
||||||
const Shape = styled(Stack, {
|
const Fallback = styled(Stack, {
|
||||||
name: 'AvatarShape',
|
name: 'AvatarFallback',
|
||||||
|
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
backgroundColor: '$white-100',
|
|
||||||
overflow: 'hidden',
|
|
||||||
|
|
||||||
variants: {
|
|
||||||
shape: {
|
|
||||||
circle: {
|
|
||||||
borderRadius: '$full',
|
|
||||||
},
|
|
||||||
rounded: {
|
|
||||||
borderRadius: '$16',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const Indicator = styled(Stack, {
|
const Indicator = styled(Stack, {
|
||||||
|
@ -191,6 +473,31 @@ const Indicator = styled(Stack, {
|
||||||
} as const,
|
} as const,
|
||||||
})
|
})
|
||||||
|
|
||||||
const Fallback = styled(Text, {
|
const LockBase = styled(Stack, {
|
||||||
name: 'AvatarFallback',
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
backgroundColor: '$white-100',
|
||||||
|
position: 'absolute',
|
||||||
|
borderRadius: '$full',
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
80: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
right: -14,
|
||||||
|
bottom: -14,
|
||||||
|
},
|
||||||
|
24: {
|
||||||
|
right: -4,
|
||||||
|
bottom: -4,
|
||||||
|
},
|
||||||
|
20: {
|
||||||
|
right: -6,
|
||||||
|
bottom: -6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
import { LockedIcon, UnlockedIcon } from '@status-im/icons'
|
|
||||||
import { type ColorTokens, Stack, styled, Text } from '@tamagui/core'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
emoji: string
|
|
||||||
color?: ColorTokens
|
|
||||||
background?: ColorTokens
|
|
||||||
size: 32 | 24 | 20
|
|
||||||
lock?: 'locked' | 'unlocked' | 'none'
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojiSizes: Record<Props['size'], number> = {
|
|
||||||
32: 14,
|
|
||||||
24: 13,
|
|
||||||
20: 11,
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=399-20709&t=kX5LC5OYFnSF8BiZ-11
|
|
||||||
const ChannelAvatar = (props: Props) => {
|
|
||||||
const { emoji, background = '$blue-50-opa-20', size, lock = 'none' } = props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Base size={size} backgroundColor={background}>
|
|
||||||
{lock !== 'none' && (
|
|
||||||
<LockBase variant={size}>
|
|
||||||
{lock === 'locked' ? (
|
|
||||||
<LockedIcon size={12} />
|
|
||||||
) : (
|
|
||||||
<UnlockedIcon size={12} />
|
|
||||||
)}
|
|
||||||
</LockBase>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Text fontSize={emojiSizes[size]}>{emoji}</Text>
|
|
||||||
</Base>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ChannelAvatar }
|
|
||||||
export type { Props as ChannelAvatarProps }
|
|
||||||
|
|
||||||
const Base = styled(Stack, {
|
|
||||||
position: 'relative',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
|
|
||||||
variants: {
|
|
||||||
size: {
|
|
||||||
'...': (size: number) => {
|
|
||||||
return {
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
borderRadius: size / 2,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const LockBase = styled(Stack, {
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
backgroundColor: '$white-100',
|
|
||||||
position: 'absolute',
|
|
||||||
borderRadius: '$16',
|
|
||||||
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
32: {
|
|
||||||
right: -4,
|
|
||||||
bottom: -4,
|
|
||||||
},
|
|
||||||
24: {
|
|
||||||
right: -4,
|
|
||||||
bottom: -4,
|
|
||||||
},
|
|
||||||
20: {
|
|
||||||
right: -6,
|
|
||||||
bottom: -6,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { cloneElement } from 'react'
|
|
||||||
|
|
||||||
import { type ColorTokens, Stack, styled } from '@tamagui/core'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: React.ReactElement
|
|
||||||
backgroundColor?: ColorTokens
|
|
||||||
color?: ColorTokens
|
|
||||||
size?: 20 | 32 | 48
|
|
||||||
}
|
|
||||||
|
|
||||||
const IconAvatar = (props: Props) => {
|
|
||||||
const {
|
|
||||||
children,
|
|
||||||
color = '$blue-50',
|
|
||||||
backgroundColor = '$blue-50-opa-20',
|
|
||||||
size = 32,
|
|
||||||
} = props
|
|
||||||
return (
|
|
||||||
<Base backgroundColor={backgroundColor} size={size}>
|
|
||||||
{cloneElement(children, { color })}
|
|
||||||
</Base>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Base = styled(Stack, {
|
|
||||||
borderRadius: '$full',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
|
|
||||||
variants: {
|
|
||||||
size: {
|
|
||||||
'...': (size: number) => {
|
|
||||||
return {
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export { IconAvatar }
|
|
||||||
export type { Props as IconAvatarProps }
|
|
|
@ -1,3 +1 @@
|
||||||
export * from './avatar'
|
export * from './avatar'
|
||||||
export * from './channel-avatar'
|
|
||||||
export * from './icon-avatar'
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* returns value for conic-gradient
|
||||||
|
*/
|
||||||
|
export const generateIdenticonRing = (colorHash: number[][]) => {
|
||||||
|
const segments = colorHash.reduce((acc, segment) => (acc += segment[0]), 0)
|
||||||
|
|
||||||
|
let prevAngle = 0
|
||||||
|
const gradient = colorHash.reduce((acc, segment, index) => {
|
||||||
|
const [length, colorIndex] = segment
|
||||||
|
const color = COLORS[colorIndex]
|
||||||
|
const nextAngle = Math.round(prevAngle + (length * 360) / segments)
|
||||||
|
|
||||||
|
acc += `${color} ${prevAngle}deg ${nextAngle}deg`
|
||||||
|
|
||||||
|
if (index !== colorHash.length - 1) {
|
||||||
|
acc += `, `
|
||||||
|
}
|
||||||
|
|
||||||
|
prevAngle = nextAngle
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, '')
|
||||||
|
|
||||||
|
return gradient
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLORS = [
|
||||||
|
'#000000',
|
||||||
|
'#726F6F',
|
||||||
|
'#C4C4C4',
|
||||||
|
'#E7E7E7',
|
||||||
|
'#FFFFFF',
|
||||||
|
'#00FF00',
|
||||||
|
'#009800',
|
||||||
|
'#B8FFBB',
|
||||||
|
'#FFC413',
|
||||||
|
'#9F5947',
|
||||||
|
'#FFFF00',
|
||||||
|
'#A8AC00',
|
||||||
|
'#FFFFB0',
|
||||||
|
'#FF5733',
|
||||||
|
'#FF0000',
|
||||||
|
'#9A0000',
|
||||||
|
'#FF9D9D',
|
||||||
|
'#FF0099',
|
||||||
|
'#C80078',
|
||||||
|
'#FF00FF',
|
||||||
|
'#900090',
|
||||||
|
'#FFB0FF',
|
||||||
|
'#9E00FF',
|
||||||
|
'#0000FF',
|
||||||
|
'#000086',
|
||||||
|
'#9B81FF',
|
||||||
|
'#3FAEF9',
|
||||||
|
'#9A6600',
|
||||||
|
'#00FFFF',
|
||||||
|
'#008694',
|
||||||
|
'#C2FFFF',
|
||||||
|
'#00F0B6',
|
||||||
|
]
|
|
@ -44,9 +44,7 @@ type Story = StoryObj<typeof Channel>
|
||||||
|
|
||||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {},
|
||||||
lock: 'none',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Locked: Story = {
|
export const Locked: Story = {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useState } from 'react'
|
||||||
import { MutedIcon, NotificationIcon, OptionsIcon } from '@status-im/icons'
|
import { MutedIcon, NotificationIcon, OptionsIcon } from '@status-im/icons'
|
||||||
import { Stack, styled } from 'tamagui'
|
import { Stack, styled } from 'tamagui'
|
||||||
|
|
||||||
import { ChannelAvatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { Counter } from '../counter'
|
import { Counter } from '../counter'
|
||||||
import { DropdownMenu } from '../dropdown-menu'
|
import { DropdownMenu } from '../dropdown-menu'
|
||||||
import { Text } from '../text'
|
import { Text } from '../text'
|
||||||
|
@ -95,7 +95,7 @@ const Channel = (props: Props) => {
|
||||||
state={active ? 'active' : selected ? 'selected' : undefined}
|
state={active ? 'active' : selected ? 'selected' : undefined}
|
||||||
>
|
>
|
||||||
<Stack flexDirection="row" gap={8} alignItems="center">
|
<Stack flexDirection="row" gap={8} alignItems="center">
|
||||||
<ChannelAvatar emoji={emoji} size={24} lock={lock} />
|
<Avatar type="channel" emoji={emoji} size={24} lock={lock} />
|
||||||
<Text size={15} weight="medium" color={textColor}>
|
<Text size={15} weight="medium" color={textColor}>
|
||||||
# {children}
|
# {children}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -63,11 +63,21 @@ const SidebarCommunity = (props: Props) => {
|
||||||
>
|
>
|
||||||
<Stack paddingHorizontal={16} paddingBottom={16}>
|
<Stack paddingHorizontal={16} paddingBottom={16}>
|
||||||
<Stack marginTop={-40} marginBottom={12}>
|
<Stack marginTop={-40} marginBottom={12}>
|
||||||
<Avatar
|
<Stack>
|
||||||
outline
|
<Stack
|
||||||
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"
|
borderWidth={2}
|
||||||
size={80}
|
borderColor={'$white-100'}
|
||||||
/>
|
borderRadius={'$full'}
|
||||||
|
width="fit-content"
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
type="community"
|
||||||
|
name="Community"
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack gap={8} marginBottom={12}>
|
<Stack gap={8} marginBottom={12}>
|
||||||
<Text size={27} weight="semibold">
|
<Text size={27} weight="semibold">
|
||||||
|
|
|
@ -65,7 +65,7 @@ const ContextTag = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base outline={outline} size={size} hasImg={hasImg} blur={blur}>
|
<Base outline={outline} size={size} hasImg={hasImg} blur={blur}>
|
||||||
{src && <Avatar size={avatarSizes[size]} src={src} />}
|
{src && <Avatar type="user" name="" size={avatarSizes[size]} src={src} />}
|
||||||
{icon && cloneElement(icon, { color: '$neutral-50' })}
|
{icon && cloneElement(icon, { color: '$neutral-50' })}
|
||||||
|
|
||||||
{Array.isArray(label) ? (
|
{Array.isArray(label) ? (
|
||||||
|
|
|
@ -59,12 +59,26 @@ const Base = styled(RNImage, {
|
||||||
variants: {
|
variants: {
|
||||||
radius: {
|
radius: {
|
||||||
none: {},
|
none: {},
|
||||||
|
6: {
|
||||||
|
borderRadius: 6,
|
||||||
|
},
|
||||||
|
8: {
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
10: {
|
||||||
|
borderRadius: 10,
|
||||||
|
},
|
||||||
12: {
|
12: {
|
||||||
borderRadius: 12, // fix this once Image is migrated to tamagui image
|
borderRadius: 12, // fix this once Image is migrated to tamagui image
|
||||||
},
|
},
|
||||||
|
16: {
|
||||||
|
borderRadius: 16,
|
||||||
|
},
|
||||||
full: {
|
full: {
|
||||||
borderRadius: 999, // fix this once Image is migrated to tamagui image
|
borderRadius: 999, // fix this once Image is migrated to tamagui image
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
backgroundColor: '$white-100',
|
||||||
})
|
})
|
||||||
|
|
|
@ -103,6 +103,8 @@ const Message = (props: MessageProps) => {
|
||||||
|
|
||||||
<XStack gap={10}>
|
<XStack gap={10}>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
type="user"
|
||||||
|
name=""
|
||||||
size={32}
|
size={32}
|
||||||
src="https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500"
|
src="https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500"
|
||||||
indicator="online"
|
indicator="online"
|
||||||
|
|
|
@ -26,7 +26,7 @@ const Reply = (props: Props) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
</Unspaced>
|
</Unspaced>
|
||||||
|
|
||||||
<Avatar size={16} src={src} />
|
<Avatar type="user" name={name} size={16} src={src} />
|
||||||
|
|
||||||
<Text size={13} weight="semibold">
|
<Text size={13} weight="semibold">
|
||||||
{name}
|
{name}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { AddUserIcon } from '@status-im/icons'
|
import { AddUserIcon } from '@status-im/icons'
|
||||||
import { Stack } from 'tamagui'
|
import { Stack } from 'tamagui'
|
||||||
|
|
||||||
import { Avatar, IconAvatar } from '../../avatar'
|
import { Avatar } from '../../avatar'
|
||||||
import { Text } from '../../text'
|
import { Text } from '../../text'
|
||||||
|
|
||||||
import type { SystemMessageState, User } from '../system-message'
|
import type { SystemMessageState, User } from '../system-message'
|
||||||
|
@ -18,22 +18,29 @@ const AddedUsersMessageContent = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconAvatar
|
<Avatar
|
||||||
|
type="icon"
|
||||||
|
// fixme: map relative avatar size to icon size
|
||||||
|
icon={<AddUserIcon size={20} />}
|
||||||
|
size={32}
|
||||||
backgroundColor={state === 'landed' ? '$transparent' : '$blue-50-opa-5'}
|
backgroundColor={state === 'landed' ? '$transparent' : '$blue-50-opa-5'}
|
||||||
color="$blue-50"
|
color="$blue-50"
|
||||||
>
|
/>
|
||||||
<AddUserIcon size={20} />
|
|
||||||
</IconAvatar>
|
|
||||||
<Stack flexDirection="row" gap={2} flexBasis="max-content" flexGrow={1}>
|
<Stack flexDirection="row" gap={2} flexBasis="max-content" flexGrow={1}>
|
||||||
<Stack flexDirection="row" gap={4} alignItems="center" flexGrow={1}>
|
<Stack flexDirection="row" gap={4} alignItems="center" flexGrow={1}>
|
||||||
<Avatar size={16} src={user.src} />
|
<Avatar type="user" name={user.name} size={16} src={user.src} />
|
||||||
<Text size={13} weight="semibold">
|
<Text size={13} weight="semibold">
|
||||||
{user.name}
|
{user.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size={13}>added </Text>
|
<Text size={13}>added </Text>
|
||||||
{users.length === 1 && (
|
{users.length === 1 && (
|
||||||
<Stack flexDirection="row" gap={4} alignItems="center">
|
<Stack flexDirection="row" gap={4} alignItems="center">
|
||||||
<Avatar size={16} src={users[0].src} />
|
<Avatar
|
||||||
|
type="user"
|
||||||
|
name={user.name}
|
||||||
|
size={16}
|
||||||
|
src={users[0].src}
|
||||||
|
/>
|
||||||
<Text size={13} weight="semibold">
|
<Text size={13} weight="semibold">
|
||||||
{users[0].name}
|
{users[0].name}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -56,7 +63,12 @@ const AddedUsersMessageContent = (props: Props) => {
|
||||||
<Text size={13}>
|
<Text size={13}>
|
||||||
{users.length === i + 1 ? ' and ' : null}
|
{users.length === i + 1 ? ' and ' : null}
|
||||||
</Text>
|
</Text>
|
||||||
<Avatar size={16} src={user.src} />
|
<Avatar
|
||||||
|
type="user"
|
||||||
|
name={user.name}
|
||||||
|
size={16}
|
||||||
|
src={user.src}
|
||||||
|
/>
|
||||||
<Text size={13} weight="semibold">
|
<Text size={13} weight="semibold">
|
||||||
{user.name}
|
{user.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { LoadingIcon, TrashIcon } from '@status-im/icons'
|
import { LoadingIcon, TrashIcon } from '@status-im/icons'
|
||||||
import { Stack } from 'tamagui'
|
import { Stack } from 'tamagui'
|
||||||
|
|
||||||
import { IconAvatar } from '../../avatar'
|
import { Avatar } from '../../avatar'
|
||||||
import { Button } from '../../button'
|
import { Button } from '../../button'
|
||||||
import { Text } from '../../text'
|
import { Text } from '../../text'
|
||||||
|
|
||||||
|
@ -22,12 +22,13 @@ const DeletedMessageContent = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconAvatar
|
<Avatar
|
||||||
|
type="icon"
|
||||||
|
size={32}
|
||||||
|
icon={<TrashIcon size={20} />}
|
||||||
backgroundColor={state === 'landed' ? '$transparent' : '$red-50-opa-5'}
|
backgroundColor={state === 'landed' ? '$transparent' : '$red-50-opa-5'}
|
||||||
color="$neutral-100"
|
color="$neutral-100"
|
||||||
>
|
/>
|
||||||
<TrashIcon size={20} />
|
|
||||||
</IconAvatar>
|
|
||||||
<Stack
|
<Stack
|
||||||
flexDirection="row"
|
flexDirection="row"
|
||||||
gap={2}
|
gap={2}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PinIcon } from '@status-im/icons'
|
import { PinIcon } from '@status-im/icons'
|
||||||
import { Stack } from 'tamagui'
|
import { Stack } from 'tamagui'
|
||||||
|
|
||||||
import { Avatar, IconAvatar } from '../../avatar'
|
import { Avatar } from '../../avatar'
|
||||||
import { Text } from '../../text'
|
import { Text } from '../../text'
|
||||||
|
|
||||||
import type { SystemMessageState, User } from '../system-message'
|
import type { SystemMessageState, User } from '../system-message'
|
||||||
|
@ -27,12 +27,13 @@ const PinnedMessageContent = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconAvatar
|
<Avatar
|
||||||
|
type="icon"
|
||||||
|
size={32}
|
||||||
|
icon={<PinIcon size={20} />}
|
||||||
backgroundColor={state === 'landed' ? '$transparent' : '$blue-50-opa-5'}
|
backgroundColor={state === 'landed' ? '$transparent' : '$blue-50-opa-5'}
|
||||||
color="$neutral-100"
|
color="$neutral-100"
|
||||||
>
|
/>
|
||||||
<PinIcon size={20} />
|
|
||||||
</IconAvatar>
|
|
||||||
<Stack
|
<Stack
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
gap={2}
|
gap={2}
|
||||||
|
@ -56,7 +57,7 @@ const PinnedMessageContent = (props: Props) => {
|
||||||
flexBasis="max-content"
|
flexBasis="max-content"
|
||||||
>
|
>
|
||||||
<Stack flexDirection="row" gap={4}>
|
<Stack flexDirection="row" gap={4}>
|
||||||
<Avatar size={16} src={author.src} />
|
<Avatar type="user" name={author.name} size={16} src={author.src} />
|
||||||
<Text size={11} weight="semibold">
|
<Text size={11} weight="semibold">
|
||||||
{author.name}
|
{author.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -12,6 +12,7 @@ type Weight = NonNullable<Variants['weight']>
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
size?: 27 | 19 | 15 | 13 | 11 | undefined
|
||||||
color?: ColorTokens
|
color?: ColorTokens
|
||||||
truncate?: boolean
|
truncate?: boolean
|
||||||
wrap?: false
|
wrap?: false
|
||||||
|
@ -26,8 +27,8 @@ type Props = {
|
||||||
// TODO: monospace should be used only for variant. Extract to separate <Address> component?
|
// TODO: monospace should be used only for variant. Extract to separate <Address> component?
|
||||||
// TODO: Ubuntu Mono should be used only for code snippets. Extract to separate <Code> component?
|
// TODO: Ubuntu Mono should be used only for code snippets. Extract to separate <Code> component?
|
||||||
const Text = (props: Props, ref: Ref<RNText>) => {
|
const Text = (props: Props, ref: Ref<RNText>) => {
|
||||||
const { color = '$neutral-100', ...rest } = props
|
const { color = '$neutral-100', size = 13, ...rest } = props
|
||||||
return <Base {...rest} ref={ref} color={color} />
|
return <Base {...rest} ref={ref} color={color} size={size} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const Base = styled(BaseText, {
|
const Base = styled(BaseText, {
|
||||||
|
|
|
@ -5,10 +5,10 @@ import { Avatar } from '../avatar'
|
||||||
import { Text } from '../text'
|
import { Text } from '../text'
|
||||||
|
|
||||||
import type { AuthorProps } from '../author/author'
|
import type { AuthorProps } from '../author/author'
|
||||||
import type { AvatarProps } from '../avatar'
|
import type { UserAvatarProps } from '../avatar'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
users: (Pick<AvatarProps, 'src' | 'indicator'> & AuthorProps)[]
|
users: (Pick<UserAvatarProps, 'src' | 'indicator'> & AuthorProps)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserList = (props: Props) => {
|
const UserList = (props: Props) => {
|
||||||
|
@ -31,7 +31,13 @@ const UserList = (props: Props) => {
|
||||||
backgroundColor: '$primary-50-opa-5',
|
backgroundColor: '$primary-50-opa-5',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar size={32} src={src} indicator={indicator} />
|
<Avatar
|
||||||
|
type="user"
|
||||||
|
name={authorProps.name}
|
||||||
|
size={32}
|
||||||
|
src={src}
|
||||||
|
indicator={indicator}
|
||||||
|
/>
|
||||||
<YStack>
|
<YStack>
|
||||||
<Author {...authorProps} />
|
<Author {...authorProps} />
|
||||||
<Text size={13} color="$neutral-50" type="monospace">
|
<Text size={13} color="$neutral-50" type="monospace">
|
||||||
|
|
Loading…
Reference in New Issue