Skip to content

Commit ada8d86

Browse files
[UnderlineNav2]: React router implementation fixes & docs improvement (#2448)
* react router implementation fixes * add changeset
1 parent 9031a0e commit ada8d86

File tree

6 files changed

+136
-94
lines changed

6 files changed

+136
-94
lines changed

.changeset/polite-dodos-behave.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+
UnderlineNav2: Add support and docs for react router configuration

docs/content/drafts/UnderlineNav2.mdx

Lines changed: 94 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import {UnderlineNav} from '@primer/react/drafts'
1717

1818
```jsx live drafts
1919
<UnderlineNav aria-label="Repository">
20-
<UnderlineNav.Item selected>Item 1</UnderlineNav.Item>
21-
<UnderlineNav.Item>Item 2</UnderlineNav.Item>
22-
<UnderlineNav.Item>Item 3</UnderlineNav.Item>
20+
<UnderlineNav.Item selected>Code</UnderlineNav.Item>
21+
<UnderlineNav.Item>Issues</UnderlineNav.Item>
22+
<UnderlineNav.Item>Pull Requests</UnderlineNav.Item>
2323
</UnderlineNav>
2424
```
2525

@@ -48,87 +48,106 @@ import {UnderlineNav} from '@primer/react/drafts'
4848

4949
### Overflow Behaviour
5050

51-
When overflow occurs, the component first hides icons if present to optimize for space and show as many items as possible.
52-
53-
#### Items Without Icons
54-
55-
```jsx live drafts
56-
<UnderlineNav aria-label="Repository">
57-
<UnderlineNav.Item selected icon={CodeIcon}>
58-
Code
59-
</UnderlineNav.Item>
60-
<UnderlineNav.Item icon={IssueOpenedIcon} counter={30}>
61-
Issues
62-
</UnderlineNav.Item>
63-
<UnderlineNav.Item icon={GitPullRequestIcon} counter={3}>
64-
Pull Requests
65-
</UnderlineNav.Item>
66-
<UnderlineNav.Item icon={CommentDiscussionIcon}>Discussions</UnderlineNav.Item>
67-
<UnderlineNav.Item icon={PlayIcon} counter={9}>
68-
Actions
69-
</UnderlineNav.Item>
70-
<UnderlineNav.Item icon={ProjectIcon} counter={7}>
71-
Projects
72-
</UnderlineNav.Item>
73-
<UnderlineNav.Item icon={ShieldLockIcon}>Security</UnderlineNav.Item>
74-
<UnderlineNav.Item icon={GraphIcon}>Insights</UnderlineNav.Item>
75-
<UnderlineNav.Item icon={GearIcon} counter={1}>
76-
Settings
77-
</UnderlineNav.Item>
78-
</UnderlineNav>
51+
Component first hides icons if they present to optimize for space and show as many items as possible. If there is still an overflow, it will display the items that don't fit in the `More` menu.
52+
53+
```javascript noinline live drafts
54+
const Navigation = () => {
55+
const items = [
56+
{navigation: 'Code', icon: CodeIcon},
57+
{navigation: 'Issues', icon: IssueOpenedIcon, counter: 120},
58+
{navigation: 'Pull Requests', icon: GitPullRequestIcon, counter: 13},
59+
{navigation: 'Discussions', icon: CommentDiscussionIcon, counter: 5},
60+
{navigation: 'Actions', icon: PlayIcon, counter: 4},
61+
{navigation: 'Projects', icon: ProjectIcon, counter: 9},
62+
{navigation: 'Insights', icon: GraphIcon},
63+
{navigation: 'Settings', icon: GearIcon, counter: 10},
64+
{navigation: 'Security', icon: ShieldLockIcon}
65+
]
66+
const [selectedIndex, setSelectedIndex] = React.useState(0)
67+
return (
68+
<Box sx={{width: 750, border: '1px solid', borderBottom: 0, borderColor: 'border.default'}}>
69+
<UnderlineNav aria-label="Repository">
70+
{items.map((item, index) => (
71+
<UnderlineNav.Item
72+
key={item.navigation}
73+
icon={item.icon}
74+
selected={index === selectedIndex}
75+
onSelect={e => {
76+
setSelectedIndex(index)
77+
e.preventDefault()
78+
}}
79+
counter={item.counter}
80+
>
81+
{item.navigation}
82+
</UnderlineNav.Item>
83+
))}
84+
</UnderlineNav>
85+
</Box>
86+
)
87+
}
88+
render(<Navigation />)
7989
```
8090

81-
#### Display `More` Menu
82-
83-
If there is still overflow, the component will behave depending on the pointer.
91+
### Loading State For Counters
8492

8593
```jsx live drafts
86-
<UnderlineNav aria-label="Repository">
87-
<UnderlineNav.Item selected icon={CodeIcon}>
94+
<UnderlineNav aria-label="Repository" loadingCounters={true}>
95+
<UnderlineNav.Item counter={4} selected>
8896
Code
8997
</UnderlineNav.Item>
90-
<UnderlineNav.Item icon={IssueOpenedIcon} counter={30}>
91-
Issues
92-
</UnderlineNav.Item>
93-
<UnderlineNav.Item icon={GitPullRequestIcon} counter={3}>
94-
Pull Requests
95-
</UnderlineNav.Item>
96-
<UnderlineNav.Item icon={CommentDiscussionIcon}>Discussions</UnderlineNav.Item>
97-
<UnderlineNav.Item icon={EyeIcon} counter={9}>
98-
Actions
99-
</UnderlineNav.Item>
100-
<UnderlineNav.Item icon={EyeIcon} counter={7}>
101-
Projects
102-
</UnderlineNav.Item>
103-
<UnderlineNav.Item icon={EyeIcon}>Security</UnderlineNav.Item>
104-
<UnderlineNav.Item icon={EyeIcon} counter={14}>
105-
Insights
106-
</UnderlineNav.Item>
107-
<UnderlineNav.Item icon={EyeIcon} counter={1}>
108-
Settings
109-
</UnderlineNav.Item>
110-
<UnderlineNav.Item icon={EyeIcon}>Wiki</UnderlineNav.Item>
98+
<UnderlineNav.Item counter={44}>Issues</UnderlineNav.Item>
99+
<UnderlineNav.Item>Pull Requests</UnderlineNav.Item>
111100
</UnderlineNav>
112101
```
113102

114-
### Loading state for counters
103+
### With React Router
115104

116-
```jsx live drafts
117-
<UnderlineNav aria-label="Repository" loadingCounters={true}>
118-
<UnderlineNav.Item counter={4} selected>
119-
Item 1
120-
</UnderlineNav.Item>
121-
<UnderlineNav.Item counter={44}>Item 2</UnderlineNav.Item>
122-
<UnderlineNav.Item>Item 3</UnderlineNav.Item>
123-
</UnderlineNav>
105+
```jsx
106+
import {Link, useNavigate} from 'react-router-dom'
107+
import {UnderlineNav} from '@primer/react/drafts'
108+
const Navigation = () => {
109+
const navigate = useNavigate()
110+
return (
111+
<UnderlineNav aria-label="Repository">
112+
<UnderlineNav.Item as={Link} to="code" counter={4} selected>
113+
Code
114+
</UnderlineNav.Item>
115+
<UnderlineNav.Item
116+
counter={44}
117+
as={Link}
118+
onSelect={() => {
119+
navigate('issues')
120+
}}
121+
>
122+
Issues
123+
</UnderlineNav.Item>
124+
<UnderlineNav.Item as={Link} to="pulls">
125+
Pull Requests
126+
</UnderlineNav.Item>
127+
</UnderlineNav>
128+
)
129+
}
124130
```
125131

132+
<Note>
133+
You can bind the routing with both 'to' and 'onSelect' prop here. However; please note that if an 'href' prop is
134+
passed, it will be ignored here.
135+
</Note>
136+
126137
## Props
127138

128139
### UnderlineNav
129140

130141
<PropsTable>
131-
<PropsTableRow name="aria-label" type="string" description="A unique name for the rendered 'nav' landmark." />
142+
<PropsTableRow name="children" required type="UnderlineNav.Item[]" />
143+
<PropsTableRow
144+
name="aria-label"
145+
type="string"
146+
description="A unique name for the rendered 'nav' landmark. It will also be used to label the arrow
147+
buttons that control the scroll behaviour on coarse pointer devices. (I.e.
148+
'Scroll ${aria-label} left/right')
149+
"
150+
/>
132151
<PropsTableRow
133152
name="loadingCounters"
134153
type="boolean"
@@ -146,18 +165,23 @@ If there is still overflow, the component will behave depending on the pointer.
146165
### UnderlineNav.Item
147166

148167
<PropsTable>
168+
<PropsTableRow
169+
name="href"
170+
type="string"
171+
description="The URL that the item navigates to. 'href' is passed to the underlying '<a>' element. If 'as' is specified, the component may need different props and 'href' is ignored. (Required prop for the react router is 'to' for example)"
172+
/>
149173
<PropsTableRow name="icon" type="Component" description="The leading icon comes before item label" />
150174
<PropsTableRow name="selected" type="boolean" description="Whether the link is selected" />
151175
<PropsTableRow
152176
name="onSelect"
153177
type="(event) => void"
154-
description="The handler that gets called when a nav link is selected"
178+
description="The handler that gets called when a nav link is selected. For example, a manual route binding can be done here(I.e. 'navigate(href)' for the react router.)"
155179
/>
156180
<PropsTableRow
157181
name="as"
158-
type="string | Component"
182+
type="string | React.ElementType"
159183
defaultValue="a"
160-
description="What kind of component needs to be rendered"
184+
description="The underlying element to render — either a HTML element name or a React component."
161185
/>
162186
<PropsTableSxRow />
163187
</PropsTable>

src/UnderlineNav2/UnderlineNav.tsx

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ export const UnderlineNav = forwardRef(
192192
React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement> | null
193193
>(null)
194194

195+
const [asNavItem, setAsNavItem] = useState('a')
196+
195197
const [iconsVisible, setIconsVisible] = useState<boolean>(true)
196198

197199
const afterSelectHandler = (event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => {
@@ -250,6 +252,7 @@ export const UnderlineNav = forwardRef(
250252
setSelectedLink,
251253
selectedLinkText,
252254
setSelectedLinkText,
255+
setAsNavItem,
253256
selectEvent,
254257
afterSelect: afterSelectHandler,
255258
variant,
@@ -275,28 +278,32 @@ export const UnderlineNav = forwardRef(
275278
{actions.map((action, index) => {
276279
const {children: actionElementChildren, ...actionElementProps} = action.props
277280
return (
278-
<ActionList.Item
279-
sx={menuItemStyles}
280-
key={index}
281-
{...actionElementProps}
282-
onSelect={(event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => {
283-
swapMenuItemWithListItem(action, index, event, updateListAndMenu)
284-
setSelectEvent(event)
285-
}}
286-
>
287-
<Box
288-
as="span"
289-
sx={{display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}
281+
<Box key={index} as="li">
282+
<ActionList.Item
283+
sx={menuItemStyles}
284+
as={asNavItem}
285+
{...actionElementProps}
286+
onSelect={(
287+
event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>
288+
) => {
289+
swapMenuItemWithListItem(action, index, event, updateListAndMenu)
290+
setSelectEvent(event)
291+
}}
290292
>
291-
{actionElementChildren}
293+
<Box
294+
as="span"
295+
sx={{display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}
296+
>
297+
{actionElementChildren}
292298

293-
{loadingCounters ? (
294-
<LoadingCounter />
295-
) : (
296-
<CounterLabel>{actionElementProps.counter}</CounterLabel>
297-
)}
298-
</Box>
299-
</ActionList.Item>
299+
{loadingCounters ? (
300+
<LoadingCounter />
301+
) : (
302+
<CounterLabel>{actionElementProps.counter}</CounterLabel>
303+
)}
304+
</Box>
305+
</ActionList.Item>
306+
</Box>
300307
)
301308
})}
302309
</ActionList>

src/UnderlineNav2/UnderlineNavContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const UnderlineNavContext = createContext<{
1010
selectedLinkText: string
1111
setSelectedLinkText: React.Dispatch<React.SetStateAction<string>>
1212
selectEvent: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement> | null
13+
setAsNavItem: React.Dispatch<React.SetStateAction<string>>
1314
afterSelect?: (event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => void
1415
variant: 'default' | 'small'
1516
loadingCounters: boolean
@@ -23,6 +24,7 @@ export const UnderlineNavContext = createContext<{
2324
selectedLinkText: '',
2425
setSelectedLinkText: () => null,
2526
selectEvent: null,
27+
setAsNavItem: () => null,
2628
variant: 'default',
2729
loadingCounters: false,
2830
iconsVisible: true

src/UnderlineNav2/UnderlineNavItem.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const UnderlineNavItem = forwardRef(
7272
selectedLinkText,
7373
setSelectedLinkText,
7474
selectEvent,
75+
setAsNavItem,
7576
afterSelect,
7677
variant,
7778
loadingCounters,
@@ -106,6 +107,7 @@ export const UnderlineNavItem = forwardRef(
106107
if (typeof onSelect === 'function' && selectEvent !== null) onSelect(selectEvent)
107108
setSelectedLinkText('')
108109
}
110+
setAsNavItem(Component)
109111
}, [
110112
ref,
111113
preSelected,
@@ -116,7 +118,9 @@ export const UnderlineNavItem = forwardRef(
116118
setChildrenWidth,
117119
setNoIconChildrenWidth,
118120
onSelect,
119-
selectEvent
121+
selectEvent,
122+
setAsNavItem,
123+
Component
120124
])
121125

122126
const keyPressHandler = React.useCallback(
@@ -126,7 +130,6 @@ export const UnderlineNavItem = forwardRef(
126130
if (typeof afterSelect === 'function') afterSelect(event)
127131
}
128132
setSelectedLink(ref as RefObject<HTMLElement>)
129-
event.preventDefault()
130133
},
131134
[onSelect, afterSelect, ref, setSelectedLink]
132135
)
@@ -137,7 +140,6 @@ export const UnderlineNavItem = forwardRef(
137140
if (typeof afterSelect === 'function') afterSelect(event)
138141
}
139142
setSelectedLink(ref as RefObject<HTMLElement>)
140-
event.preventDefault()
141143
},
142144
[onSelect, afterSelect, ref, setSelectedLink]
143145
)

src/UnderlineNav2/styles.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,7 @@ export const menuItemStyles = {
153153
// This is needed to hide the selected check icon on the menu item. https://github.com/primer/react/blob/main/src/ActionList/Selection.tsx#L32
154154
'& > span': {
155155
display: 'none'
156-
}
156+
},
157+
// To reset the style when the menu items are rendered as react router links
158+
textDecoration: 'none'
157159
}

0 commit comments

Comments
 (0)