Skip to content

Commit 5525ed2

Browse files
authored
chore(AvatarStack): Remove the CSS modules feature flag from the AvatarStack component (#5871)
1 parent f8760fe commit 5525ed2

File tree

4 files changed

+52
-395
lines changed

4 files changed

+52
-395
lines changed

.changeset/famous-emus-chew.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': minor
3+
---
4+
5+
Remove the CSS modules feature flag from the AvatarStack component

packages/react/src/AvatarStack/AvatarStack.tsx

Lines changed: 26 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -1,188 +1,22 @@
11
import {clsx} from 'clsx'
22
import React, {useEffect, useRef, useState} from 'react'
3-
import styled from 'styled-components'
4-
import {get} from '../constants'
5-
import Box from '../Box'
6-
import type {BetterCssProperties, BetterSystemStyleObject, SxProp} from '../sx'
7-
import sx, {merge} from '../sx'
3+
import type {SxProp} from '../sx'
84
import type {AvatarProps} from '../Avatar/Avatar'
95
import {DEFAULT_AVATAR_SIZE} from '../Avatar/Avatar'
106
import type {ResponsiveValue} from '../hooks/useResponsiveValue'
117
import {isResponsiveValue} from '../hooks/useResponsiveValue'
12-
import {getBreakpointDeclarations} from '../utils/getBreakpointDeclarations'
138
import {defaultSxProp} from '../utils/defaultSxProp'
149
import type {WidthOnlyViewportRangeKeys} from '../utils/types/ViewportRangeKeys'
1510
import classes from './AvatarStack.module.css'
16-
import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
17-
import {useFeatureFlag} from '../FeatureFlags'
1811
import {hasInteractiveNodes} from '../internal/utils/hasInteractiveNodes'
19-
import getGlobalFocusStyles from '../internal/utils/getGlobalFocusStyles'
12+
import {toggleSxComponent} from '../internal/utils/toggleSxComponent'
2013

21-
type StyledAvatarStackWrapperProps = {
22-
count?: number
23-
} & SxProp
24-
25-
const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_ga'
26-
27-
const AvatarStackWrapper = toggleStyledComponent(
28-
CSS_MODULES_FEATURE_FLAG,
29-
'span',
30-
styled.span<StyledAvatarStackWrapperProps>`
31-
--avatar-border-width: 1px;
32-
--overlap-size: calc(var(--avatar-stack-size) * 0.55);
33-
--overlap-size-avatar-three-plus: calc(var(--avatar-stack-size) * 0.85);
34-
--mask-size: calc(100% + (var(--avatar-border-width) * 2));
35-
--mask-start: -1;
36-
--opacity-step: 15%;
37-
38-
display: flex;
39-
position: relative;
40-
height: var(--avatar-stack-size);
41-
min-width: var(--avatar-stack-size);
42-
isolation: isolate;
43-
44-
.pc-AvatarStackBody {
45-
display: flex;
46-
position: absolute;
47-
48-
${getGlobalFocusStyles('1px')}
49-
}
50-
51-
.pc-AvatarItem {
52-
--avatar-size: var(--avatar-stack-size);
53-
flex-shrink: 0;
54-
height: var(--avatar-stack-size);
55-
width: var(--avatar-stack-size);
56-
position: relative;
57-
overflow: hidden;
58-
display: flex;
59-
transition:
60-
margin 0.2s ease-in-out,
61-
opacity 0.2s ease-in-out,
62-
mask-position 0.2s ease-in-out,
63-
mask-size 0.2s ease-in-out;
64-
65-
&:is(img) {
66-
box-shadow: 0 0 0 var(--avatar-border-width)
67-
${props => (props.count === 1 ? get('colors.avatar.border') : 'transparent')};
68-
}
69-
70-
&:first-child {
71-
margin-inline-start: 0;
72-
}
73-
74-
&:nth-child(n + 2) {
75-
margin-inline-start: calc(var(--overlap-size) * -1);
76-
mask-image: radial-gradient(at 50% 50%, rgb(0, 0, 0) 70%, rgba(0, 0, 0, 0) 71%),
77-
linear-gradient(rgb(0, 0, 0) 0 0);
78-
mask-repeat: no-repeat, no-repeat;
79-
mask-size:
80-
var(--mask-size) var(--mask-size),
81-
auto;
82-
mask-composite: exclude;
83-
// HORIZONTAL POSITION CALC FORMULA EXPLAINED:
84-
// width of the visible part of the avatar ➡️ var(--avatar-stack-size) - var(--overlap-size)
85-
// multiply by -1 for left-aligned, 1 for right-aligned ➡️ var(--mask-start)
86-
// subtract the avatar border width ➡️ var(--avatar-border-width)
87-
mask-position:
88-
calc((var(--avatar-stack-size) - var(--overlap-size)) * var(--mask-start) - var(--avatar-border-width)) center,
89-
0 0;
90-
// HACK: This padding fixes a weird rendering bug where a tiiiiny outline is visible at the edges of the element
91-
padding: 0.1px;
92-
}
93-
94-
&:nth-child(n + 3) {
95-
--overlap-size: var(--overlap-size-avatar-three-plus);
96-
opacity: calc(100% - 2 * var(--opacity-step));
97-
}
98-
99-
&:nth-child(n + 4) {
100-
opacity: calc(100% - 3 * var(--opacity-step));
101-
}
102-
103-
&:nth-child(n + 5) {
104-
opacity: calc(100% - 4 * var(--opacity-step));
105-
}
106-
107-
&:nth-child(n + 6) {
108-
opacity: 0;
109-
visibility: hidden;
110-
}
111-
}
112-
113-
&.pc-AvatarStack--two {
114-
// MIN-WIDTH CALC FORMULA EXPLAINED:
115-
// avatar size ➡️ var(--avatar-stack-size)
116-
// plus the visible part of the 2nd avatar ➡️ var(--avatar-stack-size) - var(--overlap-size)
117-
min-width: calc(var(--avatar-stack-size) + (var(--avatar-stack-size) - var(--overlap-size)));
118-
}
119-
120-
&.pc-AvatarStack--three {
121-
// MIN-WIDTH CALC FORMULA EXPLAINED:
122-
// avatar size ➡️ var(--avatar-stack-size)
123-
// plus the visible part of the 2nd avatar ➡️ var(--avatar-stack-size) - var(--overlap-size)
124-
// plus the visible part of the 3rd avatar ➡️ var(--avatar-stack-size) - var(--overlap-size-avatar-three-plus)
125-
min-width: calc(
126-
var(--avatar-stack-size) + (var(--avatar-stack-size) - var(--overlap-size)) +
127-
(var(--avatar-stack-size) - var(--overlap-size-avatar-three-plus))
128-
);
129-
}
130-
131-
&.pc-AvatarStack--three-plus {
132-
// MIN-WIDTH CALC FORMULA EXPLAINED:
133-
// avatar size ➡️ var(--avatar-stack-size)
134-
// plus the visible part of the 2nd avatar ➡️ var(--avatar-stack-size) - var(--overlap-size)
135-
// plus the visible part of the 3rd AND 4th avatar ➡️ (var(--avatar-stack-size) - var(--overlap-size-avatar-three-plus)) * 2
136-
min-width: calc(
137-
var(--avatar-stack-size) + (var(--avatar-stack-size) - var(--overlap-size)) +
138-
(var(--avatar-stack-size) - var(--overlap-size-avatar-three-plus)) * 2
139-
);
140-
}
141-
142-
&.pc-AvatarStack--right {
143-
--mask-start: 1;
144-
direction: rtl;
145-
}
146-
147-
.pc-AvatarStackBody:not(.pc-AvatarStack--disableExpand):hover,
148-
.pc-AvatarStackBody:not(.pc-AvatarStack--disableExpand):focus-within {
149-
width: auto;
150-
151-
.pc-AvatarItem {
152-
// reset size of the mask to prevent unintentially clipping due to the additional size created by the border width
153-
--mask-size: 100%;
154-
margin-inline-start: ${get('space.1')};
155-
opacity: 1;
156-
visibility: visible;
157-
// HORIZONTAL POSITION CALC FORMULA EXPLAINED:
158-
// width of the full avatar ➡️ var(--avatar-stack-size)
159-
// multiply by -1 for left-aligned, 1 for right-aligned ➡️ var(--mask-start)
160-
mask-position:
161-
calc(var(--avatar-stack-size) * var(--mask-start)) center,
162-
0 0;
163-
164-
${getGlobalFocusStyles('1px')}
165-
166-
&:first-child {
167-
margin-inline-start: 0;
168-
}
169-
}
170-
}
171-
172-
.pc-AvatarStack--disableExpand {
173-
position: relative;
174-
}
175-
176-
${sx};
177-
`,
178-
)
179-
180-
const transformChildren = (children: React.ReactNode, enabled: boolean) => {
14+
const transformChildren = (children: React.ReactNode) => {
18115
return React.Children.map(children, child => {
18216
if (!React.isValidElement(child)) return child
18317
return React.cloneElement(child, {
18418
...child.props,
185-
className: clsx(child.props.className, 'pc-AvatarItem', {[classes.AvatarItem]: enabled}),
19+
className: clsx(child.props.className, 'pc-AvatarItem', classes.AvatarItem),
18620
})
18721
})
18822
}
@@ -209,28 +43,16 @@ const AvatarStackBody = ({
20943
const bodyClassNames = clsx('pc-AvatarStackBody', {
21044
'pc-AvatarStack--disableExpand': disableExpand,
21145
})
212-
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
21346

214-
if (enabled) {
215-
return (
216-
<div
217-
data-disable-expand={disableExpand ? '' : undefined}
218-
className={clsx(bodyClassNames, classes.AvatarStackBody)}
219-
tabIndex={!hasInteractiveChildren && !disableExpand ? 0 : undefined}
220-
ref={stackContainer}
221-
>
222-
{children}
223-
</div>
224-
)
225-
}
22647
return (
227-
<Box
228-
className={bodyClassNames}
48+
<div
49+
data-disable-expand={disableExpand ? '' : undefined}
50+
className={clsx(bodyClassNames, classes.AvatarStackBody)}
22951
tabIndex={!hasInteractiveChildren && !disableExpand ? 0 : undefined}
23052
ref={stackContainer}
23153
>
23254
{children}
233-
</Box>
55+
</div>
23456
)
23557
}
23658

@@ -243,7 +65,6 @@ const AvatarStack = ({
24365
style,
24466
sx: sxProp = defaultSxProp,
24567
}: AvatarStackProps) => {
246-
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
24768
const [hasInteractiveChildren, setHasInteractiveChildren] = useState<boolean | undefined>(false)
24869
const stackContainer = useRef<HTMLDivElement>(null)
24970

@@ -321,66 +142,46 @@ const AvatarStack = ({
321142
const getResponsiveAvatarSizeStyles = () => {
322143
// if there is no size set on the AvatarStack, use the `size` props of the Avatar children to set the `--avatar-stack-size` CSS variable
323144
if (!size) {
324-
if (enabled) {
325-
return {
326-
'--stackSize-narrow': `${childSizes.narrow}px`,
327-
'--stackSize-regular': `${childSizes.regular}px`,
328-
'--stackSize-wide': `${childSizes.wide}px`,
329-
}
145+
return {
146+
'--stackSize-narrow': `${childSizes.narrow}px`,
147+
'--stackSize-regular': `${childSizes.regular}px`,
148+
'--stackSize-wide': `${childSizes.wide}px`,
330149
}
331-
332-
return getBreakpointDeclarations(
333-
childSizes,
334-
'--avatar-stack-size' as keyof React.CSSProperties,
335-
value => `${value}px`,
336-
)
337150
}
338151

339152
// if the `size` prop is set and responsive, set the `--avatar-stack-size` CSS variable for each viewport
340153
if (isResponsiveValue(size)) {
341-
if (enabled) {
342-
return {
343-
'--stackSize-narrow': `${size.narrow || DEFAULT_AVATAR_SIZE}px`,
344-
'--stackSize-regular': `${size.regular || DEFAULT_AVATAR_SIZE}px`,
345-
'--stackSize-wide': `${size.wide || DEFAULT_AVATAR_SIZE}px`,
346-
}
154+
return {
155+
'--stackSize-narrow': `${size.narrow || DEFAULT_AVATAR_SIZE}px`,
156+
'--stackSize-regular': `${size.regular || DEFAULT_AVATAR_SIZE}px`,
157+
'--stackSize-wide': `${size.wide || DEFAULT_AVATAR_SIZE}px`,
347158
}
348-
349-
return getBreakpointDeclarations(
350-
size,
351-
'--avatar-stack-size' as keyof React.CSSProperties,
352-
value => `${value || DEFAULT_AVATAR_SIZE}px`,
353-
)
354159
}
355160

356161
// if the `size` prop is set and not responsive, it is a number, so we can just set the `--avatar-stack-size` CSS variable to that number
357162
return {'--avatar-stack-size': `${size}px`} as React.CSSProperties
358163
}
359164

360-
const avatarStackSx = merge<BetterCssProperties | BetterSystemStyleObject>(
361-
!enabled && getResponsiveAvatarSizeStyles(),
362-
sxProp as SxProp,
363-
)
165+
const BaseComponentWrapper = toggleSxComponent('div')
364166

365167
return (
366-
<AvatarStackWrapper
367-
count={enabled ? undefined : count}
368-
data-avatar-count={enabled ? (count > 3 ? '3+' : count) : undefined}
369-
data-align-right={enabled && alignRight ? '' : undefined}
370-
data-responsive={enabled && (!size || isResponsiveValue(size)) ? '' : undefined}
371-
className={clsx(wrapperClassNames, {[classes.AvatarStack]: enabled})}
372-
style={enabled ? {...getResponsiveAvatarSizeStyles(), style} : style}
373-
sx={avatarStackSx}
168+
<BaseComponentWrapper
169+
data-avatar-count={count > 3 ? '3+' : count}
170+
data-align-right={alignRight ? '' : undefined}
171+
data-responsive={!size || isResponsiveValue(size) ? '' : undefined}
172+
className={clsx(wrapperClassNames, classes.AvatarStack)}
173+
style={{...getResponsiveAvatarSizeStyles(), ...style}}
174+
sx={sxProp}
374175
>
375176
<AvatarStackBody
376177
disableExpand={disableExpand}
377178
hasInteractiveChildren={hasInteractiveChildren}
378179
stackContainer={stackContainer}
379180
>
380181
{' '}
381-
{transformChildren(children, enabled)}
182+
{transformChildren(children)}
382183
</AvatarStackBody>
383-
</AvatarStackWrapper>
184+
</BaseComponentWrapper>
384185
)
385186
}
386187

0 commit comments

Comments
 (0)