Skip to content

A4A: extract A4APopoverTrigger with built-in keyboard a11y - #111038

Merged
tiagonoronha merged 4 commits into
trunkfrom
add/a4a-popover-trigger-component
May 27, 2026
Merged

tiagonoronha merged 4 commits into
trunkfrom
add/a4a-popover-trigger-component

Conversation

@tiagonoronha

@tiagonoronha tiagonoronha commented May 26, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #111013.

Proposed Changes

Screenshot 2026-05-26 at 10 21 52
  • Add a new shared A4APopoverTrigger component (client/a8c-for-agencies/components/a4a-popover/trigger.tsx) that centralizes the popover-trigger pattern: role="button", tabIndex={0}, Enter + Space activation (with preventDefault for Space), forwarded ref, and a built-in :focus-visible outline.
  • Migrate 5 existing trigger call sites to use it, removing the duplicated <span role="button" tabIndex={0} onClick onKeyDown> boilerplate and the per-site cursor: pointer / display: inline-flex / redundant :focus-visible rules:
    • agency-tier/overview-content/influenced-revenue.tsx
    • components/consolidated-stats-card/index.tsx
    • woopayments/dashboard-content/site-columns.tsx
    • purchases/licenses/license-preview/index.tsx (TransferredBadge)
    • referrals/referral-details/components/assigned-to.tsx

Why are these changes being made?

Review feedback on #111013 noted that tabbing to the influenced-revenue info icon left no visible focus indicator. The same <span role="button" tabIndex={0}> pattern was duplicated across multiple A4A surfaces, all sharing the same accessibility gaps (no focus indicator, no Space-key activation). Extracting a shared component fixes the indicator everywhere at once and prevents the pattern from drifting further.

Two existing popover-related files are intentionally untouched:

  • partner-directory/dashboard/status-badge.tsx — hover-driven, different interaction model (and was already missing a keyboard handler).
  • marketplace/checkout/pending-payment-popover.tsx — renders the popover itself, not a trigger.

Testing Instructions

  • Visit any of the migrated surfaces (e.g. Agency Tier overview → influenced revenue, Consolidated Stats Card info icons, WooPayments dashboard status info icon, the Transferred badge on a transferred license in Purchases, and the cancellation info icon in Referrals → referral details).
  • Tab through the page until focus lands on the info icon / badge — a visible outline should appear.
  • Press Enter or Space — the popover should open. Tab outside or click away to close.
  • Mouse click should continue to work as before.

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • For UI changes, have you tested the affected components in dark mode?
  • Have you tested accessibility for your changes? Ensure the feature remains usable with various user agents (e.g., browsers), interfaces (e.g., keyboard navigation), and assistive technologies (e.g., screen readers) (PCYsg-S3g-p2).
  • Have you used memoizing on expensive computations?
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    • For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)?
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?

@github-actions

github-actions Bot commented May 26, 2026 •

Copy link
Copy Markdown

Link to live branch is being generated...
Please wait a few minutes and refresh this page.

@tiagonoronha
tiagonoronha marked this pull request as ready for review May 26, 2026 09:22
Comment thread client/a8c-for-agencies/components/a4a-popover/trigger.tsx
Comment on lines +20 to +24
onKeyDown?.( event );
if ( event.key === 'Enter' || event.key === ' ' ) {
event.preventDefault();
onActivate();
}

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.

[follow-up] Issue: No tests for the shared keyboard-handling logic.

The handleKeyDown has three branches (Enter, Space with preventDefault, and passthrough for other keys). Since this component is now shared across 5 surfaces, a regression here would be wide-reaching. A minimal test covering Enter activation, Space activation with default prevention, and that other keys don't fire onActivate would satisfy the guideline.

Suggestion: Add a small test file (trigger.test.tsx) in this directory covering the key branches.

When implementing non-trivial or complicated logic, add a minimal set of non-redundant tests that cover the key branches and edge cases.

— client/a8c-for-agencies/AGENTS.md

@cleacos cleacos 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.

I found two non-blocking things worth checking:

The icon-only triggers still do not expose an accessible name. Screen readers may announce them only as “button”. Could we add aria-label or aria-labelledby at the icon-only call sites?

In the popover, the “Learn more” link appears focused immediately after opening. It looks a bit odd visually. Ideally, focus would stay on the trigger when the popover opens, and move to the link only after the first Tab press.

image image

— ClemenAgent (Powered by Codex)

Note: This review was prepared fully or partially by an AI agent on my behalf, with my oversight. Please raise any issues, inconsistencies, or missing context in the review.

@tiagonoronha

Copy link
Copy Markdown
Contributor Author

@cleacos thanks for the review — applied both suggestions:

  • Added aria-label to the icon-only triggers so screen readers announce the action.
  • Set focusOnMount={ false } on the info popovers (influenced revenue, WooPayments commission eligibility, consolidated stats card) so focus stays on the trigger when the popover opens and only moves into the content on Tab.

Pushed in 811b9b6.

@matticbot

matticbot commented May 26, 2026 •

Copy link
Copy Markdown
Contributor

This PR modifies the release build for the following Calypso Apps:

For info about this notification, see here: PCYsg-OT6-p2

  • help-center
  • notifications
  • wpcom-block-editor

To test WordPress.com changes, run install-plugin.sh $pluginSlug add/a4a-popover-trigger-component on your sandbox.

@cleacos

cleacos commented May 26, 2026

Copy link
Copy Markdown
Contributor

Thank you @tiagonoronha

Mmmm, I'm not sure if it's a regression, but now I cannot close the popover, and I can open multiple popovers by clicking in the i icon:

image

@cleacos

cleacos commented May 26, 2026

Copy link
Copy Markdown
Contributor

✅ Re-approved!

Base automatically changed from add/a4a-influenced-revenue-tooltip to trunk May 27, 2026 13:10
The popover trigger pattern (span with role="button", tabIndex, click and
keydown handlers) was duplicated across multiple call sites, all missing a
visible focus indicator and Space-key activation. Extract a shared
A4APopoverTrigger component that centralizes these accessibility concerns
and migrate the existing trigger sites.
Address review feedback on the popover triggers used for agency-tier
influenced revenue, WooPayments commission eligibility, consolidated
stats cards, and referral cancellation:

- Add aria-label to the icon-only triggers so screen readers announce
  the action instead of an unnamed "button".
- Forward focusOnMount through A4APopover and set it to false on the
  three info tooltips that previously moved focus onto a "Learn more"
  link the moment the popover opened. Focus now stays on the trigger
  and tabs into the popover content.
Switching focusOnMount to false kept focus on the trigger but also broke
the popovers' close-on-outside-focus behavior — popovers couldn't be
dismissed and clicking other triggers stacked them. Using "container"
instead moves focus into the popover wrapper (so the "Learn more" link
is no longer auto-focused) while keeping onFocusOutside functional.
@tiagonoronha
tiagonoronha force-pushed the add/a4a-popover-trigger-component branch from a8cfdcd to 60e13d4 Compare May 27, 2026 13:16
@tiagonoronha
tiagonoronha merged commit 9cb1ee2 into trunk May 27, 2026
15 checks passed
@tiagonoronha
tiagonoronha deleted the add/a4a-popover-trigger-component branch May 27, 2026 13:48
@a8ci18n

a8ci18n commented May 27, 2026 •

Copy link
Copy Markdown

This Pull Request is now available for translation here: https://translate.wordpress.com/deliverables/33689804

Some locales (Hebrew) have been temporarily machine-translated due to translator availability. All other translations are usually ready within a few days. Untranslated and machine-translated strings will be sent for translation next Monday and are expected to be completed by the following Friday.

Thank you @tiagonoronha for including a screenshot in the description! This is really helpful for our translators.

xavier-lc pushed a commit that referenced this pull request May 28, 2026
The bump of `@wordpress/compose` from 7.45 to 8.0 in this PR added a
`useFocusOnMount.Mode` namespace export with a strict literal union type:
`boolean | 'firstElement' | 'firstInputElement'`. Previously the
namespace didn't exist, so `useFocusOnMount.Mode` resolved to `any` and
the `"container"` value used in three A4A popovers (introduced in
#111038) type-checked without complaint.

At runtime, `"container"` and `true` are equivalent — both fall through
to `setFocus(node)` on the popover wrapper in both 7.45 and 8.0, since
the implementation only special-cases `false`, `'firstElement'`, and
`'firstInputElement'`. Switching to the JSX prop shorthand `focusOnMount`
(which evaluates to `true`) preserves behavior — focus moves into the
popover wrapper, `onFocusOutside` keeps working, and "Learn more" links
inside are no longer auto-focused — while satisfying the narrowed type.
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.

4 participants