Skip to content

Commit 3a102a7

Browse files
authored
Adds util function to account for SX dependent components (#5821)
1 parent 3c502ac commit 3a102a7

File tree

3 files changed

+78
-0
lines changed

3 files changed

+78
-0
lines changed

.changeset/spotty-streets-flash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': minor
3+
---
4+
5+
Adds util function to swap out SX enabled components.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react'
2+
import {render} from '@testing-library/react'
3+
import {toggleSxComponent} from '../toggleSxComponent'
4+
5+
const customSx = {color: 'red', p: 2}
6+
7+
describe('toggleSxComponent', () => {
8+
test('renders the plain component when no sx', () => {
9+
const TestComponent = toggleSxComponent({}, 'span')
10+
const {container} = render(<TestComponent />)
11+
expect(container.firstChild).toBeInstanceOf(HTMLSpanElement)
12+
})
13+
14+
test('renders Box with `as` if `sx` is provided', () => {
15+
const TestComponent = toggleSxComponent(customSx, 'div')
16+
const {container} = render(<TestComponent as="button" sx={{color: 'red'}} />)
17+
18+
expect(container.firstChild).toBeInstanceOf(HTMLButtonElement)
19+
expect(container.firstChild).toHaveStyle('color: red')
20+
})
21+
22+
test('swaps out component if `sx` is not the default', () => {
23+
const Label = toggleSxComponent(customSx, 'label') as React.ComponentType<{htmlFor: string}>
24+
const {container} = render(<Label htmlFor="bloop" />)
25+
26+
expect(container.firstChild).toBeInstanceOf(HTMLLabelElement)
27+
expect(container.firstChild).toHaveAttribute('for', 'bloop')
28+
})
29+
30+
test('passes down other props', () => {
31+
const TestComponent = toggleSxComponent(customSx, 'div')
32+
const {container} = render(<TestComponent as="button" sx={{color: 'red'}} data-foo="bar" />)
33+
34+
expect(container.firstChild).toBeInstanceOf(HTMLButtonElement)
35+
expect(container.firstChild).toHaveStyle('color: red')
36+
expect(container.firstChild).toHaveAttribute('data-foo', 'bar')
37+
})
38+
})
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react'
2+
import Box from '../../Box'
3+
import {defaultSxProp} from '../../utils/defaultSxProp'
4+
import type {BetterSystemStyleObject} from '../../sx'
5+
6+
type CSSModulesProps = {
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8+
as?: string | React.ComponentType<any>
9+
sx?: React.CSSProperties
10+
}
11+
12+
/**
13+
* Utility to toggle rendering a Box component that receives sx props
14+
* or a "plain" component based on the provided `as` prop. Other props will be passed through to an element or component created with the `as` prop.
15+
*
16+
* @param sx - the sx prop to check against the default sx prop
17+
* @param defaultAs - the default component to use when `as` is not provided
18+
*/
19+
export function toggleSxComponent<T, P extends CSSModulesProps>(
20+
sx: BetterSystemStyleObject,
21+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22+
defaultAs: string | React.ComponentType<any>,
23+
) {
24+
const Wrapper = React.forwardRef<T, P>(function Wrapper(
25+
{as: BaseComponent = defaultAs, sx: sxProp = sx, ...rest},
26+
ref,
27+
) {
28+
if (sxProp !== defaultSxProp) {
29+
return <Box as={BaseComponent} {...rest} sx={sxProp} ref={ref} />
30+
}
31+
return <BaseComponent {...rest} ref={ref} />
32+
})
33+
34+
return Wrapper
35+
}

0 commit comments

Comments
 (0)