Skip to content

Commit d349cfc

Browse files
authored
feat(SkeletonBox): Convert SkeletonBox to CSS modules behind the feature flag (#5195)
* Convert SkeletonBox to CSS modules * Create few-zebras-drive.md * Convert SkeletonText to CSS modules behind feature flag * Fix the type * Update Skeleton components to CSS modules * SkeletonAvatar converted to CSS modules * Turn off lint
1 parent d96125d commit d349cfc

File tree

10 files changed

+352
-69
lines changed

10 files changed

+352
-69
lines changed

.changeset/few-zebras-drive.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@primer/react": minor
3+
---
4+
5+
* Convert SkeletonAvatar to CSS modules behind the feature flag
6+
* Convert SkeletonBox to CSS modules behind the feature flag
7+
* Convert SkeletonText to CSS modules behind the feature flag
8+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.SkeletonAvatar {
2+
&:where([data-component='SkeletonAvatar']) {
3+
display: inline-block;
4+
width: var(--avatarSize-regular);
5+
height: var(--avatarSize-regular);
6+
/* stylelint-disable-next-line primer/typography */
7+
line-height: 1;
8+
border-radius: 50%;
9+
/* stylelint-disable-next-line primer/box-shadow */
10+
box-shadow: 0 0 0 1px var(--avatar-borderColor);
11+
}
12+
13+
&:where([data-square]) {
14+
/* stylelint-disable-next-line primer/borders */
15+
border-radius: clamp(4px, var(--avatarSize-regular) - 24px, var(--borderRadius-medium));
16+
}
17+
18+
&:where([data-responsive]) {
19+
@media screen and (--viewportRange-narrow) {
20+
width: var(--avatarSize-narrow);
21+
height: var(--avatarSize-narrow);
22+
}
23+
24+
@media screen and (--viewportRange-regular) {
25+
width: var(--avatarSize-regular);
26+
height: var(--avatarSize-regular);
27+
}
28+
29+
@media screen and (--viewportRange-wide) {
30+
width: var(--avatarSize-wide);
31+
height: var(--avatarSize-wide);
32+
}
33+
}
34+
}

packages/react/src/experimental/Skeleton/SkeletonAvatar.stories.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ Playground.args = {
2828
}
2929

3030
Playground.argTypes = {
31+
square: {
32+
control: {
33+
type: 'boolean',
34+
},
35+
},
3136
size: {
3237
control: {
3338
type: 'number',

packages/react/src/experimental/Skeleton/SkeletonAvatar.tsx

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
import React from 'react'
1+
import React, {type CSSProperties} from 'react'
22
import {getBreakpointDeclarations} from '../../utils/getBreakpointDeclarations'
33
import {get} from '../../constants'
44
import {isResponsiveValue} from '../../hooks/useResponsiveValue'
55
import type {AvatarProps} from '../../Avatar'
66
import {DEFAULT_AVATAR_SIZE} from '../../Avatar/Avatar'
77
import {SkeletonBox} from './SkeletonBox'
8+
import classes from './SkeletonAvatar.module.css'
9+
import {clsx} from 'clsx'
10+
import {useFeatureFlag} from '../../FeatureFlags'
11+
import {merge} from '../../sx'
812

913
export type SkeletonAvatarProps = Pick<AvatarProps, 'size' | 'square'> & {
1014
/** Class name for custom styling */
1115
className?: string
12-
}
16+
} & Omit<React.HTMLProps<HTMLDivElement>, 'size'>
1317

1418
const avatarSkeletonStyles = {
1519
'&[data-component="SkeletonAvatar"]': {
@@ -21,13 +25,22 @@ const avatarSkeletonStyles = {
2125
width: 'var(--avatar-size)',
2226
},
2327

24-
'&[data-avatar-shape="square"]': {
28+
'&[data-square]': {
2529
borderRadius: 'clamp(4px, var(--avatar-size) - 24px, 6px)',
2630
},
2731
}
2832

29-
export const SkeletonAvatar: React.FC<SkeletonAvatarProps> = ({size = DEFAULT_AVATAR_SIZE, square, ...rest}) => {
30-
const avatarSx = isResponsiveValue(size)
33+
export const SkeletonAvatar: React.FC<SkeletonAvatarProps> = ({
34+
size = DEFAULT_AVATAR_SIZE,
35+
square,
36+
className,
37+
style,
38+
...rest
39+
}) => {
40+
const responsive = isResponsiveValue(size)
41+
const cssSizeVars = {} as Record<string, string>
42+
const enabled = useFeatureFlag('primer_react_css_modules_team')
43+
const avatarSx = responsive
3144
? {
3245
...getBreakpointDeclarations(
3346
size,
@@ -41,12 +54,25 @@ export const SkeletonAvatar: React.FC<SkeletonAvatarProps> = ({size = DEFAULT_AV
4154
...avatarSkeletonStyles,
4255
}
4356

57+
if (enabled) {
58+
if (responsive) {
59+
for (const [key, value] of Object.entries(size)) {
60+
cssSizeVars[`--avatarSize-${key}`] = `${value}px`
61+
}
62+
} else {
63+
cssSizeVars['--avatarSize-regular'] = `${size}px`
64+
}
65+
}
66+
4467
return (
4568
<SkeletonBox
46-
sx={avatarSx}
69+
sx={enabled ? undefined : avatarSx}
70+
className={clsx(className, {[classes.SkeletonAvatar]: enabled})}
4771
{...rest}
4872
data-component="SkeletonAvatar"
49-
data-avatar-shape={square ? 'square' : undefined}
73+
data-responsive={responsive ? '' : undefined}
74+
data-square={square ? '' : undefined}
75+
style={merge(style as CSSProperties, enabled ? cssSizeVars : {})}
5076
/>
5177
)
5278
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@keyframes shimmer {
2+
from {
3+
mask-position: 200%;
4+
}
5+
6+
to {
7+
mask-position: 0%;
8+
}
9+
}
10+
11+
.SkeletonBox {
12+
display: block;
13+
height: 1rem;
14+
background-color: var(--bgColor-muted);
15+
border-radius: var(--borderRadius-small);
16+
animation: shimmer;
17+
18+
@media (prefers-reduced-motion: no-preference) {
19+
mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%);
20+
mask-size: 200%;
21+
animation: shimmer;
22+
animation-duration: 1s;
23+
animation-iteration-count: infinite;
24+
}
25+
26+
@media (forced-colors: active) {
27+
outline: 1px solid transparent;
28+
outline-offset: -1px;
29+
}
30+
}
Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,80 @@
1-
import type React from 'react'
1+
import React from 'react'
22
import styled, {keyframes} from 'styled-components'
3-
import sx, {type SxProp} from '../../sx'
3+
import sx, {merge, type SxProp} from '../../sx'
44
import {get} from '../../constants'
5+
import {type CSSProperties, type HTMLProps} from 'react'
6+
import {toggleStyledComponent} from '../../internal/utils/toggleStyledComponent'
7+
import {clsx} from 'clsx'
8+
import classes from './SkeletonBox.module.css'
9+
import {useFeatureFlag} from '../../FeatureFlags'
510

611
type SkeletonBoxProps = {
712
/** Height of the skeleton "box". Accepts any valid CSS `height` value. */
8-
height?: React.CSSProperties['height']
13+
height?: CSSProperties['height']
914
/** Width of the skeleton "box". Accepts any valid CSS `width` value. */
10-
width?: React.CSSProperties['width']
11-
} & SxProp
15+
width?: CSSProperties['width']
16+
/** The className of the skeleton box */
17+
className?: string
18+
} & SxProp &
19+
HTMLProps<HTMLDivElement>
1220

1321
const shimmer = keyframes`
1422
from { mask-position: 200%; }
1523
to { mask-position: 0%; }
1624
`
25+
const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team'
1726

18-
export const SkeletonBox = styled.div<SkeletonBoxProps>`
19-
animation: ${shimmer};
20-
display: block;
21-
background-color: var(--bgColor-muted, ${get('colors.canvas.subtle')});
22-
border-radius: 3px;
23-
height: ${props => props.height || '1rem'};
24-
width: ${props => props.width};
25-
26-
@media (prefers-reduced-motion: no-preference) {
27-
mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%);
28-
mask-size: 200%;
27+
const StyledSkeletonBox = toggleStyledComponent(
28+
CSS_MODULES_FEATURE_FLAG,
29+
'div',
30+
styled.div<SkeletonBoxProps>`
2931
animation: ${shimmer};
30-
animation-duration: 1s;
31-
animation-iteration-count: infinite;
32-
}
32+
display: block;
33+
background-color: var(--bgColor-muted, ${get('colors.canvas.subtle')});
34+
border-radius: 3px;
35+
height: ${props => props.height || '1rem'};
36+
width: ${props => props.width};
3337
34-
@media (forced-colors: active) {
35-
outline: 1px solid transparent;
36-
outline-offset: -1px;
37-
}
38+
@media (prefers-reduced-motion: no-preference) {
39+
mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%);
40+
mask-size: 200%;
41+
animation: ${shimmer};
42+
animation-duration: 1s;
43+
animation-iteration-count: infinite;
44+
}
3845
39-
${sx};
40-
`
46+
@media (forced-colors: active) {
47+
outline: 1px solid transparent;
48+
outline-offset: -1px;
49+
}
50+
51+
${sx};
52+
`,
53+
)
54+
55+
export const SkeletonBox = React.forwardRef<HTMLDivElement, SkeletonBoxProps>(function SkeletonBox(
56+
{height, width, className, style, ...props},
57+
ref,
58+
) {
59+
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
60+
return (
61+
<StyledSkeletonBox
62+
height={enabled ? undefined : height}
63+
width={enabled ? undefined : width}
64+
className={clsx(className, {[classes.SkeletonBox]: enabled})}
65+
style={
66+
enabled
67+
? merge(
68+
style as CSSProperties,
69+
{
70+
height,
71+
width,
72+
} as CSSProperties,
73+
)
74+
: style
75+
}
76+
{...props}
77+
ref={ref}
78+
/>
79+
)
80+
})
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
.SkeletonText {
2+
--font-size: var(--text-body-size-medium);
3+
--line-height: var(--text-body-lineHeight-medium);
4+
--leading: calc(var(--font-size) * var(--line-height) - var(--font-size));
5+
6+
@supports (margin-block: mod(1px, 1px)) {
7+
--leading: mod(var(--font-size) * var(--line-height), var(--font-size));
8+
}
9+
10+
height: var(--font-size);
11+
border-radius: var(--borderRadius-small);
12+
/* stylelint-disable-next-line primer/spacing */
13+
margin-block: calc(var(--leading) / 2);
14+
15+
&:where([data-in-multiline]) {
16+
/* stylelint-disable-next-line primer/spacing */
17+
margin-block-end: calc(var(--leading) * 2);
18+
19+
&:last-child {
20+
min-width: 50px;
21+
max-width: 65%;
22+
margin-bottom: 0;
23+
}
24+
}
25+
26+
&:where([data-text-skeleton-size='display']),
27+
&:where([data-text-skeleton-size='titleLarge']) {
28+
border-radius: var(--borderRadius-medium);
29+
}
30+
31+
&:where([data-text-skeleton-size='display']) {
32+
--font-size: var(--text-display-size);
33+
--line-height: var(--text-display-lineHeight);
34+
}
35+
36+
&:where([data-text-skeleton-size='titleLarge']) {
37+
--font-size: var(--text-title-size-large);
38+
--line-height: var(--text-title-lineHeight-large);
39+
}
40+
41+
&:where([data-text-skeleton-size='titleMedium']) {
42+
--font-size: var(--text-title-size-medium);
43+
--line-height: var(--text-title-lineHeight-medium);
44+
}
45+
46+
&:where([data-text-skeleton-size='titleSmall']) {
47+
--font-size: var(--text-title-size-small);
48+
--line-height: var(--text-title-lineHeight-small);
49+
}
50+
51+
&:where([data-text-skeleton-size='subtitle']) {
52+
--font-size: var(--text-subtitle-size);
53+
--line-height: var(--text-subtitle-lineHeight);
54+
}
55+
56+
&:where([data-text-skeleton-size='bodyLarge']) {
57+
--font-size: var(--text-body-size-large);
58+
--line-height: var(--text-body-lineHeight-large);
59+
}
60+
61+
&:where([data-text-skeleton-size='bodySmall']) {
62+
--font-size: var(--text-body-size-small);
63+
--line-height: var(--text-body-lineHeight-small);
64+
}
65+
}

0 commit comments

Comments
 (0)