Skip to content

Commit 507da36

Browse files
authored
Migrate the TabNav component to use CSS modules (#6176)
1 parent ac5a9c1 commit 507da36

File tree

5 files changed

+86
-75
lines changed

5 files changed

+86
-75
lines changed

.changeset/thin-bears-change.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+
Migrate the TabNav component to use CSS modules
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.TabNavTabList {
2+
display: flex;
3+
/* stylelint-disable-next-line primer/spacing */
4+
margin-bottom: -1px;
5+
overflow: auto;
6+
}
7+
8+
.TabNavNav {
9+
margin-top: 0;
10+
border-bottom: var(--borderWidth-thin) solid var(--borderColor-default);
11+
}
12+
13+
.TabNavLink {
14+
padding: var(--base-size-8) var(--base-size-12);
15+
font-size: var(--text-body-size-medium);
16+
/* stylelint-disable-next-line primer/typography */
17+
line-height: 20px;
18+
color: var(--fgColor-default);
19+
text-decoration: none;
20+
background-color: transparent;
21+
border: var(--borderWidth-thin) solid transparent;
22+
border-bottom: 0;
23+
24+
&:hover,
25+
&:focus {
26+
color: var(--fgColor-default);
27+
text-decoration: none;
28+
29+
@mixin focusOutline -6px;
30+
}
31+
32+
&.Selected {
33+
color: var(--fgColor-default);
34+
background-color: var(--bgColor-default);
35+
border-color: var(--borderColor-default);
36+
border-top-left-radius: var(--borderRadius-medium);
37+
border-top-right-radius: var(--borderRadius-medium);
38+
}
39+
}

packages/react/src/TabNav/TabNav.tsx

Lines changed: 29 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,17 @@
1-
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
21
import {clsx} from 'clsx'
32
import type {To} from 'history'
43
import React, {useRef, useState} from 'react'
5-
import styled from 'styled-components'
6-
import {get} from '../constants'
74
import {FocusKeys, useFocusZone} from '../hooks/useFocusZone'
85
import type {SxProp} from '../sx'
9-
import sx from '../sx'
106
import type {ComponentProps} from '../utils/types'
11-
import getGlobalFocusStyles from '../internal/utils/getGlobalFocusStyles'
127

13-
const ITEM_CLASS = 'TabNav-item'
14-
const SELECTED_CLASS = 'selected'
15-
16-
const TabNavBase = styled.div<SxProp>`
17-
${sx}
18-
`
19-
20-
const TabNavTabList = styled.div`
21-
display: flex;
22-
margin-bottom: -1px;
23-
overflow: auto;
24-
`
25-
26-
const TabNavNav = styled.nav`
27-
margin-top: 0;
28-
border-bottom: 1px solid ${get('colors.border.default')};
29-
`
8+
import styles from './TabNav.module.css'
9+
import {BoxWithFallback} from '../internal/components/BoxWithFallback'
3010

3111
/**
3212
* @deprecated
3313
*/
34-
export type TabNavProps = ComponentProps<typeof TabNavBase>
14+
export type TabNavProps = ComponentProps<typeof BoxWithFallback>
3515

3616
/**
3717
* @deprecated
@@ -73,11 +53,13 @@ function TabNav({children, 'aria-label': ariaLabel, ...rest}: TabNavProps) {
7353
)
7454

7555
return (
76-
<TabNavBase {...rest} ref={navRef as React.RefObject<HTMLDivElement>}>
77-
<TabNavNav aria-label={ariaLabel}>
78-
<TabNavTabList role="tablist">{children}</TabNavTabList>
79-
</TabNavNav>
80-
</TabNavBase>
56+
<BoxWithFallback {...rest} ref={navRef as React.RefObject<HTMLDivElement>}>
57+
<nav aria-label={ariaLabel} className={styles.TabNavNav}>
58+
<div role="tablist" className={styles.TabNavTabList}>
59+
{children}
60+
</div>
61+
</nav>
62+
</BoxWithFallback>
8163
)
8264
}
8365

@@ -88,44 +70,30 @@ export type TabNavLinkProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLA
8870
to?: To
8971
selected?: boolean
9072
href?: string
73+
className?: string
74+
as?: React.ElementType | 'a' | 'button' | 'div'
75+
disabled?: boolean
9176
} & SxProp
9277

9378
/**
9479
* @deprecated
9580
*/
96-
const TabNavLink = styled.a.attrs<TabNavLinkProps>(props => ({
97-
className: clsx(ITEM_CLASS, props.selected && SELECTED_CLASS, props.className),
98-
role: 'tab',
99-
'aria-selected': !!props.selected,
100-
tabIndex: -1,
101-
}))<TabNavLinkProps>`
102-
padding: 8px 12px;
103-
font-size: ${get('fontSizes.1')};
104-
line-height: 20px;
105-
color: ${get('colors.fg.default')};
106-
text-decoration: none;
107-
background-color: transparent;
108-
border: 1px solid transparent;
109-
border-bottom: 0;
110-
111-
${getGlobalFocusStyles('-6px')};
112-
113-
&:hover,
114-
&:focus {
115-
color: ${get('colors.fg.default')};
116-
text-decoration: none;
117-
}
118-
119-
&.selected {
120-
color: ${get('colors.fg.default')};
121-
border-color: ${get('colors.border.default')};
122-
border-top-right-radius: ${get('radii.2')};
123-
border-top-left-radius: ${get('radii.2')};
124-
background-color: ${get('colors.canvas.default')};
125-
}
126-
127-
${sx};
128-
` as PolymorphicForwardRefComponent<'a', TabNavLinkProps>
81+
const TabNavLink = React.forwardRef<HTMLAnchorElement, TabNavLinkProps>(function TabNavLink(
82+
{selected, className, as = 'a', ...rest}: TabNavLinkProps,
83+
ref,
84+
) {
85+
return (
86+
<BoxWithFallback
87+
as={as}
88+
ref={ref}
89+
role="tab"
90+
tabIndex={-1}
91+
aria-selected={selected ? true : undefined}
92+
className={clsx('TabNav-item', styles.TabNavLink, selected && 'selected', selected && styles.Selected, className)}
93+
{...rest}
94+
/>
95+
)
96+
})
12997

13098
TabNavLink.displayName = 'TabNav.Link'
13199

packages/react/src/TabNav/__tests__/TabNav.test.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import TabNav from '..'
33
import {render, screen} from '@testing-library/react'
44
import userEvent from '@testing-library/user-event'
55
import {Button} from '../../Button'
6-
import Box from '../../Box'
76

87
describe('TabNav', () => {
98
const tabNavMarkup = (
10-
<Box>
9+
<div>
1110
<TabNav>
1211
<TabNav.Link id="first" href="#" as="div">
1312
First
@@ -21,7 +20,7 @@ describe('TabNav', () => {
2120
</TabNav.Link>
2221
</TabNav>
2322
<Button id="my-button">My Button</Button>
24-
</Box>
23+
</div>
2524
)
2625

2726
describe('TabNav.Link', () => {

packages/react/src/TabNav/__tests__/TabNav.types.test.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ export function shouldAcceptCallWithNoProps() {
1010
)
1111
}
1212

13-
export function shouldNotAcceptSystemProps() {
14-
return (
15-
<>
16-
{/* @ts-expect-error system props should not be accepted */}
17-
<TabNav backgroundColor="maroon" />
18-
{/* @ts-expect-error system props should not be accepted */}
19-
<TabNav.Link backgroundColor="fuchsia" />
20-
</>
21-
)
22-
}
23-
2413
export function shouldAcceptButtonAsProps() {
2514
return <TabNav.Link as={Button} />
2615
}
2716

2817
export function shouldAcceptTabNavLinkprops() {
2918
return <TabNav.Link to="to something" selected as={Button} />
3019
}
20+
21+
export function shouldAcceptDisableProps() {
22+
return (
23+
<TabNav.Link
24+
disabled={true}
25+
onClick={() => {
26+
// noop
27+
}}
28+
/>
29+
)
30+
}

0 commit comments

Comments
 (0)