Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
289bf68
Initial plan
Copilot Feb 18, 2026
75494b4
Add page creator appender to Page List block
Copilot Feb 18, 2026
6152589
Refactor: hijack block appender instead of creating separate component
Copilot Feb 18, 2026
54fd9e1
Replace Page List custom modal with reusable PageCreatorAppender
scruffian Feb 27, 2026
4bb5e6e
Address review feedback on Page List page creator appender
scruffian Feb 27, 2026
6c62055
Fix appender styling and Firefox bullet alignment in Page List block
scruffian Mar 3, 2026
8ef1c87
Address review feedback on Page List page creator appender
scruffian Mar 3, 2026
1586199
Restore hasSelectedChild in showAppender condition
scruffian Mar 3, 2026
febb261
Use block-editor-inserter__toggle class for appender button
scruffian Mar 3, 2026
8d96563
Use own class for page creator appender button styling
scruffian Mar 3, 2026
8d8a4a6
Import colors module for appender button styles
scruffian Mar 3, 2026
bf7b821
Reuse block-list-appender__toggle class for appender button
scruffian Mar 3, 2026
79ca788
Add custom styles for page creator appender button
scruffian Mar 3, 2026
eefda2b
Fix appender button to use width instead of min-width
scruffian Mar 3, 2026
2fff403
Set appender button size to small for 24px height
scruffian Mar 3, 2026
2d1e301
Remove redundant __next40pxDefaultSize from appender button
scruffian Mar 3, 2026
63e4876
Fix appender selection bug, add onClose, and extract inserter-toggle …
scruffian Mar 3, 2026
de495a8
Revert inserter-toggle mixin extraction for separate PR
scruffian Mar 3, 2026
6743c14
Move appender outside ul as a div instead of li inside
Copilot Mar 3, 2026
54c6537
Fix focus loss bug: return focus to appender after page creation
Copilot Mar 3, 2026
579ce0d
Use inserter-toggle mixin for appender button styles
Copilot Mar 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions packages/base-styles/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -667,23 +667,6 @@
}
}

@mixin inserter-toggle() {
// Basic look
background: colors.$gray-900;
color: colors.$white;
padding: 0;

// TODO: Consider passing size="small" to the Inserter toggle instead.
// Special dimensions for this button.
min-width: variables.$button-size-small;
height: variables.$button-size-small;

&:hover {
color: colors.$white;
background: var(--wp-admin-theme-color);
}
}

@mixin selected-block-outline($widthRatio: 1) {
outline-color: var(--wp-admin-theme-color);
outline-style: solid;
Expand Down
15 changes: 14 additions & 1 deletion packages/block-editor/src/components/block-tools/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,20 @@
.block-editor-block-list__empty-block-inserter,
.block-editor-block-list__insertion-point-inserter {
.block-editor-inserter__toggle.components-button.has-icon {
@include inserter-toggle;
// Basic look
background: $gray-900;
color: $white;
padding: 0;

// TODO: Consider passing size="small" to the Inserter toggle instead.
// Special dimensions for this button.
min-width: $button-size-small;
height: $button-size-small;

&:hover {
color: $white;
background: var(--wp-admin-theme-color);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@use "@wordpress/base-styles/colors" as *;
@use "@wordpress/base-styles/mixins" as *;
@use "@wordpress/base-styles/variables" as *;
@use "@wordpress/base-styles/z-index" as *;

Expand Down Expand Up @@ -42,7 +41,20 @@
}

.block-editor-inserter__toggle.components-button.has-icon {
@include inserter-toggle;
// Basic look
background: $gray-900;
color: $white;
padding: 0;

// TODO: Consider passing size="small" to the Inserter toggle instead.
// Special dimensions for this button.
min-width: $button-size-small;
height: $button-size-small;

&:hover {
color: $white;
background: var(--wp-admin-theme-color);
}
}
}

Expand Down Expand Up @@ -91,16 +103,26 @@
// treating them the same, one or both can be retired.
.block-editor-inserter__toggle.components-button.has-icon,
.block-list-appender__toggle {
@include inserter-toggle;
flex-direction: row;
box-shadow: none;
height: $button-size-small;
width: $button-size-small;
min-width: $button-size-small;

// Hide by default, then we unhide it when the selected block is a direct ancestor.
display: none;

// Important to override styles form Button component.
padding: 0 !important;

// Basic look
background: $gray-900;
color: $white;

&:hover {
color: $white;
background: var(--wp-admin-theme-color);
}
}

// This content only shows up inside the empty appender.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,12 @@ function DialogWrapper( { className, title, description, onBack, children } ) {
<p id={ dialogDescriptionId }>{ description }</p>
</VisuallyHidden>

<BackButton className={ backButtonClassName } onBack={ onBack } />
{ onBack && (
<BackButton
className={ backButtonClassName }
onBack={ onBack }
/>
) }
Comment on lines +67 to +72

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.

@copilot Is this necessary for this PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is necessary. The PageCreatorAppender component reuses LinkUIPageCreator but only provides onClose, not onBack. Making the back button conditional allows the component to be used both in the Navigation Link UI (with back button) and in the standalone Page List context (without back button, just close via Popover).


{ children }
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@ import DialogWrapper from './dialog-wrapper';
*
* @param {Object} props Component props.
* @param {string} props.postType The post type to create.
* @param {Function} props.onBack Callback when user wants to go back.
* @param {Function} [props.onBack] Callback when user wants to go back. Shows a back button when provided.
* @param {Function} props.onClose Callback to close the creator without going back.
* @param {Function} props.onPageCreated Callback when page is successfully created.
* @param {string} [props.initialTitle] Initial title to pre-fill the form.
* @param {number} [props.menuOrder] Menu order for the new page.
*/
export function LinkUIPageCreator( {
postType,
onBack,
onClose,
onPageCreated,
initialTitle = '',
menuOrder,

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.

Could we get away without passing this param? It'll always be the last item.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The menuOrder param is needed to ensure newly created pages appear at the end of the list. Without it, WordPress defaults menu_order to 0, which would place new pages at the beginning.

The calculation finds max(menu_order) + 1 from existing pages. We pass it via Context rather than calculating inside LinkUIPageCreator because that component is shared with Navigation Link UI, where ordering needs might differ.

} ) {
const [ title, setTitle ] = useState( initialTitle );
const [ shouldPublish, setShouldPublish ] = useState( true );
Expand Down Expand Up @@ -74,6 +78,9 @@ export function LinkUIPageCreator( {
{
title,
status: shouldPublish ? 'publish' : 'draft',
...( menuOrder !== undefined && {
menu_order: menuOrder,
} ),
},
{ throwOnError: true }
);
Expand Down Expand Up @@ -117,6 +124,8 @@ export function LinkUIPageCreator( {

const isSubmitDisabled = isSaving || ! isTitleValid;

const handleDismiss = onBack || onClose;

return (
<DialogWrapper
className="link-ui-page-creator"
Expand Down Expand Up @@ -154,7 +163,7 @@ export function LinkUIPageCreator( {
<Button
__next40pxDefaultSize
variant="tertiary"
onClick={ onBack }
onClick={ handleDismiss }
disabled={ isSaving }
accessibleWhenDisabled
>
Expand Down
63 changes: 53 additions & 10 deletions packages/block-library/src/page-list/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,23 @@ import {
ConvertToLinksModal,
} from './convert-to-links-modal';
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
import PageCreatorAppender, {
PageCreatorContext,
} from './page-creator-appender';

// We only show the edit option when page count is <= MAX_PAGE_COUNT
// Performance of Navigation Links is not good past this value.
const MAX_PAGE_COUNT = 100;
const NOOP = () => {};

function BlockContent( {
blockProps,
innerBlocksProps,
hasResolvedPages,
blockList,
pages,
parentPageID,
showAppender,
} ) {
if ( ! hasResolvedPages ) {
return (
Expand Down Expand Up @@ -111,7 +116,16 @@ function BlockContent( {
}

if ( pages.length > 0 ) {
return <ul { ...innerBlocksProps }></ul>;
return (
<ul { ...innerBlocksProps }>
{ innerBlocksProps.children }
{ showAppender && (
<li className="wp-block-page-list__appender-container">

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 don't think this should be an <li>. It should likely be a div outside the <ul>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 6743c14. The appender is now a <div> outside the <ul>, properly wrapped in blockProps. I separated the blockProps from innerBlocksProps so the structure is now:

<div {...blockProps}>
  <ul {...innerBlocksProps}>
    {innerBlocksProps.children}
  </ul>
  {showAppender && (
    <div className="wp-block-page-list__appender-container">
      <PageCreatorAppender />
    </div>
  )}
</div>

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 had it like this before, but the problem was that sometimes it triggered a new block creation instead of opening the appender. Hopefully this time it doesn't!

<PageCreatorAppender />
</li>
) }
</ul>
);
}
}

Expand Down Expand Up @@ -259,12 +273,14 @@ export default function PageListEdit( {
parentClientId,
hasDraggedChild,
isChildOfNavigation,
isSelected,
} = useSelect(
( select ) => {
const {
getBlockParentsByBlockName,
hasSelectedInnerBlock,
hasDraggedInnerBlock,
getSelectedBlockClientId,
} = select( blockEditorStore );
const blockParents = getBlockParentsByBlockName(
clientId,
Expand All @@ -282,6 +298,7 @@ export default function PageListEdit( {
hasSelectedChild: hasSelectedInnerBlock( clientId, true ),
hasDraggedChild: hasDraggedInnerBlock( clientId, true ),
parentClientId: navigationBlockParents[ 0 ],
isSelected: getSelectedBlockClientId() === clientId,
};
},
[ clientId ]
Expand All @@ -294,6 +311,28 @@ export default function PageListEdit( {
parentPageID,
} );

const showAppender =
! isChildOfNavigation && ( isSelected || hasSelectedChild );

const nextMenuOrder = useMemo( () => {
if ( ! pages?.length ) {
return 0;
}
return (
pages.reduce(
( max, page ) => Math.max( max, page.menu_order ),
0
) + 1
);
}, [ pages ] );

const pageCreatorContextValue = useMemo(
() => ( {
menuOrder: nextMenuOrder,
} ),
[ nextMenuOrder ]
);

const innerBlocksProps = useInnerBlocksProps( blockProps, {
renderAppender: false,
__unstableDisableDropZone: true,
Expand All @@ -306,11 +345,12 @@ export default function PageListEdit( {
const { selectBlock } = useDispatch( blockEditorStore );

useEffect( () => {
if ( hasSelectedChild || hasDraggedChild ) {
if ( isChildOfNavigation && ( hasSelectedChild || hasDraggedChild ) ) {
openModal();
selectBlock( parentClientId );
}
}, [
isChildOfNavigation,
hasSelectedChild,
hasDraggedChild,
parentClientId,
Expand Down Expand Up @@ -396,14 +436,17 @@ export default function PageListEdit( {
) }
</>
) }
<BlockContent
blockProps={ blockProps }
innerBlocksProps={ innerBlocksProps }
hasResolvedPages={ hasResolvedPages }
blockList={ blockList }
pages={ pages }
parentPageID={ parentPageID }
/>
<PageCreatorContext.Provider value={ pageCreatorContextValue }>
<BlockContent
blockProps={ blockProps }
innerBlocksProps={ innerBlocksProps }
hasResolvedPages={ hasResolvedPages }
blockList={ blockList }
pages={ pages }
parentPageID={ parentPageID }
showAppender={ showAppender }
/>
</PageCreatorContext.Provider>
</>
);
}
27 changes: 27 additions & 0 deletions packages/block-library/src/page-list/editor.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@use "@wordpress/base-styles/breakpoints" as *;
@use "@wordpress/base-styles/colors" as *;
@use "@wordpress/base-styles/mixins" as *;
@use "@wordpress/base-styles/variables" as *;

Expand Down Expand Up @@ -27,6 +28,13 @@
// Make links unclickable in the editor.
.wp-block-pages-list__item__link {
pointer-events: none;

// RawHTML wraps page titles in a <div>, which is block-level.
// Firefox renders list markers on a separate line from block content.
// Force inline display so bullets align with titles across all browsers.
> div {
display: inline;
}
}

// Modal that shows conversion option.
Expand Down Expand Up @@ -61,3 +69,22 @@
.wp-block-page-list__loading-indicator-container {
padding: $grid-unit-10 $grid-unit-15;
}

// Page creator appender container and button.
.wp-block-page-list__appender-container {
list-style: none;
margin-top: $grid-unit-20;
}

.wp-block-page-list__appender-toggle.components-button {
background: $gray-900;

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.

Once #76087 merges we'll be able to use a mixin here.

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.

@copilot - #76087 is merged now. Can you update it to use the mixin?

color: $white;
padding: 0;
width: $button-size-small;
height: $button-size-small;

&:hover {
color: $white;
background: var(--wp-admin-theme-color);
}
}
54 changes: 54 additions & 0 deletions packages/block-library/src/page-list/page-creator-appender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* WordPress dependencies
*/
import {
createContext,
useContext,
useState,
useRef,
} from '@wordpress/element';
import { Button, Popover } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { plus } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { LinkUIPageCreator } from '../navigation-link/link-ui/page-creator';

export const PageCreatorContext = createContext( null );

export default function PageCreatorAppender() {
const [ showCreator, setShowCreator ] = useState( false );
const ref = useRef();
const contextData = useContext( PageCreatorContext );

return (
<>
<Button
size="small"
ref={ ref }
className="wp-block-page-list__appender-toggle"
icon={ plus }
onClick={ () => setShowCreator( true ) }
label={ __( 'Add page' ) }
showTooltip
/>
{ showCreator && (
<Popover
anchor={ ref.current }
placement="bottom-start"
shift
onClose={ () => setShowCreator( false ) }
>
<LinkUIPageCreator
postType="page"
onClose={ () => setShowCreator( false ) }
onPageCreated={ () => setShowCreator( false ) }
menuOrder={ contextData?.menuOrder }
/>
</Popover>
) }
</>
);
}
Loading