Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slimy-parrots-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': major
---

Update `Breadcrumbs` to no longer support sx
105 changes: 53 additions & 52 deletions packages/react/src/Breadcrumbs/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import {clsx} from 'clsx'
import type {To} from 'history'
import React, {useState, useRef, useCallback, useEffect, useMemo} from 'react'
import type {SxProp} from '../sx'
import type {ComponentProps} from '../utils/types'
import React, {useState, useRef, useCallback, useEffect, useMemo, type ForwardedRef} from 'react'
import classes from './Breadcrumbs.module.css'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {BoxWithFallback} from '../internal/components/BoxWithFallback'
import Details from '../Details'
import {ActionList} from '../ActionList'
import {IconButton} from '../Button/IconButton'
Expand All @@ -16,27 +12,29 @@ import {useOnEscapePress} from '../hooks/useOnEscapePress'
import {useOnOutsideClick} from '../hooks/useOnOutsideClick'
import {useFeatureFlag} from '../FeatureFlags'

export type BreadcrumbsProps = React.PropsWithChildren<
{
/**
* Optional class name for the breadcrumbs container.
*/
className?: string
/**
* Controls the overflow behavior of the breadcrumbs.
* By default all overflowing crumbs will "wrap" in the given space taking up extra height.
* In the "menu" option we'll see the overflowing crumbs as part of a menu like dropdown instead of the root breadcrumb.
* In "menu-with-root" we see that instead of the root, the menu button will take the place of the next breadcrumb.
*/
overflow?: 'wrap' | 'menu' | 'menu-with-root'
/**
* Controls the visual variant of the breadcrumbs.
* By default, the breadcrumbs will have a normal appearance.
* In the "spacious" option, the breadcrumbs will have increased padding and a more relaxed layout.
*/
variant?: 'normal' | 'spacious'
} & SxProp
>
export type BreadcrumbsProps = React.PropsWithChildren<{
/**
* Optional class name for the breadcrumbs container.
*/
className?: string
/**
* Controls the overflow behavior of the breadcrumbs.
* By default all overflowing crumbs will "wrap" in the given space taking up extra height.
* In the "menu" option we'll see the overflowing crumbs as part of a menu like dropdown instead of the root breadcrumb.
* In "menu-with-root" we see that instead of the root, the menu button will take the place of the next breadcrumb.
*/
overflow?: 'wrap' | 'menu' | 'menu-with-root'
/**
* Controls the visual variant of the breadcrumbs.
* By default, the breadcrumbs will have a normal appearance.
* In the "spacious" option, the breadcrumbs will have increased padding and a more relaxed layout.
*/
variant?: 'normal' | 'spacious'
/**
* Allows passing of CSS custom properties to the breadcrumbs container.
*/
style?: React.CSSProperties
}>

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

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

// if (typeof window !== 'undefined') {
// effectiveOverflow = overflow
// }

useEffect(() => {
const listElement = containerRef.current?.querySelector('ol')
if (
Expand Down Expand Up @@ -333,27 +327,25 @@ function Breadcrumbs({className, children, sx: sxProp, overflow = 'wrap', varian
}, [overflowMenuEnabled, overflow, menuItems, effectiveHideRoot, measureMenuButton, visibleItems, rootItem, children])

return overflowMenuEnabled ? (
<BoxWithFallback
as="nav"
<nav
className={clsx(className, classes.BreadcrumbsBase)}
aria-label="Breadcrumbs"
sx={sxProp}
style={style}
ref={containerRef}
data-overflow={overflow}
data-variant={variant}
>
<BreadcrumbsList>{finalChildren}</BreadcrumbsList>
</BoxWithFallback>
</nav>
) : (
<BoxWithFallback
as="nav"
<nav
className={clsx(className, classes.BreadcrumbsBase)}
aria-label="Breadcrumbs"
sx={sxProp}
style={style}
data-variant={variant}
>
<BreadcrumbsList>{wrappedChildren}</BreadcrumbsList>
</BoxWithFallback>
</nav>
)
}

Expand All @@ -367,31 +359,40 @@ const ItemSeparator = () => {
)
}

type StyledBreadcrumbsItemProps = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type DistributiveOmit<T, TOmitted extends PropertyKey> = T extends any ? Omit<T, TOmitted> : never

type StyledBreadcrumbsItemProps<As extends React.ElementType> = {
as?: As
to?: To
selected?: boolean
className?: string
} & SxProp &
React.HTMLAttributes<HTMLAnchorElement> &
React.ComponentPropsWithRef<'a'>

const BreadcrumbsItem = React.forwardRef(({selected, className, ...rest}, ref) => {
style?: React.CSSProperties
} & DistributiveOmit<React.ComponentPropsWithRef<React.ElementType extends As ? 'a' : As>, 'as'>

function BreadcrumbsItemComponent<As extends React.ElementType>(
props: StyledBreadcrumbsItemProps<As>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ref: ForwardedRef<any>,
) {
const {as: Component = 'a', selected, className, ...rest} = props
return (
<BoxWithFallback
as="a"
<Component
className={clsx(className, classes.Item, selected && 'selected')}
aria-current={selected ? 'page' : undefined}
ref={ref}
{...rest}
/>
)
}) as PolymorphicForwardRefComponent<'a', StyledBreadcrumbsItemProps>
}
Copy link
Contributor

Choose a reason for hiding this comment

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

👍


Breadcrumbs.displayName = 'Breadcrumbs'
BreadcrumbsItemComponent.displayName = 'Breadcrumbs.Item'

BreadcrumbsItem.displayName = 'Breadcrumbs.Item'
const BreadcrumbsItem = React.forwardRef(BreadcrumbsItemComponent)

Breadcrumbs.displayName = 'Breadcrumbs'

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

/**
Expand All @@ -407,4 +408,4 @@ export type BreadcrumbProps = BreadcrumbsProps
/**
* @deprecated Use the `BreadcrumbsItemProps` type instead
*/
export type BreadcrumbItemProps = ComponentProps<typeof BreadcrumbsItem>
export type BreadcrumbItemProps<As extends React.ElementType = 'a'> = BreadcrumbsItemProps<As>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ exports[`@primer/styled-react exports 1`] = `
"Autocomplete",
"Avatar",
"Box",
"Breadcrumb",
"Breadcrumbs",
"Button",
"Checkbox",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,9 @@ describe('@primer/react', () => {
})

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

test('Button supports `sx` prop', () => {
Expand Down
42 changes: 42 additions & 0 deletions packages/styled-react/src/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {Breadcrumbs as PrimerBreadcrumbs} from '@primer/react'
import type {
BreadcrumbsProps as PrimerBreadcrumbsProps,
BreadcrumbsItemProps as PrimerBreadcrumbsItemsProps,
} from '@primer/react'
import {sx, type SxProp} from '../sx'
import styled from 'styled-components'
import {type ForwardRefComponent} from '../polymorphic'
import type React from 'react'

type BreadcrumbsProps = PrimerBreadcrumbsProps & SxProp
type BreadcrumbsItemProps<As extends React.ElementType = 'a'> = PrimerBreadcrumbsItemsProps<As> & SxProp

const BreadcrumbsImpl = styled(PrimerBreadcrumbs).withConfig({
shouldForwardProp: prop => (prop as keyof BreadcrumbsProps) !== 'sx',
})<BreadcrumbsProps>`
${sx}
`

const StyledBreadcrumbsItem: ForwardRefComponent<'a', BreadcrumbsItemProps> = styled(PrimerBreadcrumbs.Item).withConfig(
{
shouldForwardProp: prop => (prop as keyof BreadcrumbsItemProps) !== 'sx',
},
)<BreadcrumbsItemProps>`
${sx}
`

const BreadcrumbsItem = ({as, ...props}: BreadcrumbsItemProps) => (
<StyledBreadcrumbsItem {...props} {...(as ? {forwardedAs: as} : {})} />
)

const Breadcrumbs: ForwardRefComponent<'nav', BreadcrumbsProps> & {Item: typeof BreadcrumbsItem} = Object.assign(
BreadcrumbsImpl,
{Item: BreadcrumbsItem},
)

/**
* @deprecated Use the `Breadcrumbs` component instead (i.e. `<Breadcrumb>` → `<Breadcrumbs>`)
*/
const Breadcrumb = Breadcrumbs

export {Breadcrumbs, Breadcrumb, type BreadcrumbsProps, type BreadcrumbsItemProps}
2 changes: 1 addition & 1 deletion packages/styled-react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export {ActionList} from '@primer/react'
export {Avatar} from '@primer/react'
export {Breadcrumbs} from '@primer/react'
export {Box, type BoxProps} from './components/Box'
export {Button} from '@primer/react'
export {CheckboxGroup} from '@primer/react'
Expand All @@ -26,6 +25,7 @@ export {useTheme} from '@primer/react'

export {ActionMenu} from './components/ActionMenu'
export {Autocomplete, type AutocompleteOverlayProps} from './components/Autocomplete'
export {Breadcrumbs, Breadcrumb, type BreadcrumbsProps, type BreadcrumbsItemProps} from './components/Breadcrumbs'
export {Checkbox, type CheckboxProps} from './components/Checkbox'
export {CircleBadge} from './components/CircleBadge'
export {CounterLabel, type CounterLabelProps} from './components/CounterLabel'
Expand Down
Loading