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

New block: Add a Breadcrumbs block #32500

Open
wants to merge 35 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a33368c
Add Breadcrumbs block initial implementation
andrewserong Jun 8, 2021
8a31854
Add editor style support and options for leading separator and to sho…
andrewserong Jun 8, 2021
9aa6a0b
Hook up editor view to real page titles and parents, fall back to pla…
andrewserong Jun 9, 2021
990396e
Implement nesting level support within editor UI and in server rendering
andrewserong Jun 9, 2021
13a44f2
Fix linting issues in PHP file
andrewserong Jul 8, 2021
4d2a30e
Add block test fixtures
andrewserong Jul 8, 2021
3368adc
Remove editor store dependency
andrewserong Jul 8, 2021
95f3cc6
Rearrange markup so that separator comes after the anchor except for …
andrewserong Jul 12, 2021
c796fc9
Fix PHP linting issues
andrewserong Jul 15, 2021
42442dc
Add schema.org markup using microdata
andrewserong Jul 15, 2021
4c9f483
Update panel body title and separator placeholder
andrewserong Aug 19, 2021
876306f
Add option to show site title and override its label in JavaScript
andrewserong Aug 19, 2021
d7f04e9
Implement site title option in server-side rendering
andrewserong Aug 20, 2021
390dad8
Move block fixtures to match current expected location
andrewserong Aug 20, 2021
1d17787
Fix issue with extra separator being rendered when current page title…
andrewserong Aug 20, 2021
47683f1
Move test fixtures up a level
andrewserong Aug 20, 2021
e34d7be
Update test fixture with new default showSiteTitle attribute
andrewserong Aug 20, 2021
a0b08f2
Fix linting issues
andrewserong Aug 20, 2021
0ad88de
Opt in to the font weight, style, and text transform typography controls
andrewserong Sep 17, 2021
0edc226
Update block.json alignment to only set wide and full width
andrewserong Sep 20, 2021
c2341f1
Add support for post categories
andrewserong Sep 20, 2021
a8cd2f5
Fix whitespace
andrewserong Sep 20, 2021
d1938b2
Move buildBreadcrumb util to a React component instead
andrewserong Oct 14, 2021
9b98054
Switch to flex, add gap support, switch to justification instead of a…
andrewserong Oct 14, 2021
d6fe95c
Fix linting issue
andrewserong Oct 14, 2021
d3fc3b0
Remove nestingLevel control
andrewserong Mar 1, 2022
e12ea90
Remove background color control
andrewserong Mar 1, 2022
a369cb3
Move separator and site title override controls to the editor canvas …
andrewserong Mar 3, 2022
4cebc39
Update markup to better match W3C example, try removing spacing support
andrewserong Mar 13, 2022
e12b350
Allow breadcrumb without parents to render if site title and current …
andrewserong Mar 14, 2022
43ac00a
Update test fixtures
andrewserong Mar 14, 2022
d757a12
Fixes after rebase
andrewserong Jun 5, 2023
b7c8e83
Fix linting issues
andrewserong Jun 5, 2023
7f524af
Remove schema markup, only display in site editor
andrewserong Jun 7, 2023
6afb892
Add filter for inner breadcrumbs array
andrewserong Jun 7, 2023
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
10 changes: 10 additions & 0 deletions docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ Create and save content to reuse across your site. Update the block, and the cha
- **Supports:** ~~customClassName~~, ~~html~~, ~~inserter~~
- **Attributes:** ref

## Breadcrumbs

Displays breadcrumbs of a page's hierarchy, or a post's categories ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/breadcrumbs))

- **Name:** core/breadcrumbs
- **Experimental:** fse
- **Category:** theme
- **Supports:** align (full, wide), color (link, text, ~~background~~), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** contentJustification, separator, showCurrentPageTitle, showLeadingSeparator, showSiteTitle, siteTitleOverride

## Button

Prompt visitors to take action with a button-style link. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/button))
Expand Down
1 change: 1 addition & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function gutenberg_reregister_core_block_types() {
'archives.php' => 'core/archives',
'avatar.php' => 'core/avatar',
'block.php' => 'core/block',
'breadcrumbs.php' => 'core/breadcrumbs',
'calendar.php' => 'core/calendar',
'categories.php' => 'core/categories',
'cover.php' => 'core/cover',
Expand Down
60 changes: 60 additions & 0 deletions packages/block-library/src/breadcrumbs/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"__experimental": "fse",
"name": "core/breadcrumbs",
"title": "Breadcrumbs",
"category": "theme",
"description": "Displays breadcrumbs of a page's hierarchy, or a post's categories",
"textdomain": "default",
"usesContext": [ "postId", "postType" ],
"attributes": {
"contentJustification": {
"type": "string"
},
"separator": {
"type": "string",
"default": "/"
},
"showCurrentPageTitle": {
"type": "boolean",
"default": false
},
"showLeadingSeparator": {
"type": "boolean",
"default": false
},
"showSiteTitle": {
"type": "boolean",
"default": true
},
"siteTitleOverride": {
"type": "string"
}
},
"supports": {
"align": [ "wide", "full" ],
"color": {
"background": false,
"link": true,
"__experimentalDefaultControls": {
"text": true,
"link": true
}
},
"html": false,
"typography": {
"fontSize": true,
"lineHeight": true,
"__experimentalFontFamily": true,
"__experimentalFontStyle": true,
"__experimentalFontWeight": true,
"__experimentalTextTransform": true,
"__experimentalDefaultControls": {
"fontSize": true
}
}
},
"editorStyle": "wp-block-breadcrumbs-editor",
"style": "wp-block-breadcrumbs"
}
311 changes: 311 additions & 0 deletions packages/block-library/src/breadcrumbs/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import {
BlockControls,
InspectorControls,
JustifyContentControl,
RichText,
useBlockProps,
} from '@wordpress/block-editor';
import { PanelBody, ToggleControl } from '@wordpress/components';
import { useEffect, useState, useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
import { decodeEntities } from '@wordpress/html-entities';

function Breadcrumb( {
addLeadingSeparator,
crumbTitle,
editableTitleField = undefined,
isSelected,
placeholder,
separator,
setAttributes,
showSeparator,
} ) {
let crumbAnchor;
let separatorSpan;

// Keep track of whether or not the title field has been edited.
// This allows the default site title to be rendered as full "real" text.
// Then, when it's edited, if the title is removed, it is displayed as a placeholder,
// until the block is de-selected, where it is then treated as real text again.
const [ isDirty, setIsDirty ] = useState();

useEffect( () => {
if ( ! isSelected ) {
setIsDirty( false );
}
}, [ isSelected ] );

if ( separator || isSelected ) {
separatorSpan = (
<span className="wp-block-breadcrumbs__separator">
<RichText
aria-label={ __( 'Separator character' ) }
placeholder={ __( '/' ) }
withoutInteractiveFormatting
value={ separator }
onChange={ ( html ) =>
setAttributes( { separator: html } )
}
/>
</span>
);
}

if ( editableTitleField ) {
/* eslint-disable jsx-a11y/anchor-is-valid */
crumbAnchor = (
<a href="#" onClick={ ( event ) => event.preventDefault() }>
{ isSelected ? (
<RichText
aria-label={ __( 'Title override' ) }
placeholder={ placeholder }
withoutInteractiveFormatting
value={
isDirty
? crumbTitle ?? placeholder
: crumbTitle || placeholder
}
onChange={ ( html ) => {
setIsDirty( true );
setAttributes( { [ editableTitleField ]: html } );
} }
/>
) : (
crumbTitle || placeholder
) }
</a>
);
/* eslint-enable */
} else if ( crumbTitle ) {
/* eslint-disable jsx-a11y/anchor-is-valid */
crumbAnchor = (
<a href="#" onClick={ ( event ) => event.preventDefault() }>
{ crumbTitle }
</a>
);
/* eslint-enable */
}

return (
<li className="wp-block-breadcrumbs__item">
{ addLeadingSeparator ? separatorSpan : null }
{ crumbAnchor }
{ showSeparator ? separatorSpan : null }
</li>
);
}

export default function BreadcrumbsEdit( {
attributes,
isSelected,
setAttributes,
context: { postType, postId },
} ) {
const {
contentJustification,
separator,
showCurrentPageTitle,
showLeadingSeparator,
showSiteTitle,
siteTitleOverride,
} = attributes;

const { categories, parents, post, siteTitle } = useSelect(
( select ) => {
const { getEntityRecord, getEditedEntityRecord } =
select( coreStore );

const siteData = getEntityRecord( 'root', '__unstableBase' );
const currentPost = getEditedEntityRecord(
'postType',
postType,
postId
);

const parentCategories = [];
const parentEntities = [];
let categoryId = currentPost?.categories?.[ 0 ];
let currentParentId = currentPost?.parent;

while ( currentParentId ) {
const nextParent = getEntityRecord(
'postType',
postType,
currentParentId
);

currentParentId = null;

if ( nextParent ) {
parentEntities.push( nextParent );
currentParentId = nextParent?.parent || null;
}
}

while ( categoryId ) {
const nextCategory = getEntityRecord(
'taxonomy',
'category',
categoryId
);

categoryId = null;

if ( nextCategory ) {
parentCategories.push( nextCategory );
categoryId = nextCategory?.parent || null;
}
}

return {
categories: parentCategories,
post: currentPost,
parents: parentEntities.reverse(),
siteTitle: decodeEntities( siteData?.name ),
};
},
[ postId, postType ]
);

// Construct breadcrumbs.
const breadcrumbs = useMemo( () => {
// Set breadcrumb names to real hierarchical post titles if available, and
// fall back to category names, or placeholder content if neither exists.

const crumbs = [];
let breadcrumbTitles;

if ( parents?.length ) {
breadcrumbTitles = parents.map(
( parent ) => parent?.title?.rendered || ' '
);
} else if ( categories?.length ) {
breadcrumbTitles = categories.map(
( category ) => category?.name || ' '
);
} else {
breadcrumbTitles = [ __( 'Top-level page' ), __( 'Child page' ) ];
}

// Prepend the site title or site title override if specified.
if ( showSiteTitle && siteTitle ) {
crumbs.push(
<Breadcrumb
addLeadingSeparator={ showLeadingSeparator }
crumbTitle={ siteTitleOverride }
editableTitleField={ 'siteTitleOverride' }
isSelected={ isSelected }
placeholder={ siteTitle }
separator={ separator }
setAttributes={ setAttributes }
showSeparator={ !! breadcrumbTitles.length }
key="site-title"
/>
);
}

// Append current page title if set.
if ( showCurrentPageTitle ) {
breadcrumbTitles.push( post?.title || __( 'Current page' ) );
}

breadcrumbTitles.forEach( ( item, index ) => {
crumbs.push(
<Breadcrumb
addLeadingSeparator={
index === 0 && showLeadingSeparator && ! showSiteTitle
}
crumbTitle={ item }
isSelected={ isSelected }
separator={ separator }
setAttributes={ setAttributes }
showSeparator={ index < breadcrumbTitles.length - 1 }
key={ index }
/>
);
} );

return crumbs;
}, [
categories,
isSelected,
parents,
post?.title,
separator,
setAttributes,
showCurrentPageTitle,
showLeadingSeparator,
showSiteTitle,
siteTitle,
siteTitleOverride,
] );

const blockProps = useBlockProps( {
className: classnames( {
[ `is-content-justification-${ contentJustification }` ]:
contentJustification,
} ),
} );

return (
<>
<BlockControls group="block">
<JustifyContentControl
allowedControls={ [ 'left', 'center', 'right' ] }
value={ contentJustification }
onChange={ ( value ) =>
setAttributes( { contentJustification: value } )
}
popoverProps={ {
position: 'bottom right',
isAlternate: true,
} }
/>
</BlockControls>
<InspectorControls>
<PanelBody title={ __( 'Display' ) }>
<ToggleControl
label={ __( 'Show leading separator' ) }
checked={ showLeadingSeparator }
onChange={ () =>
setAttributes( {
showLeadingSeparator: ! showLeadingSeparator,
} )
}
/>
<ToggleControl
label={ __( 'Show current page title' ) }
checked={ showCurrentPageTitle }
onChange={ () =>
setAttributes( {
showCurrentPageTitle: ! showCurrentPageTitle,
} )
}
/>
<ToggleControl
label={ __( 'Show site title' ) }
checked={ showSiteTitle }
onChange={ () =>
setAttributes( {
showSiteTitle: ! showSiteTitle,
} )
}
/>
</PanelBody>
</InspectorControls>
<nav { ...blockProps }>
<ol>{ breadcrumbs }</ol>
</nav>
</>
);
}
Loading