diff --git a/apps/editing-toolkit/editing-toolkit-plugin/dotcom-fse/tests/fse-back-button.spec.js b/apps/editing-toolkit/editing-toolkit-plugin/dotcom-fse/tests/fse-back-button.spec.js index a097dbecd47a06..1517c5b9cc568c 100644 --- a/apps/editing-toolkit/editing-toolkit-plugin/dotcom-fse/tests/fse-back-button.spec.js +++ b/apps/editing-toolkit/editing-toolkit-plugin/dotcom-fse/tests/fse-back-button.spec.js @@ -12,7 +12,7 @@ describe( 'Full Site Editing Back Button', () => { await activateTheme( 'maywood' ); // Creates a new page and uses the blank page layout. await createNewPost( { postType: 'page', title: 'New e2e Page!' } ); - await page.click( '.page-template-modal__buttons .components-button.is-primary.is-large' ); + await page.click( '.page-pattern-modal__buttons .components-button.is-primary.is-large' ); } ); it( 'Should have an overriden button', async () => { diff --git a/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/index.scss b/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/index.scss index dc4a004d92d526..300c99e82748aa 100644 --- a/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/index.scss +++ b/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/index.scss @@ -1,11 +1,11 @@ -@import '~@automattic/page-template-modal/src/styles/page-template-modal'; +@import '~@automattic/page-pattern-modal/src/styles/page-pattern-modal'; .sidebar-modal-opener { display: flex; flex-direction: column; align-items: center; justify-content: center; - .template-selector-item__label { + .pattern-selector-item__label { max-width: 300px; } } diff --git a/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/index.tsx b/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/index.tsx index 4b71241c24aa65..0ebce928e3b59b 100644 --- a/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/index.tsx +++ b/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/index.tsx @@ -3,20 +3,20 @@ */ import { registerPlugin } from '@wordpress/plugins'; import { dispatch } from '@wordpress/data'; -import { initializeTracksWithIdentity, LayoutDefinition } from '@automattic/page-template-modal'; +import { initializeTracksWithIdentity, PatternDefinition } from '@automattic/page-pattern-modal'; import React from '@wordpress/element'; /** * Internal dependencies */ -import { PageTemplatesPlugin } from './page-template-plugin'; +import { PagePatternsPlugin } from './page-patterns-plugin'; import './store'; import './index.scss'; declare global { interface Window { starterPageTemplatesConfig?: { - templates?: LayoutDefinition[]; + templates?: PatternDefinition[]; locale?: string; theme?: string; screenAction?: string; @@ -26,7 +26,7 @@ declare global { } // Load config passed from backend. -const { templates = [], tracksUserData, screenAction, theme, locale } = +const { templates: patterns = [], tracksUserData, screenAction, theme, locale } = window.starterPageTemplatesConfig ?? {}; if ( tracksUserData ) { @@ -39,9 +39,9 @@ if ( screenAction === 'add' ) { } // Always register ability to open from document sidebar. -registerPlugin( 'page-templates', { +registerPlugin( 'page-patterns', { render: () => { - return ; + return ; }, // `registerPlugin()` types assume `icon` is mandatory however it isn't diff --git a/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/page-template-plugin.tsx b/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/page-patterns-plugin.tsx similarity index 82% rename from apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/page-template-plugin.tsx rename to apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/page-patterns-plugin.tsx index aa313e2dc118b5..75f6aade57f061 100644 --- a/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/page-template-plugin.tsx +++ b/apps/editing-toolkit/editing-toolkit-plugin/starter-page-templates/page-patterns-plugin.tsx @@ -4,19 +4,19 @@ import '@wordpress/nux'; import { useSelect, useDispatch } from '@wordpress/data'; import { addFilter, removeFilter } from '@wordpress/hooks'; -import { LayoutDefinition, PageTemplateModal } from '@automattic/page-template-modal'; +import { PagePatternModal, PatternDefinition } from '@automattic/page-pattern-modal'; import React, { useCallback } from '@wordpress/element'; -const INSERTING_HOOK_NAME = 'isInsertingPageTemplate'; -const INSERTING_HOOK_NAMESPACE = 'automattic/full-site-editing/inserting-template'; +const INSERTING_HOOK_NAME = 'isInsertingPagePattern'; +const INSERTING_HOOK_NAMESPACE = 'automattic/full-site-editing/inserting-pattern'; -interface PageTemplatesPluginProps { - templates: LayoutDefinition[]; +interface PagePatternsPluginProps { + patterns: PatternDefinition[]; locale?: string; theme?: string; } -export function PageTemplatesPlugin( props: PageTemplatesPluginProps ): JSX.Element { +export function PagePatternsPlugin( props: PagePatternsPluginProps ): JSX.Element { const { setOpenState } = useDispatch( 'automattic/starter-page-layouts' ); const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); const { editPost } = useDispatch( 'core/editor' ); @@ -43,9 +43,9 @@ export function PageTemplatesPlugin( props: PageTemplatesPluginProps ): JSX.Elem }; } ); - const saveTemplateChoice = useCallback( + const savePatternChoice = useCallback( ( name: string ) => { - // Save selected template slug in meta. + // Save selected pattern slug in meta. const currentMeta = getMeta(); editPost( { meta: { @@ -57,7 +57,7 @@ export function PageTemplatesPlugin( props: PageTemplatesPluginProps ): JSX.Elem [ editPost, getMeta ] ); - const insertTemplate = useCallback( + const insertPattern = useCallback( ( title, blocks ) => { // Add filter to let the tracking library know we are inserting a template. addFilter( INSERTING_HOOK_NAME, INSERTING_HOOK_NAMESPACE, () => true ); @@ -89,11 +89,11 @@ export function PageTemplatesPlugin( props: PageTemplatesPluginProps ): JSX.Elem }, [ areTipsEnabled, disableTips, isWelcomeGuideActive, toggleFeature ] ); return ( - diff --git a/apps/editing-toolkit/package.json b/apps/editing-toolkit/package.json index 6179b2727714ad..1cc88c7804ebeb 100644 --- a/apps/editing-toolkit/package.json +++ b/apps/editing-toolkit/package.json @@ -98,7 +98,7 @@ "@automattic/i18n-utils": "^1.0.0", "@automattic/launch": "^1.0.0", "@automattic/onboarding": "^1.0.0", - "@automattic/page-template-modal": "^1.0.0-alpha.0", + "@automattic/page-pattern-modal": "^1.0.0-alpha.0", "@automattic/plans-grid": "^1.0.0-alpha.0", "@automattic/react-i18n": "^1.0.0-alpha.0", "@automattic/typography": "^1.0.0", diff --git a/desktop/e2e/tests/lib/components/gutenberg-editor-component.js b/desktop/e2e/tests/lib/components/gutenberg-editor-component.js index a04d789b76002c..0b18c2b23580da 100644 --- a/desktop/e2e/tests/lib/components/gutenberg-editor-component.js +++ b/desktop/e2e/tests/lib/components/gutenberg-editor-component.js @@ -75,9 +75,9 @@ class GutenbergEditorComponent extends AsyncBaseContainer { } async dismissPageTemplateSelector() { - if ( await driverHelper.isElementPresent( this.driver, By.css( '.page-template-modal' ) ) ) { + if ( await driverHelper.isElementPresent( this.driver, By.css( '.page-pattern-modal' ) ) ) { const useBlankButton = await this.driver.findElement( - By.css( '.page-template-modal__buttons .components-button.is-primary' ) + By.css( '.page-pattern-modal__buttons .components-button.is-primary' ) ); await this.driver.executeScript( 'arguments[0].click()', useBlankButton ); } diff --git a/packages/page-template-modal/.eslintrc.js b/packages/page-pattern-modal/.eslintrc.js similarity index 100% rename from packages/page-template-modal/.eslintrc.js rename to packages/page-pattern-modal/.eslintrc.js diff --git a/packages/page-template-modal/README.md b/packages/page-pattern-modal/README.md similarity index 57% rename from packages/page-template-modal/README.md rename to packages/page-pattern-modal/README.md index 0c326a4bb6db02..772868e2d60f68 100644 --- a/packages/page-template-modal/README.md +++ b/packages/page-pattern-modal/README.md @@ -1,8 +1,8 @@ -# Page Template Modal +# Page Pattern Modal -A modal for choosing a starting template for a new page, extracted from the editing toolkit +A modal for choosing a starting pattern for a new page, extracted from the editing toolkit ## Development Workflow This package is developed as part of the Calypso monorepo. Run `yarn` -in the root of the repository to get the required `devDependencies`. \ No newline at end of file +in the root of the repository to get the required `devDependencies`. diff --git a/packages/page-template-modal/jest.config.js b/packages/page-pattern-modal/jest.config.js similarity index 100% rename from packages/page-template-modal/jest.config.js rename to packages/page-pattern-modal/jest.config.js diff --git a/packages/page-template-modal/package.json b/packages/page-pattern-modal/package.json similarity index 89% rename from packages/page-template-modal/package.json rename to packages/page-pattern-modal/package.json index b39f907c13f3e5..f6c75893859e5b 100644 --- a/packages/page-template-modal/package.json +++ b/packages/page-pattern-modal/package.json @@ -1,7 +1,7 @@ { - "name": "@automattic/page-template-modal", + "name": "@automattic/page-pattern-modal", "version": "1.0.0-alpha.0", - "description": "Automattic Page Template Modal", + "description": "Automattic Page Pattern Modal", "homepage": "https://github.com/Automattic/wp-calypso", "license": "GPL-2.0-or-later", "author": "Automattic Inc.", @@ -15,7 +15,7 @@ "repository": { "type": "git", "url": "git+https://github.com/Automattic/wp-calypso.git", - "directory": "packages/page-template-modal" + "directory": "packages/page-pattern-modal" }, "publishConfig": { "access": "public" diff --git a/packages/page-pattern-modal/src/components/page-pattern-modal.tsx b/packages/page-pattern-modal/src/components/page-pattern-modal.tsx new file mode 100644 index 00000000000000..1fc564e85d5685 --- /dev/null +++ b/packages/page-pattern-modal/src/components/page-pattern-modal.tsx @@ -0,0 +1,384 @@ +/** + * External dependencies + */ +import { memoize } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import { Button, MenuItem, Modal, NavigableMenu, VisuallyHidden } from '@wordpress/components'; +import { Component } from '@wordpress/element'; +import { BlockInstance, parse as parseBlocks } from '@wordpress/blocks'; +import { withInstanceId } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import PatternSelectorControl from './pattern-selector-control'; +import { trackDismiss, trackSelection, trackView } from '../utils/tracking'; +import replacePlaceholders from '../utils/replace-placeholders'; +import mapBlocksRecursively from '../utils/map-blocks-recursively'; +import containsMissingBlock from '../utils/contains-missing-block'; +import { sortGroupNames } from '../utils/group-utils'; +import type { PatternCategory, PatternDefinition } from '../pattern-definition'; + +interface PagePatternModalProps { + areTipsEnabled?: boolean; + hideWelcomeGuide: () => void; + insertPattern: ( title: string | null, blocks: unknown[] ) => void; + instanceId: number; + isOpen: boolean; + isWelcomeGuideActive?: boolean; + locale?: string; + savePatternChoice: ( name: string ) => void; + setOpenState: ( isOpen: false ) => void; + siteInformation?: Record< string, string >; + patterns: PatternDefinition[]; + theme?: string; +} + +interface PagePatternModalState { + selectedCategory: string | null; +} + +class PagePatternModal extends Component< PagePatternModalProps, PagePatternModalState > { + constructor( props: PagePatternModalProps ) { + super( props ); + this.state = { + selectedCategory: this.getDefaultSelectedCategory(), + }; + } + + // Parse patterns blocks and memoize them. + getBlocksByPatternSlugs = memoize( ( patterns: PatternDefinition[] ) => { + const blocksByPatternSlugs = patterns.reduce( ( prev, { name, html } ) => { + prev[ name ] = html + ? parseBlocks( replacePlaceholders( html, this.props.siteInformation ) ) + : []; + return prev; + }, {} as Record< string, BlockInstance[] > ); + + // Remove patterns that include a missing block + return this.filterPatternsWithMissingBlocks( blocksByPatternSlugs ); + } ); + + filterPatternsWithMissingBlocks( patterns: Record< string, BlockInstance[] > ) { + return Object.entries( patterns ).reduce( ( acc, [ name, patternBlocks ] ) => { + // Does the pattern contain any missing blocks? + const patternHasMissingBlocks = containsMissingBlock( patternBlocks ); + + // Only retain the pattern in the collection if: + // 1. It does not contain any missing blocks + // 2. There are no blocks at all (likely the "blank" pattern placeholder) + if ( ! patternHasMissingBlocks || ! patternBlocks.length ) { + acc[ name ] = patternBlocks; + } + + return acc; + }, {} as Record< string, BlockInstance[] > ); + } + + getBlocksForSelection = ( selectedPattern: string ) => { + const blocks = this.getBlocksByPatternSlug( selectedPattern ); + // Modify the existing blocks returning new block object references. + return mapBlocksRecursively( blocks, function modifyBlocksForSelection( block ) { + // Ensure that core/button doesn't link to external pattern site + if ( 'core/button' === block.name && undefined !== block.attributes.url ) { + block.attributes.url = '#'; + } + + return block; + } ); + }; + + componentDidMount() { + if ( this.props.isOpen ) { + this.trackCurrentView(); + } + } + + componentDidUpdate( prevProps: PagePatternModalProps ) { + // Only track when the modal is first displayed + // and if it didn't already happen during componentDidMount. + if ( ! prevProps.isOpen && this.props.isOpen ) { + this.trackCurrentView(); + } + + // Disable welcome guide right away as it collides with the modal window. + if ( this.props.isWelcomeGuideActive || this.props.areTipsEnabled ) { + this.props.hideWelcomeGuide(); + } + } + + trackCurrentView() { + trackView( 'add-page' ); + } + + getDefaultSelectedCategory() { + const categories = this.getPatternCategories(); + if ( ! categories?.length ) { + return null; + } + + return categories[ 0 ].slug; + } + + setPattern = ( name: string ) => { + // Track selection and mark post as using a pattern in its postmeta. + trackSelection( name ); + this.props.savePatternChoice( name ); + + // Check to see if this is a blank pattern selection + // and reset the pattern if so. + if ( 'blank' === name ) { + this.props.insertPattern( '', [] ); + this.props.setOpenState( false ); + return; + } + + const pattern = this.props.patterns.find( ( t ) => t.name === name ); + const isHomepagePattern = ( pattern?.categories || {} ).hasOwnProperty( 'home' ); + + // Load content. + const blocks = this.getBlocksForSelection( name ); + + // Only overwrite the page title if the pattern is not one of the Homepage Layouts + const title = isHomepagePattern ? null : pattern?.title || ''; + + // Skip inserting if this is not a blank pattern + // and there's nothing to insert. + if ( ! blocks || ! blocks.length ) { + this.props.setOpenState( false ); + return; + } + + this.props.insertPattern( title, blocks ); + this.props.setOpenState( false ); + }; + + handleCategorySelection = ( selectedCategory: string | null ) => { + this.setState( { selectedCategory } ); + }; + + closeModal = () => { + trackDismiss(); + this.props.setOpenState( false ); + }; + + getBlocksByPatternSlug( name: string ) { + return this.getBlocksByPatternSlugs( this.props.patterns )?.[ name ] ?? []; + } + + getPatternGroups = () => { + if ( ! this.props.patterns.length ) { + return null; + } + + const patternGroups: Record< string, PatternCategory > = {}; + for ( const pattern of this.props.patterns ) { + for ( const key in pattern.categories ) { + if ( ! ( key in patternGroups ) ) { + patternGroups[ key ] = pattern.categories[ key ]; + } + } + } + + const preferredGroupOrder = [ + 'featured', + 'about', + 'blog', + 'home', + 'gallery', + 'services', + 'contact', + ]; + return sortGroupNames( preferredGroupOrder, patternGroups ); + }; + + getPatternsForGroup = ( groupName: string ): PatternDefinition[] | null => { + if ( ! this.props.patterns.length ) { + return null; + } + + if ( 'blank' === groupName ) { + return [ { name: 'blank', title: 'Blank', html: '', ID: null } ]; + } + + const patterns = []; + for ( const pattern of this.props.patterns ) { + for ( const key in pattern.categories ) { + if ( key === groupName ) { + patterns.push( pattern ); + } + } + } + + return patterns; + }; + + getPatternCategories = () => { + const groups = this.getPatternGroups(); + + if ( ! groups ) { + return null; + } + + const categories = []; + + for ( const key in groups ) { + categories.push( { slug: key, name: groups[ key ].title } ); + } + + return categories; + }; + + renderPatternGroup = () => { + const { selectedCategory } = this.state; + if ( ! selectedCategory ) { + return null; + } + + const patterns = this.getPatternsForGroup( selectedCategory ); + + if ( ! patterns?.length ) { + return null; + } + + const groupTitle = this.getPatternGroups()?.[ selectedCategory ]?.title; + + return this.renderPatternsList( patterns, groupTitle ); + }; + + renderPatternsList = ( patternsList: PatternDefinition[], groupTitle?: string ) => { + if ( ! patternsList.length ) { + return null; + } + + // The raw `patterns` prop is not filtered to remove patterns that + // contain missing Blocks. Therefore we compare with the keys of the + // filtered patterns from `getBlocksByPatternSlugs()` and filter this + // list to match. This ensures that the list of pattern thumbnails is + // filtered so that it does not include patterns that have missing Blocks. + const blocksByPatternSlug = this.getBlocksByPatternSlugs( this.props.patterns ); + + const patternsWithoutMissingBlocks = Object.keys( blocksByPatternSlug ); + + const filterOutPatternsWithMissingBlocks = ( + patternsToFilter: PatternDefinition[], + filterIn: string[] + ) => { + return patternsToFilter.filter( ( pattern ) => filterIn.includes( pattern.name ) ); + }; + + const filteredPatternsList = filterOutPatternsWithMissingBlocks( + patternsList, + patternsWithoutMissingBlocks + ); + + if ( ! filteredPatternsList.length ) { + return null; + } + + return ( + + ); + }; + + render() { + const { selectedCategory } = this.state; + const { isOpen, instanceId } = this.props; + + if ( ! isOpen ) { + return null; + } + + return ( + +
+
+

+ { __( 'Add a page', __i18n_text_domain__ ) } +

+

+ { __( + 'Pick a pre-defined layout or start with a blank page.', + __i18n_text_domain__ + ) } +

+
+ + +
+ + { __( 'Page categories', __i18n_text_domain__ ) } + + + this.handleCategorySelection( child.dataset.slug ?? null ) + } + > + { this.getPatternCategories()?.map( ( { slug, name } ) => ( + this.handleCategorySelection( slug ) } + className="page-pattern-modal__category-button" + tabIndex={ slug === selectedCategory ? undefined : -1 } + > + + { name } + + + ) ) } + +
+
+ { this.renderPatternGroup() } +
+
+
+ ); + } +} + +export default withInstanceId( PagePatternModal ); diff --git a/packages/page-pattern-modal/src/components/pattern-selector-control.tsx b/packages/page-pattern-modal/src/components/pattern-selector-control.tsx new file mode 100644 index 00000000000000..e39c93d2faf0b6 --- /dev/null +++ b/packages/page-pattern-modal/src/components/pattern-selector-control.tsx @@ -0,0 +1,71 @@ +/** + * WordPress dependencies + */ +import { BaseControl } from '@wordpress/components'; +import { memo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import PatternSelectorItem from './pattern-selector-item'; +import replacePlaceholders from '../utils/replace-placeholders'; +import { withInstanceId } from '@wordpress/compose'; +import type { PatternDefinition } from '../pattern-definition'; + +const noop = () => undefined; + +interface PatternSelectorControlProps { + instanceId: number; + label: string; + legendLabel?: string; + locale?: string; + onPatternSelect: ( patternName: string ) => void; + siteInformation?: Record< string, string >; + patterns?: PatternDefinition[]; + theme?: string; +} + +export const PatternSelectorControl = ( { + instanceId, + label, + legendLabel, + patterns = [], + theme = 'maywood', + locale = 'en', + onPatternSelect = noop, + siteInformation = {}, +}: PatternSelectorControlProps ): JSX.Element | null => { + if ( ! Array.isArray( patterns ) || ! patterns.length ) { + return null; + } + + return ( + +
    + { patterns.map( ( { ID, name, title, description } ) => ( +
  • + +
  • + ) ) } +
+
+ ); +}; + +export default memo( withInstanceId( PatternSelectorControl ) ); diff --git a/packages/page-template-modal/src/components/template-selector-item.tsx b/packages/page-pattern-modal/src/components/pattern-selector-item.tsx similarity index 67% rename from packages/page-template-modal/src/components/template-selector-item.tsx rename to packages/page-pattern-modal/src/components/pattern-selector-item.tsx index 32acf83e9005ba..66c26857dc6a48 100644 --- a/packages/page-template-modal/src/components/template-selector-item.tsx +++ b/packages/page-pattern-modal/src/components/pattern-selector-item.tsx @@ -1,15 +1,15 @@ -interface TemplateSelectorItemProps { +interface PatternSelectorItemProps { description?: string; locale: string; onSelect: ( value: string ) => void; - templatePostID: number | null; + patternPostID: number | null; title?: string; theme: string; value?: string; } -const TemplateSelectorItem = ( props: TemplateSelectorItemProps ): JSX.Element | null => { - const { value, onSelect, title, description, theme, locale, templatePostID } = props; +const PatternSelectorItem = ( props: PatternSelectorItemProps ): JSX.Element | null => { + const { value, onSelect, title, description, theme, locale, patternPostID } = props; if ( title == null || value == null ) { return null; @@ -21,7 +21,7 @@ const TemplateSelectorItem = ( props: TemplateSelectorItemProps ): JSX.Element | const previewUrl = `${ designsEndpoint }${ encodeURIComponent( theme ) }/${ encodeURIComponent( sourceSiteUrl - ) }/?post_id=${ encodeURIComponent( templatePostID ?? '' ) }&language=${ encodeURIComponent( + ) }/?post_id=${ encodeURIComponent( patternPostID ?? '' ) }&language=${ encodeURIComponent( locale ) }`; @@ -42,7 +42,7 @@ const TemplateSelectorItem = ( props: TemplateSelectorItemProps ): JSX.Element | const innerPreview = ( { onSelect( value ) } > - { innerPreview } + { innerPreview } { description } ); }; -export default TemplateSelectorItem; +export default PatternSelectorItem; diff --git a/packages/page-template-modal/src/components/test/page-template-modal.tsx b/packages/page-pattern-modal/src/components/test/page-pattern-modal.tsx similarity index 74% rename from packages/page-template-modal/src/components/test/page-template-modal.tsx rename to packages/page-pattern-modal/src/components/test/page-pattern-modal.tsx index 62393dce371c59..47e742e94478d2 100644 --- a/packages/page-template-modal/src/components/test/page-template-modal.tsx +++ b/packages/page-pattern-modal/src/components/test/page-pattern-modal.tsx @@ -12,11 +12,11 @@ import React from 'react'; /** * Internal dependencies */ -import PageTemplateModal from '../page-template-modal'; +import PagePatternModal from '../page-pattern-modal'; const noop = () => undefined; -const templates = [ +const patterns = [ { ID: null, title: 'Blank', name: 'blank' }, { ID: 1, @@ -59,15 +59,15 @@ afterEach( () => { unregisterBlockType( 'test/test-block' ); } ); -describe( '', () => { +describe( '', () => { it( 'sets the page title after selecting a layout', () => { - const insertTemplate = jest.fn(); + const insertPattern = jest.fn(); render( - @@ -75,17 +75,17 @@ describe( '', () => { // 1th button because the 0th is the mobile drop down menu fireEvent.click( screen.getAllByText( 'Blog' )[ 1 ] ); fireEvent.click( screen.getByText( 'Descriptive Name One' ) ); - expect( insertTemplate ).toHaveBeenCalledWith( 'Layout One', expect.anything() ); + expect( insertPattern ).toHaveBeenCalledWith( 'Layout One', expect.anything() ); } ); it( "doesn't set the page title after selecting a home page layout", () => { - const insertTemplate = jest.fn(); + const insertPattern = jest.fn(); render( - @@ -93,22 +93,22 @@ describe( '', () => { // 1th button because the 0th is the mobile drop down menu fireEvent.click( screen.getAllByText( 'Home' )[ 1 ] ); fireEvent.click( screen.getByText( 'Descriptive Name Two' ) ); - expect( insertTemplate ).toHaveBeenCalledWith( null, expect.anything() ); + expect( insertPattern ).toHaveBeenCalledWith( null, expect.anything() ); } ); it( 'clears the page title after selecting blank layout', () => { - const insertTemplate = jest.fn(); + const insertPattern = jest.fn(); render( - ); fireEvent.click( screen.getByText( 'Blank page' ) ); - expect( insertTemplate ).toHaveBeenCalledWith( '', expect.anything() ); + expect( insertPattern ).toHaveBeenCalledWith( '', expect.anything() ); } ); } ); diff --git a/packages/page-pattern-modal/src/index.ts b/packages/page-pattern-modal/src/index.ts new file mode 100644 index 00000000000000..a38a958655c7c8 --- /dev/null +++ b/packages/page-pattern-modal/src/index.ts @@ -0,0 +1,3 @@ +export { default as PagePatternModal } from './components/page-pattern-modal'; +export { initializeWithIdentity as initializeTracksWithIdentity } from './utils/tracking'; +export type { PatternCategory, PatternDefinition } from './pattern-definition'; diff --git a/packages/page-template-modal/src/patch-component-types.d.ts b/packages/page-pattern-modal/src/patch-component-types.d.ts similarity index 100% rename from packages/page-template-modal/src/patch-component-types.d.ts rename to packages/page-pattern-modal/src/patch-component-types.d.ts diff --git a/packages/page-template-modal/src/layout-definition.ts b/packages/page-pattern-modal/src/pattern-definition.ts similarity index 74% rename from packages/page-template-modal/src/layout-definition.ts rename to packages/page-pattern-modal/src/pattern-definition.ts index d06d853d87ab73..f586c92d7baedc 100644 --- a/packages/page-template-modal/src/layout-definition.ts +++ b/packages/page-pattern-modal/src/pattern-definition.ts @@ -1,14 +1,14 @@ -export interface LayoutCategory { +export interface PatternCategory { slug: string; title: string; description: string; } -export interface LayoutDefinition { +export interface PatternDefinition { name: string; ID: number | null; title: string; - categories?: Record< string, LayoutCategory >; + categories?: Record< string, PatternCategory >; description?: string; html?: string; modified_date?: string; diff --git a/packages/page-template-modal/src/styles/page-template-modal.scss b/packages/page-pattern-modal/src/styles/page-pattern-modal.scss similarity index 69% rename from packages/page-template-modal/src/styles/page-template-modal.scss rename to packages/page-pattern-modal/src/styles/page-pattern-modal.scss index 58c006953c8472..94eb2eefbd8201 100644 --- a/packages/page-template-modal/src/styles/page-template-modal.scss +++ b/packages/page-pattern-modal/src/styles/page-pattern-modal.scss @@ -16,12 +16,12 @@ word-wrap: normal !important; } -$template-selector-border-color: $studio-gray-5; -$template-selector-hover-border-color: $studio-gray-50; -$template-selector-empty-background: $studio-white; -$template-selector-focus-color: var( --wp-admin-theme-color ); -$template-selector-link-text: $studio-gray-60; -$template-selector-link-hover-text: var( --color-text ); +$pattern-selector-border-color: $studio-gray-5; +$pattern-selector-hover-border-color: $studio-gray-50; +$pattern-selector-empty-background: $studio-white; +$pattern-selector-focus-color: var( --wp-admin-theme-color ); +$pattern-selector-link-text: $studio-gray-60; +$pattern-selector-link-hover-text: var( --color-text ); // Breakpoints $breakpoint-mobile: 600px; @@ -32,7 +32,7 @@ $breakpoint-huge: 1648px; $sidebar-width: 324px; // Overrides of the Gutenberg modal component -.page-template-modal { +.page-pattern-modal { @media screen and ( min-width: $breakpoint-mobile ) { width: 90%; height: 90vh; @@ -53,7 +53,7 @@ $sidebar-width: 324px; } } -.page-template-modal__heading { +.page-pattern-modal__heading { font-family: $brand-serif; font-weight: 400; letter-spacing: -0.4px; @@ -73,20 +73,20 @@ $sidebar-width: 324px; } } -.page-template-modal__description { +.page-pattern-modal__description { color: $studio-gray-50; margin: 0 0 24px; font-size: 1rem; line-height: 1.5; } -.page-template-modal__inner { +.page-pattern-modal__inner { @media screen and ( min-width: $breakpoint-mobile ) { padding: 0 20px; } } -.template-selector-control { +.pattern-selector-control { margin-bottom: 20px; .components-base-control__label { @@ -94,7 +94,7 @@ $sidebar-width: 324px; } } -.template-selector-control__options { +.pattern-selector-control__options { display: grid; grid-template-columns: 1fr; column-gap: 1.5em; @@ -109,7 +109,7 @@ $sidebar-width: 324px; } } -.template-selector-item__label { +.pattern-selector-item__label { display: block; width: 100%; text-align: center; @@ -118,32 +118,32 @@ $sidebar-width: 324px; appearance: none; padding: 0; overflow: hidden; - background-color: $template-selector-empty-background; + background-color: $pattern-selector-empty-background; position: relative; transform: translateZ( 0 ); // Fix for Safari rounded border overflow (1/2). - color: $template-selector-link-text; + color: $pattern-selector-link-text; &:focus { - box-shadow: 0 0 0 1px $template-selector-empty-background, - 0 0 0 3px $template-selector-focus-color; + box-shadow: 0 0 0 1px $pattern-selector-empty-background, + 0 0 0 3px $pattern-selector-focus-color; // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; } &:hover { - color: $template-selector-link-hover-text; + color: $pattern-selector-link-hover-text; - .template-selector-item__preview-wrap { - border: solid 1px $template-selector-hover-border-color; + .pattern-selector-item__preview-wrap { + border: solid 1px $pattern-selector-hover-border-color; } } } -.template-selector-item__preview-wrap { +.pattern-selector-item__preview-wrap { width: 100%; display: block; margin: 0 auto; - background: $template-selector-empty-background; + background: $pattern-selector-empty-background; border-radius: 0; overflow: hidden; height: 0; @@ -153,7 +153,7 @@ $sidebar-width: 324px; pointer-events: none; opacity: 1; transform: translateZ( 0 ); // Fix for Safari rounded border overflow (2/2). - border: solid 1px $template-selector-border-color; + border: solid 1px $pattern-selector-border-color; margin-bottom: 1em; &.is-rendering { @@ -161,7 +161,7 @@ $sidebar-width: 324px; } } -.template-selector-item__media { +.pattern-selector-item__media { width: 100%; height: 100%; display: block; @@ -171,7 +171,7 @@ $sidebar-width: 324px; object-fit: cover; } -.page-template-modal__sidebar { +.page-pattern-modal__sidebar { @media screen and ( min-width: $breakpoint-mobile ) { position: fixed; width: calc( #{$sidebar-width} - 24px ); @@ -191,17 +191,17 @@ $sidebar-width: 324px; pointer-events: auto; } - .page-template-modal__category-list { + .page-pattern-modal__category-list { pointer-events: none; - .page-template-modal__category-button { + .page-pattern-modal__category-button { pointer-events: auto; } } } } -.page-template-modal__category-list { +.page-pattern-modal__category-list { display: none; margin: 0; padding-top: 35px; @@ -214,7 +214,7 @@ $sidebar-width: 324px; display: block; width: auto; height: auto; - color: $template-selector-link-text; + color: $pattern-selector-link-text; font-size: 0.875rem; margin-left: -12px; padding: 6px 12px; @@ -222,7 +222,7 @@ $sidebar-width: 324px; &.is-tertiary:not( :disabled ):hover { box-shadow: none; - color: $template-selector-link-hover-text !important; + color: $pattern-selector-link-hover-text !important; } &.is-tertiary:not( :disabled ):active { @@ -230,26 +230,26 @@ $sidebar-width: 324px; } &.is-tertiary:not( :disabled ):focus { - box-shadow: 0 0 0 var( --wp-admin-border-width-focus ) $template-selector-focus-color; + box-shadow: 0 0 0 var( --wp-admin-border-width-focus ) $pattern-selector-focus-color; } } - .page-template-modal__category-item-selection-wrapper { + .page-pattern-modal__category-item-selection-wrapper { border-bottom: 2px solid transparent; padding-bottom: 2px; } .components-menu-item__button:not( :disabled )[aria-selected='true'] { - color: $template-selector-link-hover-text; + color: $pattern-selector-link-hover-text; font-weight: 600; - .page-template-modal__category-item-selection-wrapper { - border-bottom-color: $template-selector-link-hover-text; + .page-pattern-modal__category-item-selection-wrapper { + border-bottom-color: $pattern-selector-link-hover-text; } } } -.page-template-modal__button-container { +.page-pattern-modal__button-container { display: grid; grid-template-columns: 1fr 1fr; column-gap: 8px; @@ -260,9 +260,9 @@ $sidebar-width: 324px; } } -.components-button.page-template-modal__blank-button { +.components-button.page-pattern-modal__blank-button { min-height: 40px; - box-shadow: inset 0 0 0 1px $template-selector-border-color; + box-shadow: inset 0 0 0 1px $pattern-selector-border-color; color: var( --color-text ); &:active { @@ -270,30 +270,30 @@ $sidebar-width: 324px; } &.is-secondary:hover:not( :disabled ) { - box-shadow: inset 0 0 0 1px $template-selector-hover-border-color; + box-shadow: inset 0 0 0 1px $pattern-selector-hover-border-color; color: var( --color-text ); } &.is-secondary:focus:not( :disabled ) { - box-shadow: 0 0 0 var( --wp-admin-border-width-focus ) $template-selector-focus-color; + box-shadow: 0 0 0 var( --wp-admin-border-width-focus ) $pattern-selector-focus-color; } &.is-secondary:hover:focus:not( :disabled ) { - box-shadow: 0 0 0 var( --wp-admin-border-width-focus ) $template-selector-focus-color; + box-shadow: 0 0 0 var( --wp-admin-border-width-focus ) $pattern-selector-focus-color; outline: 3px solid transparent; } } -.wp-core-ui select.page-template-modal__mobile-category-dropdown { +.wp-core-ui select.page-pattern-modal__mobile-category-dropdown { font-size: $default-font-size; - border-color: $template-selector-border-color; + border-color: $pattern-selector-border-color; @media screen and ( min-width: $breakpoint-mobile ) { display: none; } } -.page-template-modal__template-list-container { +.page-pattern-modal__pattern-list-container { @media screen and ( min-width: $breakpoint-mobile ) { padding-left: $sidebar-width; } diff --git a/packages/page-template-modal/src/types.d.ts b/packages/page-pattern-modal/src/types.d.ts similarity index 100% rename from packages/page-template-modal/src/types.d.ts rename to packages/page-pattern-modal/src/types.d.ts diff --git a/packages/page-template-modal/src/utils/contains-missing-block.ts b/packages/page-pattern-modal/src/utils/contains-missing-block.ts similarity index 100% rename from packages/page-template-modal/src/utils/contains-missing-block.ts rename to packages/page-pattern-modal/src/utils/contains-missing-block.ts diff --git a/packages/page-template-modal/src/utils/group-utils.ts b/packages/page-pattern-modal/src/utils/group-utils.ts similarity index 100% rename from packages/page-template-modal/src/utils/group-utils.ts rename to packages/page-pattern-modal/src/utils/group-utils.ts diff --git a/packages/page-template-modal/src/utils/map-blocks-recursively.ts b/packages/page-pattern-modal/src/utils/map-blocks-recursively.ts similarity index 100% rename from packages/page-template-modal/src/utils/map-blocks-recursively.ts rename to packages/page-pattern-modal/src/utils/map-blocks-recursively.ts diff --git a/packages/page-template-modal/src/utils/replace-placeholders.ts b/packages/page-pattern-modal/src/utils/replace-placeholders.ts similarity index 100% rename from packages/page-template-modal/src/utils/replace-placeholders.ts rename to packages/page-pattern-modal/src/utils/replace-placeholders.ts diff --git a/packages/page-template-modal/src/utils/test/group-utils.ts b/packages/page-pattern-modal/src/utils/test/group-utils.ts similarity index 100% rename from packages/page-template-modal/src/utils/test/group-utils.ts rename to packages/page-pattern-modal/src/utils/test/group-utils.ts diff --git a/packages/page-template-modal/src/utils/tracking.ts b/packages/page-pattern-modal/src/utils/tracking.ts similarity index 93% rename from packages/page-template-modal/src/utils/tracking.ts rename to packages/page-pattern-modal/src/utils/tracking.ts index 55f598af8207ab..107b63f5dbf8c2 100644 --- a/packages/page-template-modal/src/utils/tracking.ts +++ b/packages/page-pattern-modal/src/utils/tracking.ts @@ -65,9 +65,9 @@ export const trackDismiss = (): void => { /** * Track layout selection. * - * @param template Template slug. + * @param pattern Pattern slug. */ -export const trackSelection = ( template: string ): void => { +export const trackSelection = ( pattern: string ): void => { if ( ! tracksIdentity ) { return; } @@ -76,7 +76,7 @@ export const trackSelection = ( template: string ): void => { 'a8c_full_site_editing_template_selector_template_selected', { blog_id: tracksIdentity.blogid, - template, + pattern, }, ] ); }; diff --git a/packages/page-template-modal/tsconfig-cjs.json b/packages/page-pattern-modal/tsconfig-cjs.json similarity index 100% rename from packages/page-template-modal/tsconfig-cjs.json rename to packages/page-pattern-modal/tsconfig-cjs.json diff --git a/packages/page-template-modal/tsconfig.json b/packages/page-pattern-modal/tsconfig.json similarity index 100% rename from packages/page-template-modal/tsconfig.json rename to packages/page-pattern-modal/tsconfig.json diff --git a/packages/page-template-modal/src/components/page-template-modal.tsx b/packages/page-template-modal/src/components/page-template-modal.tsx deleted file mode 100644 index 5d38083bfb6934..00000000000000 --- a/packages/page-template-modal/src/components/page-template-modal.tsx +++ /dev/null @@ -1,384 +0,0 @@ -/** - * External dependencies - */ -import { memoize } from 'lodash'; -import { __ } from '@wordpress/i18n'; -import { Button, MenuItem, Modal, NavigableMenu, VisuallyHidden } from '@wordpress/components'; -import { Component } from '@wordpress/element'; -import { BlockInstance, parse as parseBlocks } from '@wordpress/blocks'; -import { withInstanceId } from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import TemplateSelectorControl from './template-selector-control'; -import { trackDismiss, trackSelection, trackView } from '../utils/tracking'; -import replacePlaceholders from '../utils/replace-placeholders'; -import mapBlocksRecursively from '../utils/map-blocks-recursively'; -import containsMissingBlock from '../utils/contains-missing-block'; -import { sortGroupNames } from '../utils/group-utils'; -import type { LayoutCategory, LayoutDefinition } from '../layout-definition'; - -interface PageTemplateModalProps { - areTipsEnabled?: boolean; - hideWelcomeGuide: () => void; - insertTemplate: ( title: string | null, blocks: BlockInstance[] ) => void; - instanceId: number; - isOpen: boolean; - isWelcomeGuideActive?: boolean; - locale?: string; - saveTemplateChoice: ( name: string ) => void; - setOpenState: ( isOpen: false ) => void; - siteInformation?: Record< string, string >; - templates: LayoutDefinition[]; - theme?: string; -} - -interface PageTemplateModalState { - selectedCategory: string | null; -} - -class PageTemplateModal extends Component< PageTemplateModalProps, PageTemplateModalState > { - constructor( props: PageTemplateModalProps ) { - super( props ); - this.state = { - selectedCategory: this.getDefaultSelectedCategory(), - }; - } - - // Parse templates blocks and memoize them. - getBlocksByTemplateSlugs = memoize( ( templates: LayoutDefinition[] ) => { - const blocksByTemplateSlugs = templates.reduce( ( prev, { name, html } ) => { - prev[ name ] = html - ? parseBlocks( replacePlaceholders( html, this.props.siteInformation ) ) - : []; - return prev; - }, {} as Record< string, BlockInstance[] > ); - - // Remove templates that include a missing block - return this.filterTemplatesWithMissingBlocks( blocksByTemplateSlugs ); - } ); - - filterTemplatesWithMissingBlocks( templates: Record< string, BlockInstance[] > ) { - return Object.entries( templates ).reduce( ( acc, [ name, templateBlocks ] ) => { - // Does the template contain any missing blocks? - const templateHasMissingBlocks = containsMissingBlock( templateBlocks ); - - // Only retain the template in the collection if: - // 1. It does not contain any missing blocks - // 2. There are no blocks at all (likely the "blank" template placeholder) - if ( ! templateHasMissingBlocks || ! templateBlocks.length ) { - acc[ name ] = templateBlocks; - } - - return acc; - }, {} as Record< string, BlockInstance[] > ); - } - - getBlocksForSelection = ( selectedTemplate: string ) => { - const blocks = this.getBlocksByTemplateSlug( selectedTemplate ); - // Modify the existing blocks returning new block object references. - return mapBlocksRecursively( blocks, function modifyBlocksForSelection( block ) { - // Ensure that core/button doesn't link to external template site - if ( 'core/button' === block.name && undefined !== block.attributes.url ) { - block.attributes.url = '#'; - } - - return block; - } ); - }; - - componentDidMount() { - if ( this.props.isOpen ) { - this.trackCurrentView(); - } - } - - componentDidUpdate( prevProps: PageTemplateModalProps ) { - // Only track when the modal is first displayed - // and if it didn't already happen during componentDidMount. - if ( ! prevProps.isOpen && this.props.isOpen ) { - this.trackCurrentView(); - } - - // Disable welcome guide right away as it collides with the modal window. - if ( this.props.isWelcomeGuideActive || this.props.areTipsEnabled ) { - this.props.hideWelcomeGuide(); - } - } - - trackCurrentView() { - trackView( 'add-page' ); - } - - getDefaultSelectedCategory() { - const categories = this.getTemplateCategories(); - if ( ! categories?.length ) { - return null; - } - - return categories[ 0 ].slug; - } - - setTemplate = ( name: string ) => { - // Track selection and mark post as using a template in its postmeta. - trackSelection( name ); - this.props.saveTemplateChoice( name ); - - // Check to see if this is a blank template selection - // and reset the template if so. - if ( 'blank' === name ) { - this.props.insertTemplate( '', [] ); - this.props.setOpenState( false ); - return; - } - - const template = this.props.templates.find( ( t ) => t.name === name ); - const isHomepageTemplate = ( template?.categories || {} ).hasOwnProperty( 'home' ); - - // Load content. - const blocks = this.getBlocksForSelection( name ); - - // Only overwrite the page title if the template is not one of the Homepage Layouts - const title = isHomepageTemplate ? null : template?.title || ''; - - // Skip inserting if this is not a blank template - // and there's nothing to insert. - if ( ! blocks || ! blocks.length ) { - this.props.setOpenState( false ); - return; - } - - this.props.insertTemplate( title, blocks ); - this.props.setOpenState( false ); - }; - - handleCategorySelection = ( selectedCategory: string | null ) => { - this.setState( { selectedCategory } ); - }; - - closeModal = () => { - trackDismiss(); - this.props.setOpenState( false ); - }; - - getBlocksByTemplateSlug( name: string ) { - return this.getBlocksByTemplateSlugs( this.props.templates )?.[ name ] ?? []; - } - - getTemplateGroups = () => { - if ( ! this.props.templates.length ) { - return null; - } - - const templateGroups: Record< string, LayoutCategory > = {}; - for ( const template of this.props.templates ) { - for ( const key in template.categories ) { - if ( ! ( key in templateGroups ) ) { - templateGroups[ key ] = template.categories[ key ]; - } - } - } - - const preferredGroupOrder = [ - 'featured', - 'about', - 'blog', - 'home', - 'gallery', - 'services', - 'contact', - ]; - return sortGroupNames( preferredGroupOrder, templateGroups ); - }; - - getTemplatesForGroup = ( groupName: string ): LayoutDefinition[] | null => { - if ( ! this.props.templates.length ) { - return null; - } - - if ( 'blank' === groupName ) { - return [ { name: 'blank', title: 'Blank', html: '', ID: null } ]; - } - - const templates = []; - for ( const template of this.props.templates ) { - for ( const key in template.categories ) { - if ( key === groupName ) { - templates.push( template ); - } - } - } - - return templates; - }; - - getTemplateCategories = () => { - const groups = this.getTemplateGroups(); - - if ( ! groups ) { - return null; - } - - const categories = []; - - for ( const key in groups ) { - categories.push( { slug: key, name: groups[ key ].title } ); - } - - return categories; - }; - - renderTemplateGroup = () => { - const { selectedCategory } = this.state; - if ( ! selectedCategory ) { - return null; - } - - const templates = this.getTemplatesForGroup( selectedCategory ); - - if ( ! templates?.length ) { - return null; - } - - const groupTitle = this.getTemplateGroups()?.[ selectedCategory ]?.title; - - return this.renderTemplatesList( templates, groupTitle ); - }; - - renderTemplatesList = ( templatesList: LayoutDefinition[], groupTitle?: string ) => { - if ( ! templatesList.length ) { - return null; - } - - // The raw `templates` prop is not filtered to remove Templates that - // contain missing Blocks. Therefore we compare with the keys of the - // filtered templates from `getBlocksByTemplateSlugs()` and filter this - // list to match. This ensures that the list of Template thumbnails is - // filtered so that it does not include Templates that have missing Blocks. - const blocksByTemplateSlug = this.getBlocksByTemplateSlugs( this.props.templates ); - - const templatesWithoutMissingBlocks = Object.keys( blocksByTemplateSlug ); - - const filterOutTemplatesWithMissingBlocks = ( - templatesToFilter: LayoutDefinition[], - filterIn: string[] - ) => { - return templatesToFilter.filter( ( template ) => filterIn.includes( template.name ) ); - }; - - const filteredTemplatesList = filterOutTemplatesWithMissingBlocks( - templatesList, - templatesWithoutMissingBlocks - ); - - if ( ! filteredTemplatesList.length ) { - return null; - } - - return ( - - ); - }; - - render() { - const { selectedCategory } = this.state; - const { isOpen, instanceId } = this.props; - - if ( ! isOpen ) { - return null; - } - - return ( - -
-
-

- { __( 'Add a page', __i18n_text_domain__ ) } -

-

- { __( - 'Pick a pre-defined layout or start with a blank page.', - __i18n_text_domain__ - ) } -

-
- - -
- - { __( 'Page categories', __i18n_text_domain__ ) } - - - this.handleCategorySelection( child.dataset.slug ?? null ) - } - > - { this.getTemplateCategories()?.map( ( { slug, name } ) => ( - this.handleCategorySelection( slug ) } - className="page-template-modal__category-button" - tabIndex={ slug === selectedCategory ? undefined : -1 } - > - - { name } - - - ) ) } - -
-
- { this.renderTemplateGroup() } -
-
-
- ); - } -} - -export default withInstanceId( PageTemplateModal ); diff --git a/packages/page-template-modal/src/components/template-selector-control.tsx b/packages/page-template-modal/src/components/template-selector-control.tsx deleted file mode 100644 index 63bff0a4f5fd40..00000000000000 --- a/packages/page-template-modal/src/components/template-selector-control.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/** - * WordPress dependencies - */ -import { BaseControl } from '@wordpress/components'; -import { memo } from '@wordpress/element'; -import { withInstanceId } from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import TemplateSelectorItem from './template-selector-item'; -import replacePlaceholders from '../utils/replace-placeholders'; -import type { LayoutDefinition } from '../layout-definition'; - -const noop = () => undefined; - -interface TemplateSelectorControlProps { - instanceId: number; - label: string; - legendLabel?: string; - locale?: string; - onTemplateSelect: ( templateName: string ) => void; - siteInformation?: Record< string, string >; - templates?: LayoutDefinition[]; - theme?: string; -} - -export const TemplateSelectorControl = ( { - instanceId, - label, - legendLabel, - templates = [], - theme = 'maywood', - locale = 'en', - onTemplateSelect = noop, - siteInformation = {}, -}: TemplateSelectorControlProps ): JSX.Element | null => { - if ( ! Array.isArray( templates ) || ! templates.length ) { - return null; - } - - return ( - -
    - { templates.map( ( { ID, name, title, description } ) => ( -
  • - -
  • - ) ) } -
-
- ); -}; - -export default memo( withInstanceId( TemplateSelectorControl ) ); diff --git a/packages/page-template-modal/src/index.ts b/packages/page-template-modal/src/index.ts deleted file mode 100644 index f49cd1949ea21e..00000000000000 --- a/packages/page-template-modal/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as PageTemplateModal } from './components/page-template-modal'; -export { initializeWithIdentity as initializeTracksWithIdentity } from './utils/tracking'; -export type { LayoutCategory, LayoutDefinition } from './layout-definition'; diff --git a/packages/tsconfig.json b/packages/tsconfig.json index aac2c0757fd587..c6d92e94b0daa5 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -22,7 +22,7 @@ { "path": "./languages" }, { "path": "./launch" }, { "path": "./onboarding" }, - { "path": "./page-template-modal" }, + { "path": "./page-pattern-modal" }, { "path": "./plans-grid" }, { "path": "./react-i18n" }, { "path": "./search" }, diff --git a/test/e2e/lib/gutenberg/gutenberg-editor-component.js b/test/e2e/lib/gutenberg/gutenberg-editor-component.js index 609d2355f968c2..639b10d6d54973 100644 --- a/test/e2e/lib/gutenberg/gutenberg-editor-component.js +++ b/test/e2e/lib/gutenberg/gutenberg-editor-component.js @@ -648,7 +648,7 @@ export default class GutenbergEditorComponent extends AsyncBaseContainer { } async dismissPageTemplateSelector() { - if ( await driverHelper.isElementPresent( this.driver, By.css( '.page-template-modal' ) ) ) { + if ( await driverHelper.isElementPresent( this.driver, By.css( '.page-pattern-modal' ) ) ) { if ( driverManager.currentScreenSize() === 'mobile' ) { // For some reason, when the screensize is set to mobile, // the welcome guide modal is not closed when the template button @@ -664,11 +664,11 @@ export default class GutenbergEditorComponent extends AsyncBaseContainer { ); await driverHelper.clickWhenClickable( this.driver, - By.css( 'button.page-template-modal__blank-button' ) + By.css( 'button.page-pattern-modal__blank-button' ) ); } else { const useBlankButton = await this.driver.findElement( - By.css( 'button.page-template-modal__blank-button' ) + By.css( 'button.page-pattern-modal__blank-button' ) ); await this.driver.executeScript( 'arguments[0].click()', useBlankButton ); }