1
1
import { clsx } from 'clsx'
2
2
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'
8
4
import type { AvatarProps } from '../Avatar/Avatar'
9
5
import { DEFAULT_AVATAR_SIZE } from '../Avatar/Avatar'
10
6
import type { ResponsiveValue } from '../hooks/useResponsiveValue'
11
7
import { isResponsiveValue } from '../hooks/useResponsiveValue'
12
- import { getBreakpointDeclarations } from '../utils/getBreakpointDeclarations'
13
8
import { defaultSxProp } from '../utils/defaultSxProp'
14
9
import type { WidthOnlyViewportRangeKeys } from '../utils/types/ViewportRangeKeys'
15
10
import classes from './AvatarStack.module.css'
16
- import { toggleStyledComponent } from '../internal/utils/toggleStyledComponent'
17
- import { useFeatureFlag } from '../FeatureFlags'
18
11
import { hasInteractiveNodes } from '../internal/utils/hasInteractiveNodes'
19
- import getGlobalFocusStyles from '../internal/utils/getGlobalFocusStyles '
12
+ import { toggleSxComponent } from '../internal/utils/toggleSxComponent '
20
13
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 ) => {
181
15
return React . Children . map ( children , child => {
182
16
if ( ! React . isValidElement ( child ) ) return child
183
17
return React . cloneElement ( child , {
184
18
...child . props ,
185
- className : clsx ( child . props . className , 'pc-AvatarItem' , { [ classes . AvatarItem ] : enabled } ) ,
19
+ className : clsx ( child . props . className , 'pc-AvatarItem' , classes . AvatarItem ) ,
186
20
} )
187
21
} )
188
22
}
@@ -209,28 +43,16 @@ const AvatarStackBody = ({
209
43
const bodyClassNames = clsx ( 'pc-AvatarStackBody' , {
210
44
'pc-AvatarStack--disableExpand' : disableExpand ,
211
45
} )
212
- const enabled = useFeatureFlag ( CSS_MODULES_FEATURE_FLAG )
213
46
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
- }
226
47
return (
227
- < Box
228
- className = { bodyClassNames }
48
+ < div
49
+ data-disable-expand = { disableExpand ? '' : undefined }
50
+ className = { clsx ( bodyClassNames , classes . AvatarStackBody ) }
229
51
tabIndex = { ! hasInteractiveChildren && ! disableExpand ? 0 : undefined }
230
52
ref = { stackContainer }
231
53
>
232
54
{ children }
233
- </ Box >
55
+ </ div >
234
56
)
235
57
}
236
58
@@ -243,7 +65,6 @@ const AvatarStack = ({
243
65
style,
244
66
sx : sxProp = defaultSxProp ,
245
67
} : AvatarStackProps ) => {
246
- const enabled = useFeatureFlag ( CSS_MODULES_FEATURE_FLAG )
247
68
const [ hasInteractiveChildren , setHasInteractiveChildren ] = useState < boolean | undefined > ( false )
248
69
const stackContainer = useRef < HTMLDivElement > ( null )
249
70
@@ -321,66 +142,46 @@ const AvatarStack = ({
321
142
const getResponsiveAvatarSizeStyles = ( ) => {
322
143
// 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
323
144
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` ,
330
149
}
331
-
332
- return getBreakpointDeclarations (
333
- childSizes ,
334
- '--avatar-stack-size' as keyof React . CSSProperties ,
335
- value => `${ value } px` ,
336
- )
337
150
}
338
151
339
152
// if the `size` prop is set and responsive, set the `--avatar-stack-size` CSS variable for each viewport
340
153
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` ,
347
158
}
348
-
349
- return getBreakpointDeclarations (
350
- size ,
351
- '--avatar-stack-size' as keyof React . CSSProperties ,
352
- value => `${ value || DEFAULT_AVATAR_SIZE } px` ,
353
- )
354
159
}
355
160
356
161
// 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
357
162
return { '--avatar-stack-size' : `${ size } px` } as React . CSSProperties
358
163
}
359
164
360
- const avatarStackSx = merge < BetterCssProperties | BetterSystemStyleObject > (
361
- ! enabled && getResponsiveAvatarSizeStyles ( ) ,
362
- sxProp as SxProp ,
363
- )
165
+ const BaseComponentWrapper = toggleSxComponent ( 'div' )
364
166
365
167
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 }
374
175
>
375
176
< AvatarStackBody
376
177
disableExpand = { disableExpand }
377
178
hasInteractiveChildren = { hasInteractiveChildren }
378
179
stackContainer = { stackContainer }
379
180
>
380
181
{ ' ' }
381
- { transformChildren ( children , enabled ) }
182
+ { transformChildren ( children ) }
382
183
</ AvatarStackBody >
383
- </ AvatarStackWrapper >
184
+ </ BaseComponentWrapper >
384
185
)
385
186
}
386
187
0 commit comments