Skip to content

Commit cc909dc

Browse files
authored
2411 the aria keyshortcuts is wrongly attached for the actionlistlinkitem components (#2657)
* fix(LinkItem): aria-keyshortcuts prop moved to li * fix(LinkItem): key shortcuts functionality * test(LinkItem): verify keyshortcut attached to correct element * test(LinkItem): ActionMenu test and story includes LinkItem * chore(eslint): add trailing commas * chore(test): merge test updates * Add changeset * Merge branch 'main' into '2411-the-aria-keyshortcuts'
1 parent d1c2b6b commit cc909dc

File tree

9 files changed

+127
-47
lines changed

9 files changed

+127
-47
lines changed

.changeset/sour-knives-drive.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+
Assign aria-keyshorcuts and role properties to the correct element in LinkItem.tsx

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ public/
1010
stats.html
1111
.env
1212
storybook-static
13+
.tool-versions

src/ActionList/ActionList.examples.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const ListLinkItem = () => (
5757
</ActionList.LeadingVisual>
5858
not a link, just an Item for comparison
5959
</ActionList.Item>
60-
<ActionList.LinkItem href="https://github.com/primer">
60+
<ActionList.LinkItem href="https://github.com/primer" aria-keyshortcuts="g">
6161
<ActionList.LeadingVisual>
6262
<LinkIcon />
6363
</ActionList.LeadingVisual>

src/ActionList/Item.tsx

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -176,44 +176,56 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
176176

177177
return (
178178
<Slots context={{variant, disabled, inlineDescriptionId, blockDescriptionId}}>
179-
{slots => (
180-
<LiBox
181-
ref={forwardedRef}
182-
sx={merge<BetterSystemStyleObject>(styles, sxProp)}
183-
onClick={clickHandler}
184-
onKeyPress={keyPressHandler}
185-
aria-disabled={disabled ? true : undefined}
186-
tabIndex={disabled || _PrivateItemWrapper ? undefined : 0}
187-
aria-labelledby={`${labelId} ${slots.InlineDescription ? inlineDescriptionId : ''}`}
188-
aria-describedby={slots.BlockDescription ? blockDescriptionId : undefined}
189-
role={role || itemRole}
190-
{...(selectionAttribute && {[selectionAttribute]: selected})}
191-
{...props}
192-
>
193-
<ItemWrapper>
194-
<Selection selected={selected} />
195-
{slots.LeadingVisual}
196-
<Box
197-
data-component="ActionList.Item--DividerContainer"
198-
sx={{display: 'flex', flexDirection: 'column', flexGrow: 1, minWidth: 0}}
199-
>
200-
<ConditionalBox if={Boolean(slots.TrailingVisual)} sx={{display: 'flex', flexGrow: 1}}>
201-
<ConditionalBox
202-
if={Boolean(slots.InlineDescription)}
203-
sx={{display: 'flex', flexGrow: 1, alignItems: 'baseline', minWidth: 0}}
204-
>
205-
<Box as="span" id={labelId} sx={{flexGrow: slots.InlineDescription ? 0 : 1}}>
206-
{props.children}
207-
</Box>
208-
{slots.InlineDescription}
179+
{slots => {
180+
const menuItemProps = {
181+
onClick: clickHandler,
182+
onKeyPress: keyPressHandler,
183+
'aria-disabled': disabled ? true : undefined,
184+
tabIndex: disabled ? undefined : 0,
185+
'aria-labelledby': `${labelId} ${slots.InlineDescription ? inlineDescriptionId : ''}`,
186+
'aria-describedby': slots.BlockDescription ? blockDescriptionId : undefined,
187+
...(selectionAttribute && {[selectionAttribute]: selected}),
188+
role: role || itemRole,
189+
}
190+
const containerProps = _PrivateItemWrapper
191+
? {
192+
role: role || itemRole ? 'none' : undefined,
193+
}
194+
: menuItemProps
195+
const wrapperProps = _PrivateItemWrapper ? menuItemProps : {}
196+
197+
return (
198+
<LiBox
199+
ref={forwardedRef}
200+
sx={merge<BetterSystemStyleObject>(styles, sxProp)}
201+
{...containerProps}
202+
{...props}
203+
>
204+
<ItemWrapper {...wrapperProps}>
205+
<Selection selected={selected} />
206+
{slots.LeadingVisual}
207+
<Box
208+
data-component="ActionList.Item--DividerContainer"
209+
sx={{display: 'flex', flexDirection: 'column', flexGrow: 1, minWidth: 0}}
210+
>
211+
<ConditionalBox if={Boolean(slots.TrailingVisual)} sx={{display: 'flex', flexGrow: 1}}>
212+
<ConditionalBox
213+
if={Boolean(slots.InlineDescription)}
214+
sx={{display: 'flex', flexGrow: 1, alignItems: 'baseline', minWidth: 0}}
215+
>
216+
<Box as="span" id={labelId} sx={{flexGrow: slots.InlineDescription ? 0 : 1}}>
217+
{props.children}
218+
</Box>
219+
{slots.InlineDescription}
220+
</ConditionalBox>
221+
{slots.TrailingVisual}
209222
</ConditionalBox>
210-
{slots.TrailingVisual}
211-
</ConditionalBox>
212-
{slots.BlockDescription}
213-
</Box>
214-
</ItemWrapper>
215-
</LiBox>
216-
)}
223+
{slots.BlockDescription}
224+
</Box>
225+
</ItemWrapper>
226+
</LiBox>
227+
)
228+
}}
217229
</Slots>
218230
)
219231
},

src/ActionList/LinkItem.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ export const LinkItem = React.forwardRef(({sx = {}, active, as: Component, ...pr
3939
<Item
4040
active={active}
4141
sx={{paddingY: 0, paddingX: 0}}
42-
_PrivateItemWrapper={({children}) => (
43-
<Link as={Component} sx={merge(styles, sx as SxProp)} {...props} ref={forwardedRef}>
42+
_PrivateItemWrapper={({children, ...rest}) => (
43+
<Link as={Component} sx={merge(styles, sx as SxProp)} {...props} {...rest} ref={forwardedRef}>
4444
{children}
4545
</Link>
4646
)}

src/NavList/__snapshots__/NavList.test.tsx.snap

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,14 @@ exports[`NavList renders a simple list 1`] = `
305305
class="c0"
306306
>
307307
<li
308-
aria-labelledby="react-aria-1 "
309308
class="c1 c2"
310309
>
311310
<a
312311
aria-current="page"
312+
aria-labelledby="react-aria-1 "
313313
class="c3"
314314
href="/"
315+
tabindex="0"
315316
>
316317
<div
317318
class="c4"
@@ -327,12 +328,13 @@ exports[`NavList renders a simple list 1`] = `
327328
</a>
328329
</li>
329330
<li
330-
aria-labelledby="react-aria-4 "
331331
class="c1 c6"
332332
>
333333
<a
334+
aria-labelledby="react-aria-4 "
334335
class="c3"
335336
href="/about"
337+
tabindex="0"
336338
>
337339
<div
338340
class="c4"
@@ -348,12 +350,13 @@ exports[`NavList renders a simple list 1`] = `
348350
</a>
349351
</li>
350352
<li
351-
aria-labelledby="react-aria-7 "
352353
class="c1 c6"
353354
>
354355
<a
356+
aria-labelledby="react-aria-7 "
355357
class="c3"
356358
href="/contact"
359+
tabindex="0"
357360
>
358361
<div
359362
class="c4"
@@ -735,13 +738,14 @@ exports[`NavList renders with groups 1`] = `
735738
class="c4"
736739
>
737740
<li
738-
aria-labelledby="react-aria-2 "
739741
class="c5 c6"
740742
>
741743
<a
742744
aria-current="page"
745+
aria-labelledby="react-aria-2 "
743746
class="c7"
744747
href="/getting-started"
748+
tabindex="0"
745749
>
746750
<div
747751
class="c8"
@@ -782,12 +786,13 @@ exports[`NavList renders with groups 1`] = `
782786
class="c4"
783787
>
784788
<li
785-
aria-labelledby="react-aria-6 "
786789
class="c5 c10"
787790
>
788791
<a
792+
aria-labelledby="react-aria-6 "
789793
class="c7"
790794
href="/Avatar"
795+
tabindex="0"
791796
>
792797
<div
793798
class="c8"
@@ -1255,13 +1260,14 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav
12551260
id="react-aria-2"
12561261
>
12571262
<li
1258-
aria-labelledby="react-aria-3 "
12591263
class="c2 c10"
12601264
>
12611265
<a
12621266
aria-current="page"
1267+
aria-labelledby="react-aria-3 "
12631268
class="c11"
12641269
href="#"
1270+
tabindex="0"
12651271
>
12661272
<div
12671273
class="c4"
@@ -1742,13 +1748,14 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t
17421748
id="react-aria-2"
17431749
>
17441750
<li
1745-
aria-labelledby="react-aria-3 "
17461751
class="c2 c10"
17471752
>
17481753
<a
17491754
aria-current="page"
1755+
aria-labelledby="react-aria-3 "
17501756
class="c11"
17511757
href="#"
1758+
tabindex="0"
17521759
>
17531760
<div
17541761
class="c4"

src/__tests__/ActionList.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ function SimpleActionList(): JSX.Element {
1818
<ActionList.Item>Copy link</ActionList.Item>
1919
<ActionList.Item>Edit file</ActionList.Item>
2020
<ActionList.Item variant="danger">Delete file</ActionList.Item>
21+
<ActionList.LinkItem href="//github.com" title="anchor" aria-keyshortcuts="d">
22+
Link Item
23+
</ActionList.LinkItem>
2124
</ActionList>
2225
</BaseStyles>
2326
</SSRProvider>
@@ -63,6 +66,15 @@ describe('ActionList', () => {
6366
ActionList,
6467
})
6568

69+
it('should have aria-keyshortcuts applied to the correct element', async () => {
70+
const {container} = HTMLRender(<SimpleActionList />)
71+
72+
const linkOptions = await waitFor(() => container.querySelectorAll('a'))
73+
74+
expect(linkOptions[0]).toHaveAttribute('aria-keyshortcuts', 'd')
75+
expect(linkOptions[0].parentElement).not.toHaveAttribute('aria-keyshortcuts', 'd')
76+
})
77+
6678
it('should have no axe violations', async () => {
6779
const {container} = HTMLRender(<SimpleActionList />)
6880
const results = await axe(container)

src/__tests__/ActionMenu.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ function Example(): JSX.Element {
2424
<ActionList.Item variant="danger" onClick={event => event.preventDefault()}>
2525
Delete file
2626
</ActionList.Item>
27+
<ActionList.LinkItem href="//github.com" title="anchor" aria-keyshortcuts="s">
28+
Github
29+
</ActionList.LinkItem>
2730
</ActionList>
2831
</ActionMenu.Overlay>
2932
</ActionMenu>
@@ -169,6 +172,26 @@ describe('ActionMenu', () => {
169172

170173
expect(component.getAllByRole('menuitem')[0]).toEqual(document.activeElement)
171174
})
175+
it('should be able to select an Item with aria-keyshortcuts after opening Menu with click', async () => {
176+
const component = HTMLRender(<Example />)
177+
const button = component.getByRole('button')
178+
179+
const user = userEvent.setup()
180+
await user.click(button)
181+
182+
expect(component.queryByRole('menu')).toBeInTheDocument()
183+
184+
// linkItem button is the active element at this point
185+
await user.keyboard('{ArrowDown}{s}')
186+
187+
expect(component.getAllByRole('menuitem')[4]).toEqual(document.activeElement)
188+
await user.keyboard('{ArrowUp}')
189+
expect(component.getAllByRole('menuitem')[3]).toEqual(document.activeElement)
190+
191+
// assumes mnemonics aria-keyshortcuts are ignored
192+
await user.keyboard('{g}')
193+
expect(component.getAllByRole('menuitem')[3]).toEqual(document.activeElement)
194+
})
172195

173196
it('should select last element when ArrowUp is pressed after opening Menu with click', async () => {
174197
const component = HTMLRender(<Example />)

src/stories/ActionMenu/fixtures.stories.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export function ExternalAnchor(): JSX.Element {
145145
Delete file
146146
<ActionList.TrailingVisual>⌘D</ActionList.TrailingVisual>
147147
</ActionList.Item>
148+
<ActionList.Divider />
148149
</ActionList>
149150
</ActionMenu.Overlay>
150151
</ActionMenu>
@@ -759,6 +760,25 @@ export function MnemonicsTest(): JSX.Element {
759760
</Box>
760761
</ActionList.TrailingVisual>
761762
</ActionList.Item>
763+
<ActionList.LinkItem aria-keyshortcuts="d" href="//github.com">
764+
User defined Link
765+
<ActionList.TrailingVisual>
766+
<Box
767+
as="span"
768+
sx={{
769+
backgroundColor: 'canvas.default',
770+
border: '1px solid',
771+
borderColor: 'border.default',
772+
borderRadius: 2,
773+
padding: '2px 6px',
774+
fontSize: 0,
775+
}}
776+
>
777+
d
778+
</Box>
779+
</ActionList.TrailingVisual>
780+
</ActionList.LinkItem>
781+
<ActionList.LinkItem href="//github.com">Github</ActionList.LinkItem>
762782
<ActionList.Item disabled>Disabled</ActionList.Item>
763783
</ActionList>
764784
</ActionMenu.Overlay>

0 commit comments

Comments
 (0)