Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ProgressBar): Convert ProgressBar to CSS modules behind team feature flag #5304

Merged
merged 12 commits into from
Nov 19, 2024
5 changes: 5 additions & 0 deletions .changeset/small-foxes-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

Convert ProgressBar to CSS modules behind feature flag
50 changes: 50 additions & 0 deletions packages/react/src/ProgressBar/ProgressBar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@keyframes shimmer {
from {
mask-position: 200%;
}

to {
mask-position: 0%;
}
}

.ProgressBarItem {
width: var(--progress-width);
/* stylelint-disable-next-line primer/colors */
background-color: var(--progress-bg);

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

.ProgressBarContainer {
display: flex;
overflow: hidden;
/* stylelint-disable-next-line primer/colors */
background-color: var(--borderColor-default);
border-radius: var(--borderRadius-small);
gap: 2px;

&:where([data-progress-display='inline']) {
display: inline-flex;
}

&:where([data-progress-bar-size='default']) {
height: 8px;
}

&:where([data-progress-bar-size='small']) {
height: 5px;
}

&:where([data-progress-bar-size='large']) {
height: 10px;
}
}
108 changes: 80 additions & 28 deletions packages/react/src/ProgressBar/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import {width} from 'styled-system'
import {get} from '../constants'
import type {SxProp} from '../sx'
import sx from '../sx'
import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
import {clsx} from 'clsx'
import classes from './ProgressBar.module.css'
import {useFeatureFlag} from '../FeatureFlags'

type ProgressProp = {
className?: string
progress?: string | number
bg?: string
}
Expand All @@ -16,22 +21,28 @@ const shimmer = keyframes`
to { mask-position: 0%; }
`

const ProgressItem = styled.span<ProgressProp & SxProp>`
width: ${props => (props.progress ? `${props.progress}%` : 0)};
background-color: ${props => get(`colors.${props.bg || 'success.emphasis'}`)};

@media (prefers-reduced-motion: no-preference) {
&[data-animated='true'] {
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;
const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team'

const ProgressItem = toggleStyledComponent(
CSS_MODULES_FEATURE_FLAG,
'span',
styled.span<ProgressProp & SxProp>`
width: ${props => (props.progress ? `${props.progress}%` : 0)};
background-color: ${props => get(`colors.${props.bg || 'success.emphasis'}`)};

@media (prefers-reduced-motion: no-preference) {
&[data-animated='true'] {
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};
`
${sx};
`,
)

const sizeMap = {
small: '5px',
Expand All @@ -46,22 +57,37 @@ type StyledProgressContainerProps = {
} & WidthProps &
SxProp

const ProgressContainer = styled.span<StyledProgressContainerProps>`
display: ${props => (props.inline ? 'inline-flex' : 'flex')};
overflow: hidden;
background-color: ${get('colors.border.default')};
border-radius: ${get('radii.1')};
height: ${props => sizeMap[props.barSize || 'default']};
gap: 2px;
${width}
${sx};
`
const ProgressContainer = toggleStyledComponent(
CSS_MODULES_FEATURE_FLAG,
'span',
styled.span<StyledProgressContainerProps>`
display: ${props => (props.inline ? 'inline-flex' : 'flex')};
overflow: hidden;
background-color: ${get('colors.border.default')};
border-radius: ${get('radii.1')};
height: ${props => sizeMap[props.barSize || 'default']};
gap: 2px;
${width}
${sx};
`,
)

export type ProgressBarItems = React.HTMLAttributes<HTMLSpanElement> & {'aria-label'?: string} & ProgressProp & SxProp
export type ProgressBarItems = React.HTMLAttributes<HTMLSpanElement> & {
'aria-label'?: string
className?: string
} & ProgressProp &
SxProp

export const Item = forwardRef<HTMLSpanElement, ProgressBarItems>(
(
{progress, 'aria-label': ariaLabel, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText, ...rest},
{
progress,
'aria-label': ariaLabel,
'aria-valuenow': ariaValueNow,
'aria-valuetext': ariaValueText,
className,
...rest
},
forwardRef,
) => {
const progressAsNumber = typeof progress === 'string' ? parseInt(progress, 10) : progress
Expand All @@ -74,13 +100,25 @@ export const Item = forwardRef<HTMLSpanElement, ProgressBarItems>(
'aria-valuetext': ariaValueText,
}

const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)

const progressBarWidth = '--progress-width'
const progressBarBg = '--progress-bg'
const styles: {[key: string]: string} = {}

const bgType = rest.bg && rest.bg.split('.')
styles[progressBarWidth] = progress ? `${progress}%` : '0%'
styles[progressBarBg] = (bgType && `var(--bgColor-${bgType[0]}-${bgType[1]})`) || 'var(--bgColor-success-emphasis)'

return (
<ProgressItem
className={clsx(className, {[classes.ProgressBarItem]: enabled})}
{...rest}
role="progressbar"
aria-label={ariaLabel}
ref={forwardRef}
progress={progress}
style={enabled ? styles : undefined}
{...ariaAttributes}
/>
)
Expand All @@ -89,7 +127,10 @@ export const Item = forwardRef<HTMLSpanElement, ProgressBarItems>(

Item.displayName = 'ProgressBar.Item'

export type ProgressBarProps = React.HTMLAttributes<HTMLSpanElement> & {bg?: string} & StyledProgressContainerProps &
export type ProgressBarProps = React.HTMLAttributes<HTMLSpanElement> & {
bg?: string
className?: string
} & StyledProgressContainerProps &
ProgressProp

export const ProgressBar = forwardRef<HTMLSpanElement, ProgressBarProps>(
Expand All @@ -103,6 +144,7 @@ export const ProgressBar = forwardRef<HTMLSpanElement, ProgressBarProps>(
'aria-label': ariaLabel,
'aria-valuenow': ariaValueNow,
'aria-valuetext': ariaValueText,
className,
...rest
}: ProgressBarProps,
forwardRef,
Expand All @@ -114,9 +156,19 @@ export const ProgressBar = forwardRef<HTMLSpanElement, ProgressBarProps>(
// Get the number of non-empty nodes passed as children, this will exclude
// booleans, null, and undefined
const validChildren = React.Children.toArray(children).length
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)

const cssModulesProps = !enabled
? {barSize}
: {'data-progress-display': rest.inline ? 'inline' : 'block', 'data-progress-bar-size': barSize}

return (
<ProgressContainer ref={forwardRef} barSize={barSize} {...rest}>
<ProgressContainer
ref={forwardRef}
className={clsx(className, {[classes.ProgressBarContainer]: enabled})}
{...cssModulesProps}
{...rest}
>
{validChildren ? (
children
) : (
Expand Down
Loading