Skip to content

Publicize: migrate all Button consumers to @wordpress/ui#48150

Open
lezama wants to merge 14 commits intotrunkfrom
try/publicize-connection-button-wp-ui
Open

Publicize: migrate all Button consumers to @wordpress/ui#48150
lezama wants to merge 14 commits intotrunkfrom
try/publicize-connection-button-wp-ui

Conversation

@lezama
Copy link
Copy Markdown
Contributor

@lezama lezama commented Apr 17, 2026

Part of #48160

Proposed changes

Migrates every Button consumer in the Publicize package from @automattic/jetpack-components to @wordpress/ui, split across commits one per logical surface. Covers:

  • Admin dashboard header ("Connect accounts", "Write a post")
  • Pricing page CTAs ("Get Social", "Start for free")
  • Settings toggles ("Change defaults" for SIG, "Create a note" for Social Notes)
  • Connections modal ("Connect", "Connect more", "Connect an account", service/connection chevron toggles, "Disconnect" in both outline and minimal contexts)
  • Broken-connection flow ("Manage connections" + "Reconnect", swapped to @wordpress/ui Link)
  • Media picker ("Choose image")
  • Per-network share buttons

Prop mapping applied:

@automattic/jetpack-components @wordpress/ui
variant="primary" variant="solid"
variant="secondary" variant="outline"
variant="tertiary" variant="minimal"
variant="link" Link component (or variant="unstyled" for anchor-shaped cases)
isLoading loading
size="normal" size="default"
href={...} (for navigation-on-click) replaced with an onClick handler that sets window.location.href (@wordpress/ui Button renders a <button>, which doesn't honor href); share buttons' existing click handlers already worked
isDestructive removed (no equivalent — Disconnect uses outline/minimal now)
fullWidth replaced with CSS (width: 100%) where layout required it

Two test-side fixes were needed because @wordpress/ui Button (Base UI-backed) uses aria-disabled="true" (with focusableWhenDisabled: true default) instead of the native disabled attribute, and Link renders <a> (role link) rather than <button>:

  • disconnect.test.js: toBeDisabled()toHaveAttribute('aria-disabled', 'true')
  • reconnect.test.js: getByRole('button')getByRole('link'); same aria-disabled swap

Other information

  • Generate changelog entries for this PR (using AI).

@wordpress/ui is already a direct dependency of the Publicize package, so no package.json changes were needed.

Does this pull request change what data or activity we track or use?

No.

Testing instructions

Sequenced to touch every changed button in the fewest sessions. Each step leaves the site in the state the next step needs.

Before you start

Deploy the branch to a Jurassic Ninja (or equivalent) site with Jetpack Social (or Jetpack with Publicize enabled). Confirm you're signed in to WordPress.com.

1. Pricing page (tests Get Social + Start for free buttons)

On a fresh install the pricing page shows automatically. If it doesn't:

wp option update jetpack-social_show_pricing_page 1

Then open the Social admin (/wp-admin/admin.php?page=jetpack-social):

  • ✅ "Get Social" is a solid button; clicking it navigates to the plan redirect URL (no middle-click / open-in-new-tab — it's a <button> with an onClick handler, not an anchor).
  • ✅ "Start for free" is an outline button (click it to proceed).

2. Admin dashboard — no connections yet (tests Connect accounts, Write a post, Change defaults, Choose image in media picker, Create a note)

  • ✅ Header: "Connect accounts" is a solid button; "Write a post" is an outline button — clicking it navigates to the post editor (same-tab only; no middle-click / open-in-new-tab since these are <button>s with onClick, not anchors).
  • Toggle Enable Social Image Generator on.
    • ✅ "Change defaults" outline button appears.
    • Click it → the template picker modal opens.
      • ✅ Media picker shows a "Choose image" outline button inside the dashed box — with its outline visible (a CSS-override regression that was also fixed on this branch).
    • Close the modal.
  • Toggle Enable Social Notes on.
    • ✅ "Create a note" outline button appears; clicking it navigates to the new-note editor.

3. Connections modal (tests service/connection chevrons, Connect / Connect more, Connect an account footer, Disconnect in two variants)

Click "Connect accounts" (from the header) or "Connect an account" (from the connection management widget) to open the Connections modal.

  • ✅ Each service row has a minimal chevron toggle (expand/collapse).
  • ✅ "Connect" buttons per service render solid.
  • Connect any service (Mastodon or Bluesky are fastest — they don't require a real OAuth roundtrip through wpcom).
  • After connecting:
    • ✅ "Connect more" appears on the same service row, outline variant.
    • ✅ Footer "Connect an account" flips from solid → outline.
    • Click the new connection row's chevron (minimal) to expand it.
      • ✅ "Disconnect" small outline button appears. Click it and confirm to verify the aria-disabled state while deleting (button text becomes "Disconnecting…"). Then reconnect for the next step.
    • Separately, expand the service panel itself (not the connection row).
      • ✅ Inside the service panel's connection list, the "Disconnect" button renders with the minimal variant (different visual — this is variant="minimal" via service-connection-info).

4. Broken-connection state (tests Manage connections link in notice + Reconnect unstyled link)

No UI path marks a connection broken, but the social store exposes a client-only updateConnection action that lets us fake it. On the Social admin page, open DevTools Console and run:

// Grab any existing connection id (use the one you just connected in step 3):
const [ conn ] = wp.data.select( 'jetpack-social-plugin' ).getConnections();

// Flip it into the broken state — purely in Redux, no server roundtrip:
wp.data.dispatch( 'jetpack-social-plugin' ).updateConnection(
    conn.connection_id,
    { status: 'broken' }
);

The page updates immediately — no reload needed:

  • ✅ Red Notice at the top: "A social connection needs attention. Manage connections to fix it." "Manage connections" is a brand-colored underlined link (role link; variant="default" of @wordpress/ui Link).
  • Click "Manage connections" → Connections modal opens.
    • ✅ The broken connection row shows a "Reconnect" unstyled link — it inherits the surrounding row's text color/size (that's variant="unstyled").

To restore real state, just reload the page — connections rehydrate from the server.

Tip: try 'must_reauth' as the status too. The notice text stays the same but it also exercises the getMustReauthConnections selector path in broken-connections-notice.tsx.

5. Per-network share buttons (tests share-buttons)

  • Open any post in the block editor.
  • Publish it and look at the post-publish panel; alternatively, use the classic-editor manual sharing meta box.
  • ✅ Network share buttons (Facebook / X / LinkedIn / etc.) render with each network's icon/styling; clicking opens a centered popup window to the service's share URL (same behavior as before the migration — the onClick handler calls window.open with popup sizing).

6. Quick a11y pass (~30 seconds)

  • Tab through the admin page: every interactive element receives a visible focus ring.
  • Disabled buttons (e.g. "Disconnecting…") still get focus but announce as disabled (via aria-disabled="true").

Screenshots

Before After
Screenshot from 2026-04-19 11-32-24 Screenshot from 2026-04-19 11-32-38
Screenshot from 2026-04-19 11-30-52 Screenshot from 2026-04-19 11-30-41
Screenshot from 2026-04-19 11-30-09 Screenshot from 2026-04-19 11-30-15
Screenshot from 2026-04-19 11-28-47 Screenshot from 2026-04-19 11-29-00
Screenshot from 2026-04-19 11-28-10 Screenshot from 2026-04-19 11-28-18
Screenshot from 2026-04-19 11-27-14 Screenshot from 2026-04-19 11-27-21
Screenshot from 2026-04-19 11-25-02 Screenshot from 2026-04-19 11-25-39

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 17, 2026

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the try/publicize-connection-button-wp-ui branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack try/publicize-connection-button-wp-ui

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 17, 2026

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Social plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@github-actions github-actions bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Apr 17, 2026
@jp-launch-control
Copy link
Copy Markdown

jp-launch-control bot commented Apr 17, 2026

Code Coverage Summary

Coverage changed in 7 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/packages/publicize/_inc/components/connection-management/disconnect.tsx 12/16 (75.00%) -15.91% 3 ❤️‍🩹
projects/packages/publicize/_inc/components/form/broken-connections-notice.tsx 0/13 (0.00%) 0.00% 3 ❤️‍🩹
projects/packages/publicize/_inc/components/admin-page/header/index.js 8/9 (88.89%) -11.11% 1 ❤️‍🩹
projects/packages/publicize/_inc/components/admin-page/pricing-page/index.tsx 11/18 (61.11%) -3.59% 1 ❤️‍🩹
projects/packages/publicize/_inc/components/admin-page/toggles/social-notes-toggle/index.tsx 15/26 (57.69%) -2.31% 1 ❤️‍🩹

Full summary · PHP report · JS report

@lezama lezama force-pushed the try/publicize-connection-button-wp-ui branch from 7c6de1c to 0520baf Compare April 17, 2026 18:35
@dhasilva dhasilva marked this pull request as ready for review April 18, 2026 15:15
@dhasilva dhasilva force-pushed the try/publicize-connection-button-wp-ui branch from 0520baf to fb7209c Compare April 18, 2026 15:18
dhasilva and others added 8 commits April 18, 2026 12:15
Migrates the Button in connect-form, connection-info, service-item, and
media-picker from @automattic/jetpack-components to @wordpress/ui, mapping
the variants (primary→solid, secondary→outline, tertiary→minimal).

In media-picker, the shared .preview class used border:0 to strip chrome
from the image-wrapper button. That worked for the old secondary Button
(which draws its border via box-shadow) but wipes the outline of the new
@wordpress/ui Button. Gives the picker Button its own .picker-button class
so the two buttons no longer share conflicting styles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s/ui

Swap the Button-as-link usage for a proper Link component. The Reconnect
control and the "fix" link inside BrokenConnectionsNotice are now anchors
with preventDefault click handlers; Reconnect uses aria-disabled instead
of the now-absent disabled prop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…m styles

Disconnect now renders a wp/ui Button (variant "outline" by default,
"minimal" inside the manage-connections service row) or a wp/ui Link
when called with variant="link" from the unsupported-service branch.
Drops the old isDestructive + buttonClassName props; wp/ui Button has no
destructive tone, and the ConfirmDialog already protects against
accidental disconnects.

Also removes the orphan .fields-item wrapper and .connect-button rule in
the connect form. The wrapper was forcing flex-basis: 50% on the submit
button, which at wp/ui defaults caused "Connect more" to wrap across two
lines. The .connect-button SCSS targeted :global(.components-button),
which wp/ui Button never emits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops useBreakpointMatch in favor of a CSS media query: scoping
justify-self: flex-start to >=600px lets the grid default stretch
kick in on mobile, replicating the old fullWidth={isSmall} behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ress/ui

Mirrors the SIG toggle change: drop useBreakpointMatch + fullWidth, move
justify-self: flex-start into the >=600px media query so the button still
fills its grid cell below 600px.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap @automattic/jetpack-components Button for wp/ui Button with the
SocialServiceIcon rendered as a child. wp/ui's Icon/IconButton extracts
icon.props onto a bare SVG, so SocialServiceIcon (a wrapper component)
gets its props stripped — Button-with-child renders it correctly.

Replicate IconButton's square shape via CSS custom properties
(--wp-ui-button-aspect-ratio, --wp-ui-button-padding-inline,
--wp-ui-button-min-width). Also override --wp-ui-button-border-color to
transparent so the per-network background isn't framed by wp/ui's
default brand-blue border. Add an explicit hover rule for the
darker-gray hover state, scoped to .icon-button; unlayered CSS beats
wp/ui's in-layer hover rule regardless of specificity.

Drop the dead buttonVariant prop (no caller passed it, and the type
would be awkward post-migration with CopyToClipboard still on the old
Button). Drop dead SCSS selectors targeting :global(.components-button),
which wp/ui Button doesn't emit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces fullWidth with a width:100% .cta-button class on both CTAs,
drops useBreakpointMatch and the dead isLarge && styles.button
className (the .button class had already been removed from the SCSS).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dhasilva and others added 2 commits April 19, 2026 09:42
The @wordpress/ui Button (Base UI-backed) represents disabled state via
aria-disabled="true" + data-disabled="" rather than the native disabled
attribute, because focusableWhenDisabled defaults to true so screen-reader
users don't lose focus on a disabled control. jest-dom's toBeDisabled()
only matches the native attribute, so update the assertion to check
aria-disabled directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The @wordpress/ui Button (Base UI-backed) renders a <button> element and
silently ignores href/target/rel. That drops link semantics (role=link),
broke the Jetpack Social e2e connection test's getByRole('link', { name:
'Write a post' }) assertion, and left target/rel non-functional for share
buttons.

Use Base UI's render prop ({ <a /> }) + nativeButton={ false } on the four
remaining href-bearing Buttons so they render as anchors while keeping the
button's visual chrome. The Button stylesheet already has &[href]{...}
rules anticipating this use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dhasilva dhasilva changed the title Publicize: migrate Connect an account button to @wordpress/ui Publicize: migrate all Button consumers to @wordpress/ui Apr 19, 2026
@dhasilva dhasilva self-assigned this Apr 19, 2026
@dhasilva dhasilva removed the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Apr 19, 2026
dhasilva and others added 3 commits April 19, 2026 10:27
Base UI's useButton hook hard-codes role=button into the props it merges
onto the rendered element whenever nativeButton is false, even when the
render prop is <a/>. Result: the previous fix produced an <a href>
element but the accessibility tree still reported it as role=button, so
the social plugin e2e's getByRole('link', { name: 'Write a post' }) kept
failing.

Pass role="link" explicitly on the four anchor-rendered Buttons (header
"Write a post", pricing "Get Social", social-notes "Create a note", and
per-network share buttons). In useButton's merge call
(use-button/useButton.js:169), otherExternalProps come after Base UI's
{ role: 'button' }, so our override wins.

Verified locally by running plugins/social tests/e2e connection.test.ts
against a docker + tunnel env — 3/3 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The render={<a/>} + role=link approach rendered @wordpress/ui Buttons as
anchors, but collided with WP admin's unlayered `a { color }` rule — the
button's layered color lost the cascade, bleaching label text against the
solid variant. Adding a CSS override would work, but it compounds
workarounds against a library primitive whose semantics (Button = button)
are actually correct.

Instead: let the Button stay a <button>, navigate via an explicit onClick
handler, and update the e2e test to assert role=button. Trade-off: loses
middle-click / right-click "open in new tab"; acceptable here because
these three CTAs ("Write a post", "Get Social", "Create a note") are
flow-driven, not navigation-surface links.

Share buttons already had onClick handlers (preventDefault + window.open),
so they just drop the render props. Per-file handlers use useCallback for
stable identity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added the [Plugin] Social Issues about the Jetpack Social plugin label Apr 19, 2026
@dhasilva dhasilva added [Status] Needs Review This PR is ready for review. and removed [Status] In Progress labels Apr 19, 2026
@dhasilva dhasilva requested a review from a team April 19, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

E2E Tests [Feature] Publicize Now Jetpack Social, auto-sharing [Package] Publicize [Plugin] Social Issues about the Jetpack Social plugin [Status] Needs Review This PR is ready for review. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants