Skip to content

Commit dbf82f4

Browse files
Select Panel: Functional adjustments (#4906)
* super wip * just use the actionlist component and revert the type updates * Update packages/react/src/FilteredActionList/FilteredActionList.tsx * Some more progress * revert the type changes and cast it :/ * clean * wip * wip wip * add stories * fix linting * add tests for groups * Map groups * Update story names for e2e tests * oops remove unintended file * update story name * same - update story name * disable animations * test(vrt): update snapshots * Update tests since new action list has different semantics for group headings * logging * pass the rest * extract children and use before text * remove logging * test(vrt): update snapshots * add active styles to ActionList * rename component name to be clearer * remove variant full from examples * tiny clean up * fix showItemDividers * another tiny cleanup * pull MappedActionListItem to make it stable * test(vrt): update snapshots * show active styles only when used with keyboard * backward compat: expose id as data-id * update snapshots * add story for long strings * fishing for errors * backward compatibility for renderItem * remove todo now * add a feature flag * clean up dual filter list setup * run jests test with both states of feature flags * refactor snapshot tests with scenarios * remove feature flag for main * test(vrt): update snapshots * add feature flag to e2e matrix * test(vrt): update snapshots * backward compat: allow groupMetadata to be empty array * sigh newline * Create sour-cooks-dress.md * copy SelectPanel snapshots from main * remove unrelated changes in this PR * test(vrt): update snapshots * add more bindkeys * add for deprecated version as well * label listbox/ActionList by panel title * Create wicked-books-occur.md * remove Home and End * add test for PageDown and PageUp * update changeset * active styles for both directly and indirectly * add role=combobox * update snapshot * test(vrt): update snapshots * remove features from deprecated version * update test * only use aria-labelledby when aria-label is not passed * remove combobox for this PR --------- Co-authored-by: Armagan Ersoz <[email protected]> Co-authored-by: broccolinisoup <[email protected]> Co-authored-by: siddharthkp <[email protected]>
1 parent 373ce95 commit dbf82f4

File tree

5 files changed

+64
-2
lines changed

5 files changed

+64
-2
lines changed

.changeset/wicked-books-occur.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@primer/react": minor
3+
---
4+
5+
SelectPanel: Support <kbd>PageDown</kbd> and <kbd>PageUp</kbd> for keyboard navigation
6+
7+
SelectPanel: Label `listbox` by the title of the panel

packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {ScrollIntoViewOptions} from '@primer/behaviors'
2-
import {scrollIntoView} from '@primer/behaviors'
2+
import {scrollIntoView, FocusKeys} from '@primer/behaviors'
33
import type {KeyboardEventHandler} from 'react'
44
import React, {useCallback, useEffect, useRef} from 'react'
55
import styled from 'styled-components'
@@ -86,6 +86,7 @@ export function FilteredActionList({
8686
useFocusZone(
8787
{
8888
containerRef: listContainerRef,
89+
bindKeys: FocusKeys.ArrowVertical | FocusKeys.PageUpDown,
8990
focusOutBehavior: 'wrap',
9091
focusableElementFilter: element => {
9192
return !(element instanceof HTMLInputElement)

packages/react/src/SelectPanel/SelectPanel.test.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const items: SelectPanelProps['items'] = [
2424
},
2525
]
2626

27-
function BasicSelectPanel() {
27+
function BasicSelectPanel(passthroughProps: Record<string, unknown>) {
2828
const [selected, setSelected] = React.useState<SelectPanelProps['items']>([])
2929
const [filter, setFilter] = React.useState('')
3030
const [open, setOpen] = React.useState(false)
@@ -51,6 +51,7 @@ function BasicSelectPanel() {
5151
onOpenChange={isOpen => {
5252
setOpen(isOpen)
5353
}}
54+
{...passthroughProps}
5455
/>
5556
</ThemeProvider>
5657
)
@@ -200,6 +201,22 @@ for (const useModernActionList of [false, true]) {
200201
expect(onOpenChange).toHaveBeenLastCalledWith(false, 'click-outside')
201202
})
202203

204+
it('should label the list by title unless a aria-label is explicitly passed', async () => {
205+
const user = userEvent.setup()
206+
207+
renderWithFlag(<BasicSelectPanel />, useModernActionList)
208+
await user.click(screen.getByText('Select items'))
209+
expect(screen.getByRole('listbox', {name: 'test title'})).toBeInTheDocument()
210+
})
211+
212+
it('should label the list by aria-label when explicitly passed', async () => {
213+
const user = userEvent.setup()
214+
215+
renderWithFlag(<BasicSelectPanel aria-label="Custom label" />, useModernActionList)
216+
await user.click(screen.getByText('Select items'))
217+
expect(screen.getByRole('listbox', {name: 'Custom label'})).toBeInTheDocument()
218+
})
219+
203220
describe('selection', () => {
204221
it('should select an active option when activated', async () => {
205222
const user = userEvent.setup()
@@ -288,6 +305,35 @@ for (const useModernActionList of [false, true]) {
288305
screen.getByRole('option', {name: 'item one'}).id,
289306
)
290307
})
308+
309+
it('should support navigating through items with PageDown and PageUp', async () => {
310+
if (!useModernActionList) return // this feature is only enabled with feature flag on
311+
312+
const user = userEvent.setup()
313+
314+
renderWithFlag(<BasicSelectPanel />, useModernActionList)
315+
316+
await user.click(screen.getByText('Select items'))
317+
318+
// First item by default should be the active element
319+
expect(document.activeElement!).toHaveAttribute(
320+
'aria-activedescendant',
321+
screen.getByRole('option', {name: 'item one'}).id,
322+
)
323+
324+
await user.type(document.activeElement!, '{PageDown}')
325+
326+
expect(document.activeElement!).toHaveAttribute(
327+
'aria-activedescendant',
328+
screen.getByRole('option', {name: 'item three'}).id,
329+
)
330+
331+
await user.type(document.activeElement!, '{PageUp}')
332+
expect(document.activeElement!).toHaveAttribute(
333+
'aria-activedescendant',
334+
screen.getByRole('option', {name: 'item one'}).id,
335+
)
336+
})
291337
})
292338

293339
describe('filtering', () => {

packages/react/src/SelectPanel/SelectPanel.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ export function SelectPanel({
218218
placeholderText={placeholderText}
219219
{...listProps}
220220
role="listbox"
221+
// browsers give aria-labelledby precedence over aria-label so we need to make sure
222+
// we don't accidentally override props.aria-label
223+
aria-labelledby={listProps['aria-label'] ? undefined : titleId}
221224
aria-multiselectable={isMultiSelectVariant(selected) ? 'true' : 'false'}
222225
selectionVariant={isMultiSelectVariant(selected) ? 'multiple' : 'single'}
223226
items={itemsToRender}

packages/react/src/deprecated/ActionList/List.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ export interface ListPropsBase {
3737
*/
3838
id?: string
3939

40+
/**
41+
* aria-label to attach to the base DOM node of the list
42+
*/
43+
'aria-label'?: string
44+
4045
/**
4146
* A `List`-level custom `Item` renderer. Every `Item` within this `List`
4247
* without a `Group`-level or `Item`-level custom `Item` renderer will be

0 commit comments

Comments
 (0)