Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Masterbar: fix invalid HTML markup #98510

Merged
merged 5 commits into from
Jan 21, 2025
Merged

Masterbar: fix invalid HTML markup #98510

merged 5 commits into from
Jan 21, 2025

Conversation

ciampo
Copy link
Contributor

@ciampo ciampo commented Jan 16, 2025

Related to #98365

Proposed Changes

  • Move master bar subitems from being children of the parent menu item to being siblings
  • Do not render interactive elements such as button and a as children of button
  • Normalize the markup, always rendering the item wrapper — this makes it more predictable

Why are these changes being made?

The current structure generates invalid (button rendered as a child of another button) and generally inaccessible (interactive elements like a as children of button) HTML markup

Note

This PR only aims at fixing the most critical issue (invalid markup) with the masterbar markup. As documented in #98365, the master bar semantics and general accessibility are still far from ideal — but that should be tackled in separate PRs. Given the effort required for that potential refactor, we should discuss our options carefully before embarking on that endeavour, especially given how we're slowly showing wp-admin screens over calypso ones.

Testing Instructions

  • Load Calypso in a variety of pages/scenarios:
    • Logged out: eg. /discover
    • Logged in: eg. home page
    • With paid items in the cart
  • Test on multiple viewport sizes
  • Test both pointer, keyboard, and touchscreen interactions

In all of these scenarios:

  • The master bar should not include invalid HTML markup (button or a as a child of button)
  • The error in the browser console about button rendered inside a button currently displayed on trunk should be gone
  • The look&feel and the UX should be the same as on trunk

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?
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • 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)? The length of text and words vary significantly between languages.
  • 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)?

@matticbot
Copy link
Contributor

matticbot commented Jan 16, 2025

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

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

  • notifications
  • wpcom-block-editor

To test WordPress.com changes, run install-plugin.sh $pluginSlug fix/masterbar-invalid-markup on your sandbox.

@matticbot
Copy link
Contributor

matticbot commented Jan 16, 2025

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

App Entrypoints (~69 bytes removed 📉 [gzipped])

name         parsed_size           gzip_size
entry-main        -411 B  (-0.0%)      -69 B  (-0.0%)
entry-login       -411 B  (-0.0%)      -69 B  (-0.0%)

Common code that is always downloaded and parsed every time the app is loaded, no matter which route is used.

Async-loaded Components (~70 bytes removed 📉 [gzipped])

name                                        parsed_size           gzip_size
async-load-calypso-my-sites-checkout-modal       -411 B  (-0.0%)      -70 B  (-0.0%)

React components that are loaded lazily, when a certain part of UI is displayed for the first time.

Legend

What is parsed and gzip size?

Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

Generated by performance advisor bot at iscalypsofastyet.com.

@ciampo ciampo force-pushed the fix/masterbar-invalid-markup branch 3 times, most recently from 6b6d4cc to b2eb737 Compare January 17, 2025 17:22
@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Jan 17, 2025
@ciampo ciampo requested review from okmttdhr, xavier-lc, a team and lsl January 17, 2025 17:46
@ciampo ciampo self-assigned this Jan 17, 2025
@ciampo ciampo added [Type] Bug When a feature is broken and / or not performing as intended Masterbar labels Jan 17, 2025
@ciampo ciampo marked this pull request as ready for review January 17, 2025 17:47
@ciampo ciampo force-pushed the fix/masterbar-invalid-markup branch 2 times, most recently from 1096e2a to 3d58fb3 Compare January 19, 2025 17:19
Comment on lines -63 to -64
componentButtonRef = React.createRef< HTMLButtonElement >();
componentDivRef = React.createRef< HTMLDivElement >();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

By always rendering the wrapper, and either and a or a button, componentButtonRef is not necessary anymore, and componentDivRef was renamed to wapperRef for better clarity

Comment on lines -194 to -199
const isInComponentButtonRef = this.componentButtonRef.current?.contains(
event.target as Node
);
const isInComponentDivRef = this.componentDivRef.current?.contains( event.target as Node );

if ( ! isInComponentButtonRef && ! isInComponentDivRef ) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Related to the comment above, since the wrapper button was removed, the logic can be simplified to only check whether we're in the wrapper div (which is now always rendered)

@@ -221,49 +216,30 @@ class MasterbarItem extends Component< MasterbarItemProps > {
disabled: this.props.disabled,
};

if ( this.props.url && ! this.props.subItems ) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved from

if ( this.props.url && ! this.props.subItems ) {
  // render anchor tag
}

if ( this.props.url && this.props.subItems ) {
  // render button with anchor tag and sub-menu items as children
}

// else, if props.url is not defined
// render a wrapper div > button > menu items as children (no anchor tag)

to:

  • always rendering a wrapper div
  • render either a button or an a depending on props.url
  • render the sub-menu items (when defined) as sibling of the button/a

href={ this.props.url }
ref={ this.props.innerRef as LegacyRef< HTMLAnchorElement > }
onTouchEnd={ this.toggleMenuByTouch }
tabIndex={ -1 }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we don't render a/button nested in a parent button, the tabindex={-1} shouldn't be necessary anymore


return (
<div className={ this.props.wrapperClassName } ref={ this.componentDivRef }>
<button
<div
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Always rendering the div could cause some styles to break, but I hopefully managed to catch and amend them all. Lots of testing needed to make sure I didn't miss any edge case

@ciampo
Copy link
Contributor Author

ciampo commented Jan 19, 2025

E2E test failures could be related, I'll look into them and report.

Copy link
Contributor

@lsl lsl left a comment

Choose a reason for hiding this comment

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

Nice simplification, I don't see any major regressions.

lsl
lsl previously requested changes Jan 19, 2025
Copy link
Contributor

@lsl lsl left a comment

Choose a reason for hiding this comment

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

Sorry. Monday brain, I just realized that this wasn't changing the sidebar but the masterbar. Testing that, there is a blocking regression on mouse hover here:

Peek.2025-01-20.09-50.mp4

Please dismiss the "needs changes" review once resolved.

- avoid nesting a inside button: render one or the other
- always render wrapper div for consistency
- render subitems as siblings of the parent menuitem (and not nested)
- less forks in the logic
- tweak styles to follow the new markup
@ciampo ciampo force-pushed the fix/masterbar-invalid-markup branch from 136c854 to abd76a2 Compare January 20, 2025 10:58
@ciampo ciampo requested a review from lsl January 20, 2025 12:33
@ciampo
Copy link
Contributor Author

ciampo commented Jan 20, 2025

@lsl 's bug should be addressed with abd76a2, and e2e tests are also passing.

Can I get another round of review? Given the breadth of the changes, I'd feel better with more people reviewing this PR.

@ciampo ciampo dismissed lsl’s stale review January 20, 2025 14:13

Blocking bug should be fixed

Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

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

LGTM and tests well 👍

A few unrelated potential follow-ups I've noticed while testing on more narrow views (<782px):

  • The sites menu item (the top left one) doesn't have a pointer cursor when hovering
  • The "log out" item when hovering the Me item appears misaligned:

Screenshot 2025-01-20 at 17 05 29

@ciampo ciampo merged commit ff8b5a2 into trunk Jan 21, 2025
14 checks passed
@ciampo ciampo deleted the fix/masterbar-invalid-markup branch January 21, 2025 08:54
@github-actions github-actions bot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Jan 21, 2025
@ciampo
Copy link
Contributor Author

ciampo commented Jan 21, 2025

Follow-up PRs:

  • The sites menu item (the top left one) doesn't have a pointer cursor when hovering

#98651

  • The "log out" item when hovering the Me item appears misaligned

#98656

JessBoctor pushed a commit that referenced this pull request Jan 22, 2025
* Reorganize and simplify markup:
- avoid nesting a inside button: render one or the other
- always render wrapper div for consistency
- render subitems as siblings of the parent menuitem (and not nested)
- less forks in the logic
- tweak styles to follow the new markup

* Add back subitems, tweak CSS selectors to match new markup

* Remove code comments

* Revert to && instead of ternary

* Keep submenu opened while hovering
@ciampo ciampo mentioned this pull request Jan 23, 2025
@@ -221,49 +216,30 @@ class MasterbarItem extends Component< MasterbarItemProps > {
disabled: this.props.disabled,
};

if ( this.props.url && ! this.props.subItems ) {
return (
const MenuItem = ( props: React.HTMLAttributes< HTMLElement > ) =>
Copy link
Member

Choose a reason for hiding this comment

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

FYI it's not a good idea to create components inside a render method. It causes the component to be recreated on every render which can have side effects (like this). See #98897 which should fix this issue by moving the component's definition outside the render method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Masterbar [Type] Bug When a feature is broken and / or not performing as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants