Skip to content

Commit

Permalink
feat(Breadcrumbs): Convert Breadcrumbs to css module behind feature f…
Browse files Browse the repository at this point in the history
…lag (#5150)

* Convert Breadcrumbs to css module behind feature flag

* Create fair-wolves-attack.md

* Update @primer/react version to minor

* Change `ComponentPropsWithoutRef` to `ComponentPropsWithRef`

* Lint fix

* Use old styled.a for item

* Update snapshot and type

* Refactor BreadcrumbsItem component with generics

* Remove ref

* Unused import

* Refactor BreadcrumbsItem to support polymorphic components and add tests for "as" prop

* Remove test for 'as' prop in BreadcrumbsItem

* Remove snapshot test for 'as' prop in BreadcrumbsItem
  • Loading branch information
jonrohan authored Nov 15, 2024
1 parent d1b7bce commit e89edbc
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 53 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-wolves-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

Convert Breadcrumbs to css module behind feature flag
60 changes: 60 additions & 0 deletions packages/react/src/Breadcrumbs/Breadcrumbs.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.BreadcrumbsBase {
display: flex;
justify-content: space-between;
}

.BreadcrumbsList {
padding-left: 0;
margin-top: 0;
margin-bottom: 0;
}

.ItemWrapper {
display: inline-block;
font-size: var(--text-body-size-medium);
white-space: nowrap;
list-style: none;

&::after {
display: inline-block;
height: 0.8em;
/* stylelint-disable-next-line primer/spacing */
margin: 0 0.5em;
font-size: var(--text-body-size-medium);
content: '';
/* stylelint-disable-next-line primer/borders, primer/colors */
border-right: 0.1em solid var(--fgColor-muted);
transform: rotate(15deg) translateY(0.0625em);
}

&:first-child {
margin-left: 0;
}

&:last-child {
&::after {
content: none;
}
}
}

.Item {
display: inline-block;
font-size: var(--text-body-size-medium);
color: var(--fgColor-link);
text-decoration: none;

&:hover,
&:focus {
text-decoration: underline;
}
}

.ItemSelected {
color: var(--fgColor-default);
pointer-events: none;

&:focus {
text-decoration: none;
}
}
154 changes: 102 additions & 52 deletions packages/react/src/Breadcrumbs/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,82 +7,132 @@ import {get} from '../constants'
import type {SxProp} from '../sx'
import sx from '../sx'
import type {ComponentProps} from '../utils/types'
import classes from './Breadcrumbs.module.css'
import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
import {useFeatureFlag} from '../FeatureFlags'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'

const SELECTED_CLASS = 'selected'
const CSS_MODULES_FLAG = 'primer_react_css_modules_team'

const Wrapper = styled.li`
display: inline-block;
white-space: nowrap;
list-style: none;
&::after {
font-size: ${get('fontSizes.1')};
content: '';
const Wrapper = toggleStyledComponent(
CSS_MODULES_FLAG,
'li',
styled.li`
display: inline-block;
height: 0.8em;
margin: 0 0.5em;
border-right: 0.1em solid;
border-color: ${get('colors.fg.muted')};
transform: rotate(15deg) translateY(0.0625em);
}
&:first-child {
margin-left: 0;
}
&:last-child {
white-space: nowrap;
list-style: none;
&::after {
content: none;
font-size: ${get('fontSizes.1')};
content: '';
display: inline-block;
height: 0.8em;
margin: 0 0.5em;
border-right: 0.1em solid;
border-color: ${get('colors.fg.muted')};
transform: rotate(15deg) translateY(0.0625em);
}
}
`
&:first-child {
margin-left: 0;
}
&:last-child {
&::after {
content: none;
}
}
`,
)

const BreadcrumbsBase = styled.nav<SxProp>`
display: flex;
justify-content: space-between;
${sx};
`
const BreadcrumbsBase = toggleStyledComponent(
CSS_MODULES_FLAG,
'nav',
styled.nav<SxProp>`
display: flex;
justify-content: space-between;
${sx};
`,
)

export type BreadcrumbsProps = React.PropsWithChildren<
{
className?: string
} & SxProp
>

const BreadcrumbsList = ({children}: React.PropsWithChildren) => {
const enabled = useFeatureFlag(CSS_MODULES_FLAG)
if (enabled) {
return <ol className={classes.BreadcrumbsList}>{children}</ol>
}

return (
<Box as="ol" my={0} pl={0}>
{children}
</Box>
)
}

function Breadcrumbs({className, children, sx: sxProp}: React.PropsWithChildren<BreadcrumbsProps>) {
const wrappedChildren = React.Children.map(children, child => <Wrapper>{child}</Wrapper>)
const enabled = useFeatureFlag(CSS_MODULES_FLAG)
const wrappedChildren = React.Children.map(children, child => (
<Wrapper className={clsx({[classes.ItemWrapper]: enabled})}>{child}</Wrapper>
))
return (
<BreadcrumbsBase className={className} aria-label="Breadcrumbs" sx={sxProp}>
<Box as="ol" my={0} pl={0}>
{wrappedChildren}
</Box>
<BreadcrumbsBase
className={clsx(className, {[classes.BreadcrumbsBase]: enabled})}
aria-label="Breadcrumbs"
sx={sxProp}
>
<BreadcrumbsList>{wrappedChildren}</BreadcrumbsList>
</BreadcrumbsBase>
)
}

type StyledBreadcrumbsItemProps = {
to?: To
selected?: boolean
} & SxProp

const BreadcrumbsItem = styled.a.attrs<StyledBreadcrumbsItemProps>(props => ({
className: clsx(props.selected && SELECTED_CLASS, props.className),
'aria-current': props.selected ? 'page' : null,
}))<StyledBreadcrumbsItemProps>`
color: ${get('colors.accent.fg')};
display: inline-block;
font-size: ${get('fontSizes.1')};
text-decoration: none;
&:hover,
&:focus {
text-decoration: underline;
}
&.selected {
color: ${get('colors.fg.default')};
pointer-events: none;
}
&.selected:focus {
className?: string
} & SxProp &
React.ComponentPropsWithoutRef<'a'>

const StyledBreadcrumbsItem = toggleStyledComponent(
CSS_MODULES_FLAG,
'a',
styled.a`
color: ${get('colors.accent.fg')};
display: inline-block;
font-size: ${get('fontSizes.1')};
text-decoration: none;
}
${sx};
`
&:hover,
&:focus {
text-decoration: underline;
}
&.selected {
color: ${get('colors.fg.default')};
pointer-events: none;
}
&.selected:focus {
text-decoration: none;
}
${sx};
`,
)

const BreadcrumbsItem = React.forwardRef(({selected, className, ...rest}, ref) => {
const enabled = useFeatureFlag(CSS_MODULES_FLAG)
return (
<StyledBreadcrumbsItem
className={clsx(className, {
[SELECTED_CLASS]: selected,
[classes.Item]: enabled,
[classes.ItemSelected]: enabled && selected,
})}
aria-current={selected ? 'page' : null}
ref={ref}
{...rest}
/>
)
}) as PolymorphicForwardRefComponent<'a', StyledBreadcrumbsItemProps>

Breadcrumbs.displayName = 'Breadcrumbs'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,5 @@ exports[`Breadcrumbs.Item respects the "selected" prop 1`] = `
<a
aria-current="page"
className="c0 selected"
selected={true}
/>
`;

0 comments on commit e89edbc

Please sign in to comment.