Repository navigation
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #5180 +/- ##
========================================
Coverage 97.74% 97.74%
========================================
Files 1000 1008 +8
Lines 32309 32662 +353
Branches 11949 12061 +112
========================================
+ Hits 31579 31926 +347
- Misses 685 691 +6
Partials 45 45 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
9f3d361 to
290cc68
Compare
e691621 to
c9c005e
Compare
290cc68 to
5601acf
Compare
4c74889 to
5aaaf47
Compare
228673d to
b05ade6
Compare
1dfd1e0 to
10601c8
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved keyboard navigation, focus, scrolling, accessibility, and renderer-contract defects affect the new component’s behavior.
9 open issues
Scroll on repeated keyboard navigation requests · New Preserve parent group state for child renderers · New Avoid dangling aria-describedby for custom disabled content · New Reconcile highlight index when options change · New Wrap navigation based on highlightable options · New Initialize keyboard focus despite stale hover highlights · New Exclude nonmatching group headers from type-ahead targets · New Ignore handled events before updating type-ahead search · New Define an OptionList-specific renderer contract · New
What changed in this PR
Adds OptionList as an inline single- or multi-selection component, building on #5179’s shared selection models. It is the first caller of the new listbox engine, while Select and Multiselect retain their existing behavior.
Changes:
- Adds the public component, grouped selection, custom rendering, and localized announcements.
- Introduces listbox keyboard, focus, scrolling, and item-rendering support.
- Adds test-utils, unit and integration tests, demo pages, and generated API snapshots.
| File | Description |
|---|---|
src/test-utils/dom/option-list/index.ts |
Adds option and group wrappers. |
src/option-list/test-classes/styles.scss |
Defines stable test selectors. |
src/option-list/styles.scss |
Adds component root styling. |
src/option-list/internal.tsx |
Connects public options to the listbox engine. |
src/option-list/interfaces.ts |
Defines the public component API. |
src/option-list/index.tsx |
Adds the public entry point. |
src/option-list/__tests__/option-list.test.tsx |
Tests component behavior and accessibility. |
src/option-list/__integ__/option-list.test.ts |
Tests browser selection and focus. |
src/option-list/__integ__/option-list-scroll.test.ts |
Tests keyboard scrolling across containers. |
src/internal/components/selectable-item/styles.scss |
Supports hiding the first divider. |
src/internal/components/selectable-item/interfaces.ts |
Adds the divider-control prop. |
src/internal/components/selectable-item/index.tsx |
Applies the divider-control class. |
src/internal/components/options-list/utils/use-keyboard.ts |
Makes Escape handling optional. |
src/internal/components/options-list/index.tsx |
Supports mouse-leave and default status. |
src/internal/components/listbox/use-type-ahead.ts |
Adds buffered keyboard matching. |
src/internal/components/listbox/use-listbox.ts |
Implements the listbox interaction controller. |
src/internal/components/listbox/parts/styles.scss |
Styles selection indicators and items. |
src/internal/components/listbox/parts/scroll-to-index.ts |
Adds ancestor-aware option scrolling. |
src/internal/components/listbox/parts/render-options.tsx |
Supports the new listbox item renderer. |
src/internal/components/listbox/parts/list.tsx |
Renders and measures inline lists. |
src/internal/components/listbox/parts/item.tsx |
Renders selection states and disabled reasons. |
src/internal/components/listbox/index.tsx |
Connects rendering and highlight scrolling. |
src/internal/components/listbox/__tests__/use-type-ahead.test.ts |
Tests type-ahead handling. |
src/i18n/messages/all.zh-TW.json |
Adds Traditional Chinese selection text. |
src/i18n/messages/all.zh-CN.json |
Adds Simplified Chinese selection text. |
src/i18n/messages/all.tr.json |
Adds Turkish selection text. |
src/i18n/messages/all.th.json |
Adds Thai selection text. |
src/i18n/messages/all.pt-BR.json |
Adds Brazilian Portuguese selection text. |
src/i18n/messages/all.ko.json |
Adds Korean selection text. |
src/i18n/messages/all.ja.json |
Adds Japanese selection text. |
src/i18n/messages/all.it.json |
Adds Italian selection text. |
src/i18n/messages/all.id.json |
Adds Indonesian selection text. |
src/i18n/messages/all.fr.json |
Adds French selection text. |
src/i18n/messages/all.es.json |
Adds Spanish selection text. |
src/i18n/messages/all.en.json |
Adds English selection text. |
src/i18n/messages/all.en-GB.json |
Adds British English selection text. |
src/i18n/messages/all.de.json |
Adds German selection text. |
src/i18n/messages/all.ar.json |
Adds Arabic selection text. |
src/i18n/messages-types.ts |
Registers the localized message type. |
src/__tests__/snapshot-tests/__snapshots__/test-utils-wrappers.test.tsx.snap |
Records generated wrapper APIs. |
src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap |
Records component test selectors. |
src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap |
Records component and wrapper documentation. |
src/__tests__/required-props-for-components.ts |
Supplies shared-test defaults. |
pages/option-list/simple.page.tsx |
Demonstrates single, multi, and grouped selection. |
pages/option-list/scroll.scss |
Defines demo scroll containers. |
pages/option-list/scroll.page.tsx |
Demonstrates configurable scrolling and dropdown placement. |
pages/option-list/permutations.page.tsx |
Adds visual permutations. |
eslint.config.mjs |
Restricts direct runtime imports from Select and Multiselect. |
build-tools/utils/pluralize.js |
Registers OptionList’s generated plural name. |
Review effort: Balanced
Use PermutationsPage for the permutations page, like other components. The simple and scroll pages already use SimplePage. No behaviour change.
Scrolling the OptionList root through className exercises the same path as a wrapper div: the highlight scrolls into its nearest scrolling ancestor either way. Styling a component's root is also not a supported pattern, so keep only the wrapper case.
OptionList's renderOption now uses its own item types with the icon slot and multi-selection group states. Replaces Select's renderer type. No runtime change.
The parent group item took the disabled state of the option instead of its group, so an enabled group with one disabled option reported different parent states to its options. Pass the group's own props in both selection modes, and regenerate the documenter snapshot, which also listed renderOption item fields that the source doesn't document.
With renderOption, a disabled option kept aria-describedby pointing to its disabled reason, but the hidden description element wasn't rendered, so the reference was dangling. Keep the hidden description mounted for custom content so screen readers still announce the reason. The tooltip stays limited to the default content.
…ptions In single selection, group headers can't be highlighted, and an empty group leaves a header row at the start or end of the list. ArrowUp and ArrowDown checked the raw first and last rows, so they didn't wrap and the highlight got stuck. Check against the first and last highlightable options instead. Also note that keyboard navigation must use the scrolling setters.
…over If an option was hovered while the list wasn't focused, keyboard focus entry (Tab or ref.focus()) kept that mouse highlight instead of highlighting the selected option, or the first option. The keyboard style and the scroll into view were skipped. Only keep the current highlight for focus caused by a click.
Options are matched by value, so options without one couldn't be told apart: with two options missing a value, selecting the second also marked the first as selected. Make value required on OptionListProps.Option, so this is caught at compile time. Group values stay optional, because selection never uses them. A group header doesn't appear in selectedOptions, and choosing it in multi selection toggles its options by their own values. The group value only locates the header in test utils. Map the options the list returns back to the consumer's options through typed lookups instead of falling back to internal options.
Custom renderOption content can show text the option's own fields don't contain, so the default highlight announcement no longer matches what users see. Select already exposes renderHighlightedAriaLive for this. Add the same prop to OptionList. It receives the consumer's original option and group objects and its return value replaces the default announcement for the highlighted option.
The scroll effect runs on mount with highlighted index -1. Single-selection group headers are not interactive and also render with data-mouse-target="-1", so the lookup found a header and called scrollIntoView on it. That could scroll the page or a surrounding container when an unfocused grouped list mounted. Return early from scrollOptionIntoView for negative indexes.
Split the single option-list.test.tsx into feature files that follow the Select and Multiselect __tests__ layout, with shared fixtures and helpers (Stateful, renderOptionList, option fixtures, key/click helpers) in common.tsx: - option-list.test.tsx: rendering, selection, ref, root attributes - disabled.test.tsx: disabled options and disabled reasons (Select: disabled) - keyboard.test.tsx: arrow/Home/End, Enter/Space, Escape, type-ahead, blur - highlight.test.tsx: focus entry, hover, scrolling, highlight reconciliation when options change; the only file that mocks scrollIntoView - group-state.test.tsx: groups, group checkbox state, group navigation and group type-ahead (Select/Multiselect: group-state) - render-option.test.tsx: renderOption item shapes (Select/Multiselect) - option-list-a11y.test.tsx: axe checks and renderHighlightedAriaLive (Select: select-a11y) - i18n.test.tsx: selectedAriaLabel via props and the i18n provider Merged tests (assertions kept, test removed): - "blur clears the highlight" and "has no aria-activedescendant without a highlighted option" ran the same render/focus/blur steps; merged into "blur clears the highlight and aria-activedescendant". - "multi: group header items include the checkbox state" was covered by the icon/group-state renderOption test except the group option check, which moved into that test.
- keyboard: removed "type-ahead highlights the matching option"; the same 't' -> 'c' highlight is asserted by "type-ahead after Space selects moves the highlight to the matching option". - keyboard: folded "Home and End move the highlight without the browser scrolling the page" into "focus highlights the first option, arrows move with wrap-around, Home and End jump", which now checks defaultPrevented on the End and Home keydown events. - i18n: removed "announces the highlighted option as selected with i18nStrings.selectedAriaLabel"; "i18nStrings.selectedAriaLabel takes precedence over the provider message" asserts the prop string is rendered. - highlight: folded "repeating a keyboard move that keeps the highlight scrolls it into view again" into "keyboard moves scroll the highlighted option into view" (sequence down, end, end, home, up; 5 scrolls). - disabled: folded "shows the disabled reason tooltip when a disabled option is highlighted with the keyboard" into "Escape closes the disabled reason tooltip and keeps the highlight and selection", which now uses a ReactNode disabledReason and asserts the tooltip content before pressing Escape.
- option-list.test.tsx > selection > "single: uses only the first selected option": covered by listbox single-selection.test.ts > "only the first selected option is kept". - option-list.test.tsx > rendering > "hides the top divider of the first option only": asserted a CSS class only. - highlight.test.tsx > "does not show a highlight before the user interacts with the list": covered by keyboard > "blur clears the highlight and aria-activedescendant" and highlight > "focus from a pointer adds no highlight and does not scroll". - group-state.test.tsx > "announces the group of the highlighted option": covered by listbox use-announcement.test.ts group tests and option-list-a11y > renderHighlightedAriaLive group test.
9e4db4e to
4925c40
Compare
| @@ -0,0 +1,65 @@ | |||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | |||
There was a problem hiding this comment.
nit: The permutations page is very long and can exceed our limit if a more cases are added. I recommend to split it in two pages, e.g. permutations-single and permutations-multi.
| // OptionList and the shared listbox engine sit below Select and Multiselect, so they must not depend on them at | ||
| // runtime. Type-only imports (e.g. the `renderOption` types) are allowed. The remaining listbox files join this | ||
| // rule when Select and Multiselect move onto the engine. | ||
| files: [ |
|
|
||
| export interface OptionListProps extends BaseComponentProps { | ||
| /** | ||
| * Specifies an array of options that are displayed inline as a list. |
There was a problem hiding this comment.
The "displayed inline" part can be misunderstood. I'd drop the "inline" bit.
| * - `disabled` (boolean) - (Optional) Determines whether the option group is disabled. Disables every option in the group. | ||
| * - `options` (Option[]) - The options in the group. | ||
| * | ||
| * In multi selection, choosing a group header selects or deselects all its enabled options. |
There was a problem hiding this comment.
nit: Why do we need to describe this behaviour here?

Description
Stacked on #5179.
What: Adds OptionList, an inline list with single or multi selection, plus its test-utils and dev pages. It
runs on the new internal list engine, with OptionList as its only caller. Select and Multiselect behave the same.
Related links, issue #, if available: Docs: CUJAl2wq1b6Z and 5evHnSwN7iHe
How has this been tested?
option-listdev pages.The following items are to be evaluated by the author(s) and the reviewer(s).
Correctness
CONTRIBUTING.md.CONTRIBUTING.md.Security
checkSafeUrlfunction.Testing
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.