diff --git a/.changeset/small-foxes-tan.md b/.changeset/small-foxes-tan.md
new file mode 100644
index 00000000000..bc5ee27c455
--- /dev/null
+++ b/.changeset/small-foxes-tan.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": minor
+---
+
+Convert ProgressBar to CSS modules behind feature flag
diff --git a/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-colorblind-linux.png b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-colorblind-linux.png
new file mode 100644
index 00000000000..6fe55fca51d
Binary files /dev/null and b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-colorblind-linux.png differ
diff --git a/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-dimmed-linux.png b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-dimmed-linux.png
new file mode 100644
index 00000000000..1aa04d731a1
Binary files /dev/null and b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-dimmed-linux.png differ
diff --git a/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-high-contrast-linux.png b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-high-contrast-linux.png
new file mode 100644
index 00000000000..ea3000d0cfd
Binary files /dev/null and b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-high-contrast-linux.png differ
diff --git a/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-linux.png b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-linux.png
new file mode 100644
index 00000000000..b272c909a6f
Binary files /dev/null and b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-linux.png differ
diff --git a/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-tritanopia-linux.png b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-tritanopia-linux.png
new file mode 100644
index 00000000000..6fe55fca51d
Binary files /dev/null and b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-dark-tritanopia-linux.png differ
diff --git a/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-colorblind-linux.png b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-colorblind-linux.png
new file mode 100644
index 00000000000..1fcdfa55c72
Binary files /dev/null and b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-colorblind-linux.png differ
diff --git a/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-high-contrast-linux.png b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-high-contrast-linux.png
new file mode 100644
index 00000000000..d828f979366
Binary files /dev/null and b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-high-contrast-linux.png differ
diff --git a/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-linux.png b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-linux.png
new file mode 100644
index 00000000000..baf2d5630f7
Binary files /dev/null and b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-linux.png differ
diff --git a/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-tritanopia-linux.png b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-tritanopia-linux.png
new file mode 100644
index 00000000000..1fcdfa55c72
Binary files /dev/null and b/.playwright/snapshots/components/ProgressBar.test.ts-snapshots/ProgressBar-Dev-SX-Props-light-tritanopia-linux.png differ
diff --git a/e2e/components/ProgressBar.test.ts b/e2e/components/ProgressBar.test.ts
index ac87493b2a1..acef0768594 100644
--- a/e2e/components/ProgressBar.test.ts
+++ b/e2e/components/ProgressBar.test.ts
@@ -2,228 +2,75 @@ import {test, expect} from '@playwright/test'
import {visit} from '../test-helpers/storybook'
import {themes} from '../test-helpers/themes'
-test.describe('ProgressBar', () => {
- test.describe('Default', () => {
- for (const theme of themes) {
- test.describe(theme, () => {
- test('default @vrt', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar--default',
- globals: {
- colorScheme: theme,
- },
- })
-
- // Default state
- expect(await page.screenshot()).toMatchSnapshot(`ProgressBar.Default.${theme}.png`)
- })
-
- test('axe @aat', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar--default',
- globals: {
- colorScheme: theme,
- },
- })
- await expect(page).toHaveNoViolations()
- })
- })
- }
- })
-
- test.describe('Progress Zero', () => {
- for (const theme of themes) {
- test.describe(theme, () => {
- test('default @vrt', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--progress-zero',
- globals: {
- colorScheme: theme,
- },
- })
-
- // Default state
- expect(await page.screenshot()).toMatchSnapshot(`ProgressBar.Progress Zero.${theme}.png`)
- })
-
- test('axe @aat', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--progress-zero',
- globals: {
- colorScheme: theme,
- },
- })
- await expect(page).toHaveNoViolations()
- })
- })
- }
- })
-
- test.describe('Progress Half', () => {
- for (const theme of themes) {
- test.describe(theme, () => {
- test('default @vrt', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--progress-half',
- globals: {
- colorScheme: theme,
- },
- })
-
- // Default state
- expect(await page.screenshot()).toMatchSnapshot(`ProgressBar.Progress Half.${theme}.png`)
- })
-
- test('axe @aat', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--progress-half',
- globals: {
- colorScheme: theme,
- },
- })
- await expect(page).toHaveNoViolations()
- })
- })
- }
- })
-
- test.describe('Progress Done', () => {
- for (const theme of themes) {
- test.describe(theme, () => {
- test('default @vrt', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--progress-done',
- globals: {
- colorScheme: theme,
- },
- })
-
- // Default state
- expect(await page.screenshot()).toMatchSnapshot(`ProgressBar.Progress Done.${theme}.png`)
- })
-
- test('axe @aat', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--progress-done',
- globals: {
- colorScheme: theme,
- },
- })
- await expect(page).toHaveNoViolations()
- })
- })
- }
- })
+const stories = [
+ {
+ title: 'Default',
+ id: 'components-progressbar--default',
+ },
+ {
+ title: 'Progress Zero',
+ id: 'components-progressbar-features--progress-zero',
+ },
+ {
+ title: 'Progress Half',
+ id: 'components-progressbar-features--progress-half',
+ },
+ {
+ title: 'Progress Done',
+ id: 'components-progressbar-features--progress-done',
+ },
+ {
+ title: 'Size Small',
+ id: 'components-progressbar-features--size-small',
+ },
+ {
+ title: 'Size Large',
+ id: 'components-progressbar-features--size-large',
+ },
+ {
+ title: 'Inline',
+ id: 'components-progressbar-features--inline',
+ },
+ {
+ title: 'Color',
+ id: 'components-progressbar-features--color',
+ },
+ {
+ title: 'Dev SX Props',
+ id: 'components-progressbar-dev--default',
+ },
+] as const
- test.describe('Size Small', () => {
- for (const theme of themes) {
- test.describe(theme, () => {
- test('default @vrt', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--size-small',
- globals: {
- colorScheme: theme,
- },
- })
-
- // Default state
- expect(await page.screenshot()).toMatchSnapshot(`ProgressBar.Size Small.${theme}.png`)
- })
-
- test('axe @aat', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--size-small',
- globals: {
- colorScheme: theme,
- },
- })
- await expect(page).toHaveNoViolations()
- })
- })
- }
- })
-
- test.describe('Size Large', () => {
- for (const theme of themes) {
- test.describe(theme, () => {
- test('default @vrt', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--size-large',
- globals: {
- colorScheme: theme,
- },
- })
-
- // Default state
- expect(await page.screenshot()).toMatchSnapshot(`ProgressBar.Size Large.${theme}.png`)
- })
-
- test('axe @aat', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--size-large',
- globals: {
- colorScheme: theme,
- },
- })
- await expect(page).toHaveNoViolations()
- })
- })
- }
- })
-
- test.describe('Inline', () => {
- for (const theme of themes) {
- test.describe(theme, () => {
- test('default @vrt', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--inline',
- globals: {
- colorScheme: theme,
- },
- })
-
- // Default state
- expect(await page.screenshot()).toMatchSnapshot(`ProgressBar.Inline.${theme}.png`)
- })
-
- test('axe @aat', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--inline',
- globals: {
- colorScheme: theme,
- },
- })
- await expect(page).toHaveNoViolations()
- })
- })
- }
- })
-
- test.describe('Color', () => {
- for (const theme of themes) {
- test.describe(theme, () => {
- test('default @vrt', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--color',
- globals: {
- colorScheme: theme,
- },
- })
-
- // Default state
- expect(await page.screenshot()).toMatchSnapshot(`ProgressBar.Color.${theme}.png`)
- })
-
- test('axe @aat', async ({page}) => {
- await visit(page, {
- id: 'components-progressbar-features--color',
- globals: {
- colorScheme: theme,
- },
- })
- await expect(page).toHaveNoViolations()
- })
- })
- }
- })
+test.describe('ProgressBar', () => {
+ for (const story of stories) {
+ test.describe(story.title, () => {
+ for (const theme of themes) {
+ test.describe(theme, () => {
+ test('@vrt', async ({page}) => {
+ await visit(page, {
+ id: story.id,
+ globals: {
+ colorScheme: theme,
+ },
+ })
+
+ // Default state
+ expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
+ `ProgressBar.${story.title}.${theme}.png`,
+ )
+ })
+
+ test('axe @aat', async ({page}) => {
+ await visit(page, {
+ id: story.id,
+ globals: {
+ colorScheme: theme,
+ },
+ })
+ await expect(page).toHaveNoViolations()
+ })
+ })
+ }
+ })
+ }
})
diff --git a/packages/react/src/ProgressBar/ProgressBar.dev.stories.tsx b/packages/react/src/ProgressBar/ProgressBar.dev.stories.tsx
new file mode 100644
index 00000000000..4d3afa7d319
--- /dev/null
+++ b/packages/react/src/ProgressBar/ProgressBar.dev.stories.tsx
@@ -0,0 +1,20 @@
+import React from 'react'
+import {ProgressBar} from '.'
+
+export default {
+ title: 'Components/ProgressBar/Dev',
+ component: ProgressBar,
+}
+
+export const Default = () => (
+
+)
diff --git a/packages/react/src/ProgressBar/ProgressBar.module.css b/packages/react/src/ProgressBar/ProgressBar.module.css
new file mode 100644
index 00000000000..e9f3badcd38
--- /dev/null
+++ b/packages/react/src/ProgressBar/ProgressBar.module.css
@@ -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;
+ }
+}
diff --git a/packages/react/src/ProgressBar/ProgressBar.tsx b/packages/react/src/ProgressBar/ProgressBar.tsx
index 1a49d5a828a..69c048bcc09 100644
--- a/packages/react/src/ProgressBar/ProgressBar.tsx
+++ b/packages/react/src/ProgressBar/ProgressBar.tsx
@@ -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
}
@@ -16,22 +21,28 @@ const shimmer = keyframes`
to { mask-position: 0%; }
`
-const ProgressItem = styled.span`
- 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`
+ 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',
@@ -46,22 +57,37 @@ type StyledProgressContainerProps = {
} & WidthProps &
SxProp
-const ProgressContainer = styled.span`
- 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`
+ 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 & {'aria-label'?: string} & ProgressProp & SxProp
+export type ProgressBarItems = React.HTMLAttributes & {
+ 'aria-label'?: string
+ className?: string
+} & ProgressProp &
+ SxProp
export const Item = forwardRef(
(
- {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
@@ -74,13 +100,25 @@ export const Item = forwardRef(
'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 (
)
@@ -89,7 +127,10 @@ export const Item = forwardRef(
Item.displayName = 'ProgressBar.Item'
-export type ProgressBarProps = React.HTMLAttributes & {bg?: string} & StyledProgressContainerProps &
+export type ProgressBarProps = React.HTMLAttributes & {
+ bg?: string
+ className?: string
+} & StyledProgressContainerProps &
ProgressProp
export const ProgressBar = forwardRef(
@@ -103,6 +144,7 @@ export const ProgressBar = forwardRef(
'aria-label': ariaLabel,
'aria-valuenow': ariaValueNow,
'aria-valuetext': ariaValueText,
+ className,
...rest
}: ProgressBarProps,
forwardRef,
@@ -114,9 +156,19 @@ export const ProgressBar = forwardRef(
// 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 (
-
+
{validChildren ? (
children
) : (