Skip to content

Commit cbeed21

Browse files
authored
feat(Header): Convert Header to CSS modules behind team feature flag (#5192)
* feat(Header): Convert Header to CSS modules behind team feature flag * dev stories * prettier fix * update types * Fix missing 'as' prop and update tests * format * replace full class with data attr
1 parent 002be35 commit cbeed21

File tree

7 files changed

+315
-58
lines changed

7 files changed

+315
-58
lines changed

.changeset/odd-frogs-listen.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+
Update `Header` component to use CSS modules behind the feature flag primer_react_css_modules_team
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.HeaderDev {
2+
background-color: var(--label-olive-bgColor-active);
3+
}
4+
5+
.HeaderDevItem {
6+
padding-left: var(--base-size-24);
7+
}
8+
9+
.HeaderDevLink {
10+
color: var(--color-prettylights-syntax-markup-inserted-text);
11+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React from 'react'
2+
import type {Meta} from '@storybook/react'
3+
import {MarkGithubIcon} from '@primer/octicons-react'
4+
5+
import Header from './Header'
6+
import Avatar from '../Avatar'
7+
import Octicon from '../Octicon'
8+
9+
import classes from './Header.dev.module.css'
10+
import {FeatureFlags} from '../FeatureFlags'
11+
12+
export default {
13+
title: 'Components/Header/Dev',
14+
component: Header,
15+
} as Meta<typeof Header>
16+
17+
export const WithCss = () => (
18+
<FeatureFlags
19+
flags={{
20+
primer_react_css_modules_team: true,
21+
primer_react_css_modules_staff: true,
22+
primer_react_css_modules_ga: true,
23+
}}
24+
>
25+
<Header as="summary" className={classes.HeaderDev}>
26+
<Header.Item id="github">
27+
<Header.Link href="#" className={classes.HeaderDevLink}>
28+
<Octicon icon={MarkGithubIcon} size={32} sx={{mr: 2}} />
29+
<span>GitHub</span>
30+
</Header.Link>
31+
</Header.Item>
32+
<Header.Item full>Menu</Header.Item>
33+
<Header.Item className={classes.HeaderDevItem}>
34+
<Avatar src="https://github.com/octocat.png" size={20} square alt="@octocat" />
35+
</Header.Item>
36+
</Header>
37+
</FeatureFlags>
38+
)
39+
40+
export const WithSx = () => (
41+
<Header as="summary" sx={{backgroundColor: 'blue'}}>
42+
<Header.Item id="github">
43+
<Header.Link href="#" sx={{fontSize: 3}}>
44+
<Octicon icon={MarkGithubIcon} size={32} sx={{mr: 2}} />
45+
<span>GitHub</span>
46+
</Header.Link>
47+
</Header.Item>
48+
<Header.Item full>Menu</Header.Item>
49+
<Header.Item sx={{mr: 2}}>
50+
<Avatar src="https://github.com/octocat.png" size={20} square alt="@octocat" />
51+
</Header.Item>
52+
</Header>
53+
)
54+
55+
export const WithSxAndCSS = () => (
56+
<FeatureFlags
57+
flags={{
58+
primer_react_css_modules_team: true,
59+
primer_react_css_modules_staff: true,
60+
primer_react_css_modules_ga: true,
61+
}}
62+
>
63+
<Header as="summary" className={classes.HeaderDev} sx={{backgroundColor: 'orange'}}>
64+
<Header.Item id="github">
65+
<Header.Link href="#" className={classes.HeaderDevLink} sx={{p: 0, color: 'black'}}>
66+
<Octicon icon={MarkGithubIcon} size={32} sx={{mr: 2}} />
67+
<span>GitHub</span>
68+
</Header.Link>
69+
</Header.Item>
70+
<Header.Item full>Menu</Header.Item>
71+
<Header.Item className={classes.HeaderDevItem} sx={{m: 0}}>
72+
<Avatar src="https://github.com/octocat.png" size={20} square alt="@octocat" />
73+
</Header.Item>
74+
</Header>
75+
</FeatureFlags>
76+
)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.Header {
2+
z-index: 32;
3+
display: flex;
4+
padding: var(--base-size-16);
5+
overflow: auto;
6+
font-size: var(--text-body-size-medium);
7+
line-height: var(--text-title-lineHeight-large);
8+
color: var(--header-fgColor-default);
9+
background-color: var(--header-bgColor);
10+
align-items: center;
11+
flex-wrap: nowrap;
12+
}
13+
14+
.HeaderItem {
15+
display: flex;
16+
margin-right: var(--base-size-16);
17+
align-self: stretch;
18+
align-items: center;
19+
flex-wrap: nowrap;
20+
21+
&:where([data-full]) {
22+
flex: auto;
23+
}
24+
}
25+
26+
.HeaderLink {
27+
display: flex;
28+
font-weight: var(--text-title-weight-large);
29+
color: var(--header-fgColor-logo);
30+
text-decoration: none;
31+
white-space: nowrap;
32+
cursor: pointer;
33+
align-items: center;
34+
35+
&:hover,
36+
&:focus {
37+
color: var(--header-fgColor-default);
38+
}
39+
}

packages/react/src/Header/Header.tsx

Lines changed: 121 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,68 +4,132 @@ import {get} from '../constants'
44
import type {SxProp} from '../sx'
55
import sx from '../sx'
66
import type {ComponentProps} from '../utils/types'
7+
import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
8+
import {useFeatureFlag} from '../FeatureFlags'
9+
import React from 'react'
10+
import {clsx} from 'clsx'
11+
import classes from './Header.module.css'
12+
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
713

8-
type StyledHeaderItemProps = {full?: boolean} & SxProp
9-
type StyledHeaderProps = SxProp
10-
type StyledHeaderLinkProps = {to?: Location | Pathname} & SxProp
11-
12-
const Header = styled.header<StyledHeaderProps>`
13-
z-index: 32;
14-
display: flex;
15-
padding: ${get('space.3')};
16-
font-size: ${get('fontSizes.1')};
17-
line-height: ${get('lineHeights.default')};
18-
color: ${get('colors.header.text')};
19-
background-color: ${get('colors.header.bg')};
20-
align-items: center;
21-
flex-wrap: nowrap;
22-
overflow: auto;
23-
24-
${sx};
25-
`
26-
const HeaderItem = styled.div<StyledHeaderItemProps>`
27-
display: flex;
28-
margin-right: ${get('space.3')};
29-
align-self: stretch;
30-
align-items: center;
31-
flex-wrap: nowrap;
32-
33-
${({full}) =>
34-
full &&
35-
css`
36-
flex: auto;
37-
`};
38-
39-
${sx};
40-
`
14+
type StyledHeaderProps = React.ComponentProps<'header'> & SxProp
15+
type StyledHeaderItemProps = React.ComponentProps<'div'> & SxProp & {full?: boolean}
16+
type StyledHeaderLinkProps = React.ComponentProps<'a'> & SxProp & {to?: Location | Pathname}
4117

42-
HeaderItem.displayName = 'Header.Item'
18+
const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team'
4319

44-
const HeaderLink = styled.a.attrs<StyledHeaderLinkProps>(({to}) => {
45-
const isReactRouter = typeof to === 'string'
46-
if (isReactRouter) {
47-
// according to their docs, NavLink supports aria-current:
48-
// https://reacttraining.com/react-router/web/api/NavLink/aria-current-string
49-
return {'aria-current': 'page'}
50-
} else {
51-
return {}
52-
}
53-
})<StyledHeaderLinkProps>`
54-
font-weight: ${get('fontWeights.bold')};
55-
color: ${get('colors.header.logo')};
56-
white-space: nowrap;
57-
cursor: pointer;
58-
text-decoration: none;
59-
display: flex;
60-
align-items: center;
61-
62-
&:hover,
63-
&:focus {
20+
const StyledHeader = toggleStyledComponent(
21+
CSS_MODULES_FEATURE_FLAG,
22+
'header',
23+
styled.header<StyledHeaderProps>`
24+
z-index: 32;
25+
display: flex;
26+
padding: ${get('space.3')};
27+
font-size: ${get('fontSizes.1')};
28+
line-height: ${get('lineHeights.default')};
6429
color: ${get('colors.header.text')};
65-
}
30+
background-color: ${get('colors.header.bg')};
31+
align-items: center;
32+
flex-wrap: nowrap;
33+
overflow: auto;
34+
35+
${sx};
36+
`,
37+
)
38+
39+
const Header = React.forwardRef<HTMLElement, StyledHeaderProps>(function Header(
40+
{children, className, ...rest},
41+
forwardRef,
42+
) {
43+
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
44+
return (
45+
<StyledHeader ref={forwardRef} className={clsx(className, {[classes.Header]: enabled})} {...rest}>
46+
{children}
47+
</StyledHeader>
48+
)
49+
}) as PolymorphicForwardRefComponent<'header', StyledHeaderProps>
50+
51+
Header.displayName = 'Header'
52+
53+
const StyledHeaderItem = toggleStyledComponent(
54+
CSS_MODULES_FEATURE_FLAG,
55+
'div',
56+
styled.div<StyledHeaderItemProps>`
57+
display: flex;
58+
margin-right: ${get('space.3')};
59+
align-self: stretch;
60+
align-items: center;
61+
flex-wrap: nowrap;
62+
63+
${({full}) =>
64+
full &&
65+
css`
66+
flex: auto;
67+
`};
68+
69+
${sx};
70+
`,
71+
)
72+
73+
const HeaderItem = React.forwardRef<HTMLElement, StyledHeaderItemProps>(function HeaderItem(
74+
{children, className, ...rest},
75+
forwardRef,
76+
) {
77+
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
78+
return (
79+
<StyledHeaderItem
80+
ref={forwardRef}
81+
className={clsx(className, enabled && classes.HeaderItem)}
82+
data-full={rest.full}
83+
{...rest}
84+
>
85+
{children}
86+
</StyledHeaderItem>
87+
)
88+
})
89+
90+
HeaderItem.displayName = 'Header.Item'
91+
92+
const StyledHeaderLink = toggleStyledComponent(
93+
CSS_MODULES_FEATURE_FLAG,
94+
'a',
95+
styled.a.attrs<StyledHeaderLinkProps>(({to}) => {
96+
const isReactRouter = typeof to === 'string'
97+
if (isReactRouter) {
98+
// according to their docs, NavLink supports aria-current:
99+
// https://reacttraining.com/react-router/web/api/NavLink/aria-current-string
100+
return {'aria-current': 'page'}
101+
} else {
102+
return {}
103+
}
104+
})<StyledHeaderLinkProps>`
105+
font-weight: ${get('fontWeights.bold')};
106+
color: ${get('colors.header.logo')};
107+
white-space: nowrap;
108+
cursor: pointer;
109+
text-decoration: none;
110+
display: flex;
111+
align-items: center;
112+
113+
&:hover,
114+
&:focus {
115+
color: ${get('colors.header.text')};
116+
}
117+
118+
${sx};
119+
`,
120+
)
66121

67-
${sx};
68-
`
122+
const HeaderLink = React.forwardRef<HTMLElement, StyledHeaderLinkProps>(function HeaderLink(
123+
{children, className, ...rest},
124+
forwardRef,
125+
) {
126+
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
127+
return (
128+
<StyledHeaderLink ref={forwardRef} className={clsx(className, enabled && classes.HeaderLink)} {...rest}>
129+
{children}
130+
</StyledHeaderLink>
131+
)
132+
})
69133

70134
HeaderLink.displayName = 'Header.Link'
71135

0 commit comments

Comments
 (0)