Skip to content

feat: ButtonDropdown async loading and manual filtering - #5105

Open
NathanZlion wants to merge 17 commits into
mainfrom
dev-v3-natidere-button-dropdown-async-filtering
Open

NathanZlion wants to merge 17 commits into
mainfrom
dev-v3-natidere-button-dropdown-async-filtering

Conversation

@NathanZlion

@NathanZlion NathanZlion commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Description

Adds async loading and manual filtering to ButtonDropdown. These follow the same model Select and Multiselect already use.

Until now actions were required to be passed upfront synchronously. Changes in this PR enable Asynchronous Loading and Manual Filtering feature for Button Dropdown.

  • Async loading: the new onLoadItems event fires when the dropdown opens, when the user scrolls to the end, on Retry, and when a group is expanded. asyncLoadingProps sets the status (pending, loading, finished, error) and its texts.
  • Async groups: expandable groups can load their items when expanded. getExpandableItemsAsyncLoadingState reports each group's own status.
  • Manual filtering: with filteringType="manual", the component stops filtering on the client and passes the filter text to onLoadItems.

The status footer, live region and Retry are Select's existing shared code. useLoadItems is adapted from Select, with group loading added.

API proposal: K9PFTzwiUlNH · Bug bash: KytjqOibVjRy · Release sign-off: 9XKd8i0qDr2l

Related links, issue #, if available: n/a

How has this been tested?

  • Unit tests cover the main list, groups on desktop and mobile, Retry and focus, keyboard behaviour and manual filtering. There are no new integration tests.
  • The feature was bug bashed, and an A11Y sanity check has been requested.

To review, open the dev pages:

Review checklist

The following items are to be evaluated by the author(s) and the reviewer(s).

Correctness

  • Changes include appropriate documentation updates.
  • Changes are backward-compatible if not indicated, see CONTRIBUTING.md.
  • Changes do not include unsupported browser features, see CONTRIBUTING.md.
  • Changes were manually tested for accessibility, see accessibility guidelines.

Security

Testing

  • Changes are covered with new/existing unit tests?
  • Changes are covered with new/existing integration tests?

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

NathanZlion and others added 16 commits September 3, 2026 10:36
- Render DropdownStatus directly (no DropdownFooter border) when
  items are empty and status is sticky (loading/error state)
- Apply fix to both main dropdown (internal.tsx) and expandable
  group sub-dropdown (expandable-category-element.tsx)
- Remove dead code: fireGroupLoadItems was exported but never called
- Add use-load-items.test.tsx covering handleLoadMore and
  handleRecoveryClick branches
- Improve test coverage for DropdownStatus direct-render path
…n-dropdown-async-filtering

# Conflicts:
#	src/button-dropdown/utils/use-button-dropdown.ts
…recovery

- Share the expandable group status logic between the desktop fly-out and the
  mobile inline group so both render loading, error, empty, and finished states
- Route group recovery through the root so focus returns to the filter input in
  filtering mode; group events never carry the root filtering text
- Pass the real async status to the options list so a short first page loads
  the next one, as in Select
- Treat a group status of pending as finished: groups load as a single page
- Test utils: expandedGroupDropdown also resolves inline mobile groups; drop the
  duplicate stylesheet import
Reference getExpandableItemsAsyncLoadingState instead of a non-existent prop,
describe when onLoadItems fires, document that the text properties receive the
group id, and use items instead of options throughout.
Status, item presets, filtering type, delay, and expandToViewport are now
AppContext URL params so each state is linkable; fetched results stay local.
Descriptions now match the actual 5 s timeouts.
…t utils

The root and group status footers now carry distinct markers, so
findErrorRecoveryButton, findStatusIndicator, and findFooterRegion never
return an expanded group's status when the root status was requested, and
vice versa. Also documents noMatch for manual filtering and when the
open event is deduplicated.
Narrow getExpandableItemsAsyncLoadingState to item groups, move
test-utils-only classes to test-classes/styles.scss, and reduce the
async-loading and manual-filtering dev pages to a single component
selected through a scenario URL param.
Clarify behavior when status is null or undefined in documentation.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Render mobile group loading, error and empty status outside the
  group's role="menu" so the Retry link is not inside the menu. The
  menu still references the status with aria-describedby.
- Document that expandable groups need an id for async loading and
  warn when a group without an id is expanded.
- Keep the dropdown open when Enter or Space is pressed while an
  expanded group's items are still loading.
The root list showed the `empty` content whenever it had no items,
including while `statusType` was `pending`. The API documents `empty`
as shown only when the status is `finished` or not set, so gate it on
the normalized status.
@codecov

codecov Bot commented Oct 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.72%. Comparing base (163e9b6) to head (0ce3191).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5105      +/-   ##
==========================================
+ Coverage   97.70%   97.72%   +0.01%     
==========================================
  Files         989      992       +3     
  Lines       31806    31943     +137     
  Branches    11748    11837      +89     
==========================================
+ Hits        31077    31215     +138     
+ Misses        722      682      -40     
- Partials        7       46      +39     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@NathanZlion
NathanZlion marked this pull request as ready for review October 5, 2026 14:57
@NathanZlion
NathanZlion requested a review from a team as a code owner October 5, 2026 14:57
@NathanZlion
NathanZlion requested review from SpyZzey and removed request for a team October 5, 2026 14:57
@SpyZzey
SpyZzey requested a balanced review from Copilot October 7, 2026 10:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Finished-status rows create invalid ARIA menu ownership on desktop and mobile.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
What changed in this PR

Adds asynchronous loading, expandable-group loading, and manual filtering to ButtonDropdown.

Changes:

  • Adds loading APIs, status footers, retry behavior, and i18n.
  • Supports manually filtered items and async expandable groups.
  • Adds unit tests, test utilities, snapshots, and demo pages.
File Description
src/​test-utils/​dom/​button-dropdown/​index.ts Adds scoped status and recovery selectors.
src/​i18n/​messages/​all.en.json Adds default retry and error labels.
src/​i18n/​messages-types.ts Types the new translations.
src/​button-dropdown/​utils/​use-load-items.ts Implements loading-event behavior.
src/​button-dropdown/​utils/​use-highlighted-menu.ts Tracks pending group highlights.
src/​button-dropdown/​utils/​use-button-dropdown.ts Integrates filtering, loading, retry, and group expansion.
src/​button-dropdown/​test-classes/​styles.scss Adds test-only status markers.
src/​button-dropdown/​status-footer.tsx Wraps shared status-footer interactions.
src/​button-dropdown/​items-list.tsx Passes async state into groups.
src/​button-dropdown/​internal.tsx Integrates the feature into rendering and behavior.
src/​button-dropdown/​internal-interfaces.ts Extends internal async-loading props.
src/​button-dropdown/​interfaces.ts Defines the new public API.
src/​button-dropdown/​index.tsx Forwards the public props.
src/​button-dropdown/​category-elements/​use-expandable-group-status.ts Derives per-group status UI.
src/​button-dropdown/​category-elements/​mobile-expandable-category-element.tsx Renders mobile group statuses.
src/​button-dropdown/​category-elements/​expandable-category-element.tsx Renders desktop group statuses.
src/​button-dropdown/​__tests__/​use-load-items.test.tsx Tests loading-event semantics.
src/​button-dropdown/​__tests__/​button-dropdown-async-loading.test.tsx Tests desktop async behavior.
src/​button-dropdown/​__tests__/​button-dropdown-async-loading-mobile.test.tsx Tests mobile async groups.
src/​__tests__/​snapshot-tests/​__snapshots__/​test-utils-selectors.test.tsx.snap Updates selector snapshots.
src/​__tests__/​snapshot-tests/​__snapshots__/​documenter.test.ts.snap Updates generated API snapshots.
pages/​button-dropdown/​manual-filtering.page.tsx Adds manual-filtering scenarios.
pages/​button-dropdown/​button-dropdown.async.example.page.tsx Adds a full async example.
pages/​button-dropdown/​async-loading.page.tsx Adds configurable async-loading scenarios.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

) : null}
{groupDropdownStatus.content && !groupDropdownStatus.isSticky ? (
// Non-sticky status (finished text) follows the items, like in the main dropdown.
<li role="presentation">
) : null}
{groupDropdownStatus.content && !groupDropdownStatus.isSticky ? (
// Finished text has no interactive content, so it follows the items inside the menu, like on desktop.
<li role="presentation">
{dropdownStatus.content && !dropdownStatus.isSticky ? (
// Non-sticky status (finished text) scrolls together with the items, like the
// list bottom in Select, instead of covering the last item.
<li role="presentation">

This branch was successfully deployed

3 active deployments
dev-pages-react16 — 0ce31917 Deployed Oct 5, 2026 by NathanZlion via deploy (React 16) / deploy #2546
dev-pages-react18 — 0ce31917 Deployed Oct 5, 2026 by NathanZlion via deploy (React 18) / deploy #2546
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants