Skip to content

Commit e89edbc

Browse files
authored
feat(Breadcrumbs): Convert Breadcrumbs to css module behind feature flag (#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
1 parent d1b7bce commit e89edbc

File tree

4 files changed

+167
-53
lines changed

4 files changed

+167
-53
lines changed

.changeset/fair-wolves-attack.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+
Convert Breadcrumbs to css module behind feature flag
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.BreadcrumbsBase {
2+
display: flex;
3+
justify-content: space-between;
4+
}
5+
6+
.BreadcrumbsList {
7+
padding-left: 0;
8+
margin-top: 0;
9+
margin-bottom: 0;
10+
}
11+
12+
.ItemWrapper {
13+
display: inline-block;
14+
font-size: var(--text-body-size-medium);
15+
white-space: nowrap;
16+
list-style: none;
17+
18+
&::after {
19+
display: inline-block;
20+
height: 0.8em;
21+
/* stylelint-disable-next-line primer/spacing */
22+
margin: 0 0.5em;
23+
font-size: var(--text-body-size-medium);
24+
content: '';
25+
/* stylelint-disable-next-line primer/borders, primer/colors */
26+
border-right: 0.1em solid var(--fgColor-muted);
27+
transform: rotate(15deg) translateY(0.0625em);
28+
}
29+
30+
&:first-child {
31+
margin-left: 0;
32+
}
33+
34+
&:last-child {
35+
&::after {
36+
content: none;
37+
}
38+
}
39+
}
40+
41+
.Item {
42+
display: inline-block;
43+
font-size: var(--text-body-size-medium);
44+
color: var(--fgColor-link);
45+
text-decoration: none;
46+
47+
&:hover,
48+
&:focus {
49+
text-decoration: underline;
50+
}
51+
}
52+
53+
.ItemSelected {
54+
color: var(--fgColor-default);
55+
pointer-events: none;
56+
57+
&:focus {
58+
text-decoration: none;
59+
}
60+
}

packages/react/src/Breadcrumbs/Breadcrumbs.tsx

Lines changed: 102 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,82 +7,132 @@ import {get} from '../constants'
77
import type {SxProp} from '../sx'
88
import sx from '../sx'
99
import type {ComponentProps} from '../utils/types'
10+
import classes from './Breadcrumbs.module.css'
11+
import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
12+
import {useFeatureFlag} from '../FeatureFlags'
13+
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
1014

1115
const SELECTED_CLASS = 'selected'
16+
const CSS_MODULES_FLAG = 'primer_react_css_modules_team'
1217

13-
const Wrapper = styled.li`
14-
display: inline-block;
15-
white-space: nowrap;
16-
list-style: none;
17-
&::after {
18-
font-size: ${get('fontSizes.1')};
19-
content: '';
18+
const Wrapper = toggleStyledComponent(
19+
CSS_MODULES_FLAG,
20+
'li',
21+
styled.li`
2022
display: inline-block;
21-
height: 0.8em;
22-
margin: 0 0.5em;
23-
border-right: 0.1em solid;
24-
border-color: ${get('colors.fg.muted')};
25-
transform: rotate(15deg) translateY(0.0625em);
26-
}
27-
&:first-child {
28-
margin-left: 0;
29-
}
30-
&:last-child {
23+
white-space: nowrap;
24+
list-style: none;
3125
&::after {
32-
content: none;
26+
font-size: ${get('fontSizes.1')};
27+
content: '';
28+
display: inline-block;
29+
height: 0.8em;
30+
margin: 0 0.5em;
31+
border-right: 0.1em solid;
32+
border-color: ${get('colors.fg.muted')};
33+
transform: rotate(15deg) translateY(0.0625em);
3334
}
34-
}
35-
`
35+
&:first-child {
36+
margin-left: 0;
37+
}
38+
&:last-child {
39+
&::after {
40+
content: none;
41+
}
42+
}
43+
`,
44+
)
3645

37-
const BreadcrumbsBase = styled.nav<SxProp>`
38-
display: flex;
39-
justify-content: space-between;
40-
${sx};
41-
`
46+
const BreadcrumbsBase = toggleStyledComponent(
47+
CSS_MODULES_FLAG,
48+
'nav',
49+
styled.nav<SxProp>`
50+
display: flex;
51+
justify-content: space-between;
52+
${sx};
53+
`,
54+
)
4255

4356
export type BreadcrumbsProps = React.PropsWithChildren<
4457
{
4558
className?: string
4659
} & SxProp
4760
>
4861

62+
const BreadcrumbsList = ({children}: React.PropsWithChildren) => {
63+
const enabled = useFeatureFlag(CSS_MODULES_FLAG)
64+
if (enabled) {
65+
return <ol className={classes.BreadcrumbsList}>{children}</ol>
66+
}
67+
68+
return (
69+
<Box as="ol" my={0} pl={0}>
70+
{children}
71+
</Box>
72+
)
73+
}
74+
4975
function Breadcrumbs({className, children, sx: sxProp}: React.PropsWithChildren<BreadcrumbsProps>) {
50-
const wrappedChildren = React.Children.map(children, child => <Wrapper>{child}</Wrapper>)
76+
const enabled = useFeatureFlag(CSS_MODULES_FLAG)
77+
const wrappedChildren = React.Children.map(children, child => (
78+
<Wrapper className={clsx({[classes.ItemWrapper]: enabled})}>{child}</Wrapper>
79+
))
5180
return (
52-
<BreadcrumbsBase className={className} aria-label="Breadcrumbs" sx={sxProp}>
53-
<Box as="ol" my={0} pl={0}>
54-
{wrappedChildren}
55-
</Box>
81+
<BreadcrumbsBase
82+
className={clsx(className, {[classes.BreadcrumbsBase]: enabled})}
83+
aria-label="Breadcrumbs"
84+
sx={sxProp}
85+
>
86+
<BreadcrumbsList>{wrappedChildren}</BreadcrumbsList>
5687
</BreadcrumbsBase>
5788
)
5889
}
5990

6091
type StyledBreadcrumbsItemProps = {
6192
to?: To
6293
selected?: boolean
63-
} & SxProp
64-
65-
const BreadcrumbsItem = styled.a.attrs<StyledBreadcrumbsItemProps>(props => ({
66-
className: clsx(props.selected && SELECTED_CLASS, props.className),
67-
'aria-current': props.selected ? 'page' : null,
68-
}))<StyledBreadcrumbsItemProps>`
69-
color: ${get('colors.accent.fg')};
70-
display: inline-block;
71-
font-size: ${get('fontSizes.1')};
72-
text-decoration: none;
73-
&:hover,
74-
&:focus {
75-
text-decoration: underline;
76-
}
77-
&.selected {
78-
color: ${get('colors.fg.default')};
79-
pointer-events: none;
80-
}
81-
&.selected:focus {
94+
className?: string
95+
} & SxProp &
96+
React.ComponentPropsWithoutRef<'a'>
97+
98+
const StyledBreadcrumbsItem = toggleStyledComponent(
99+
CSS_MODULES_FLAG,
100+
'a',
101+
styled.a`
102+
color: ${get('colors.accent.fg')};
103+
display: inline-block;
104+
font-size: ${get('fontSizes.1')};
82105
text-decoration: none;
83-
}
84-
${sx};
85-
`
106+
&:hover,
107+
&:focus {
108+
text-decoration: underline;
109+
}
110+
&.selected {
111+
color: ${get('colors.fg.default')};
112+
pointer-events: none;
113+
}
114+
&.selected:focus {
115+
text-decoration: none;
116+
}
117+
${sx};
118+
`,
119+
)
120+
121+
const BreadcrumbsItem = React.forwardRef(({selected, className, ...rest}, ref) => {
122+
const enabled = useFeatureFlag(CSS_MODULES_FLAG)
123+
return (
124+
<StyledBreadcrumbsItem
125+
className={clsx(className, {
126+
[SELECTED_CLASS]: selected,
127+
[classes.Item]: enabled,
128+
[classes.ItemSelected]: enabled && selected,
129+
})}
130+
aria-current={selected ? 'page' : null}
131+
ref={ref}
132+
{...rest}
133+
/>
134+
)
135+
}) as PolymorphicForwardRefComponent<'a', StyledBreadcrumbsItemProps>
86136

87137
Breadcrumbs.displayName = 'Breadcrumbs'
88138

packages/react/src/Breadcrumbs/__tests__/__snapshots__/BreadcrumbsItem.test.tsx.snap

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,5 @@ exports[`Breadcrumbs.Item respects the "selected" prop 1`] = `
2828
<a
2929
aria-current="page"
3030
className="c0 selected"
31-
selected={true}
3231
/>
3332
`;

0 commit comments

Comments
 (0)