Skip to content

Commit 642d28c

Browse files
committed
Convert SideNavLink to CSS modules behind feature flag
1 parent 6d3f928 commit 642d28c

File tree

2 files changed

+212
-92
lines changed

2 files changed

+212
-92
lines changed

packages/react/src/SideNav.module.css

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,100 @@
1515
border-left: 0;
1616
}
1717
}
18+
19+
.SideNavLink {
20+
position: relative;
21+
display: block;
22+
width: 100%;
23+
/* stylelint-disable-next-line primer/typography */
24+
font-size: 14px;
25+
text-align: left;
26+
text-decoration: none;
27+
28+
& > .SideNav {
29+
border-bottom: none;
30+
}
31+
32+
.SideNav.SideNavVariant-normal > & {
33+
padding: var(--base-size-16);
34+
color: var(--fgColor-default);
35+
border: 0;
36+
border-top: var(--borderWidth-thin) solid var(--borderColor-muted);
37+
38+
&:first-child {
39+
border-top: 0;
40+
border-top-left-radius: var(--borderRadius-medium);
41+
border-top-right-radius: var(--borderRadius-medium);
42+
}
43+
44+
&:last-child {
45+
border-bottom-right-radius: var(--borderRadius-medium);
46+
border-bottom-left-radius: var(--borderRadius-medium);
47+
}
48+
49+
/* Bar on the left */
50+
&::before {
51+
position: absolute;
52+
top: 0;
53+
bottom: 0;
54+
left: 0;
55+
z-index: 1;
56+
width: 3px;
57+
pointer-events: none;
58+
content: '';
59+
}
60+
61+
&:hover {
62+
text-decoration: none;
63+
background-color: var(--bgColor-neutral-muted);
64+
}
65+
66+
&:focus {
67+
z-index: 1;
68+
text-decoration: none;
69+
background-color: var(--bgColor-neutral-muted);
70+
outline: solid 2px var(--fgColor-accent);
71+
}
72+
73+
&[aria-current='page'],
74+
&[aria-selected='true'] {
75+
background-color: var(--bgColor-default);
76+
77+
/* Bar on the left */
78+
/* stylelint-disable-next-line selector-max-specificity */
79+
&::before {
80+
/* stylelint-disable-next-line primer/colors */
81+
background-color: var(--underlineNav-borderColor-active, var(--color-primer-border-active, #fd8c73));
82+
}
83+
}
84+
}
85+
86+
.SideNav.SideNavVariant-lightweight > & {
87+
padding: var(--base-size-4) 0;
88+
color: var(--fgColor-accent);
89+
90+
&:hover {
91+
color: var(--fgColor-default);
92+
text-decoration: none;
93+
}
94+
95+
&:focus {
96+
z-index: 1;
97+
color: var(--fgColor-default);
98+
text-decoration: none;
99+
outline: solid 1px var(--fgColor-accent);
100+
}
101+
102+
&[aria-current='page'],
103+
&[aria-selected='true'] {
104+
font-weight: var(--base-text-weight-medium);
105+
color: var(--fgColor-default);
106+
}
107+
}
108+
}
109+
110+
.SideNavLinkFull {
111+
display: flex;
112+
align-items: center;
113+
justify-content: space-between;
114+
}

packages/react/src/SideNav.tsx

Lines changed: 115 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import styled, {css} from 'styled-components'
33

44
import Box from './Box'
55
import type {ComponentProps} from './utils/types'
6-
import Link from './Link'
7-
import React from 'react'
6+
import Link, {type LinkProps} from './Link'
7+
import React, {type PropsWithChildren} from 'react'
88
import {clsx} from 'clsx'
99
import type {SxProp} from './sx'
1010
import sx from './sx'
@@ -61,10 +61,14 @@ function SideNav({
6161
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
6262
const variantClassName = variant === 'lightweight' ? 'lightweight' : 'normal'
6363
const newClassName = clsx(
64-
{[classes.SideNav]: enabled, [classes.SideNavBordered]: enabled && bordered},
65-
'sidenav',
64+
{
65+
[classes.SideNav]: enabled,
66+
[classes.SideNavBordered]: enabled && bordered,
67+
[classes[`SideNavVariant-${variantClassName}`]]: enabled,
68+
sidenav: !enabled,
69+
[`variant-${variantClassName}`]: !enabled,
70+
},
6671
className,
67-
`variant-${variantClassName}`,
6872
)
6973
return (
7074
<StyledNav
@@ -79,11 +83,12 @@ function SideNav({
7983
)
8084
}
8185

82-
type StyledSideNavLinkProps = {
86+
type StyledSideNavLinkProps = PropsWithChildren<{
8387
to?: To
8488
selected?: boolean
8589
variant?: 'full' | 'normal'
86-
}
90+
}> &
91+
LinkProps
8792

8893
// used for variant normal hover, focus pseudo selectors
8994
const CommonAccessibilityVariantNormalStyles = css`
@@ -97,106 +102,124 @@ const CommonAccessibilityVariantLightWeightStyles = css`
97102
text-decoration: none;
98103
`
99104

100-
const SideNavLink = styled(Link).attrs<StyledSideNavLinkProps>(props => {
101-
const isReactRouter = typeof props.to === 'string'
102-
if (isReactRouter || props.selected) {
103-
// according to their docs, NavLink supports aria-current:
104-
// https://reacttraining.com/react-router/web/api/NavLink/aria-current-string
105-
return {'aria-current': 'page'}
106-
} else {
107-
return {}
108-
}
109-
})<StyledSideNavLinkProps & SxProp>`
110-
position: relative;
111-
display: block;
112-
${props =>
113-
props.variant === 'full' &&
114-
css`
115-
display: flex;
116-
align-items: center;
117-
justify-content: space-between;
118-
`}
119-
width: 100%;
120-
text-align: left;
121-
font-size: var(--base-size-14);
122-
123-
& > .sidenav {
124-
border-bottom: none;
125-
}
126-
127-
.sidenav.variant-normal > & {
128-
color: var(--fgColor-default);
129-
padding: var(--base-size-16);
130-
border: 0;
131-
border-top: var(--borderWidth-thin) solid var(--borderColor-muted);
132-
133-
&:first-child {
134-
border-top: 0;
135-
border-top-right-radius: var(--borderRadius-medium);
136-
border-top-left-radius: var(--borderRadius-medium);
137-
}
138-
139-
&:last-child {
140-
border-bottom-right-radius: var(--borderRadius-medium);
141-
border-bottom-left-radius: var(--borderRadius-medium);
142-
}
143-
144-
// Bar on the left
145-
&::before {
146-
position: absolute;
147-
top: 0;
148-
bottom: 0;
149-
left: 0;
150-
z-index: 1;
151-
width: 3px;
152-
pointer-events: none;
153-
content: '';
154-
}
105+
const StyledSideNavLink = toggleStyledComponent(
106+
CSS_MODULES_FEATURE_FLAG,
107+
Link,
108+
styled(Link)<StyledSideNavLinkProps & SxProp>`
109+
position: relative;
110+
display: block;
111+
width: 100%;
112+
font-size: 14px;
113+
text-align: left;
114+
text-decoration: none;
115+
${props =>
116+
props.variant === 'full' &&
117+
css`
118+
display: flex;
119+
align-items: center;
120+
justify-content: space-between;
121+
`}
155122
156-
&:hover {
157-
${CommonAccessibilityVariantNormalStyles}
123+
& > .sidenav {
124+
border-bottom: none;
158125
}
159126
160-
&:focus {
161-
${CommonAccessibilityVariantNormalStyles}
162-
outline: solid 2px var(--fgColor-accent);
163-
z-index: 1;
164-
}
127+
.sidenav.variant-normal > & {
128+
color: var(--fgColor-default);
129+
padding: var(--base-size-16);
130+
border: 0;
131+
border-top: var(--borderWidth-thin) solid var(--borderColor-muted);
132+
133+
&:first-child {
134+
border-top: 0;
135+
border-top-right-radius: var(--borderRadius-medium);
136+
border-top-left-radius: var(--borderRadius-medium);
137+
}
165138
166-
&[aria-current='page'],
167-
&[aria-selected='true'] {
168-
background-color: var(--bgColor-default);
139+
&:last-child {
140+
border-bottom-right-radius: var(--borderRadius-medium);
141+
border-bottom-left-radius: var(--borderRadius-medium);
142+
}
169143
170144
// Bar on the left
171145
&::before {
172-
background-color: #fd8c73;
146+
position: absolute;
147+
top: 0;
148+
bottom: 0;
149+
left: 0;
150+
z-index: 1;
151+
width: 3px;
152+
pointer-events: none;
153+
content: '';
173154
}
174-
}
175-
}
176155
177-
.sidenav.variant-lightweight > & {
178-
padding: var(--base-size-4) 0;
179-
color: var(--fgColor-accent);
156+
&:hover {
157+
${CommonAccessibilityVariantNormalStyles}
158+
}
180159
181-
&:hover {
182-
${CommonAccessibilityVariantLightWeightStyles}
183-
}
160+
&:focus {
161+
${CommonAccessibilityVariantNormalStyles}
162+
outline: solid 2px var(--fgColor-accent);
163+
z-index: 1;
164+
}
165+
166+
&[aria-current='page'],
167+
&[aria-selected='true'] {
168+
background-color: var(--bgColor-default);
184169
185-
&:focus {
186-
${CommonAccessibilityVariantLightWeightStyles}
187-
outline: solid 1px var(--fgColor-accent);
188-
z-index: 1;
170+
// Bar on the left
171+
&::before {
172+
background-color: var(--underlineNav-borderColor-active, var(--color-primer-border-active, #fd8c73));
173+
}
174+
}
189175
}
190176
191-
&[aria-current='page'],
192-
&[aria-selected='true'] {
193-
color: var(--fgColor-default);
194-
font-weight: var(--base-text-weight-medium);
177+
.sidenav.variant-lightweight > & {
178+
padding: var(--base-size-4) 0;
179+
color: var(--fgColor-accent);
180+
181+
&:hover {
182+
${CommonAccessibilityVariantLightWeightStyles}
183+
}
184+
185+
&:focus {
186+
${CommonAccessibilityVariantLightWeightStyles}
187+
outline: solid 1px var(--fgColor-accent);
188+
z-index: 1;
189+
}
190+
191+
&[aria-current='page'],
192+
&[aria-selected='true'] {
193+
color: var(--fgColor-default);
194+
font-weight: var(--base-text-weight-medium);
195+
}
195196
}
196-
}
197197
198-
${sx};
199-
`
198+
${sx};
199+
`,
200+
)
201+
202+
const SideNavLink = ({selected, to, variant, className, children, ...rest}: StyledSideNavLinkProps) => {
203+
const isReactRouter = typeof to === 'string'
204+
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
205+
const newClassName = clsx(
206+
{[classes.SideNavLink]: true, [classes.SideNavLinkFull]: enabled && variant === 'full'},
207+
className,
208+
)
209+
210+
// according to their docs, NavLink supports aria-current:
211+
// https://reacttraining.com/react-router/web/api/NavLink/aria-current-string
212+
return (
213+
<StyledSideNavLink
214+
aria-current={isReactRouter || selected ? 'page' : undefined}
215+
className={newClassName}
216+
variant={variant}
217+
{...rest}
218+
>
219+
{children}
220+
</StyledSideNavLink>
221+
)
222+
}
200223

201224
SideNavLink.displayName = 'SideNav.Link'
202225

0 commit comments

Comments
 (0)