diff --git a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-colorblind-linux.png b/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-colorblind-linux.png deleted file mode 100644 index 37061d02921..00000000000 Binary files a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-colorblind-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-dimmed-linux.png b/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-dimmed-linux.png deleted file mode 100644 index 69ec67fb461..00000000000 Binary files a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-dimmed-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-high-contrast-linux.png b/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-high-contrast-linux.png deleted file mode 100644 index 9b45f91ec50..00000000000 Binary files a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-high-contrast-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-linux.png b/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-linux.png deleted file mode 100644 index 37061d02921..00000000000 Binary files a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-tritanopia-linux.png b/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-tritanopia-linux.png deleted file mode 100644 index 37061d02921..00000000000 Binary files a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-dark-tritanopia-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-colorblind-linux.png b/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-colorblind-linux.png deleted file mode 100644 index 6ef721d7adb..00000000000 Binary files a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-colorblind-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-high-contrast-linux.png b/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-high-contrast-linux.png deleted file mode 100644 index 8ff90a243af..00000000000 Binary files a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-high-contrast-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-linux.png b/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-linux.png deleted file mode 100644 index 6ef721d7adb..00000000000 Binary files a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-tritanopia-linux.png b/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-tritanopia-linux.png deleted file mode 100644 index 6ef721d7adb..00000000000 Binary files a/.playwright/snapshots/components/ButtonGroup.test.ts-snapshots/ButtonGroup-SX-Prop-light-tritanopia-linux.png and /dev/null differ diff --git a/e2e/components/ButtonGroup.test.ts b/e2e/components/ButtonGroup.test.ts index 4b9b3de07f4..1a9eab686b9 100644 --- a/e2e/components/ButtonGroup.test.ts +++ b/e2e/components/ButtonGroup.test.ts @@ -2,57 +2,116 @@ import {test, expect} from '@playwright/test' import {visit} from '../test-helpers/storybook' import {themes} from '../test-helpers/themes' -const stories = [ - { - title: 'Default', - id: 'components-buttongroup--default', - }, - { - title: 'Playground', - id: 'components-buttongroup--playground', - }, - { - title: 'Icon Buttons', - id: 'components-buttongroup-features--icon-buttons', - }, - { - title: 'As Toolbar', - id: 'components-buttongroup-features--as-toolbar', - }, - { - title: 'SX Prop', - id: 'components-buttongroup-dev--sx-prop', - }, -] as const - test.describe('ButtonGroup', () => { - 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()).toMatchSnapshot(`ButtonGroup.${story.title}.${theme}.png`) - }) - - test('axe @aat', async ({page}) => { - await visit(page, { - id: story.id, - globals: { - colorScheme: theme, - }, - }) - await expect(page).toHaveNoViolations() - }) - }) - } - }) - } + test.describe('Default', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-buttongroup--default', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`ButtonGroup.Default.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-buttongroup--default', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Playground', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-buttongroup--playground', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`ButtonGroup.Playground.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-buttongroup--playground', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Icon Buttons', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-buttongroup-features--icon-buttons', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`ButtonGroup.Icon Buttons.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-buttongroup-features--icon-buttons', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('As Toolbar', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-buttongroup-features--as-toolbar', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`ButtonGroup.As Toolbar.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-buttongroup-features--as-toolbar', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) }) diff --git a/packages/react/src/ButtonGroup/ButtonGroup.dev.stories.tsx b/packages/react/src/ButtonGroup/ButtonGroup.dev.stories.tsx deleted file mode 100644 index 7726324ad32..00000000000 --- a/packages/react/src/ButtonGroup/ButtonGroup.dev.stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import type {Meta} from '@storybook/react' -import ButtonGroup from './ButtonGroup' -import {Button} from '../Button' - -export default { - title: 'Components/ButtonGroup/Dev', - component: ButtonGroup, -} as Meta - -export const SxProp = () => ( - - - - - -) diff --git a/packages/react/src/ButtonGroup/ButtonGroup.tsx b/packages/react/src/ButtonGroup/ButtonGroup.tsx index b3d59baacb6..dd9761424eb 100644 --- a/packages/react/src/ButtonGroup/ButtonGroup.tsx +++ b/packages/react/src/ButtonGroup/ButtonGroup.tsx @@ -1,24 +1,88 @@ +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' import {FocusKeys, useFocusZone} from '../hooks/useFocusZone' import {useProvidedRefOrCreate} from '../hooks' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' -import {defaultSxProp} from '../utils/defaultSxProp' -import Box from '../Box' -import type {SxProp} from '../sx' -export type ButtonGroupProps = { - /** The role of the group */ - role?: string - /** className passed in for styling */ - className?: string -} & SxProp +const StyledButtonGroup = toggleStyledComponent( + 'primer_react_css_modules_ga', + 'div', + styled.div` + display: inline-flex; + vertical-align: middle; + isolation: isolate; + + && > *:not([data-loading-wrapper]):is(button, a) { + margin-inline-end: -1px; + position: relative; + border-radius: 0; + + :first-of-type { + border-top-left-radius: ${get('radii.2')}; + border-bottom-left-radius: ${get('radii.2')}; + } + + :last-of-type { + 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 { + 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')}; + } + } + } + + [data-loading-wrapper] > * { + margin-inline-end: -1px; + position: relative; + border-radius: 0; + + :focus, + :active, + :hover { + z-index: 1; + } + } + + ${sx}; + `, +) + +export type ButtonGroupProps = ComponentProps const ButtonGroup = React.forwardRef(function ButtonGroup( - {className, role, sx, ...rest}, + {children, className, role, ...rest}, forwardRef, ) { + const enabled = useFeatureFlag('primer_react_css_modules_ga') const buttonRef = useProvidedRefOrCreate(forwardRef as React.RefObject) useFocusZone({ @@ -28,13 +92,18 @@ const ButtonGroup = React.forwardRef(function But focusOutBehavior: 'wrap', }) - if (sx !== defaultSxProp) { - return ( - - ) - } - - return
+ return ( + + {children} + + ) }) as PolymorphicForwardRefComponent<'div', ButtonGroupProps> ButtonGroup.displayName = 'ButtonGroup'