diff --git a/.changeset/orange-steaks-do.md b/.changeset/orange-steaks-do.md new file mode 100644 index 00000000000..72d9632560f --- /dev/null +++ b/.changeset/orange-steaks-do.md @@ -0,0 +1,5 @@ +--- +"@primer/react": patch +--- + +Migrate `ButtonGroup` component to use CSS modules behind the `primer_react_css_modules_team` feature flag diff --git a/e2e/components/ButtonGroup.test.ts b/e2e/components/ButtonGroup.test.ts index c3ca9822ff7..4fdcd7ab984 100644 --- a/e2e/components/ButtonGroup.test.ts +++ b/e2e/components/ButtonGroup.test.ts @@ -11,6 +11,24 @@ test.describe('ButtonGroup', () => { id: 'components-buttongroup--default', globals: { colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: true, + }, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`ButtonGroup.Default.${theme}.png`) + }) + + test('default @vrt (styled-components)', async ({page}) => { + await visit(page, { + id: 'components-buttongroup--default', + globals: { + colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: false, + }, }, }) @@ -23,6 +41,22 @@ test.describe('ButtonGroup', () => { id: 'components-buttongroup--default', globals: { colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: true, + }, + }, + }) + await expect(page).toHaveNoViolations() + }) + + test('axe @aat (styled-components)', async ({page}) => { + await visit(page, { + id: 'components-buttongroup--default', + globals: { + colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: false, + }, }, }) await expect(page).toHaveNoViolations() @@ -39,6 +73,24 @@ test.describe('ButtonGroup', () => { id: 'components-buttongroup--playground', globals: { colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: true, + }, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`ButtonGroup.Playground.${theme}.png`) + }) + + test('default @vrt (styled-components)', async ({page}) => { + await visit(page, { + id: 'components-buttongroup--playground', + globals: { + colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: false, + }, }, }) @@ -51,6 +103,22 @@ test.describe('ButtonGroup', () => { id: 'components-buttongroup--playground', globals: { colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: true, + }, + }, + }) + await expect(page).toHaveNoViolations() + }) + + test('axe @aat (styled-components)', async ({page}) => { + await visit(page, { + id: 'components-buttongroup--playground', + globals: { + colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: false, + }, }, }) await expect(page).toHaveNoViolations() @@ -67,6 +135,24 @@ test.describe('ButtonGroup', () => { id: 'components-buttongroup-features--icon-buttons', globals: { colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: true, + }, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`ButtonGroup.Icon Buttons.${theme}.png`) + }) + + test('default @vrt (styled-components)', async ({page}) => { + await visit(page, { + id: 'components-buttongroup-features--icon-buttons', + globals: { + colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: false, + }, }, }) @@ -79,6 +165,22 @@ test.describe('ButtonGroup', () => { id: 'components-buttongroup-features--icon-buttons', globals: { colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: true, + }, + }, + }) + await expect(page).toHaveNoViolations() + }) + + test('axe @aat (styled-components)', async ({page}) => { + await visit(page, { + id: 'components-buttongroup-features--icon-buttons', + globals: { + colorScheme: theme, + featureFlags: { + primer_react_css_modules_team: false, + }, }, }) await expect(page).toHaveNoViolations() diff --git a/packages/react/src/ButtonGroup/ButtonGroup.module.css b/packages/react/src/ButtonGroup/ButtonGroup.module.css new file mode 100644 index 00000000000..56e5b808a2a --- /dev/null +++ b/packages/react/src/ButtonGroup/ButtonGroup.module.css @@ -0,0 +1,60 @@ +.ButtonGroup { + display: inline-flex; + vertical-align: middle; + isolation: isolate; + + & > *:not([data-loading-wrapper]) { + /* stylelint-disable-next-line primer/spacing */ + margin-inline-end: -1px; + position: relative; + border-radius: 0; + + &:first-child { + border-top-left-radius: var(--borderRadius-medium); + border-bottom-left-radius: var(--borderRadius-medium); + } + + &:last-child { + border-top-right-radius: var(--borderRadius-medium); + border-bottom-right-radius: var(--borderRadius-medium); + } + + &:focus, + &:active, + &:hover { + z-index: 1; + } + } + + /* if child is loading button */ + [data-loading-wrapper] { + &:first-child { + button, + a { + border-top-left-radius: var(--borderRadius-medium); + border-bottom-left-radius: var(--borderRadius-medium); + } + } + + &:last-child { + button, + a { + border-top-right-radius: var(--borderRadius-medium); + border-bottom-right-radius: var(--borderRadius-medium); + } + } + } + + [data-loading-wrapper] > * { + /* stylelint-disable-next-line primer/spacing */ + margin-inline-end: -1px; + position: relative; + border-radius: 0; + + &:focus, + &:active, + &:hover { + z-index: 1; + } + } +} diff --git a/packages/react/src/ButtonGroup/ButtonGroup.tsx b/packages/react/src/ButtonGroup/ButtonGroup.tsx index 0ba399c9ad3..aeed944a1cf 100644 --- a/packages/react/src/ButtonGroup/ButtonGroup.tsx +++ b/packages/react/src/ButtonGroup/ButtonGroup.tsx @@ -1,68 +1,96 @@ import styled from 'styled-components' +import React from 'react' import {get} from '../constants' import sx from '../sx' import type {ComponentProps} from '../utils/types' +import classes from './ButtonGroup.module.css' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import {clsx} from 'clsx' +import {useFeatureFlag} from '../FeatureFlags' -const ButtonGroup = styled.div` - display: inline-flex; - vertical-align: middle; - isolation: isolate; +const StyledButtonGroup = toggleStyledComponent( + 'primer_react_css_modules_team', + styled.div` + display: inline-flex; + vertical-align: middle; + isolation: isolate; - && > *:not([data-loading-wrapper]) { - margin-inline-end: -1px; - position: relative; - border-radius: 0; + && > *:not([data-loading-wrapper]) { + margin-inline-end: -1px; + position: relative; + border-radius: 0; - :first-child { - border-top-left-radius: ${get('radii.2')}; - border-bottom-left-radius: ${get('radii.2')}; - } - - :last-child { - border-top-right-radius: ${get('radii.2')}; - border-bottom-right-radius: ${get('radii.2')}; - } - - :focus, - :active, - :hover { - z-index: 1; - } - } - - // if child is loading button - [data-loading-wrapper] { - :first-child { - button, - a { + :first-child { border-top-left-radius: ${get('radii.2')}; border-bottom-left-radius: ${get('radii.2')}; } - } - :last-child { - button, - a { + :last-child { border-top-right-radius: ${get('radii.2')}; border-bottom-right-radius: ${get('radii.2')}; } + + :focus, + :active, + :hover { + z-index: 1; + } } - } - [data-loading-wrapper] > * { - margin-inline-end: -1px; - position: relative; - border-radius: 0; + // if child is loading button + [data-loading-wrapper] { + :first-child { + button, + a { + border-top-left-radius: ${get('radii.2')}; + border-bottom-left-radius: ${get('radii.2')}; + } + } + + :last-child { + button, + a { + border-top-right-radius: ${get('radii.2')}; + border-bottom-right-radius: ${get('radii.2')}; + } + } + } - :focus, - :active, - :hover { - z-index: 1; + [data-loading-wrapper] > * { + margin-inline-end: -1px; + position: relative; + border-radius: 0; + + :focus, + :active, + :hover { + z-index: 1; + } } - } - ${sx}; -` + ${sx}; + `, +) + +export type ButtonGroupProps = ComponentProps +const ButtonGroup = React.forwardRef(function ButtonGroup( + {children, className, ...rest}, + forwardRef, +) { + const enabled = useFeatureFlag('primer_react_css_modules_team') + return ( + + {children} + + ) +}) + +ButtonGroup.displayName = 'ButtonGroup' -export type ButtonGroupProps = ComponentProps export default ButtonGroup