Skip to content

Normalize admin page headers with unified AdminHeader wrapping @wordpress/admin-ui#47313

Open
CGastrell wants to merge 13 commits intotrunkfrom
update/normalize-admin-page-headers
Open

Normalize admin page headers with unified AdminHeader wrapping @wordpress/admin-ui#47313
CGastrell wants to merge 13 commits intotrunkfrom
update/normalize-admin-page-headers

Conversation

@CGastrell
Copy link
Contributor

@CGastrell CGastrell commented Feb 25, 2026

Summary

Introduces a unified AdminHeader component in @automattic/jetpack-components that wraps @wordpress/admin-ui's Page component, providing a consistent header across all Jetpack admin pages. Migrates publicize/Social as the first proof of concept.

What this PR does

  • Adds AdminHeader component to jetpack-components — a thin wrapper around @wordpress/admin-ui Page that composes a logo + title into the header. Defaults to the Jetpack bolt icon when no custom logo is provided.
  • Extends AdminPage props with title, subTitle, logo, actions, tabs, breadcrumbs, badges. When title is present, renders the new AdminHeader; otherwise falls back to the legacy header prop path. Fully backward compatible.
  • Migrates publicize admin page to use title="Social" instead of the custom AdminPageHeader component. Deletes the now-unused page-header/ directory (inline SVG logo, styles, component, test).
Before After
image image
image image
image image

Long-term plan

This is a step in the p1HpG7-xCB-p2 effort (i1 designs by @keoshi and @sanjagrbic). Figma: fBCpFRsWEkIP0rx9AXNIJt-fi-4580_27730

Architecture:

AdminPage (jetpack-components)
  └── AdminHeader (jetpack-components) — thin wrapper, adds logo default
        └── Page (@wordpress/admin-ui) — upstream Gutenberg component

AdminHeader adds only two things that @wordpress/admin-ui Page doesn't have:

  1. A default JetpackLogo bolt icon when no logo prop is provided
  2. Logo composed alongside the title text in an HStack

Migration pattern for each remaining product:

  1. Pass title="ProductName" to AdminPage (optionally subTitle, actions, tabs)
  2. Delete the old custom header component
  3. Add a changelog entry

Products to migrate next (~13 remaining):

Product Current approach Migration complexity
backup Custom <Header> with logo SVG Simple — swap to title prop
videopress header={<JetpackVideoPressLogo/>} Simple
search (upsell) Custom <Header> with logo Simple
search (dashboard) Inline header Simple
newsletter Custom <Header> with logo + title Simple
my-jetpack Default <JetpackLogo/> fallback Simple
protect header={<JetpackProtectLogo/>} Simple
classic-theme-helper Default fallback Simple
starter-plugin Default fallback Simple
boost Fully custom Header Medium — custom layout
jetpack (main plugin) Custom Masthead class component Medium — class component
crm PHP-rendered header Medium — PHP/JS boundary
forms Local admin-ui copy Special — already uses admin-ui pattern, needs to switch to shared component

Future convergence: Once all products use AdminHeader, the wrapper itself can eventually be removed in favor of using @wordpress/admin-ui Page directly — the props already map 1:1.

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

No

Testing instructions:

  • jetpack build packages/publicize passes
  • jetpack test js packages/publicize passes
  • jetpack test php packages/publicize passes
  • Visual check: Social admin page renders header with Jetpack bolt icon + "Social" title
  • Verify no regressions on other admin pages (AdminPage backward compatibility)

🤖 Generated with Claude Code

CGastrell and others added 10 commits February 25, 2026 01:26
…e headers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…/admin-ui pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…min-ui alignment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…der.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 05:23
@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 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 or WordPress.com Site Helper), and enable the update/normalize-admin-page-headers branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack update/normalize-admin-page-headers
bin/jetpack-downloader test jetpack-mu-wpcom-plugin update/normalize-admin-page-headers

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
Contributor

github-actions bot commented Feb 25, 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!

@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 Feb 25, 2026
@CGastrell CGastrell added the Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR label Feb 25, 2026
@CGastrell CGastrell self-assigned this Feb 25, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a unified AdminHeader component in @automattic/jetpack-components that standardizes admin page headers across Jetpack products by wrapping the @wordpress/admin-ui Page component. It migrates the publicize/Social admin page as a proof of concept, demonstrating the new architecture while maintaining full backward compatibility.

Changes:

  • Adds new AdminHeader component that provides a consistent header with logo (defaulting to Jetpack bolt) and title composition
  • Extends AdminPage with new props (title, subTitle, logo, actions, tabs) for the unified header pattern while preserving the legacy header prop for backward compatibility
  • Migrates publicize admin page to use the new pattern, removing the old custom AdminPageHeader component and associated files

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
projects/js-packages/components/components/admin-header/index.tsx New AdminHeader component that wraps @wordpress/admin-ui Page with Jetpack-specific defaults
projects/js-packages/components/components/admin-header/types.ts TypeScript types for AdminHeader props
projects/js-packages/components/index.ts Exports the new AdminHeader component
projects/js-packages/components/components/admin-page/types.ts Adds new props for unified header support, deprecates legacy header prop
projects/js-packages/components/components/admin-page/index.tsx Implements conditional rendering logic for unified vs legacy header
projects/js-packages/components/package.json Adds @wordpress/admin-ui@1.8.0 dependency
projects/js-packages/components/changelog/update-normalize-admin-page-headers Documents minor addition of AdminHeader component
projects/packages/publicize/_inc/components/admin-page/index.tsx Migrates to use title prop and actions for license text
projects/packages/publicize/_inc/components/admin-page/page-header/index.jsx Deleted - old custom header component
projects/packages/publicize/_inc/components/admin-page/page-header/logo.js Deleted - inline SVG logo
projects/packages/publicize/_inc/components/admin-page/page-header/styles.module.scss Deleted - old header styles
projects/packages/publicize/_inc/components/admin-page/test/page-header.test.jsx Deleted - tests for old header component
projects/packages/publicize/changelog/update-normalize-admin-page-headers Documents patch-level change for header migration
pnpm-lock.yaml Updates lock file with new @wordpress/admin-ui dependency
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

/**
* Custom header. Optional
* Custom header. Optional.
* @deprecated Use `title` and `tagline` props instead for the unified header.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The deprecation notice references a header prop but doesn't mention alternatives for cases where showHeader is true but no title is provided. Consider clarifying that when neither title nor header is provided, the default JetpackLogo will be rendered.

Suggested change
* @deprecated Use `title` and `tagline` props instead for the unified header.
* @deprecated Use `title` and `subTitle` props instead for the unified header. When `showHeader` is true and
* neither `title` nor `header` is provided, the default JetpackLogo will be rendered.

Copilot uses AI. Check for mistakes.
export { default as AdminSection } from './components/admin-section/basic/index.tsx';
export { default as AdminSectionHero } from './components/admin-section/hero/index.tsx';
export { default as AdminPage } from './components/admin-page/index.tsx';
export { default as AdminHeader } from './components/admin-header/index.tsx';
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Consider exporting the AdminHeaderProps type from the main index.ts for consumers who need to reference these types in their TypeScript code. This follows the pattern established for other components like ButtonProps (line 52 in index.ts).

Suggested change
export { default as AdminHeader } from './components/admin-header/index.tsx';
export { default as AdminHeader } from './components/admin-header/index.tsx';
export type { AdminHeaderProps } from './components/admin-header/types.ts';

Copilot uses AI. Check for mistakes.
moduleName={ moduleName }
showHeader={ false }
title={ __( 'Social', 'jetpack-publicize-pkg' ) }
showHeader={ true }
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The showHeader={ true } prop is redundant since showHeader defaults to true in the AdminPage component definition. Consider removing this line for cleaner code.

Suggested change
showHeader={ true }

Copilot uses AI. Check for mistakes.
Comment on lines 100 to 101
// should we add a subTitle? Page seems already crowded
// subTitle={ __( 'Share your posts with your social media network.', 'jetpack-publicize-pkg' ) }
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The commented-out code suggests uncertainty about whether to include a subtitle. If this is intentional for future consideration, consider converting it to a TODO comment with context. If it's not needed, remove it entirely to reduce code clutter.

Suggested change
// should we add a subTitle? Page seems already crowded
// subTitle={ __( 'Share your posts with your social media network.', 'jetpack-publicize-pkg' ) }
// TODO: Consider adding a subtitle here if the page layout is simplified in the future.

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +94
const licenseAction =
! hasSocialPaidFeatures() && isJetpackSite
? createInterpolateElement(
__(
'Already have an existing plan or license key? <a>Click here to get started</a>',
'jetpack-publicize-pkg'
),
{
a: <a href={ getMyJetpackUrl( '#/add-license' ) } />,
}
)
: null;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The deleted test file page-header.test.jsx contained tests for the license text functionality (showing/hiding based on paid features and site type). These tests should be migrated to ensure the license action behavior is still covered. Consider adding test cases to social-admin-page.test.jsx to verify that the license action appears in the header's actions prop when appropriate.

Copilot uses AI. Check for mistakes.
/**
* Custom header. Optional
* Custom header. Optional.
* @deprecated Use `title` and `tagline` props instead for the unified header.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The deprecation comment mentions "tagline" but the actual new prop is called "subTitle". Update the documentation to use the correct prop name for consistency.

Suggested change
* @deprecated Use `title` and `tagline` props instead for the unified header.
* @deprecated Use `title` and `subTitle` props instead for the unified header.

Copilot uses AI. Check for mistakes.
@jp-launch-control
Copy link

jp-launch-control bot commented Feb 25, 2026

Code Coverage Summary

Coverage changed in 1 file.

File Coverage Δ% Δ Uncovered
projects/packages/publicize/_inc/components/admin-page/index.tsx 18/18 (100.00%) 0.00% 0 💚

2 files are newly checked for coverage.

File Coverage
projects/js-packages/components/components/admin-header/index.tsx 1/4 (25.00%) ❤️‍🩹
projects/js-packages/components/components/admin-header/types.ts 0/0 (—%) 🤷

Full summary · PHP report · JS report

Coverage check overridden by Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR .

@CGastrell CGastrell added [Status] In Progress and removed [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. labels Feb 25, 2026
CGastrell and others added 2 commits February 25, 2026 11:22
…ve redundant props and comments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 15:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to +58
const AdminHeader: FC< AdminHeaderProps > = ( {
logo,
title,
subTitle,
actions,
tabs = null,
className,
breadcrumbs = null,
badges = null,
} ) => {
const classes = className;

// While admin-ui Page has a title prop, it fails to render both the logo and
// text. Internally it tries to accommodate both inside Heading.
// Composing here with Heading as it is on admin-ui Page.
const composedTitle = title ? (
<HStack spacing={ 2 } justify="left">
{ logo || <JetpackLogo showText={ false } height={ 20 } /> }
<Heading as="h2" level={ 3 } weight={ 500 } truncate>
{ title }
</Heading>
</HStack>
) : undefined;

return (
<Page
className={ classes }
title={ composedTitle }
subTitle={ subTitle }
actions={ actions }
breadcrumbs={ breadcrumbs }
badges={ badges }
showSidebarToggle={ false }
>
{ tabs }
</Page>
);
};

export default AdminHeader;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The new AdminHeader component is introduced without any test coverage. While the old AdminPageHeader test was deleted (which is appropriate since that component was removed), the new component should have tests to verify its prop handling, logo defaulting behavior, and integration with the @wordpress/admin-ui Page component. Consider adding tests for AdminHeader similar to the test coverage that existed for the old AdminPageHeader.

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +94
const licenseAction =
! hasSocialPaidFeatures() && isJetpackSite
? createInterpolateElement(
__(
'Already have an existing plan or license key? <a>Click here to get started</a>',
'jetpack-publicize-pkg'
),
{
a: <a href={ getMyJetpackUrl( '#/add-license' ) } />,
}
)
: null;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The deleted page-header.test.jsx file contained tests verifying that the license action text ("Already have an existing plan or license key?") appears conditionally based on whether the site has paid features and whether it's a Jetpack site. This behavior is now implemented in the licenseAction variable (lines 83-94) and passed as the actions prop, but there's no test coverage for this functionality in the remaining test files. Consider adding tests to social-admin-page.test.jsx to verify the license action appears/disappears under the correct conditions.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +55
return (
<Page
className={ classes }
title={ composedTitle }
subTitle={ subTitle }
actions={ actions }
breadcrumbs={ breadcrumbs }
badges={ badges }
showSidebarToggle={ false }
>
{ tabs }
</Page>
);
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The @wordpress/admin-ui Page component is designed to wrap the entire page content (header + body), as seen in its usage throughout the forms package. However, AdminHeader uses Page only for the header portion, with only tabs passed as children (line 53), while the actual page content is rendered separately in AdminPage (line 102). This creates an unusual architecture where Page doesn't contain the page content. Consider either: (1) refactoring to have Page wrap all content, or (2) using a more direct approach to render just the header elements without wrapping them in the full Page component. The current approach may cause issues if @wordpress/admin-ui expects Page to manage layout for its entire content area.

Copilot uses AI. Check for mistakes.
// text. Internally it tries to accommodate both inside Heading.
// Composing here with Heading as it is on admin-ui Page.
const composedTitle = title ? (
<HStack spacing={ 2 } justify="left">
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The HStack uses justify="left", but the codebase more commonly uses justify="start" for left alignment (see examples in forms/routes/responses/integrations-modal.tsx:184, forms/src/blocks/contact-form/components/jetpack-integrations-modal/helpers/akismet.tsx:76, and many others). While both work, using justify="start" would be more consistent with the rest of the codebase.

Suggested change
<HStack spacing={ 2 } justify="left">
<HStack spacing={ 2 } justify="start">

Copilot uses AI. Check for mistakes.
/**
* Tab navigation displayed below the title/tagline in the unified header.
*/
tabs?: ReactNode;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The PR description states that AdminPage props are extended with breadcrumbs and badges, but these props are not defined in AdminPageProps nor are they being passed through to AdminHeader. If these props are intended to be part of the public API, they should be added to AdminPageProps and passed to AdminHeader at lines 72-78. If they were mentioned in error, the PR description should be updated.

Copilot uses AI. Check for mistakes.
…actionPlugin externalization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR [Feature] Publicize Now Jetpack Social, auto-sharing [JS Package] Components [Package] Publicize RNA [Status] In Progress [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants