diff --git a/.changeset/cool-buckets-end.md b/.changeset/cool-buckets-end.md new file mode 100644 index 00000000000..dad8c852f04 --- /dev/null +++ b/.changeset/cool-buckets-end.md @@ -0,0 +1,8 @@ +--- +'@primer/react': patch +--- + +- Corrects the math to calculate the width of AvatarStack containers. +- Prevents `.pc-AvatarStackBody` from being removed from document flow by `position: absolute`. This isn't strictly necessary now that we're correctly setting the width of the stack, but it's an extra level of safety for preserving the correct layout. + + diff --git a/src/AvatarStack/AvatarStack.tsx b/src/AvatarStack/AvatarStack.tsx index 2351eb5b588..880e6d52e37 100644 --- a/src/AvatarStack/AvatarStack.tsx +++ b/src/AvatarStack/AvatarStack.tsx @@ -19,26 +19,14 @@ const AvatarStackWrapper = styled.span` --avatar-two-margin: calc(var(--avatar-stack-size) * -0.55); --avatar-three-margin: calc(var(--avatar-stack-size) * -0.85); - // this calc explained: - // 1. avatar size + the non-overlapping part of the second avatar - // 2. + the non-overlapping part of the second and third avatar - // 3. + the border widths of all previous avatars - --avatar-stack-three-plus-min-width: calc( - var(--avatar-stack-size) + - calc( - calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + - calc(var(--avatar-stack-size) + var(--avatar-three-margin)) * 2 - ) + calc(var(--avatar-border-width) * 3) - ); display: flex; position: relative; height: var(--avatar-stack-size); - min-width: ${props => (props.count === 1 ? 'var(--avatar-stack-size)' : props.count === 2 ? '30px' : '38px')}; + min-width: var(--avatar-stack-size); .pc-AvatarStackBody { display: flex; position: absolute; - width: var(--avatar-stack-three-plus-min-width); } .pc-AvatarItem { @@ -92,8 +80,30 @@ const AvatarStackWrapper = styled.span` ); } + &.pc-AvatarStack--three { + // this calc explained: + // 1. avatar size + the non-overlapping part of the second avatar + // 2. + the non-overlapping part of the third avatar + min-width: calc( + var(--avatar-stack-size) + + calc( + calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + + calc(var(--avatar-stack-size) + var(--avatar-three-margin)) + ) + ); + } + &.pc-AvatarStack--three-plus { - min-width: var(--avatar-stack-three-plus-min-width); + // this calc explained: + // 1. avatar size + the non-overlapping part of the second avatar + // 2. + the non-overlapping part of the third and fourth avatar + min-width: calc( + var(--avatar-stack-size) + + calc( + calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + + calc(var(--avatar-stack-size) + var(--avatar-three-margin)) * 2 + ) + ); } &.pc-AvatarStack--right { @@ -150,6 +160,10 @@ const AvatarStackWrapper = styled.span` } } + .pc-AvatarStack--disableExpand { + position: relative; + } + ${sx}; ` const transformChildren = (children: React.ReactNode) => { @@ -173,7 +187,8 @@ const AvatarStack = ({children, alignRight, disableExpand, size, sx: sxProp = de const count = React.Children.count(children) const wrapperClassNames = clsx({ 'pc-AvatarStack--two': count === 2, - 'pc-AvatarStack--three-plus': count > 2, + 'pc-AvatarStack--three': count === 3, + 'pc-AvatarStack--three-plus': count > 3, 'pc-AvatarStack--right': alignRight, }) const bodyClassNames = clsx('pc-AvatarStackBody', { diff --git a/src/__tests__/__snapshots__/AvatarStack.test.tsx.snap b/src/__tests__/__snapshots__/AvatarStack.test.tsx.snap index 09ddd3157d5..8db11d1caf6 100644 --- a/src/__tests__/__snapshots__/AvatarStack.test.tsx.snap +++ b/src/__tests__/__snapshots__/AvatarStack.test.tsx.snap @@ -5,14 +5,13 @@ exports[`Avatar renders consistently 1`] = ` --avatar-border-width: 1px; --avatar-two-margin: calc(var(--avatar-stack-size) * -0.55); --avatar-three-margin: calc(var(--avatar-stack-size) * -0.85); - --avatar-stack-three-plus-min-width: calc( var(--avatar-stack-size) + calc( calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + calc(var(--avatar-stack-size) + var(--avatar-three-margin)) * 2 ) + calc(var(--avatar-border-width) * 3) ); display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; position: relative; height: var(--avatar-stack-size); - min-width: 38px; + min-width: var(--avatar-stack-size); --avatar-stack-size: 20px; } @@ -22,7 +21,6 @@ exports[`Avatar renders consistently 1`] = ` display: -ms-flexbox; display: flex; position: absolute; - width: var(--avatar-stack-three-plus-min-width); } .c0 .pc-AvatarItem { @@ -72,8 +70,12 @@ exports[`Avatar renders consistently 1`] = ` min-width: calc( var(--avatar-stack-size) + calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + var(--avatar-border-width) ); } +.c0.pc-AvatarStack--three { + min-width: calc( var(--avatar-stack-size) + calc( calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + calc(var(--avatar-stack-size) + var(--avatar-three-margin)) ) ); +} + .c0.pc-AvatarStack--three-plus { - min-width: var(--avatar-stack-three-plus-min-width); + min-width: calc( var(--avatar-stack-size) + calc( calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + calc(var(--avatar-stack-size) + var(--avatar-three-margin)) * 2 ) ); } .c0.pc-AvatarStack--right { @@ -134,6 +136,10 @@ exports[`Avatar renders consistently 1`] = ` margin-left: 0; } +.c0 .pc-AvatarStack--disableExpand { + position: relative; +} + @@ -170,14 +176,13 @@ exports[`Avatar respects alignRight props 1`] = ` --avatar-border-width: 1px; --avatar-two-margin: calc(var(--avatar-stack-size) * -0.55); --avatar-three-margin: calc(var(--avatar-stack-size) * -0.85); - --avatar-stack-three-plus-min-width: calc( var(--avatar-stack-size) + calc( calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + calc(var(--avatar-stack-size) + var(--avatar-three-margin)) * 2 ) + calc(var(--avatar-border-width) * 3) ); display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; position: relative; height: var(--avatar-stack-size); - min-width: 38px; + min-width: var(--avatar-stack-size); --avatar-stack-size: 20px; } @@ -187,7 +192,6 @@ exports[`Avatar respects alignRight props 1`] = ` display: -ms-flexbox; display: flex; position: absolute; - width: var(--avatar-stack-three-plus-min-width); } .c0 .pc-AvatarItem { @@ -237,8 +241,12 @@ exports[`Avatar respects alignRight props 1`] = ` min-width: calc( var(--avatar-stack-size) + calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + var(--avatar-border-width) ); } +.c0.pc-AvatarStack--three { + min-width: calc( var(--avatar-stack-size) + calc( calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + calc(var(--avatar-stack-size) + var(--avatar-three-margin)) ) ); +} + .c0.pc-AvatarStack--three-plus { - min-width: var(--avatar-stack-three-plus-min-width); + min-width: calc( var(--avatar-stack-size) + calc( calc(var(--avatar-stack-size) + var(--avatar-two-margin)) + calc(var(--avatar-stack-size) + var(--avatar-three-margin)) * 2 ) ); } .c0.pc-AvatarStack--right { @@ -299,6 +307,10 @@ exports[`Avatar respects alignRight props 1`] = ` margin-left: 0; } +.c0 .pc-AvatarStack--disableExpand { + position: relative; +} +