Skip to content

Commit

Permalink
[C-4323] Improve Avatar (#8273)
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanjeffers committed Apr 30, 2024
1 parent 909db7c commit 82e1c67
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 141 deletions.
116 changes: 80 additions & 36 deletions packages/harmony/src/components/artwork/Artwork.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { ComponentProps, useState } from 'react'
import { ComponentProps, useEffect, useState } from 'react'

import { useTheme } from '@emotion/react'

import { Box, BoxProps } from 'components/layout'
import { Box, BoxProps, Flex } from 'components/layout'
import { Skeleton } from 'components/skeleton'

export type ArtworkProps = { isLoading?: boolean } & Pick<
export type ArtworkProps = { isLoading?: boolean; borderWidth?: number } & Pick<
ComponentProps<'img'>,
'src'
> &
BoxProps

/**
* The artwork component displays the track cover art and appears in several
* locations such as track tiles, track and playlist, pages,
Expand All @@ -18,44 +19,87 @@ export type ArtworkProps = { isLoading?: boolean } & Pick<
* identify their favorite tracks.
*/
export const Artwork = (props: ArtworkProps) => {
const { isLoading: isLoadingProp, src, ...other } = props
const [isLoadingState, setIsLoading] = useState(isLoadingProp ?? true)
const {
isLoading: isLoadingProp,
src,
borderRadius = 's',
borderWidth,
shadow,
children,
...other
} = props
const [isLoadingState, setIsLoadingState] = useState(!!src)
const isLoading = isLoadingProp ?? isLoadingState
const { motion } = useTheme()
const { color, motion } = useTheme()

useEffect(() => {
setIsLoadingState(!!src)
}, [src])

return (
<Box borderRadius='s' border='default' {...other}>
{isLoading ? (
<Skeleton
borderRadius='s'
h='100%'
<Box {...other}>
<Box
borderRadius={borderRadius}
border='default'
shadow={shadow}
css={{ borderWidth }}
>
{isLoading ? (
<Skeleton
borderRadius={borderRadius}
h='100%'
w='100%'
css={{ zIndex: 1, position: 'absolute' }}
/>
) : null}
<Box
w='100%'
css={{ zIndex: 1, position: 'absolute' }}
pt='100%'
borderRadius={borderRadius}
css={{
backgroundColor: src
? color.background.surface2
: color.neutral.n400
}}
/>
) : null}
<Box w='100%' pt='100%' backgroundColor='surface2' />
<Box
as='img'
borderRadius='s'
h='100%'
w='100%'
onLoad={() => {
setIsLoading(false)
}}
// @ts-ignore
src={src}
css={[
{
position: 'absolute',
top: 0,
left: 0,
objectFit: 'cover',
opacity: 0,
transition: `opacity ${motion.calm}`
},
!isLoading && { opacity: 1 }
]}
/>
{src ? (
<Box
as='img'
borderRadius={borderRadius}
h='100%'
w='100%'
onLoad={() => {
setIsLoadingState(false)
}}
// @ts-ignore
src={src}
css={{
position: 'absolute',
top: 0,
objectFit: 'cover',
opacity: isLoading ? 0 : 1,
transition: `opacity ${motion.calm}`
}}
/>
) : null}
{children ? (
<Flex
alignItems='center'
justifyContent='center'
h='100%'
w='100%'
borderRadius={borderRadius}
css={{
position: 'absolute',
top: 0,
backgroundColor: src ? color.static.black : undefined,
opacity: src ? 0.4 : undefined
}}
>
{children}
</Flex>
) : null}
</Box>
</Box>
)
}
30 changes: 13 additions & 17 deletions packages/harmony/src/components/avatar/Avatar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Flex } from 'components/layout/Flex'
import { Paper } from 'components/layout/Paper'
import { IconCamera } from 'icons'
import shadowBackground from 'storybook/assets/shadowBackground.jpeg'
// TODO: Get final image assets from Sammie

import { Avatar } from './Avatar'

Expand All @@ -15,11 +14,15 @@ const meta: Meta<typeof Avatar> = {
parameters: {
controls: {}
},
args: {
size: 'large'
},

render: (props) => {
return (
<Box w={80} h={80}>
<Flex gap='l'>
<Avatar {...props} />
</Box>
</Flex>
)
}
}
Expand All @@ -40,14 +43,10 @@ export const NoImage: Story = {
},
render: (props) => (
<Flex gap='m'>
<Box w={80} h={80}>
<Avatar {...props} />
</Box>
<Box w={80} h={80}>
<Avatar {...props}>
<IconCamera color='staticWhite' />
</Avatar>
</Box>
<Avatar {...props} />
<Avatar {...props}>
<IconCamera color='staticWhite' />
</Avatar>
</Flex>
)
}
Expand All @@ -58,13 +57,10 @@ export const Strong: Story = {
},
render: (props) => (
<Paper w={350} h={160}>
<Box
w={80}
h={80}
<Avatar
css={{ position: 'absolute', top: '40px', left: '32px' }}
>
<Avatar {...props} />
</Box>
{...props}
/>
<Flex direction='column' h='100%' w='100%'>
<Box
h='100%'
Expand Down
87 changes: 30 additions & 57 deletions packages/harmony/src/components/avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,39 @@
import { CSSObject, useTheme } from '@emotion/react'
import { Artwork, ArtworkProps } from '../artwork'

import type { AvatarProps } from './types'
type ArtworkInnerProps = Omit<ArtworkProps, 'borderWidth'>

/*
* The Avatar component is a visual indicator used to quickly identify a
* user’s account.
*/
export const Avatar = (props: AvatarProps) => {
const {
src,
strokeWidth = 'default',
size = 'auto',
variant = 'default',
className,
children
} = props
const { color } = useTheme()

const sizeMap = {
auto: '100%',
small: '24px',
medium: '40px',
large: '72px',
xl: '80px'
}
export type AvatarProps = ArtworkInnerProps & {
variant?: 'default' | 'strong'
size?: 'auto' | 'small' | 'medium' | 'large' | 'xl' | 'xxl'
borderWidth?: 'thin' | 'default'
}

const strokeWidthMap = {
thin: '1.2px',
default: '2px'
}
const sizeMap = {
auto: '100%',
small: 24,
medium: 40,
large: 72,
xl: 80,
xxl: 120
}

const rootCss: CSSObject = {
height: sizeMap[size],
width: sizeMap[size],
overflow: 'hidden',
borderRadius: 'calc(infinity * 1px)',
border: `${strokeWidthMap[strokeWidth]} solid ${color.border.default}`,
boxSizing: 'border-box',
boxShadow:
variant === 'strong'
? '0px 0.5px 1.5px 0px rgba(0, 0, 0, 0.03), 0px 1.5px 5px 0px rgba(0, 0, 0, 0.08), 0px 6px 15px 0px rgba(0, 0, 0, 0.10)'
: 'none',
backgroundColor: color.neutral.n400,
position: 'relative',
zIndex: 1
}
const borderWidthMap = {
thin: 1.2,
default: 2
}

const imgCss: CSSObject = {
height: sizeMap[size],
width: sizeMap[size],
backgroundImage: src ? `url(${src})` : undefined,
backgroundColor: src ? 'unset' : color.neutral.n400,
backgroundSize: 'cover',
zIndex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
export const Avatar = (props: AvatarProps) => {
const { variant, size = 'auto', borderWidth = 'default', ...other } = props

return (
<div css={rootCss} className={className}>
<div css={imgCss}>{children}</div>
</div>
<Artwork
borderRadius='circle'
h={sizeMap[size]}
w={sizeMap[size]}
shadow={variant === 'strong' ? 'emphasis' : 'flat'}
borderWidth={borderWidthMap[borderWidth]}
css={variant === 'strong' ? { zIndex: 1 } : undefined}
{...other}
/>
)
}
2 changes: 1 addition & 1 deletion packages/harmony/src/components/avatar/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { Avatar } from './Avatar'
export * from './types'
export type { AvatarProps } from './Avatar'
3 changes: 3 additions & 0 deletions packages/harmony/src/foundations/color/primitive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const primitiveTheme = {
day: {
static: {
white: '#FFFFFF',
black: '#000000',
primary: '#CC0FE0'
},
primary: {
Expand Down Expand Up @@ -52,6 +53,7 @@ export const primitiveTheme = {
dark: {
static: {
white: '#FFFFFF',
black: '#000000',
primary: '#CC0FE0'
},
primary: {
Expand Down Expand Up @@ -101,6 +103,7 @@ export const primitiveTheme = {
matrix: {
static: {
white: '#FFFFFF',
black: '#000000',
primary: '#CC0FE0'
},
primary: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export const cornerRadius = {
l: 12,
xl: 16,
'2xl': 32,
'3xl': 48
'3xl': 48,
circle: 1000
}

export type CornerRadius = typeof cornerRadius
Expand Down

0 comments on commit 82e1c67

Please sign in to comment.