Skip to content

Commit 1f9f582

Browse files
Breadcrumbs: Remove sx from component (#6844)
Co-authored-by: Marie Lucca <[email protected]>
1 parent 15824db commit 1f9f582

File tree

6 files changed

+104
-54
lines changed

6 files changed

+104
-54
lines changed

.changeset/slimy-parrots-love.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': major
3+
---
4+
5+
Update `Breadcrumbs` to no longer support sx

packages/react/src/Breadcrumbs/Breadcrumbs.tsx

Lines changed: 53 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import {clsx} from 'clsx'
22
import type {To} from 'history'
3-
import React, {useState, useRef, useCallback, useEffect, useMemo} from 'react'
4-
import type {SxProp} from '../sx'
5-
import type {ComponentProps} from '../utils/types'
3+
import React, {useState, useRef, useCallback, useEffect, useMemo, type ForwardedRef} from 'react'
64
import classes from './Breadcrumbs.module.css'
7-
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
8-
import {BoxWithFallback} from '../internal/components/BoxWithFallback'
95
import Details from '../Details'
106
import {ActionList} from '../ActionList'
117
import {IconButton} from '../Button/IconButton'
@@ -16,27 +12,29 @@ import {useOnEscapePress} from '../hooks/useOnEscapePress'
1612
import {useOnOutsideClick} from '../hooks/useOnOutsideClick'
1713
import {useFeatureFlag} from '../FeatureFlags'
1814

19-
export type BreadcrumbsProps = React.PropsWithChildren<
20-
{
21-
/**
22-
* Optional class name for the breadcrumbs container.
23-
*/
24-
className?: string
25-
/**
26-
* Controls the overflow behavior of the breadcrumbs.
27-
* By default all overflowing crumbs will "wrap" in the given space taking up extra height.
28-
* In the "menu" option we'll see the overflowing crumbs as part of a menu like dropdown instead of the root breadcrumb.
29-
* In "menu-with-root" we see that instead of the root, the menu button will take the place of the next breadcrumb.
30-
*/
31-
overflow?: 'wrap' | 'menu' | 'menu-with-root'
32-
/**
33-
* Controls the visual variant of the breadcrumbs.
34-
* By default, the breadcrumbs will have a normal appearance.
35-
* In the "spacious" option, the breadcrumbs will have increased padding and a more relaxed layout.
36-
*/
37-
variant?: 'normal' | 'spacious'
38-
} & SxProp
39-
>
15+
export type BreadcrumbsProps = React.PropsWithChildren<{
16+
/**
17+
* Optional class name for the breadcrumbs container.
18+
*/
19+
className?: string
20+
/**
21+
* Controls the overflow behavior of the breadcrumbs.
22+
* By default all overflowing crumbs will "wrap" in the given space taking up extra height.
23+
* In the "menu" option we'll see the overflowing crumbs as part of a menu like dropdown instead of the root breadcrumb.
24+
* In "menu-with-root" we see that instead of the root, the menu button will take the place of the next breadcrumb.
25+
*/
26+
overflow?: 'wrap' | 'menu' | 'menu-with-root'
27+
/**
28+
* Controls the visual variant of the breadcrumbs.
29+
* By default, the breadcrumbs will have a normal appearance.
30+
* In the "spacious" option, the breadcrumbs will have increased padding and a more relaxed layout.
31+
*/
32+
variant?: 'normal' | 'spacious'
33+
/**
34+
* Allows passing of CSS custom properties to the breadcrumbs container.
35+
*/
36+
style?: React.CSSProperties
37+
}>
4038

4139
const BreadcrumbsList = ({children}: React.PropsWithChildren) => {
4240
return <ol className={classes.BreadcrumbsList}>{children}</ol>
@@ -146,7 +144,7 @@ const getValidChildren = (children: React.ReactNode) => {
146144
return React.Children.toArray(children).filter(child => React.isValidElement(child)) as React.ReactElement[]
147145
}
148146

149-
function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', variant = 'normal'}: BreadcrumbsProps) {
147+
function Breadcrumbs({className, children, style, overflow = 'wrap', variant = 'normal'}: BreadcrumbsProps) {
150148
const overflowMenuEnabled = useFeatureFlag('primer_react_breadcrumbs_overflow_menu')
151149
const wrappedChildren = React.Children.map(children, child => <li className={classes.ItemWrapper}>{child}</li>)
152150
const containerRef = useRef<HTMLElement>(null)
@@ -176,10 +174,6 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', varian
176174
const MENU_BUTTON_FALLBACK_WIDTH = 32 // Design system small IconButton
177175
const [menuButtonWidth, setMenuButtonWidth] = useState(MENU_BUTTON_FALLBACK_WIDTH)
178176

179-
// if (typeof window !== 'undefined') {
180-
// effectiveOverflow = overflow
181-
// }
182-
183177
useEffect(() => {
184178
const listElement = containerRef.current?.querySelector('ol')
185179
if (
@@ -333,27 +327,25 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', varian
333327
}, [overflowMenuEnabled, overflow, menuItems, effectiveHideRoot, measureMenuButton, visibleItems, rootItem, children])
334328

335329
return overflowMenuEnabled ? (
336-
<BoxWithFallback
337-
as="nav"
330+
<nav
338331
className={clsx(className, classes.BreadcrumbsBase)}
339332
aria-label="Breadcrumbs"
340-
sx={sxProp}
333+
style={style}
341334
ref={containerRef}
342335
data-overflow={overflow}
343336
data-variant={variant}
344337
>
345338
<BreadcrumbsList>{finalChildren}</BreadcrumbsList>
346-
</BoxWithFallback>
339+
</nav>
347340
) : (
348-
<BoxWithFallback
349-
as="nav"
341+
<nav
350342
className={clsx(className, classes.BreadcrumbsBase)}
351343
aria-label="Breadcrumbs"
352-
sx={sxProp}
344+
style={style}
353345
data-variant={variant}
354346
>
355347
<BreadcrumbsList>{wrappedChildren}</BreadcrumbsList>
356-
</BoxWithFallback>
348+
</nav>
357349
)
358350
}
359351

@@ -367,31 +359,40 @@ const ItemSeparator = () => {
367359
)
368360
}
369361

370-
type StyledBreadcrumbsItemProps = {
362+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
363+
type DistributiveOmit<T, TOmitted extends PropertyKey> = T extends any ? Omit<T, TOmitted> : never
364+
365+
type StyledBreadcrumbsItemProps<As extends React.ElementType> = {
366+
as?: As
371367
to?: To
372368
selected?: boolean
373369
className?: string
374-
} & SxProp &
375-
React.HTMLAttributes<HTMLAnchorElement> &
376-
React.ComponentPropsWithRef<'a'>
377-
378-
const BreadcrumbsItem = React.forwardRef(({selected, className, ...rest}, ref) => {
370+
style?: React.CSSProperties
371+
} & DistributiveOmit<React.ComponentPropsWithRef<React.ElementType extends As ? 'a' : As>, 'as'>
372+
373+
function BreadcrumbsItemComponent<As extends React.ElementType>(
374+
props: StyledBreadcrumbsItemProps<As>,
375+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
376+
ref: ForwardedRef<any>,
377+
) {
378+
const {as: Component = 'a', selected, className, ...rest} = props
379379
return (
380-
<BoxWithFallback
381-
as="a"
380+
<Component
382381
className={clsx(className, classes.Item, selected && 'selected')}
383382
aria-current={selected ? 'page' : undefined}
384383
ref={ref}
385384
{...rest}
386385
/>
387386
)
388-
}) as PolymorphicForwardRefComponent<'a', StyledBreadcrumbsItemProps>
387+
}
389388

390-
Breadcrumbs.displayName = 'Breadcrumbs'
389+
BreadcrumbsItemComponent.displayName = 'Breadcrumbs.Item'
391390

392-
BreadcrumbsItem.displayName = 'Breadcrumbs.Item'
391+
const BreadcrumbsItem = React.forwardRef(BreadcrumbsItemComponent)
392+
393+
Breadcrumbs.displayName = 'Breadcrumbs'
393394

394-
export type BreadcrumbsItemProps = ComponentProps<typeof BreadcrumbsItem>
395+
export type BreadcrumbsItemProps<As extends React.ElementType = 'a'> = StyledBreadcrumbsItemProps<As>
395396
export default Object.assign(Breadcrumbs, {Item: BreadcrumbsItem})
396397

397398
/**
@@ -407,4 +408,4 @@ export type BreadcrumbProps = BreadcrumbsProps
407408
/**
408409
* @deprecated Use the `BreadcrumbsItemProps` type instead
409410
*/
410-
export type BreadcrumbItemProps = ComponentProps<typeof BreadcrumbsItem>
411+
export type BreadcrumbItemProps<As extends React.ElementType = 'a'> = BreadcrumbsItemProps<As>

packages/styled-react/src/__tests__/__snapshots__/exports.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ exports[`@primer/styled-react exports 1`] = `
77
"Autocomplete",
88
"Avatar",
99
"Box",
10+
"Breadcrumb",
1011
"Breadcrumbs",
1112
"Button",
1213
"Checkbox",

packages/styled-react/src/__tests__/primer-react.browser.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,9 @@ describe('@primer/react', () => {
119119
})
120120

121121
test('Breadcrumbs.Item supports `sx` prop', () => {
122-
render(<Breadcrumbs.Item data-testid="component" sx={{background: 'red'}} />)
122+
render(<Breadcrumbs.Item data-testid="component" sx={{background: 'red'}} href="#" />)
123123
expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)')
124+
expect(window.getComputedStyle(screen.getByRole('link')).backgroundColor).toBe('rgb(255, 0, 0)')
124125
})
125126

126127
test('Button supports `sx` prop', () => {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {Breadcrumbs as PrimerBreadcrumbs} from '@primer/react'
2+
import type {
3+
BreadcrumbsProps as PrimerBreadcrumbsProps,
4+
BreadcrumbsItemProps as PrimerBreadcrumbsItemsProps,
5+
} from '@primer/react'
6+
import {sx, type SxProp} from '../sx'
7+
import styled from 'styled-components'
8+
import {type ForwardRefComponent} from '../polymorphic'
9+
import type React from 'react'
10+
11+
type BreadcrumbsProps = PrimerBreadcrumbsProps & SxProp
12+
type BreadcrumbsItemProps<As extends React.ElementType = 'a'> = PrimerBreadcrumbsItemsProps<As> & SxProp
13+
14+
const BreadcrumbsImpl = styled(PrimerBreadcrumbs).withConfig({
15+
shouldForwardProp: prop => (prop as keyof BreadcrumbsProps) !== 'sx',
16+
})<BreadcrumbsProps>`
17+
${sx}
18+
`
19+
20+
const StyledBreadcrumbsItem: ForwardRefComponent<'a', BreadcrumbsItemProps> = styled(PrimerBreadcrumbs.Item).withConfig(
21+
{
22+
shouldForwardProp: prop => (prop as keyof BreadcrumbsItemProps) !== 'sx',
23+
},
24+
)<BreadcrumbsItemProps>`
25+
${sx}
26+
`
27+
28+
const BreadcrumbsItem = ({as, ...props}: BreadcrumbsItemProps) => (
29+
<StyledBreadcrumbsItem {...props} {...(as ? {forwardedAs: as} : {})} />
30+
)
31+
32+
const Breadcrumbs: ForwardRefComponent<'nav', BreadcrumbsProps> & {Item: typeof BreadcrumbsItem} = Object.assign(
33+
BreadcrumbsImpl,
34+
{Item: BreadcrumbsItem},
35+
)
36+
37+
/**
38+
* @deprecated Use the `Breadcrumbs` component instead (i.e. `<Breadcrumb>` → `<Breadcrumbs>`)
39+
*/
40+
const Breadcrumb = Breadcrumbs
41+
42+
export {Breadcrumbs, Breadcrumb, type BreadcrumbsProps, type BreadcrumbsItemProps}

packages/styled-react/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export {ActionList} from '@primer/react'
22
export {Avatar} from '@primer/react'
3-
export {Breadcrumbs} from '@primer/react'
43
export {Box, type BoxProps} from './components/Box'
54
export {Button} from '@primer/react'
65
export {CheckboxGroup} from '@primer/react'
@@ -26,6 +25,7 @@ export {useTheme} from '@primer/react'
2625

2726
export {ActionMenu} from './components/ActionMenu'
2827
export {Autocomplete, type AutocompleteOverlayProps} from './components/Autocomplete'
28+
export {Breadcrumbs, Breadcrumb, type BreadcrumbsProps, type BreadcrumbsItemProps} from './components/Breadcrumbs'
2929
export {Checkbox, type CheckboxProps} from './components/Checkbox'
3030
export {CircleBadge} from './components/CircleBadge'
3131
export {CounterLabel, type CounterLabelProps} from './components/CounterLabel'

0 commit comments

Comments
 (0)