Skip to content

Commit

Permalink
Style book: create static categories (WordPress#65430)
Browse files Browse the repository at this point in the history
This commit creates a static category map based on list defined in Style Book: Iterate on presentation and design. WordPress#53431. It also:

- Sorts examples based on static categories and their subcategories
- Introduces simple layout for subcategories (under theme)
- Adds unit tests for utils
- Converts utils to TypeScript

Co-authored-by: ramonjd <[email protected]>
Co-authored-by: aaronrobertshaw <[email protected]>
Co-authored-by: vcanales <[email protected]>
Co-authored-by: jasmussen <[email protected]>
  • Loading branch information
5 people authored and huubl committed Oct 2, 2024
1 parent 8339b8a commit 6d8f2a8
Show file tree
Hide file tree
Showing 7 changed files with 609 additions and 158 deletions.
91 changes: 91 additions & 0 deletions packages/edit-site/src/components/style-book/categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* WordPress dependencies
*/
import { getCategories } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import type {
BlockExample,
StyleBookCategory,
CategoryExamples,
} from './types';
import {
STYLE_BOOK_CATEGORIES,
STYLE_BOOK_THEME_SUBCATEGORIES,
} from './constants';

/**
* Returns category examples for a given category definition and list of examples.
* @param {StyleBookCategory} categoryDefinition The category definition.
* @param {BlockExample[]} examples An array of block examples.
* @return {CategoryExamples|undefined} An object containing the category examples.
*/
export function getExamplesByCategory(
categoryDefinition: StyleBookCategory,
examples: BlockExample[]
): CategoryExamples | undefined {
if ( ! categoryDefinition?.slug || ! examples?.length ) {
return;
}

if ( categoryDefinition?.subcategories?.length ) {
return categoryDefinition.subcategories.reduce(
( acc, subcategoryDefinition ) => {
const subcategoryExamples = getExamplesByCategory(
subcategoryDefinition,
examples
);
if ( subcategoryExamples ) {
acc.subcategories = [
...acc.subcategories,
subcategoryExamples,
];
}
return acc;
},
{
title: categoryDefinition.title,
slug: categoryDefinition.slug,
subcategories: [],
}
);
}

const blocksToInclude = categoryDefinition?.blocks || [];
const blocksToExclude = categoryDefinition?.exclude || [];
const categoryExamples = examples.filter( ( example ) => {
return (
! blocksToExclude.includes( example.name ) &&
( example.category === categoryDefinition.slug ||
blocksToInclude.includes( example.name ) )
);
} );

if ( ! categoryExamples.length ) {
return;
}

return {
title: categoryDefinition.title,
slug: categoryDefinition.slug,
examples: categoryExamples,
};
}

/**
* Returns category examples for a given category definition and list of examples.
*
* @return {StyleBookCategory[]} An array of top-level category definitions.
*/
export function getTopLevelStyleBookCategories(): StyleBookCategory[] {
const reservedCategories = [
...STYLE_BOOK_THEME_SUBCATEGORIES,
...STYLE_BOOK_CATEGORIES,
].map( ( { slug } ) => slug );
const extraCategories = getCategories().filter(
( { slug } ) => ! reservedCategories.includes( slug )
);
return [ ...STYLE_BOOK_CATEGORIES, ...extraCategories ];
}
191 changes: 191 additions & 0 deletions packages/edit-site/src/components/style-book/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import type { StyleBookCategory } from './types';

export const STYLE_BOOK_THEME_SUBCATEGORIES: Omit<
StyleBookCategory,
'subcategories'
>[] = [
{
slug: 'site-identity',
title: __( 'Site Identity' ),
blocks: [ 'core/site-logo', 'core/site-title', 'core/site-tagline' ],
},
{
slug: 'design',
title: __( 'Design' ),
blocks: [ 'core/navigation', 'core/avatar', 'core/post-time-to-read' ],
exclude: [ 'core/home-link', 'core/navigation-link' ],
},
{
slug: 'posts',
title: __( 'Posts' ),
blocks: [
'core/post-title',
'core/post-excerpt',
'core/post-author',
'core/post-author-name',
'core/post-author-biography',
'core/post-date',
'core/post-terms',
'core/term-description',
'core/query-title',
'core/query-no-results',
'core/query-pagination',
'core/query-numbers',
],
},
{
slug: 'comments',
title: __( 'Comments' ),
blocks: [
'core/comments-title',
'core/comments-pagination',
'core/comments-pagination-numbers',
'core/comments',
'core/comments-author-name',
'core/comment-content',
'core/comment-date',
'core/comment-edit-link',
'core/comment-reply-link',
'core/comment-template',
'core/post-comments-count',
'core/post-comments-link',
],
},
];

export const STYLE_BOOK_CATEGORIES: StyleBookCategory[] = [
{
slug: 'text',
title: __( 'Text' ),
blocks: [
'core/post-content',
'core/home-link',
'core/navigation-link',
],
},
{
slug: 'colors',
title: __( 'Colors' ),
blocks: [ 'custom/colors' ],
},
{
slug: 'theme',
title: __( 'Theme' ),
subcategories: STYLE_BOOK_THEME_SUBCATEGORIES,
},
{
slug: 'media',
title: __( 'Media' ),
blocks: [ 'core/post-featured-image' ],
},
{
slug: 'widgets',
title: __( 'Widgets' ),
blocks: [],
},
{
slug: 'embed',
title: __( 'Embeds' ),
include: [],
},
];

// The content area of the Style Book is rendered within an iframe so that global styles
// are applied to elements within the entire content area. To support elements that are
// not part of the block previews, such as headings and layout for the block previews,
// additional CSS rules need to be passed into the iframe. These are hard-coded below.
// Note that button styles are unset, and then focus rules from the `Button` component are
// applied to the `button` element, targeted via `.edit-site-style-book__example`.
// This is to ensure that browser default styles for buttons are not applied to the previews.
export const STYLE_BOOK_IFRAME_STYLES = `
// Forming a "block formatting context" to prevent margin collapsing.
// @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context
.is-root-container {
display: flow-root;
}
body {
position: relative;
padding: 32px !important;
}
.edit-site-style-book__examples {
max-width: 1200px;
margin: 0 auto;
}
.edit-site-style-book__example {
max-width: 900px;
border-radius: 2px;
cursor: pointer;
display: flex;
flex-direction: column;
gap: 40px;
padding: 16px;
width: 100%;
box-sizing: border-box;
scroll-margin-top: 32px;
scroll-margin-bottom: 32px;
margin: 0 auto 40px auto;
}
.edit-site-style-book__example.is-selected {
box-shadow: 0 0 0 1px var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
}
.edit-site-style-book__example:focus:not(:disabled) {
box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
outline: 3px solid transparent;
}
.edit-site-style-book__examples.is-wide .edit-site-style-book__example {
flex-direction: row;
}
.edit-site-style-book__subcategory-title,
.edit-site-style-book__example-title {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
font-size: 11px;
font-weight: 500;
line-height: normal;
margin: 0;
text-align: left;
text-transform: uppercase;
}
.edit-site-style-book__subcategory-title {
font-size: 16px;
margin-bottom: 40px;
border-bottom: 1px solid #ddd;
padding-bottom: 8px;
}
.edit-site-style-book__examples.is-wide .edit-site-style-book__example-title {
text-align: right;
width: 120px;
}
.edit-site-style-book__example-preview {
width: 100%;
}
.edit-site-style-book__example-preview .block-editor-block-list__insertion-point,
.edit-site-style-book__example-preview .block-list-appender {
display: none;
}
.edit-site-style-book__example-preview .is-root-container > .wp-block:first-child {
margin-top: 0;
}
.edit-site-style-book__example-preview .is-root-container > .wp-block:last-child {
margin-bottom: 0;
}
`;
63 changes: 63 additions & 0 deletions packages/edit-site/src/components/style-book/examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import {
getBlockType,
getBlockTypes,
getBlockFromExample,
createBlock,
} from '@wordpress/blocks';

/**
* Internal dependencies
*/
import type { BlockExample } from './types';

/**
* Returns a list of examples for registered block types.
*
* @return {BlockExample[]} An array of block examples.
*/
export function getExamples(): BlockExample[] {
const nonHeadingBlockExamples = getBlockTypes()
.filter( ( blockType ) => {
const { name, example, supports } = blockType;
return (
name !== 'core/heading' &&
!! example &&
supports.inserter !== false
);
} )
.map( ( blockType ) => ( {
name: blockType.name,
title: blockType.title,
category: blockType.category,
blocks: getBlockFromExample( blockType.name, blockType.example ),
} ) );
const isHeadingBlockRegistered = !! getBlockType( 'core/heading' );

if ( ! isHeadingBlockRegistered ) {
return nonHeadingBlockExamples;
}

// Use our own example for the Heading block so that we can show multiple
// heading levels.
const headingsExample = {
name: 'core/heading',
title: __( 'Headings' ),
category: 'text',
blocks: [ 1, 2, 3, 4, 5, 6 ].map( ( level ) => {
return createBlock( 'core/heading', {
content: sprintf(
// translators: %d: heading level e.g: "1", "2", "3"
__( 'Heading %d' ),
level
),
level,
} );
} ),
};

return [ headingsExample, ...nonHeadingBlockExamples ];
}
Loading

0 comments on commit 6d8f2a8

Please sign in to comment.