Skip to content
Merged
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
26 changes: 26 additions & 0 deletions docs/content/AvatarStack.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: AvatarStack
---

AvatarStack is used to display more than one Avatar in an inline stack.

AvatarStack
```jsx live
<AvatarStack>
<img alt="Primer" src="https://avatars.githubusercontent.com/primer"/>
<img alt="GitHub" src="https://avatars.githubusercontent.com/github"/>
<img alt="Atom" src="https://avatars.githubusercontent.com/atom"/>
<img alt="Desktop" src="https://avatars.githubusercontent.com/desktop"/>
</AvatarStack>
```

## System props

AvatarStack components get `COMMON` system props, AvatarStack.Item components do not get any system props. Read our [System Props](/system-props) doc page for a full list of available props.


## AvatarStack Component props

| Name | Type | Default | Description |
| :- | :- | :-: | :- |
| alignRight | Boolean | | Creates right aligned AvatarStack |
2 changes: 2 additions & 0 deletions docs/src/@primer/gatsby-theme-doctocat/nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
children:
- title: Avatar
url: /Avatar
- title: AvatarStack
url: /AvatarStack
- title: BorderBox
url: /BorderBox
- title: Box
Expand Down
5 changes: 5 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ declare module '@primer/components' {

export const Dialog: React.FunctionComponent<DialogProps>

export interface AvatarStackProps extends CommonProps, Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'> {
alignRight?: boolean
}

export const AvatarStack: React.FunctionComponent<AvatarStackProps>
export interface ProgressBarProps
extends BaseProps, CommonProps, Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'> {
progress?: number | string
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

158 changes: 158 additions & 0 deletions src/AvatarStack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import {get, COMMON} from './constants'
import theme from './theme'

const alignRightStyles = theme => {
return `
right: 0;
flex-direction: row-reverse;

&:hover .AvatarItem {
margin-right: 0;
margin-left: 3px;
}

.AvatarItem-more {
background: ${get('colors.gray.3')(theme)};

&::before {
width: 5px;
}

&::after {
background: ${get('colors.gray.1')(theme)};
width: 2px;
}
}

.AvatarItem {
margin-right: 0;
margin-left: -11px;
border-right: 0;
border-left: ${get('borders.1')(theme)} ${get('colors.white')(theme)};
}
`
}

const transformChildren = children => {
const count = children.length
return React.Children.map(children, (child, index) => {
return (
<>
{count > 3 && index === 2 && <div className="AvatarItem-more AvatarItem" />}
{React.cloneElement(child, {className: 'AvatarItem'})}
</>
)
})
}

const AvatarStackWrapper = styled.span`
display: inline-block;
position: relative;
min-width: ${props => (props.count === 1 ? '26px' : props.count === 2 ? '36px' : '46px')};
height: 20px;
${COMMON}
`

const AvatarStackBody = styled.span`
display: flex;
position: absolute;
background: white;

&:hover {
.AvatarItem {
margin-right: 3px;
}

.AvatarItem:nth-child(n + 4) {
display: flex;
opacity: 1;
}

.AvatarItem-more {
display: none !important;
}
}

.AvatarItem {
position: relative;
z-index: 2;
display: flex;
width: 20px;
height: 20px;
box-sizing: content-box;
margin-right: -11px;
background-color: ${get('colors.white')};
border-right: ${get('borders.1')} ${get('colors.white')};
border-radius: 2px;
transition: margin 0.1s ease-in-out;

&:first-child {
z-index: 3;
}

&:last-child {
z-index: 1;
border-right: 0;
}

img {
border-radius: 2px;
width: inherit;
}

// Account for 4+ avatars
&:nth-child(n + 4) {
display: none;
opacity: 0;
}
}

.AvatarItem-more {
z-index: 1;
margin-right: 0;
background: ${get('colors.gray.1')};

&::before,
&::after {
position: absolute;
display: block;
height: 20px;
content: '';
border-radius: 2px;
outline: ${get('borders.1')} ${get('colors.white')};
}

&::before {
width: 17px;
background: ${get('colors.gray.2')};
}

&::after {
width: 14px;
background: ${get('colors.gray.3')};
}
}
${props => (props.alignRight ? alignRightStyles(props.theme) : '')}
`
const AvatarStack = ({children = [], alignRight, ...rest}) => {
return (
<AvatarStackWrapper count={children.length} {...rest}>
<AvatarStackBody alignRight={alignRight} className="AvatarStackBody">
{transformChildren(children)}
</AvatarStackBody>
</AvatarStackWrapper>
)
}

AvatarStack.defaultProps = {
theme
}

AvatarStack.propTypes = {
...COMMON.propTypes,
alignRight: PropTypes.bool
}
export default AvatarStack
41 changes: 41 additions & 0 deletions src/__tests__/AvatarStack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable jsx-a11y/alt-text */
import React from 'react'
import AvatarStack from '../AvatarStack'
import {render} from '../utils/testing'
import {COMMON} from '../constants'

const avatarComp = (
<AvatarStack>
<img src="https://avatars.githubusercontent.com/primer" />
<img src="https://avatars.githubusercontent.com/github" />
<img src="https://avatars.githubusercontent.com/primer" />
<img src="https://avatars.githubusercontent.com/github" />
</AvatarStack>
)

const rightAvatarComp = (
<AvatarStack alignRight>
<img src="https://avatars.githubusercontent.com/primer" />
<img src="https://avatars.githubusercontent.com/github" />
<img src="https://avatars.githubusercontent.com/primer" />
<img src="https://avatars.githubusercontent.com/github" />
</AvatarStack>
)

describe('Avatar', () => {
it('implements common system props', () => {
expect(AvatarStack).toImplementSystemProps(COMMON)
})

it('has default theme', () => {
expect(AvatarStack).toSetDefaultTheme()
})

it('matches snapshot', () => {
expect(render(avatarComp)).toMatchSnapshot()
})

it('respects alignRight props', () => {
expect(render(rightAvatarComp)).toMatchSnapshot()
})
})
Loading