From 1d178be779b58cc95200819af24c4e4ca7757412 Mon Sep 17 00:00:00 2001 From: Marie Lucca <40550942+francinelucca@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:08:46 -0500 Subject: [PATCH] =?UTF-8?q?Revert=20"chore(Skeleton):=20Remove=20the=20CSS?= =?UTF-8?q?=20module=20feature=20flag=20from=20Skeleton=20(#5=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 9bd2f79070a2cd30fa99c0641e7acc0ed34c166b. --- .changeset/thirty-wasps-sleep.md | 5 -- .../src/experimental/Skeleton/FeatureFlag.tsx | 1 + .../experimental/Skeleton/SkeletonAvatar.tsx | 50 +++++++++-- .../src/experimental/Skeleton/SkeletonBox.tsx | 90 ++++++++++++------- .../experimental/Skeleton/SkeletonText.tsx | 88 ++++++++++++++++-- 5 files changed, 184 insertions(+), 50 deletions(-) delete mode 100644 .changeset/thirty-wasps-sleep.md create mode 100644 packages/react/src/experimental/Skeleton/FeatureFlag.tsx diff --git a/.changeset/thirty-wasps-sleep.md b/.changeset/thirty-wasps-sleep.md deleted file mode 100644 index 582d351b8f8..00000000000 --- a/.changeset/thirty-wasps-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@primer/react": minor ---- - -Remove the CSS module feature flag from Skeleton diff --git a/packages/react/src/experimental/Skeleton/FeatureFlag.tsx b/packages/react/src/experimental/Skeleton/FeatureFlag.tsx new file mode 100644 index 00000000000..267b7ef5906 --- /dev/null +++ b/packages/react/src/experimental/Skeleton/FeatureFlag.tsx @@ -0,0 +1 @@ +export const CSS_MODULE_FLAG = 'primer_react_css_modules_ga' diff --git a/packages/react/src/experimental/Skeleton/SkeletonAvatar.tsx b/packages/react/src/experimental/Skeleton/SkeletonAvatar.tsx index 635b5e4bf39..5221154a39c 100644 --- a/packages/react/src/experimental/Skeleton/SkeletonAvatar.tsx +++ b/packages/react/src/experimental/Skeleton/SkeletonAvatar.tsx @@ -1,17 +1,36 @@ 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' +import {CSS_MODULE_FLAG} from './FeatureFlag' export type SkeletonAvatarProps = Pick & { /** Class name for custom styling */ className?: string } & Omit, 'size'> +const avatarSkeletonStyles = { + '&[data-component="SkeletonAvatar"]': { + borderRadius: '50%', + boxShadow: `0 0 0 1px ${get('colors.avatar.border')}`, + display: 'inline-block', + lineHeight: get('lineHeights.condensedUltra'), + height: 'var(--avatar-size)', + width: 'var(--avatar-size)', + }, + + '&[data-square]': { + borderRadius: 'clamp(4px, var(--avatar-size) - 24px, 6px)', + }, +} + export const SkeletonAvatar: React.FC = ({ size = DEFAULT_AVATAR_SIZE, square, @@ -21,23 +40,40 @@ export const SkeletonAvatar: React.FC = ({ }) => { const responsive = isResponsiveValue(size) const cssSizeVars = {} as Record + const enabled = useFeatureFlag(CSS_MODULE_FLAG) + const avatarSx = responsive + ? { + ...getBreakpointDeclarations( + size, + '--avatar-size' as keyof React.CSSProperties, + value => `${value || DEFAULT_AVATAR_SIZE}px`, + ), + ...avatarSkeletonStyles, + } + : { + '--avatar-size': `${size}px`, + ...avatarSkeletonStyles, + } - if (responsive) { - for (const [key, value] of Object.entries(size)) { - cssSizeVars[`--avatarSize-${key}`] = `${value}px` + if (enabled) { + if (responsive) { + for (const [key, value] of Object.entries(size)) { + cssSizeVars[`--avatarSize-${key}`] = `${value}px` + } + } else { + cssSizeVars['--avatarSize-regular'] = `${size}px` } - } else { - cssSizeVars['--avatarSize-regular'] = `${size}px` } return ( ) } diff --git a/packages/react/src/experimental/Skeleton/SkeletonBox.tsx b/packages/react/src/experimental/Skeleton/SkeletonBox.tsx index 861dd5d56e3..8a8bcaea7b1 100644 --- a/packages/react/src/experimental/Skeleton/SkeletonBox.tsx +++ b/packages/react/src/experimental/Skeleton/SkeletonBox.tsx @@ -1,10 +1,13 @@ import React from 'react' -import {merge, type SxProp} from '../../sx' -import {type CSSProperties} from 'react' +import styled, {keyframes} from 'styled-components' +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 {defaultSxProp} from '../../utils/defaultSxProp' -import Box from '../../Box' +import {useFeatureFlag} from '../../FeatureFlags' +import {CSS_MODULE_FLAG} from './FeatureFlag' type SkeletonBoxProps = { /** Height of the skeleton "box". Accepts any valid CSS `height` value. */ @@ -14,39 +17,62 @@ type SkeletonBoxProps = { /** The className of the skeleton box */ className?: string } & SxProp & - React.ComponentPropsWithoutRef<'div'> + HTMLProps + +const shimmer = keyframes` + from { mask-position: 200%; } + to { mask-position: 0%; } +` + +const StyledSkeletonBox = toggleStyledComponent( + CSS_MODULE_FLAG, + 'div', + styled.div` + 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%; + animation: ${shimmer}; + animation-duration: 1s; + animation-iteration-count: infinite; + } + + @media (forced-colors: active) { + outline: 1px solid transparent; + outline-offset: -1px; + } + + ${sx}; + `, +) export const SkeletonBox = React.forwardRef(function SkeletonBox( - {height, width, className, sx: sxProp = defaultSxProp, style, ...props}, + {height, width, className, style, ...props}, ref, ) { - if (sxProp !== defaultSxProp) { - return ( - - ) - } - + const enabled = useFeatureFlag(CSS_MODULE_FLAG) return ( -
diff --git a/packages/react/src/experimental/Skeleton/SkeletonText.tsx b/packages/react/src/experimental/Skeleton/SkeletonText.tsx index 5babb759611..08c2680363f 100644 --- a/packages/react/src/experimental/Skeleton/SkeletonText.tsx +++ b/packages/react/src/experimental/Skeleton/SkeletonText.tsx @@ -1,8 +1,11 @@ import React, {type CSSProperties, type HTMLProps} from 'react' +import Box from '../../Box' import {SkeletonBox} from './SkeletonBox' import classes from './SkeletonText.module.css' +import {useFeatureFlag} from '../../FeatureFlags' import {clsx} from 'clsx' import {merge} from '../../sx' +import {CSS_MODULE_FLAG} from './FeatureFlag' type SkeletonTextProps = { /** Size of the text that the skeleton is replacing. */ @@ -15,6 +18,61 @@ type SkeletonTextProps = { className?: string } & Omit, 'size'> +const skeletonTextStyles = { + '&[data-component="SkeletonText"]': { + '--font-size': 'var(--text-body-size-medium, 0.875rem)', + '--line-height': 'var(--text-body-lineHeight-medium, 1.4285)', + '--leading': 'calc(var(--font-size) * var(--line-height) - var(--font-size))', + borderRadius: 'var(--borderRadius-small, 0.1875rem)', + height: 'var(--font-size)', + marginBlock: 'calc(var(--leading) / 2)', + }, + '&[data-in-multiline="true"]': { + marginBlockEnd: 'calc(var(--leading) * 2)', + }, + '&[data-in-multiline="true"]:last-child': { + maxWidth: '65%', + minWidth: '50px', + marginBottom: 0, + }, + '@supports (margin-block: mod(1px, 1px))': { + '&[data-component="SkeletonText"]': { + '--leading': 'mod(var(--font-size) * var(--line-height), var(--font-size))', + }, + }, + '&[data-text-skeleton-size="display"], &[data-text-skeleton-size="titleLarge"]': { + borderRadius: 'var(--borderRadius-medium, 0.375rem)', + }, + '&[data-text-skeleton-size="display"]': { + '--font-size': 'var(--text-display-size, 2.5rem)', + '--line-height': 'var(--text-display-lineHeight, 1.4)', + }, + '&[data-text-skeleton-size="titleLarge"]': { + '--font-size': 'var(--text-title-size-large, 2.5rem)', + '--line-height': 'var(--text-title-lineHeight-large, 1.5)', + }, + '&[data-text-skeleton-size="titleMedium"]': { + '--font-size': 'var(--text-title-size-medium, 1.25rem)', + '--line-height': 'var(--text-title-lineHeight-medium, 1.6)', + }, + '&[data-text-skeleton-size="titleSmall"]': { + '--font-size': 'var(--text-title-size-small, 1rem)', + '--line-height': 'var(--text-title-lineHeight-small, 1.5)', + }, + '&[data-text-skeleton-size="subtitle"]': { + '--font-size': 'var(--text-subtitle-size, 1.25rem)', + '--line-height': 'var(--text-subtitle-lineHeight, 1.6)', + }, + '&[data-text-skeleton-size="bodyLarge"]': { + '--font-size': 'var(--text-body-size-large, 1rem)', + '--line-height': 'var(--text-body-lineHeight-large, 1.5)', + }, + '&[data-text-skeleton-size="bodySmall"]': { + '--font-size': 'var(--text-body-size-small, 0.75rem)', + '--line-height': 'var(--text-body-lineHeight-small, 1.6666)', + }, +} + export const SkeletonText: React.FC = ({ lines = 1, maxWidth, @@ -23,22 +81,39 @@ export const SkeletonText: React.FC = ({ style, ...rest }) => { + const enabled = useFeatureFlag(CSS_MODULE_FLAG) + if (lines < 2) { return ( ) } else { return ( -
{Array.from({length: lines}, (_, index) => ( = ({ data-component="SkeletonText" data-in-multiline="true" data-text-skeleton-size={size} - className={clsx(className, classes.SkeletonText)} + sx={enabled ? {} : skeletonTextStyles} + className={clsx(className, {[classes.SkeletonText]: enabled})} {...rest} /> ))} -
+ ) } }