Skip to content

Refactor Avatar to use Box #2019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 21 additions & 24 deletions src/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import styled from 'styled-components'
import {get} from './constants'
import sx, {SxProp} from './sx'
import {ComponentProps} from './utils/types'
import React from 'react'
import Box from './Box'
import {SxProp, merge, BetterSystemStyleObject} from './sx'

type StyledAvatarProps = {
export type AvatarProps = {
/** Sets the width and height of the avatar. */
size?: number
/** Sets the shape of the avatar to a square if true. If false, the avatar will be circular. */
Expand All @@ -12,33 +11,31 @@ type StyledAvatarProps = {
src: string
/** Provide alt text when the Avatar is used without the user's name next to it. */
alt?: string
} & SxProp
} & SxProp &
Omit<React.ComponentProps<'img'>, 'ref'>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use ComponentPropsWithoutRef here

Suggested change
Omit<React.ComponentProps<'img'>, 'ref'>
React.ComponentPropsWithoutRef<'img'>


function getBorderRadius({size, square}: StyledAvatarProps) {
function getBorderRadius({size, square}: Pick<AvatarProps, 'size' | 'square'>) {
if (square) {
return size && size <= 24 ? '4px' : '6px'
} else {
return '50%'
}
}

const Avatar = styled.img.attrs<StyledAvatarProps>(props => ({
height: props.size,
width: props.size
}))<StyledAvatarProps>`
display: inline-block;
overflow: hidden; // Ensure page layout in Firefox should images fail to load
line-height: ${get('lineHeights.condensedUltra')};
vertical-align: middle;
border-radius: ${props => getBorderRadius(props)};
${sx}
`
const Avatar = React.forwardRef<HTMLImageElement, AvatarProps>((props, ref) => {
const {size = 20, alt = '', square = false, sx = {}, ...rest} = props

Avatar.defaultProps = {
size: 20,
alt: '',
square: false
}
const styles: BetterSystemStyleObject = {
display: 'inline-block',
overflow: 'hidden',
lineHeight: 'condensedUltra',
verticalAlign: 'middle',
width: size,
height: size,
borderRadius: getBorderRadius({size, square})
}
return <Box as="img" alt={alt} ref={ref} sx={merge<BetterSystemStyleObject>(styles, sx)} {...rest} />
})

export type AvatarProps = ComponentProps<typeof Avatar>
Avatar.displayName = 'Avatar'
export default Avatar
10 changes: 6 additions & 4 deletions src/__tests__/Avatar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ describe('Avatar', () => {
it('renders small by default', () => {
const size = 20
const result = render(<Avatar src="primer.png" />)
expect(result.props.width).toEqual(size)
expect(result.props.height).toEqual(size)

expect(result).toHaveStyleRule('width', px(size))
expect(result).toHaveStyleRule('height', px(size))
})

it('respects the size prop', () => {
const result = render(<Avatar size={40} src="primer.png" alt="github" />)
expect(result.props.width).toEqual(40)
expect(result.props.height).toEqual(40)

expect(result).toHaveStyleRule('width', '40px')
expect(result).toHaveStyleRule('height', '40px')
})

it('passes through the src prop', () => {
Expand Down
5 changes: 2 additions & 3 deletions src/__tests__/__snapshots__/Avatar.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ exports[`Avatar renders consistently 1`] = `
overflow: hidden;
line-height: 1;
vertical-align: middle;
width: 20px;
height: 20px;
border-radius: 50%;
}

<img
alt=""
className="c0"
height={20}
size={20}
width={20}
/>
`;
35 changes: 7 additions & 28 deletions src/__tests__/__snapshots__/Token.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ exports[`Token components AvatarToken renders all sizes 1`] = `
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 50%;
width: 100%;
height: 100%;
border-radius: 50%;
}

.c5 {
Expand Down Expand Up @@ -166,10 +166,7 @@ exports[`Token components AvatarToken renders all sizes 1`] = `
<img
alt=""
className="c3"
height={16}
size={16}
src="https://avatars.githubusercontent.com/mperrotti"
width={16}
/>
</span>
</span>
Expand Down Expand Up @@ -217,9 +214,9 @@ exports[`Token components AvatarToken renders all sizes 2`] = `
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 50%;
width: 100%;
height: 100%;
border-radius: 50%;
}

.c5 {
Expand Down Expand Up @@ -377,10 +374,7 @@ exports[`Token components AvatarToken renders all sizes 2`] = `
<img
alt=""
className="c3"
height={20}
size={20}
src="https://avatars.githubusercontent.com/mperrotti"
width={20}
/>
</span>
</span>
Expand Down Expand Up @@ -428,9 +422,9 @@ exports[`Token components AvatarToken renders all sizes 3`] = `
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 50%;
width: 100%;
height: 100%;
border-radius: 50%;
}

.c5 {
Expand Down Expand Up @@ -588,10 +582,7 @@ exports[`Token components AvatarToken renders all sizes 3`] = `
<img
alt=""
className="c3"
height={24}
size={24}
src="https://avatars.githubusercontent.com/mperrotti"
width={24}
/>
</span>
</span>
Expand Down Expand Up @@ -639,9 +630,9 @@ exports[`Token components AvatarToken renders all sizes 4`] = `
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 50%;
width: 100%;
height: 100%;
border-radius: 50%;
}

.c5 {
Expand Down Expand Up @@ -799,10 +790,7 @@ exports[`Token components AvatarToken renders all sizes 4`] = `
<img
alt=""
className="c3"
height={32}
size={32}
src="https://avatars.githubusercontent.com/mperrotti"
width={32}
/>
</span>
</span>
Expand Down Expand Up @@ -850,9 +838,9 @@ exports[`Token components AvatarToken renders all sizes 5`] = `
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 50%;
width: 100%;
height: 100%;
border-radius: 50%;
}

.c5 {
Expand Down Expand Up @@ -1010,10 +998,7 @@ exports[`Token components AvatarToken renders all sizes 5`] = `
<img
alt=""
className="c3"
height={32}
size={32}
src="https://avatars.githubusercontent.com/mperrotti"
width={32}
/>
</span>
</span>
Expand Down Expand Up @@ -1144,9 +1129,9 @@ exports[`Token components AvatarToken renders isSelected 1`] = `
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 50%;
width: 100%;
height: 100%;
border-radius: 50%;
}

.c4 {
Expand Down Expand Up @@ -1252,10 +1237,7 @@ exports[`Token components AvatarToken renders isSelected 1`] = `
<img
alt=""
className="c3"
height={20}
size={20}
src="https://avatars.githubusercontent.com/mperrotti"
width={20}
/>
</span>
</span>
Expand All @@ -1273,9 +1255,9 @@ exports[`Token components AvatarToken renders with a remove button 1`] = `
overflow: hidden;
line-height: 1;
vertical-align: middle;
border-radius: 50%;
width: 100%;
height: 100%;
border-radius: 50%;
}

.c5 {
Expand Down Expand Up @@ -1433,10 +1415,7 @@ exports[`Token components AvatarToken renders with a remove button 1`] = `
<img
alt=""
className="c3"
height={20}
size={20}
src="https://avatars.githubusercontent.com/mperrotti"
width={20}
/>
</span>
</span>
Expand Down
51 changes: 51 additions & 0 deletions src/stories/Avatar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Avatar from '../Avatar'
import {Meta} from '@storybook/react'
import React from 'react'
import {ThemeProvider} from '..'
import BaseStyles from '../BaseStyles'

const meta: Meta = {
title: 'Building blocks/Avatar/fixtures',
component: Avatar,
decorators: [
(Story: React.ComponentType): JSX.Element => (
<ThemeProvider>
<BaseStyles>
<Story />
</BaseStyles>
</ThemeProvider>
)
]
}
export default meta

export function SimpleAvatar(): JSX.Element {
return <Avatar alt="Primer logo" src="https://avatars.githubusercontent.com/primer" />
}

export function CustomSize(): JSX.Element {
return <Avatar size={48} alt="Primer logo" src="https://avatars.githubusercontent.com/primer" />
}

export function SquareAvatar(): JSX.Element {
return <Avatar square alt="Primer logo" src="https://avatars.githubusercontent.com/primer" />
}
Comment on lines +26 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could just be Storybook controls

Copy link
Member Author

@siddharthkp siddharthkp Apr 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True! I'm creating extra stories so that they can be part of our visual regression testing suite :)

Should I also add a story with all the controls? (.langermank calls it the playground story in primer-css)


export function DefaultAltText(): JSX.Element {
return <Avatar src="https://avatars.githubusercontent.com/primer" />
}

export function AcceptsRef(): JSX.Element {
const ref = React.useRef<HTMLImageElement>(null)

return <Avatar ref={ref} src="https://avatars.githubusercontent.com/primer" data-test-id="avatar" />
}

export function AcceptsSxProp(): JSX.Element {
return (
<>
<Avatar sx={{marginRight: 4}} src="https://avatars.githubusercontent.com/primer" />
<span>text pushed to right because avatar has margin</span>
</>
)
}