Skip to content

Commit

Permalink
feat(SkeletonBox): Convert SkeletonBox to CSS modules behind the feat…
Browse files Browse the repository at this point in the history
…ure 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
  • Loading branch information
jonrohan authored Nov 1, 2024
1 parent d96125d commit d349cfc
Show file tree
Hide file tree
Showing 10 changed files with 352 additions and 69 deletions.
8 changes: 8 additions & 0 deletions .changeset/few-zebras-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@primer/react": minor
---

* Convert SkeletonAvatar to CSS modules behind the feature flag
* Convert SkeletonBox to CSS modules behind the feature flag
* Convert SkeletonText to CSS modules behind the feature flag

34 changes: 34 additions & 0 deletions packages/react/src/experimental/Skeleton/SkeletonAvatar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.SkeletonAvatar {
&:where([data-component='SkeletonAvatar']) {
display: inline-block;
width: var(--avatarSize-regular);
height: var(--avatarSize-regular);
/* stylelint-disable-next-line primer/typography */
line-height: 1;
border-radius: 50%;
/* stylelint-disable-next-line primer/box-shadow */
box-shadow: 0 0 0 1px var(--avatar-borderColor);
}

&:where([data-square]) {
/* stylelint-disable-next-line primer/borders */
border-radius: clamp(4px, var(--avatarSize-regular) - 24px, var(--borderRadius-medium));
}

&:where([data-responsive]) {
@media screen and (--viewportRange-narrow) {
width: var(--avatarSize-narrow);
height: var(--avatarSize-narrow);
}

@media screen and (--viewportRange-regular) {
width: var(--avatarSize-regular);
height: var(--avatarSize-regular);
}

@media screen and (--viewportRange-wide) {
width: var(--avatarSize-wide);
height: var(--avatarSize-wide);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ Playground.args = {
}

Playground.argTypes = {
square: {
control: {
type: 'boolean',
},
},
size: {
control: {
type: 'number',
Expand Down
40 changes: 33 additions & 7 deletions packages/react/src/experimental/Skeleton/SkeletonAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import React from 'react'
import React, {type CSSProperties} from 'react'
import {getBreakpointDeclarations} from '../../utils/getBreakpointDeclarations'
import {get} from '../../constants'
import {isResponsiveValue} from '../../hooks/useResponsiveValue'
import type {AvatarProps} from '../../Avatar'
import {DEFAULT_AVATAR_SIZE} from '../../Avatar/Avatar'
import {SkeletonBox} from './SkeletonBox'
import classes from './SkeletonAvatar.module.css'
import {clsx} from 'clsx'
import {useFeatureFlag} from '../../FeatureFlags'
import {merge} from '../../sx'

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

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

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

export const SkeletonAvatar: React.FC<SkeletonAvatarProps> = ({size = DEFAULT_AVATAR_SIZE, square, ...rest}) => {
const avatarSx = isResponsiveValue(size)
export const SkeletonAvatar: React.FC<SkeletonAvatarProps> = ({
size = DEFAULT_AVATAR_SIZE,
square,
className,
style,
...rest
}) => {
const responsive = isResponsiveValue(size)
const cssSizeVars = {} as Record<string, string>
const enabled = useFeatureFlag('primer_react_css_modules_team')
const avatarSx = responsive
? {
...getBreakpointDeclarations(
size,
Expand All @@ -41,12 +54,25 @@ export const SkeletonAvatar: React.FC<SkeletonAvatarProps> = ({size = DEFAULT_AV
...avatarSkeletonStyles,
}

if (enabled) {
if (responsive) {
for (const [key, value] of Object.entries(size)) {
cssSizeVars[`--avatarSize-${key}`] = `${value}px`
}
} else {
cssSizeVars['--avatarSize-regular'] = `${size}px`
}
}

return (
<SkeletonBox
sx={avatarSx}
sx={enabled ? undefined : avatarSx}
className={clsx(className, {[classes.SkeletonAvatar]: enabled})}
{...rest}
data-component="SkeletonAvatar"
data-avatar-shape={square ? 'square' : undefined}
data-responsive={responsive ? '' : undefined}
data-square={square ? '' : undefined}
style={merge(style as CSSProperties, enabled ? cssSizeVars : {})}
/>
)
}
30 changes: 30 additions & 0 deletions packages/react/src/experimental/Skeleton/SkeletonBox.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@keyframes shimmer {
from {
mask-position: 200%;
}

to {
mask-position: 0%;
}
}

.SkeletonBox {
display: block;
height: 1rem;
background-color: var(--bgColor-muted);
border-radius: var(--borderRadius-small);
animation: shimmer;

@media (prefers-reduced-motion: no-preference) {
mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%);
mask-size: 200%;
animation: shimmer;
animation-duration: 1s;
animation-iteration-count: infinite;
}

@media (forced-colors: active) {
outline: 1px solid transparent;
outline-offset: -1px;
}
}
90 changes: 65 additions & 25 deletions packages/react/src/experimental/Skeleton/SkeletonBox.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,80 @@
import type React from 'react'
import React from 'react'
import styled, {keyframes} from 'styled-components'
import sx, {type SxProp} from '../../sx'
import sx, {merge, type SxProp} from '../../sx'
import {get} from '../../constants'
import {type CSSProperties, type HTMLProps} from 'react'
import {toggleStyledComponent} from '../../internal/utils/toggleStyledComponent'
import {clsx} from 'clsx'
import classes from './SkeletonBox.module.css'
import {useFeatureFlag} from '../../FeatureFlags'

type SkeletonBoxProps = {
/** Height of the skeleton "box". Accepts any valid CSS `height` value. */
height?: React.CSSProperties['height']
height?: CSSProperties['height']
/** Width of the skeleton "box". Accepts any valid CSS `width` value. */
width?: React.CSSProperties['width']
} & SxProp
width?: CSSProperties['width']
/** The className of the skeleton box */
className?: string
} & SxProp &
HTMLProps<HTMLDivElement>

const shimmer = keyframes`
from { mask-position: 200%; }
to { mask-position: 0%; }
`
const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team'

export const SkeletonBox = styled.div<SkeletonBoxProps>`
animation: ${shimmer};
display: block;
background-color: var(--bgColor-muted, ${get('colors.canvas.subtle')});
border-radius: 3px;
height: ${props => props.height || '1rem'};
width: ${props => props.width};
@media (prefers-reduced-motion: no-preference) {
mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%);
mask-size: 200%;
const StyledSkeletonBox = toggleStyledComponent(
CSS_MODULES_FEATURE_FLAG,
'div',
styled.div<SkeletonBoxProps>`
animation: ${shimmer};
animation-duration: 1s;
animation-iteration-count: infinite;
}
display: block;
background-color: var(--bgColor-muted, ${get('colors.canvas.subtle')});
border-radius: 3px;
height: ${props => props.height || '1rem'};
width: ${props => props.width};
@media (forced-colors: active) {
outline: 1px solid transparent;
outline-offset: -1px;
}
@media (prefers-reduced-motion: no-preference) {
mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%);
mask-size: 200%;
animation: ${shimmer};
animation-duration: 1s;
animation-iteration-count: infinite;
}
${sx};
`
@media (forced-colors: active) {
outline: 1px solid transparent;
outline-offset: -1px;
}
${sx};
`,
)

export const SkeletonBox = React.forwardRef<HTMLDivElement, SkeletonBoxProps>(function SkeletonBox(
{height, width, className, style, ...props},
ref,
) {
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
return (
<StyledSkeletonBox
height={enabled ? undefined : height}
width={enabled ? undefined : width}
className={clsx(className, {[classes.SkeletonBox]: enabled})}
style={
enabled
? merge(
style as CSSProperties,
{
height,
width,
} as CSSProperties,
)
: style
}
{...props}
ref={ref}
/>
)
})
65 changes: 65 additions & 0 deletions packages/react/src/experimental/Skeleton/SkeletonText.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
.SkeletonText {
--font-size: var(--text-body-size-medium);
--line-height: var(--text-body-lineHeight-medium);
--leading: calc(var(--font-size) * var(--line-height) - var(--font-size));

@supports (margin-block: mod(1px, 1px)) {
--leading: mod(var(--font-size) * var(--line-height), var(--font-size));
}

height: var(--font-size);
border-radius: var(--borderRadius-small);
/* stylelint-disable-next-line primer/spacing */
margin-block: calc(var(--leading) / 2);

&:where([data-in-multiline]) {
/* stylelint-disable-next-line primer/spacing */
margin-block-end: calc(var(--leading) * 2);

&:last-child {
min-width: 50px;
max-width: 65%;
margin-bottom: 0;
}
}

&:where([data-text-skeleton-size='display']),
&:where([data-text-skeleton-size='titleLarge']) {
border-radius: var(--borderRadius-medium);
}

&:where([data-text-skeleton-size='display']) {
--font-size: var(--text-display-size);
--line-height: var(--text-display-lineHeight);
}

&:where([data-text-skeleton-size='titleLarge']) {
--font-size: var(--text-title-size-large);
--line-height: var(--text-title-lineHeight-large);
}

&:where([data-text-skeleton-size='titleMedium']) {
--font-size: var(--text-title-size-medium);
--line-height: var(--text-title-lineHeight-medium);
}

&:where([data-text-skeleton-size='titleSmall']) {
--font-size: var(--text-title-size-small);
--line-height: var(--text-title-lineHeight-small);
}

&:where([data-text-skeleton-size='subtitle']) {
--font-size: var(--text-subtitle-size);
--line-height: var(--text-subtitle-lineHeight);
}

&:where([data-text-skeleton-size='bodyLarge']) {
--font-size: var(--text-body-size-large);
--line-height: var(--text-body-lineHeight-large);
}

&:where([data-text-skeleton-size='bodySmall']) {
--font-size: var(--text-body-size-small);
--line-height: var(--text-body-lineHeight-small);
}
}
Loading

0 comments on commit d349cfc

Please sign in to comment.