Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/three-jokes-bow.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Make sure all components accept `className` as a prop on outermost component element.
19 changes: 17 additions & 2 deletions packages/react/src/Banner/Banner.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import {Banner} from '../Banner'
import {FeatureFlags} from '../FeatureFlags'

describe('Banner', () => {
let spy: jest.SpyInstance
Expand DownExpand Up@@ -30,8 +31,22 @@ describe('Banner', () => {
})

it('should support a custom `className` on the outermost element', () => {
const {container} = render(<Banner title="test" className="test" />)
expect(container.firstChild).toHaveClass('test')
const Element = () => <Banner title="test" className="test-class-name" />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(render(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(render(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('should label the landmark element with the corresponding variant label text', () => {
Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import Breadcrumbs, {Breadcrumb} from '..'
import {render, behavesAsComponent, checkExports} from '../../utils/testing'
import {render as HTMLRender} from '@testing-library/react'
import axe from 'axe-core'
import {FeatureFlags} from '../../FeatureFlags'

describe('Breadcrumbs', () => {
behavesAsComponent({Component: Breadcrumbs, options: {skipAs: true}})
Expand All@@ -12,6 +13,25 @@ describe('Breadcrumbs', () => {
Breadcrumb,
})

it('should support `className` on the outermost element', () => {
const Element = () => <Breadcrumbs className={'test-class-name'} />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(HTMLRender(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(HTMLRender(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('should have no axe violations', async () => {
const {container} = HTMLRender(<Breadcrumbs />)
const results = await axe.run(container)
Expand Down
19 changes: 19 additions & 0 deletions packages/react/src/Button/__tests__/Button.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,25 @@ describe('Button', () => {
options: {skipSx: true, skipAs: true},
})

it('should support `className` on the outermost element', () => {
const Element = () => <Button className={'test-class-name'} />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(render(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(render(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('renders a <button>', () => {
const container = render(<Button id="test-button">Default</Button>)
const button = container.getByRole('button')
Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/Checkbox/Checkbox.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event'
import React from 'react'
import Checkbox from '../Checkbox'
import {behavesAsComponent, checkExports} from '../utils/testing'
import {FeatureFlags} from '../FeatureFlags'

describe('Checkbox', () => {
beforeEach(() => {
Expand All@@ -14,6 +15,25 @@ describe('Checkbox', () => {
default: Checkbox,
})

it('should support `className` on the outermost element', () => {
const Element = () => <Checkbox className={'test-class-name'} />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(render(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(render(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('renders a valid checkbox input', () => {
const {getByRole} = render(<Checkbox />)

Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/CounterLabel/CounterLabel.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import {CounterLabel} from '..'
import {behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender} from '@testing-library/react'
import axe from 'axe-core'
import {FeatureFlags} from '../FeatureFlags'

describe('CounterLabel', () => {
behavesAsComponent({Component: CounterLabel, options: {skipAs: true, skipSx: true}})
Expand All@@ -11,6 +12,25 @@ describe('CounterLabel', () => {
default: CounterLabel,
})

it('should support `className` on the outermost element', () => {
const Element = () => <CounterLabel className={'test-class-name'} />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(HTMLRender(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(HTMLRender(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('renders a <span>', () => {
const {container} = HTMLRender(<CounterLabel>1234</CounterLabel>)
expect(container.firstChild?.nodeName).toEqual('SPAN')
Expand Down
25 changes: 20 additions & 5 deletions packages/react/src/Heading/__tests__/Heading.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {render, behavesAsComponent, checkExports} from '../../utils/testing'
import {render as HTMLRender, screen} from '@testing-library/react'
import axe from 'axe-core'
import ThemeProvider from '../../ThemeProvider'
import {FeatureFlags} from '../../FeatureFlags'

const theme = {
breakpoints: ['400px', '640px', '960px', '1280px'],
Expand DownExpand Up@@ -35,6 +36,25 @@ describe('Heading', () => {
default: Heading,
})

it('should support `className` on the outermost element', () => {
const Element = () => <Heading className={'test-class-name'} />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(HTMLRender(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(HTMLRender(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('renders <h2> by default', () => {
expect(render(<Heading />).type).toEqual('h2')
})
Expand DownExpand Up@@ -149,11 +169,6 @@ describe('Heading', () => {
expect(screen.getByText('test')).not.toHaveClass(/^Heading__StyledHeading/)
})

it('should support `className` on the outermost element', () => {
const {container} = HTMLRender(<Heading className="test">test</Heading>)
expect(container.firstChild).toHaveClass('test')
})

it('should support overrides with sx if provided', () => {
HTMLRender(
<Heading
Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/Link/__tests__/Link.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import Link from '..'
import {render, behavesAsComponent, checkExports} from '../../utils/testing'
import {render as HTMLRender} from '@testing-library/react'
import axe from 'axe-core'
import {FeatureFlags} from '../../FeatureFlags'

describe('Link', () => {
behavesAsComponent({Component: Link})
Expand All@@ -11,6 +12,25 @@ describe('Link', () => {
default: Link,
})

it('should support `className` on the outermost element', () => {
const Element = () => <Link href="#" className={'test-class-name'} />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(HTMLRender(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(HTMLRender(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('should have no axe violations', async () => {
const {container} = HTMLRender(<Link href="www.github.com">GitHub</Link>)
const results = await axe.run(container)
Expand Down
10 changes: 9 additions & 1 deletion packages/react/src/Spinner/Spinner.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,10 +19,17 @@ export type SpinnerProps = {
srText?: string | null
/** @deprecated Use `srText` instead. */
'aria-label'?: string
className?: string
} & HTMLDataAttributes &
SxProp

function Spinner({size: sizeKey = 'medium', srText = 'Loading', 'aria-label': ariaLabel, ...props}: SpinnerProps) {
function Spinner({
size: sizeKey = 'medium',
srText = 'Loading',
'aria-label': ariaLabel,
className,
...props
}: SpinnerProps) {
const size = sizeMap[sizeKey]
const hasHiddenLabel = srText !== null && ariaLabel === undefined
const labelId = useId()
Expand All@@ -38,6 +45,7 @@ function Spinner({size: sizeKey = 'medium', srText = 'Loading', 'aria-label': ar
aria-hidden
aria-label={ariaLabel ?? undefined}
aria-labelledby={hasHiddenLabel ? labelId : undefined}
className={className}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the only component I added className support to, the rest already had it

{...props}
>
<circle
Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/__tests__/Avatar.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import theme from '../theme'
import {px, render, behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender} from '@testing-library/react'
import axe from 'axe-core'
import {FeatureFlags} from '../FeatureFlags'

describe('Avatar', () => {
behavesAsComponent({Component: Avatar})
Expand All@@ -12,6 +13,25 @@ describe('Avatar', () => {
default: Avatar,
})

it('should support `className` on the outermost element', () => {
const Element = () => <Avatar src="primer.png" className={'test-class-name'} />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(HTMLRender(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(HTMLRender(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('should have no axe violations', async () => {
const {container} = HTMLRender(<Avatar src="primer.png" />)
const results = await axe.run(container)
Expand Down
27 changes: 27 additions & 0 deletions packages/react/src/__tests__/AvatarStack.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import {AvatarStack} from '..'
import {render, behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender} from '@testing-library/react'
import axe from 'axe-core'
import {FeatureFlags} from '../FeatureFlags'

const avatarComp = (
<AvatarStack>
Expand DownExpand Up@@ -33,6 +34,32 @@ describe('Avatar', () => {
default: AvatarStack,
})

it('should support `className` on the outermost element', () => {
const Element = () => (
<AvatarStack className={'test-class-name'}>
<img src="https://avatars.githubusercontent.com/primer" alt="" />
<img src="https://avatars.githubusercontent.com/github" alt="" />
<img src="https://avatars.githubusercontent.com/primer" alt="" />
<img src="https://avatars.githubusercontent.com/github" alt="" />
</AvatarStack>
)
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(HTMLRender(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(HTMLRender(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('should have no axe violations', async () => {
const {container} = HTMLRender(avatarComp)
const results = await axe.run(container)
Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/__tests__/Box.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import React from 'react'
import {Box} from '..'
import theme from '../theme'
import {behavesAsComponent, checkExports, render} from '../utils/testing'
import {FeatureFlags} from '../FeatureFlags'

describe('Box', () => {
behavesAsComponent({Component: Box})
Expand All@@ -12,6 +13,25 @@ describe('Box', () => {
default: Box,
})

it('should support `className` on the outermost element', () => {
const Element = () => <Box className={'test-class-name'} />
const FeatureFlagElement = () => {
return (
<FeatureFlags
flags={{
primer_react_css_modules_team: true,
primer_react_css_modules_staff: true,
primer_react_css_modules_ga: true,
}}
>
<Element />
</FeatureFlags>
)
}
expect(HTMLRender(<Element />).container.firstChild).toHaveClass('test-class-name')
expect(HTMLRender(<FeatureFlagElement />).container.firstChild).toHaveClass('test-class-name')
})

it('should have no axe violations', async () => {
const {container} = HTMLRender(<Box />)
const results = await axe.run(container)
Expand Down
Loading