Normalize admin page headers with unified AdminHeader wrapping @wordpress/admin-ui#47313
Normalize admin page headers with unified AdminHeader wrapping @wordpress/admin-ui#47313
Conversation
…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>
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
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:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
There was a problem hiding this comment.
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
AdminHeadercomponent that provides a consistent header with logo (defaulting to Jetpack bolt) and title composition - Extends
AdminPagewith new props (title,subTitle,logo,actions,tabs) for the unified header pattern while preserving the legacyheaderprop for backward compatibility - Migrates publicize admin page to use the new pattern, removing the old custom
AdminPageHeadercomponent 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. |
There was a problem hiding this comment.
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.
| * @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. |
| 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'; |
There was a problem hiding this comment.
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).
| 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'; |
| moduleName={ moduleName } | ||
| showHeader={ false } | ||
| title={ __( 'Social', 'jetpack-publicize-pkg' ) } | ||
| showHeader={ true } |
There was a problem hiding this comment.
The showHeader={ true } prop is redundant since showHeader defaults to true in the AdminPage component definition. Consider removing this line for cleaner code.
| showHeader={ true } |
| // should we add a subTitle? Page seems already crowded | ||
| // subTitle={ __( 'Share your posts with your social media network.', 'jetpack-publicize-pkg' ) } |
There was a problem hiding this comment.
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.
| // 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. |
| 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; |
There was a problem hiding this comment.
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.
| /** | ||
| * Custom header. Optional | ||
| * Custom header. Optional. | ||
| * @deprecated Use `title` and `tagline` props instead for the unified header. |
There was a problem hiding this comment.
The deprecation comment mentions "tagline" but the actual new prop is called "subTitle". Update the documentation to use the correct prop name for consistency.
| * @deprecated Use `title` and `tagline` props instead for the unified header. | |
| * @deprecated Use `title` and `subTitle` props instead for the unified header. |
Code Coverage SummaryCoverage changed in 1 file.
2 files are newly checked for coverage.
Full summary · PHP report · JS report Coverage check overridden by
Coverage tests to be added later
|
…ve redundant props and comments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
| return ( | ||
| <Page | ||
| className={ classes } | ||
| title={ composedTitle } | ||
| subTitle={ subTitle } | ||
| actions={ actions } | ||
| breadcrumbs={ breadcrumbs } | ||
| badges={ badges } | ||
| showSidebarToggle={ false } | ||
| > | ||
| { tabs } | ||
| </Page> | ||
| ); |
There was a problem hiding this comment.
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.
| // 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"> |
There was a problem hiding this comment.
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.
| <HStack spacing={ 2 } justify="left"> | |
| <HStack spacing={ 2 } justify="start"> |
| /** | ||
| * Tab navigation displayed below the title/tagline in the unified header. | ||
| */ | ||
| tabs?: ReactNode; |
There was a problem hiding this comment.
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.
…actionPlugin externalization. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Introduces a unified
AdminHeadercomponent in@automattic/jetpack-componentsthat wraps@wordpress/admin-ui'sPagecomponent, providing a consistent header across all Jetpack admin pages. Migrates publicize/Social as the first proof of concept.What this PR does
AdminHeadercomponent to jetpack-components — a thin wrapper around@wordpress/admin-uiPagethat composes a logo + title into the header. Defaults to the Jetpack bolt icon when no custom logo is provided.AdminPageprops withtitle,subTitle,logo,actions,tabs,breadcrumbs,badges. Whentitleis present, renders the newAdminHeader; otherwise falls back to the legacyheaderprop path. Fully backward compatible.title="Social"instead of the customAdminPageHeadercomponent. Deletes the now-unusedpage-header/directory (inline SVG logo, styles, component, test).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:
AdminHeaderadds only two things that@wordpress/admin-uiPage doesn't have:logoprop is providedMigration pattern for each remaining product:
title="ProductName"toAdminPage(optionallysubTitle,actions,tabs)Products to migrate next (~13 remaining):
<Header>with logo SVGtitlepropheader={<JetpackVideoPressLogo/>}<Header>with logo<Header>with logo + title<JetpackLogo/>fallbackheader={<JetpackProtectLogo/>}HeaderMastheadclass componentFuture convergence: Once all products use
AdminHeader, the wrapper itself can eventually be removed in favor of using@wordpress/admin-uiPagedirectly — 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/publicizepassesjetpack test js packages/publicizepassesjetpack test php packages/publicizepasses🤖 Generated with Claude Code