diff --git a/.github/workflows/enforce-pr-labels.yml b/.github/workflows/enforce-pr-labels.yml
index 320ef1375029c..4ef163694b947 100644
--- a/.github/workflows/enforce-pr-labels.yml
+++ b/.github/workflows/enforce-pr-labels.yml
@@ -14,5 +14,5 @@ jobs:
count: 1
labels: '[Type] Automated Testing, [Type] Breaking Change, [Type] Bug, [Type] Build Tooling, [Type] Code Quality, [Type] Copy, [Type] Developer Documentation, [Type] Enhancement, [Type] Experimental, [Type] Feature, [Type] New API, [Type] Task, [Type] Performance, [Type] Project Management, [Type] Regression, [Type] Security, [Type] WP Core Ticket, Backport from WordPress Core'
add_comment: true
- message: "**Warning: Type of PR label error**\n\n To merge this PR, it requires {{ errorString }} {{ count }} label indicating the type of PR. Other labels are optional and not being checked here. \n- **Type-related labels to choose from**: {{ provided }}.\n- **Labels found**: {{ applied }}.\n\nRead more about [Type labels in Gutenberg](https://github.com/WordPress/gutenberg/labels?q=type)."
+ message: "**Warning: Type of PR label mismatch**\n\n To merge this PR, it requires {{ errorString }} {{ count }} label indicating the type of PR. Other labels are optional and not being checked here. \n- **Type-related labels to choose from**: {{ provided }}.\n- **Labels found**: {{ applied }}.\n\nRead more about [Type labels in Gutenberg](https://github.com/WordPress/gutenberg/labels?q=type). Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task."
exit_type: failure
diff --git a/docs/getting-started/devenv/get-started-with-create-block.md b/docs/getting-started/devenv/get-started-with-create-block.md
index a01c08a4ce2f4..3a2c6607b82cf 100644
--- a/docs/getting-started/devenv/get-started-with-create-block.md
+++ b/docs/getting-started/devenv/get-started-with-create-block.md
@@ -1,6 +1,6 @@
# Get started with create-block
-Custom blocks for the Block Editor in WordPress are typically registered using plugins and are defined through a specific set of files. The [`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) package is an officially supported tool to scaffold the structure of files needed to create and register a block. It generates all the necessary code to start a project and integrates a modern JavaScript build setup (using [`wp-scripts`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts.md)) with no configuration required.
+Custom blocks for the Block Editor in WordPress are typically registered using plugins and are defined through a specific set of files. The [`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) package is an officially supported tool to scaffold the structure of files needed to create and register a block. It generates all the necessary code to start a project and integrates a modern JavaScript build setup (using [`wp-scripts`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/)) with no configuration required.
The package is designed to help developers quickly set up a block development environment following WordPress best practices.
diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md
index 8a190869f99e7..ea97ce28e4d85 100644
--- a/docs/reference-guides/data/data-core.md
+++ b/docs/reference-guides/data/data-core.md
@@ -150,6 +150,19 @@ _Returns_
- `undefined< 'edit' >`: Current user object.
+### getDefaultTemplateId
+
+Returns the default template use to render a given query.
+
+_Parameters_
+
+- _state_ `State`: Data state.
+- _query_ `TemplateQuery`: Query.
+
+_Returns_
+
+- `string`: The default template id for the given query.
+
### getEditedEntityRecord
Returns the specified entity record, merged with its edits.
@@ -648,6 +661,19 @@ _Returns_
- `Object`: Action object.
+### receiveDefaultTemplateId
+
+Returns an action object used to set the template for a given query.
+
+_Parameters_
+
+- _query_ `Object`: The lookup query.
+- _templateId_ `string`: The resolved template id.
+
+_Returns_
+
+- `Object`: Action object.
+
### receiveEntityRecords
Returns an action object used in signalling that entity records have been received.
diff --git a/lib/experimental/data-views.php b/lib/experimental/data-views.php
index e0346184ffc21..e9fb2134f3b39 100644
--- a/lib/experimental/data-views.php
+++ b/lib/experimental/data-views.php
@@ -40,7 +40,7 @@ function _gutenberg_register_data_views_post_type() {
'wp_dataviews_type',
array( 'wp_dataviews' ),
array(
- 'public' => true,
+ 'public' => false,
'hierarchical' => false,
'labels' => array(
'name' => __( 'Dataview types', 'gutenberg' ),
diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php
index 19dfcaab49533..9655178d70667 100644
--- a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php
+++ b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php
@@ -344,6 +344,18 @@ public function update_font_library_permissions_check() {
return true;
}
+ /**
+ * Checks whether the font directory exists or not.
+ *
+ * @since 6.5.0
+ *
+ * @return bool Whether the font directory exists.
+ */
+ private function has_upload_directory() {
+ $upload_dir = WP_Font_Library::get_fonts_dir();
+ return is_dir( $upload_dir );
+ }
+
/**
* Checks whether the user has write permissions to the temp and fonts directories.
*
@@ -418,12 +430,29 @@ public function install_fonts( $request ) {
$response_status = 400;
}
- if ( $this->needs_write_permission( $fonts_to_install ) && ! $this->has_write_permission() ) {
- $errors[] = new WP_Error(
- 'cannot_write_fonts_folder',
- __( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' )
- );
- $response_status = 500;
+ if ( $this->needs_write_permission( $fonts_to_install ) ) {
+ $upload_dir = WP_Font_Library::get_fonts_dir();
+ if ( ! $this->has_upload_directory() ) {
+ if ( ! wp_mkdir_p( $upload_dir ) ) {
+ $errors[] = new WP_Error(
+ 'cannot_create_fonts_folder',
+ sprintf(
+ /* translators: %s: Directory path. */
+ __( 'Error: Unable to create directory %s.', 'gutenberg' ),
+ esc_html( $upload_dir )
+ )
+ );
+ $response_status = 500;
+ }
+ }
+
+ if ( $this->has_upload_directory() && ! $this->has_write_permission() ) {
+ $errors[] = new WP_Error(
+ 'cannot_write_fonts_folder',
+ __( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' )
+ );
+ $response_status = 500;
+ }
}
if ( ! empty( $errors ) ) {
diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php
index 41223c0815886..eae731e243891 100644
--- a/lib/experimental/interactivity-api/directive-processing.php
+++ b/lib/experimental/interactivity-api/directive-processing.php
@@ -8,37 +8,9 @@
*/
/**
- * Process directives in each block.
- *
- * @param string $block_content The block content.
- * @param array $block The full block.
- *
- * @return string Filtered block content.
- */
-function gutenberg_interactivity_process_directives_in_root_blocks( $block_content, $block ) {
- // Don't process inner blocks or root blocks that don't contain directives.
- if ( ! WP_Directive_Processor::is_root_block( $block ) || strpos( $block_content, 'data-wp-' ) === false ) {
- return $block_content;
- }
-
- // TODO: Add some directive/components registration mechanism.
- $directives = array(
- 'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind',
- 'data-wp-context' => 'gutenberg_interactivity_process_wp_context',
- 'data-wp-class' => 'gutenberg_interactivity_process_wp_class',
- 'data-wp-style' => 'gutenberg_interactivity_process_wp_style',
- 'data-wp-text' => 'gutenberg_interactivity_process_wp_text',
- );
-
- $tags = new WP_Directive_Processor( $block_content );
- $tags = gutenberg_interactivity_process_directives( $tags, 'data-wp-', $directives );
- return $tags->get_updated_html();
-}
-add_filter( 'render_block', 'gutenberg_interactivity_process_directives_in_root_blocks', 10, 2 );
-
-/**
- * Mark the inner blocks with a temporary property so we can discard them later,
- * and process only the root blocks.
+ * Process the Interactivity API directives using the root blocks of the
+ * outermost rendering, ignoring the root blocks of inner blocks like Patterns,
+ * Template Parts or Content.
*
* @param array $parsed_block The parsed block.
* @param array $source_block The source block.
@@ -46,16 +18,56 @@ function gutenberg_interactivity_process_directives_in_root_blocks( $block_conte
*
* @return array The parsed block.
*/
-function gutenberg_interactivity_mark_inner_blocks( $parsed_block, $source_block, $parent_block ) {
- if ( ! isset( $parent_block ) ) {
+function gutenberg_interactivity_process_directives( $parsed_block, $source_block, $parent_block ) {
+ static $is_inside_root_block = false;
+ static $process_directives_in_root_blocks = null;
+
+ if ( ! isset( $process_directives_in_root_blocks ) ) {
+ /**
+ * Process directives in each root block.
+ *
+ * @param string $block_content The block content.
+ * @param array $block The full block.
+ *
+ * @return string Filtered block content.
+ */
+ $process_directives_in_root_blocks = static function ( $block_content, $block ) use ( &$is_inside_root_block ) {
+
+ if ( WP_Directive_Processor::is_root_block( $block ) ) {
+
+ $directives = array(
+ 'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind',
+ 'data-wp-context' => 'gutenberg_interactivity_process_wp_context',
+ 'data-wp-class' => 'gutenberg_interactivity_process_wp_class',
+ 'data-wp-style' => 'gutenberg_interactivity_process_wp_style',
+ 'data-wp-text' => 'gutenberg_interactivity_process_wp_text',
+ );
+
+ $tags = new WP_Directive_Processor( $block_content );
+ $tags = gutenberg_interactivity_process_rendered_html( $tags, 'data-wp-', $directives );
+ $is_inside_root_block = false;
+ return $tags->get_updated_html();
+
+ }
+
+ return $block_content;
+ };
+ add_filter( 'render_block', $process_directives_in_root_blocks, 10, 2 );
+ }
+
+ if ( ! isset( $parent_block ) && ! $is_inside_root_block ) {
WP_Directive_Processor::add_root_block( $parsed_block );
+ $is_inside_root_block = true;
}
+
return $parsed_block;
}
-add_filter( 'render_block_data', 'gutenberg_interactivity_mark_inner_blocks', 10, 3 );
+add_filter( 'render_block_data', 'gutenberg_interactivity_process_directives', 10, 3 );
+
/**
- * Process directives.
+ * Traverses the HTML searching for Interactivity API directives and processing
+ * them.
*
* @param WP_Directive_Processor $tags An instance of the WP_Directive_Processor.
* @param string $prefix Attribute prefix.
@@ -64,7 +76,7 @@ function gutenberg_interactivity_mark_inner_blocks( $parsed_block, $source_block
* @return WP_Directive_Processor The modified instance of the
* WP_Directive_Processor.
*/
-function gutenberg_interactivity_process_directives( $tags, $prefix, $directives ) {
+function gutenberg_interactivity_process_rendered_html( $tags, $prefix, $directives ) {
$context = new WP_Directive_Context();
$tag_stack = array();
diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss
index 8883af42ee2ca..fae3f03786dfe 100644
--- a/packages/block-editor/src/components/link-control/style.scss
+++ b/packages/block-editor/src/components/link-control/style.scss
@@ -61,6 +61,10 @@ $preview-image-height: 140px;
.block-editor-link-control__field {
margin: $grid-unit-20; // allow margin collapse for vertical spacing.
+ .components-base-control__label {
+ color: $gray-900;
+ }
+
input[type="text"],
// Specificity overide of URLInput defaults.
&.block-editor-url-input input[type="text"].block-editor-url-input__input {
@@ -419,6 +423,10 @@ $preview-image-height: 140px;
.components-base-control__field {
display: flex; // don't allow label to wrap under checkbox.
+
+ .components-checkbox-control__label {
+ color: $gray-900;
+ }
}
// Cancel left margin inherited from WP Admin Forms CSS.
diff --git a/packages/block-library/src/form-submit-button/edit.js b/packages/block-library/src/form-submit-button/edit.js
index f8d7a65c6877a..4b22b26fd4755 100644
--- a/packages/block-library/src/form-submit-button/edit.js
+++ b/packages/block-library/src/form-submit-button/edit.js
@@ -14,6 +14,7 @@ const TEMPLATE = [
{
text: __( 'Submit' ),
tagName: 'button',
+ type: 'submit',
},
],
],
diff --git a/packages/block-library/src/navigation/edit/overlay-menu-preview.js b/packages/block-library/src/navigation/edit/overlay-menu-preview.js
index adb1377a604ed..8ea07351821c8 100644
--- a/packages/block-library/src/navigation/edit/overlay-menu-preview.js
+++ b/packages/block-library/src/navigation/edit/overlay-menu-preview.js
@@ -20,7 +20,7 @@ export default function OverlayMenuPreview( { setAttributes, hasIcon, icon } ) {
__nextHasNoMarginBottom
label={ __( 'Show icon button' ) }
help={ __(
- 'Configure the visual appearance of the button opening the overlay menu.'
+ 'Configure the visual appearance of the button that toggles the overlay menu.'
) }
onChange={ ( value ) => setAttributes( { hasIcon: value } ) }
checked={ hasIcon }
diff --git a/packages/block-library/src/query/index.php b/packages/block-library/src/query/index.php
index 201ceed737a12..b6a5733632ff4 100644
--- a/packages/block-library/src/query/index.php
+++ b/packages/block-library/src/query/index.php
@@ -44,7 +44,11 @@ function render_block_core_query( $attributes, $content, $block ) {
$block->block_type->supports['interactivity'] = true;
// Add a div to announce messages using `aria-live`.
- $last_div_position = strripos( $content, '' );
+ $html_tag = 'div';
+ if ( ! empty( $attributes['tagName'] ) ) {
+ $html_tag = esc_attr( $attributes['tagName'] );
+ }
+ $last_tag_position = strripos( $content, '' . $html_tag . '>' );
$content = substr_replace(
$content,
'
',
- $last_div_position,
+ $last_tag_position,
0
);
}
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 72edad0df4e10..5d05abc7026b2 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -5,10 +5,17 @@
### Internal
- Migrate `Divider` from `reakit` to `ariakit` ([#55622](https://github.com/WordPress/gutenberg/pull/55622))
+- Migrate `DisclosureContent` from `reakit` to `ariakit` and TypeScript ([#55639](https://github.com/WordPress/gutenberg/pull/55639))
+
+### Experimental
+
+- `Tabs`: Add `focusable` prop to the `Tabs.TabPanel` sub-component ([#55287](https://github.com/WordPress/gutenberg/pull/55287))
+- `Tabs`: Update sub-components to accept relevant HTML element props ([#55860](https://github.com/WordPress/gutenberg/pull/55860))
### Enhancements
- `ToggleGroupControl`: Add opt-in prop for 40px default size ([#55789](https://github.com/WordPress/gutenberg/pull/55789)).
+- `TextControl`: Add opt-in prop for 40px default size ([#55471](https://github.com/WordPress/gutenberg/pull/55471)).
## 25.11.0 (2023-11-02)
diff --git a/packages/components/src/disclosure/index.js b/packages/components/src/disclosure/index.js
deleted file mode 100644
index 5458ba053eef6..0000000000000
--- a/packages/components/src/disclosure/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Accessible Disclosure component that controls visibility of a section of
- * content. It follows the WAI-ARIA Disclosure Pattern.
- *
- * @see https://reakit.io/docs/disclosure/
- *
- * The plan is to build own API that accounts for future breaking changes
- * in Reakit (https://github.com/WordPress/gutenberg/pull/28085).
- */
-/* eslint-disable-next-line no-restricted-imports */
-export { DisclosureContent } from 'reakit';
diff --git a/packages/components/src/disclosure/index.tsx b/packages/components/src/disclosure/index.tsx
new file mode 100644
index 0000000000000..5bacfcabc349a
--- /dev/null
+++ b/packages/components/src/disclosure/index.tsx
@@ -0,0 +1,44 @@
+/**
+ * External dependencies
+ */
+// eslint-disable-next-line no-restricted-imports
+import * as Ariakit from '@ariakit/react';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import type { DisclosureContentProps } from './types';
+import type { WordPressComponentProps } from '../context';
+
+/**
+ * Accessible Disclosure component that controls visibility of a section of
+ * content. It follows the WAI-ARIA Disclosure Pattern.
+ */
+const UnforwardedDisclosureContent = (
+ {
+ visible,
+ children,
+ ...props
+ }: WordPressComponentProps< DisclosureContentProps, 'div', false >,
+ ref: React.ForwardedRef< any >
+) => {
+ const disclosure = Ariakit.useDisclosureStore( { open: visible } );
+
+ return (
+
+ { children }
+
+ );
+};
+
+export const DisclosureContent = forwardRef( UnforwardedDisclosureContent );
+export default DisclosureContent;
diff --git a/packages/components/src/disclosure/types.tsx b/packages/components/src/disclosure/types.tsx
new file mode 100644
index 0000000000000..6a0a746bb6397
--- /dev/null
+++ b/packages/components/src/disclosure/types.tsx
@@ -0,0 +1,10 @@
+export type DisclosureContentProps = {
+ /**
+ * If set to `true` the content will be shown, otherwise it's hidden.
+ */
+ visible?: boolean;
+ /**
+ * The content to display within the component.
+ */
+ children: React.ReactNode;
+};
diff --git a/packages/components/src/tabs/README.md b/packages/components/src/tabs/README.md
index 6907f385fda37..423216e940584 100644
--- a/packages/components/src/tabs/README.md
+++ b/packages/components/src/tabs/README.md
@@ -159,19 +159,6 @@ The children elements, which should be a series of `Tabs.TabPanel` components.
- Required: No
-###### `className`: `string`
-
-The class name to apply to the tablist.
-
-- Required: No
-- Default: ''
-
-###### `style`: `React.CSSProperties`
-
-Custom CSS styles for the tablist.
-
-- Required: No
-
#### Tab
##### Props
@@ -182,24 +169,12 @@ The id of the tab, which is prepended with the `Tabs` instance ID.
- Required: Yes
-###### `style`: `React.CSSProperties`
-
-Custom CSS styles for the tab.
-
-- Required: No
-
###### `children`: `React.ReactNode`
The children elements, generally the text to display on the tab.
- Required: No
-###### `className`: `string`
-
-The class name to apply to the tab.
-
-- Required: No
-
###### `disabled`: `boolean`
Determines if the tab button should be disabled.
@@ -229,14 +204,9 @@ The id of the tabpanel, which is combined with the `Tabs` instance ID and the su
- Required: Yes
-###### `className`: `string`
-
-The class name to apply to the tabpanel.
-
-- Required: No
-
-###### `style`: `React.CSSProperties`
+###### `focusable`: `boolean`
-Custom CSS styles for the tab.
+Determines whether or not the tabpanel element should be focusable. If `false`, pressing the tab key will skip over the tabpanel, and instead focus on the first focusable element in the panel (if there is one).
- Required: No
+- Default: `true`
diff --git a/packages/components/src/tabs/stories/index.story.tsx b/packages/components/src/tabs/stories/index.story.tsx
index 3b6ba022f6d91..08e2958988170 100644
--- a/packages/components/src/tabs/stories/index.story.tsx
+++ b/packages/components/src/tabs/stories/index.story.tsx
@@ -42,8 +42,16 @@ const Template: StoryFn< typeof Tabs > = ( props ) => {
Selected tab: Tab 2
-
+
Selected tab: Tab 3
+
+ This tabpanel has its focusable prop set to
+ false, so it won't get a tab stop.
+
+ Instead, the [Tab] key will move focus to the first
+ focusable element within the panel.
+
+
);
diff --git a/packages/components/src/tabs/styles.ts b/packages/components/src/tabs/styles.ts
index 091ba608fb6ec..cb735f3177662 100644
--- a/packages/components/src/tabs/styles.ts
+++ b/packages/components/src/tabs/styles.ts
@@ -101,3 +101,19 @@ export const Tab = styled( Ariakit.Tab )`
}
}
`;
+
+export const TabPanel = styled( Ariakit.TabPanel )`
+ &:focus {
+ box-shadow: none;
+ outline: none;
+ }
+
+ &:focus-visible {
+ border-radius: 2px;
+ box-shadow: 0 0 0 var( --wp-admin-border-width-focus )
+ ${ COLORS.theme.accent };
+ // Windows high contrast mode.
+ outline: 2px solid transparent;
+ outline-offset: 0;
+ }
+`;
diff --git a/packages/components/src/tabs/tab.tsx b/packages/components/src/tabs/tab.tsx
index 75b3df1c1ba01..03e5d80871c56 100644
--- a/packages/components/src/tabs/tab.tsx
+++ b/packages/components/src/tabs/tab.tsx
@@ -11,11 +11,12 @@ import type { TabProps } from './types';
import warning from '@wordpress/warning';
import { TabsContext } from './context';
import { Tab as StyledTab } from './styles';
+import type { WordPressComponentProps } from '../context';
-export const Tab = forwardRef< HTMLButtonElement, TabProps >( function Tab(
- { children, id, className, disabled, render, style },
- ref
-) {
+export const Tab = forwardRef<
+ HTMLButtonElement,
+ WordPressComponentProps< TabProps, 'button', false >
+>( function Tab( { children, id, disabled, render, ...otherProps }, ref ) {
const context = useContext( TabsContext );
if ( ! context ) {
warning( '`Tabs.TabList` must be wrapped in a `Tabs` component.' );
@@ -28,10 +29,9 @@ export const Tab = forwardRef< HTMLButtonElement, TabProps >( function Tab(
ref={ ref }
store={ store }
id={ instancedTabId }
- className={ className }
- style={ style }
disabled={ disabled }
render={ render }
+ { ...otherProps }
>
{ children }
diff --git a/packages/components/src/tabs/tablist.tsx b/packages/components/src/tabs/tablist.tsx
index 02255fefd2082..7a53115910796 100644
--- a/packages/components/src/tabs/tablist.tsx
+++ b/packages/components/src/tabs/tablist.tsx
@@ -16,25 +16,26 @@ import { forwardRef } from '@wordpress/element';
import type { TabListProps } from './types';
import { useTabsContext } from './context';
import { TabListWrapper } from './styles';
+import type { WordPressComponentProps } from '../context';
-export const TabList = forwardRef< HTMLDivElement, TabListProps >(
- function TabList( { children, className, style }, ref ) {
- const context = useTabsContext();
- if ( ! context ) {
- warning( '`Tabs.TabList` must be wrapped in a `Tabs` component.' );
- return null;
- }
- const { store } = context;
- return (
- }
- >
- { children }
-
- );
+export const TabList = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< TabListProps, 'div', false >
+>( function TabList( { children, ...otherProps }, ref ) {
+ const context = useTabsContext();
+ if ( ! context ) {
+ warning( '`Tabs.TabList` must be wrapped in a `Tabs` component.' );
+ return null;
}
-);
+ const { store } = context;
+ return (
+ }
+ { ...otherProps }
+ >
+ { children }
+
+ );
+} );
diff --git a/packages/components/src/tabs/tabpanel.tsx b/packages/components/src/tabs/tabpanel.tsx
index fb62fc9191233..f477d1d3b4b43 100644
--- a/packages/components/src/tabs/tabpanel.tsx
+++ b/packages/components/src/tabs/tabpanel.tsx
@@ -1,8 +1,6 @@
/**
* External dependencies
*/
-// eslint-disable-next-line no-restricted-imports
-import * as Ariakit from '@ariakit/react';
/**
* WordPress dependencies
@@ -14,29 +12,32 @@ import { forwardRef, useContext } from '@wordpress/element';
* Internal dependencies
*/
import type { TabPanelProps } from './types';
+import { TabPanel as StyledTabPanel } from './styles';
import warning from '@wordpress/warning';
import { TabsContext } from './context';
+import type { WordPressComponentProps } from '../context';
-export const TabPanel = forwardRef< HTMLDivElement, TabPanelProps >(
- function TabPanel( { children, id, className, style }, ref ) {
- const context = useContext( TabsContext );
- if ( ! context ) {
- warning( '`Tabs.TabPanel` must be wrapped in a `Tabs` component.' );
- return null;
- }
- const { store, instanceId } = context;
-
- return (
-
- { children }
-
- );
+export const TabPanel = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< TabPanelProps, 'div', false >
+>( function TabPanel( { children, id, focusable = true, ...otherProps }, ref ) {
+ const context = useContext( TabsContext );
+ if ( ! context ) {
+ warning( '`Tabs.TabPanel` must be wrapped in a `Tabs` component.' );
+ return null;
}
-);
+ const { store, instanceId } = context;
+
+ return (
+
+ { children }
+
+ );
+} );
diff --git a/packages/components/src/tabs/test/index.tsx b/packages/components/src/tabs/test/index.tsx
index a89e680e244d8..d2a035e436c19 100644
--- a/packages/components/src/tabs/test/index.tsx
+++ b/packages/components/src/tabs/test/index.tsx
@@ -7,7 +7,6 @@ import userEvent from '@testing-library/user-event';
/**
* WordPress dependencies
*/
-import { wordpress, category, media } from '@wordpress/icons';
import { useState } from '@wordpress/element';
/**
@@ -15,7 +14,6 @@ import { useState } from '@wordpress/element';
*/
import Tabs from '..';
import type { TabsProps } from '../types';
-import type { IconType } from '../../icon';
type Tab = {
id: string;
@@ -23,9 +21,11 @@ type Tab = {
content: React.ReactNode;
tab: {
className?: string;
- icon?: IconType;
disabled?: boolean;
};
+ tabpanel?: {
+ focusable?: boolean;
+ };
};
const TABS: Tab[] = [
@@ -33,19 +33,19 @@ const TABS: Tab[] = [
id: 'alpha',
title: 'Alpha',
content: 'Selected tab: Alpha',
- tab: { className: 'alpha-class', icon: wordpress },
+ tab: { className: 'alpha-class' },
},
{
id: 'beta',
title: 'Beta',
content: 'Selected tab: Beta',
- tab: { className: 'beta-class', icon: category },
+ tab: { className: 'beta-class' },
},
{
id: 'gamma',
title: 'Gamma',
content: 'Selected tab: Gamma',
- tab: { className: 'gamma-class', icon: media },
+ tab: { className: 'gamma-class' },
},
];
@@ -55,17 +55,15 @@ const TABS_WITH_DELTA: Tab[] = [
id: 'delta',
title: 'Delta',
content: 'Selected tab: Delta',
- tab: { className: 'delta-class', icon: media },
+ tab: { className: 'delta-class' },
},
];
const UncontrolledTabs = ( {
tabs,
- showTabIcons = false,
...props
}: Omit< TabsProps, 'children' > & {
tabs: Tab[];
- showTabIcons?: boolean;
} ) => {
return (
@@ -76,14 +74,17 @@ const UncontrolledTabs = ( {
id={ tabObj.id }
className={ tabObj.tab.className }
disabled={ tabObj.tab.disabled }
- icon={ showTabIcons ? tabObj.tab.icon : undefined }
>
- { showTabIcons ? null : tabObj.title }
+ { tabObj.title }
) ) }
{ tabs.map( ( tabObj ) => (
-
+
{ tabObj.content }
) ) }
@@ -93,11 +94,9 @@ const UncontrolledTabs = ( {
const ControlledTabs = ( {
tabs,
- showTabIcons = false,
...props
}: Omit< TabsProps, 'children' > & {
tabs: Tab[];
- showTabIcons?: boolean;
} ) => {
const [ selectedTabId, setSelectedTabId ] = useState<
string | undefined | null
@@ -119,9 +118,8 @@ const ControlledTabs = ( {
id={ tabObj.id }
className={ tabObj.tab.className }
disabled={ tabObj.tab.disabled }
- icon={ showTabIcons ? tabObj.tab.icon : undefined }
>
- { showTabIcons ? null : tabObj.title }
+ { tabObj.title }
) ) }
@@ -184,6 +182,63 @@ describe( 'Tabs', () => {
);
} );
} );
+ describe( 'Focus Behavior', () => {
+ it( 'should focus on the related TabPanel when pressing the Tab key', async () => {
+ const user = userEvent.setup();
+
+ render( );
+
+ const selectedTabPanel = await screen.findByRole( 'tabpanel' );
+
+ // Tab should initially focus the first tab in the tablist, which
+ // is Alpha.
+ await user.keyboard( '[Tab]' );
+ expect(
+ await screen.findByRole( 'tab', { name: 'Alpha' } )
+ ).toHaveFocus();
+
+ // By default the tabpanel should receive focus
+ await user.keyboard( '[Tab]' );
+ expect( selectedTabPanel ).toHaveFocus();
+ } );
+ it( 'should not focus on the related TabPanel when pressing the Tab key if `focusable: false` is set', async () => {
+ const user = userEvent.setup();
+
+ const TABS_WITH_ALPHA_FOCUSABLE_FALSE = TABS.map( ( tabObj ) =>
+ tabObj.id === 'alpha'
+ ? {
+ ...tabObj,
+ content: (
+ <>
+ Selected Tab: Alpha
+
+ >
+ ),
+ tabpanel: { focusable: false },
+ }
+ : tabObj
+ );
+
+ render(
+
+ );
+
+ const alphaButton = await screen.findByRole( 'button', {
+ name: /alpha button/i,
+ } );
+
+ // Tab should initially focus the first tab in the tablist, which
+ // is Alpha.
+ await user.keyboard( '[Tab]' );
+ expect(
+ await screen.findByRole( 'tab', { name: 'Alpha' } )
+ ).toHaveFocus();
+ // Because the alpha tabpanel is set to `focusable: false`, pressing
+ // the Tab key should focus the button, not the tabpanel
+ await user.keyboard( '[Tab]' );
+ expect( alphaButton ).toHaveFocus();
+ } );
+ } );
describe( 'Tab Attributes', () => {
it( "should apply the tab's `className` to the tab button", async () => {
diff --git a/packages/components/src/tabs/types.ts b/packages/components/src/tabs/types.ts
index 88e25eb5a3863..8b07193741091 100644
--- a/packages/components/src/tabs/types.ts
+++ b/packages/components/src/tabs/types.ts
@@ -4,11 +4,6 @@
// eslint-disable-next-line no-restricted-imports
import type * as Ariakit from '@ariakit/react';
-/**
- * Internal dependencies
- */
-import type { IconType } from '../icon';
-
export type TabsContextProps =
| {
/**
@@ -78,14 +73,6 @@ export type TabListProps = {
* The children elements, which should be a series of `Tabs.TabPanel` components.
*/
children?: React.ReactNode;
- /**
- * The class name to apply to the tablist.
- */
- className?: string;
- /**
- * Custom CSS styles for the rendered tablist.
- */
- style?: React.CSSProperties;
};
export type TabProps = {
@@ -93,22 +80,10 @@ export type TabProps = {
* The id of the tab, which is prepended with the `Tabs` instanceId.
*/
id: string;
- /**
- * Custom CSS styles for the tab.
- */
- style?: React.CSSProperties;
/**
* The children elements, generally the text to display on the tab.
*/
children?: React.ReactNode;
- /**
- * The class name to apply to the tab button.
- */
- className?: string;
- /**
- * The icon used for the tab button.
- */
- icon?: IconType;
/**
* Determines if the tab button should be disabled.
*
@@ -128,15 +103,15 @@ export type TabPanelProps = {
*/
children?: React.ReactNode;
/**
- * A unique identifier for the TabPanel, which is used to generate a unique `id` for the underlying element.
+ * A unique identifier for the tabpanel, which is used to generate a unique `id` for the underlying element.
*/
id: string;
/**
- * The class name to apply to the tabpanel.
- */
- className?: string;
- /**
- * Custom CSS styles for the rendered `TabPanel` component.
+ * Determines whether or not the tabpanel element should be focusable.
+ * If `false`, pressing the tab key will skip over the tabpanel, and instead
+ * focus on the first focusable element in the panel (if there is one).
+ *
+ * @default true
*/
- style?: React.CSSProperties;
+ focusable?: boolean;
};
diff --git a/packages/components/src/text-control/index.tsx b/packages/components/src/text-control/index.tsx
index 31b1462a3b3a4..30298357c3c01 100644
--- a/packages/components/src/text-control/index.tsx
+++ b/packages/components/src/text-control/index.tsx
@@ -2,6 +2,7 @@
* External dependencies
*/
import type { ChangeEvent, ForwardedRef } from 'react';
+import classnames from 'classnames';
/**
* WordPress dependencies
@@ -22,6 +23,7 @@ function UnforwardedTextControl(
) {
const {
__nextHasNoMarginBottom,
+ __next40pxDefaultSize = false,
label,
hideLabelFromVision,
value,
@@ -46,7 +48,9 @@ function UnforwardedTextControl(
className={ className }
>
`: Current user object.
+### getDefaultTemplateId
+
+Returns the default template use to render a given query.
+
+_Parameters_
+
+- _state_ `State`: Data state.
+- _query_ `TemplateQuery`: Query.
+
+_Returns_
+
+- `string`: The default template id for the given query.
+
### getEditedEntityRecord
Returns the specified entity record, merged with its edits.
diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js
index c4b19819ed7a4..9e7277f35a62a 100644
--- a/packages/core-data/src/actions.js
+++ b/packages/core-data/src/actions.js
@@ -908,3 +908,19 @@ export function receiveNavigationFallbackId( fallbackId ) {
fallbackId,
};
}
+
+/**
+ * Returns an action object used to set the template for a given query.
+ *
+ * @param {Object} query The lookup query.
+ * @param {string} templateId The resolved template id.
+ *
+ * @return {Object} Action object.
+ */
+export function receiveDefaultTemplateId( query, templateId ) {
+ return {
+ type: 'RECEIVE_DEFAULT_TEMPLATE',
+ query,
+ templateId,
+ };
+}
diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js
index 68c0cc233d7b6..a21623d8ba89d 100644
--- a/packages/core-data/src/reducer.js
+++ b/packages/core-data/src/reducer.js
@@ -572,6 +572,26 @@ export function themeGlobalStyleRevisions( state = {}, action ) {
return state;
}
+/**
+ * Reducer managing the template lookup per query.
+ *
+ * @param {Record} state Current state.
+ * @param {Object} action Dispatched action.
+ *
+ * @return {Record} Updated state.
+ */
+export function defaultTemplates( state = {}, action ) {
+ switch ( action.type ) {
+ case 'RECEIVE_DEFAULT_TEMPLATE':
+ return {
+ ...state,
+ [ JSON.stringify( action.query ) ]: action.templateId,
+ };
+ }
+
+ return state;
+}
+
export default combineReducers( {
terms,
users,
@@ -592,4 +612,5 @@ export default combineReducers( {
blockPatternCategories,
userPatternCategories,
navigationFallbackId,
+ defaultTemplates,
} );
diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js
index 5fc7cd14f35c0..cd2a65a60b013 100644
--- a/packages/core-data/src/resolvers.js
+++ b/packages/core-data/src/resolvers.js
@@ -707,3 +707,14 @@ export const getNavigationFallbackId =
] );
}
};
+
+export const getDefaultTemplateId =
+ ( query ) =>
+ async ( { dispatch } ) => {
+ const template = await apiFetch( {
+ path: addQueryArgs( '/wp/v2/templates/lookup', query ),
+ } );
+ if ( template ) {
+ dispatch.receiveDefaultTemplateId( query, template.id );
+ }
+ };
diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts
index b6b36fad2ee93..2a046941611c7 100644
--- a/packages/core-data/src/selectors.ts
+++ b/packages/core-data/src/selectors.ts
@@ -46,6 +46,7 @@ export interface State {
users: UserState;
navigationFallbackId: EntityRecordKey;
userPatternCategories: Array< UserPatternCategory >;
+ defaultTemplates: Record< string, string >;
}
type EntityRecordKey = string | number;
@@ -81,6 +82,12 @@ interface UserState {
byId: Record< EntityRecordKey, ET.User< 'edit' > >;
}
+type TemplateQuery = {
+ slug?: string;
+ is_custom?: boolean;
+ ignore_empty?: boolean;
+};
+
export interface UserPatternCategory {
id: number;
name: string;
@@ -1351,3 +1358,18 @@ export function getCurrentThemeGlobalStylesRevisions(
return state.themeGlobalStyleRevisions[ currentGlobalStylesId ];
}
+
+/**
+ * Returns the default template use to render a given query.
+ *
+ * @param state Data state.
+ * @param query Query.
+ *
+ * @return The default template id for the given query.
+ */
+export function getDefaultTemplateId(
+ state: State,
+ query: TemplateQuery
+): string {
+ return state.defaultTemplates[ JSON.stringify( query ) ];
+}
diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js
index 62da6a005c0d0..84cf7ed617f18 100644
--- a/packages/data/src/components/use-select/index.js
+++ b/packages/data/src/components/use-select/index.js
@@ -193,7 +193,14 @@ function useStaticSelect( storeName ) {
function useMappingSelect( suspense, mapSelect, deps ) {
const registry = useRegistry();
const isAsync = useAsyncMode();
- const store = useMemo( () => Store( registry, suspense ), [ registry ] );
+ const store = useMemo(
+ () => Store( registry, suspense ),
+ [ registry, suspense ]
+ );
+
+ // These are "pass-through" dependencies from the parent hook,
+ // and the parent should catch any hook rule violations.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
const selector = useCallback( mapSelect, deps );
const { subscribe, getValue } = store( selector, isAsync );
const result = useSyncExternalStore( subscribe, getValue, getValue );
diff --git a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts
index da21f17aade11..5883a2b92a5bc 100644
--- a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts
+++ b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts
@@ -47,7 +47,9 @@ export async function visitSiteEditor(
* loading is done.
*/
await this.page
- .locator( '.edit-site-canvas-loader' )
+ // Spinner was used instead of the progress bar in an earlier version of
+ // the site editor.
+ .locator( '.edit-site-canvas-loader, .edit-site-canvas-spinner' )
// Bigger timeout is needed for larger entities, for example the large
// post html fixture that we load for performance tests, which often
// doesn't make it under the default 10 seconds.
diff --git a/packages/e2e-test-utils-playwright/src/editor/click-block-options-menu-item.ts b/packages/e2e-test-utils-playwright/src/editor/click-block-options-menu-item.ts
index 2c473352c6123..e8c02fb11dcb7 100644
--- a/packages/e2e-test-utils-playwright/src/editor/click-block-options-menu-item.ts
+++ b/packages/e2e-test-utils-playwright/src/editor/click-block-options-menu-item.ts
@@ -12,8 +12,7 @@ import type { Editor } from './index';
export async function clickBlockOptionsMenuItem( this: Editor, label: string ) {
await this.clickBlockToolbarButton( 'Options' );
await this.page
- .locator(
- `role=menu[name="Options"i] >> role=menuitem[name="${ label }"i]`
- )
+ .getByRole( 'menu', { name: 'Options' } )
+ .getByRole( 'menuitem', { name: label } )
.click();
}
diff --git a/packages/e2e-tests/plugins/interactive-blocks.php b/packages/e2e-tests/plugins/interactive-blocks.php
index a6bd468493840..956508a11361e 100644
--- a/packages/e2e-tests/plugins/interactive-blocks.php
+++ b/packages/e2e-tests/plugins/interactive-blocks.php
@@ -39,8 +39,8 @@ function () {
// HTML is not correct or malformed.
if ( 'true' === $_GET['disable_directives_ssr'] ) {
remove_filter(
- 'render_block',
- 'gutenberg_interactivity_process_directives_in_root_blocks'
+ 'render_block_data',
+ 'gutenberg_interactivity_process_directives'
);
}
}
diff --git a/packages/e2e-tests/specs/editor/plugins/child-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/child-blocks.test.js
deleted file mode 100644
index c7ca368003397..0000000000000
--- a/packages/e2e-tests/specs/editor/plugins/child-blocks.test.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * WordPress dependencies
- */
-import {
- activatePlugin,
- closeGlobalBlockInserter,
- createNewPost,
- deactivatePlugin,
- getAllBlockInserterItemTitles,
- insertBlock,
- openGlobalBlockInserter,
-} from '@wordpress/e2e-test-utils';
-
-describe( 'Child Blocks', () => {
- beforeAll( async () => {
- await activatePlugin( 'gutenberg-test-child-blocks' );
- } );
-
- beforeEach( async () => {
- await createNewPost();
- } );
-
- afterAll( async () => {
- await deactivatePlugin( 'gutenberg-test-child-blocks' );
- } );
-
- it( 'are hidden from the global block inserter', async () => {
- await openGlobalBlockInserter();
- await expect( await getAllBlockInserterItemTitles() ).not.toContain(
- 'Child Blocks Child'
- );
- } );
-
- it( 'shows up in a parent block', async () => {
- await insertBlock( 'Child Blocks Unrestricted Parent' );
- await closeGlobalBlockInserter();
- await page.waitForSelector(
- '[data-type="test/child-blocks-unrestricted-parent"] .block-editor-default-block-appender'
- );
- await page.click(
- '[data-type="test/child-blocks-unrestricted-parent"] .block-editor-default-block-appender'
- );
- await openGlobalBlockInserter();
- const inserterItemTitles = await getAllBlockInserterItemTitles();
- expect( inserterItemTitles ).toContain( 'Child Blocks Child' );
- expect( inserterItemTitles.length ).toBeGreaterThan( 20 );
- } );
-
- it( 'display in a parent block with allowedItems', async () => {
- await insertBlock( 'Child Blocks Restricted Parent' );
- await closeGlobalBlockInserter();
- await page.waitForSelector(
- '[data-type="test/child-blocks-restricted-parent"] .block-editor-default-block-appender'
- );
- await page.click(
- '[data-type="test/child-blocks-restricted-parent"] .block-editor-default-block-appender'
- );
- await openGlobalBlockInserter();
- const allowedBlocks = await getAllBlockInserterItemTitles();
- expect( allowedBlocks.sort() ).toEqual( [
- 'Child Blocks Child',
- 'Image',
- 'Paragraph',
- ] );
- } );
-} );
diff --git a/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js b/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js
deleted file mode 100644
index 3a75f9656c71e..0000000000000
--- a/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js
+++ /dev/null
@@ -1,137 +0,0 @@
-/**
- * WordPress dependencies
- */
-import {
- activatePlugin,
- createNewPost,
- deactivatePlugin,
- findSidebarPanelToggleButtonWithTitle,
- insertBlock,
- openDocumentSettingsSidebar,
- publishPost,
- saveDraft,
-} from '@wordpress/e2e-test-utils';
-
-describe( 'Meta boxes', () => {
- beforeAll( async () => {
- await activatePlugin( 'gutenberg-test-plugin-meta-box' );
- } );
-
- beforeEach( async () => {
- await createNewPost();
- } );
-
- afterAll( async () => {
- await deactivatePlugin( 'gutenberg-test-plugin-meta-box' );
- } );
-
- it( 'Should save the post', async () => {
- // Save should not be an option for new empty post.
- expect( await page.$( '.editor-post-save-draft' ) ).toBe( null );
-
- // Add title to enable valid non-empty post save.
- await page.type( '.editor-post-title__input', 'Hello Meta' );
- expect( await page.$( '.editor-post-save-draft' ) ).not.toBe( null );
-
- await saveDraft();
-
- // After saving, affirm that the button returns to Save Draft.
- await page.waitForSelector( '.editor-post-save-draft' );
- } );
-
- it( 'Should render dynamic blocks when the meta box uses the excerpt for front end rendering', async () => {
- // Publish a post so there's something for the latest posts dynamic block to render.
- await page.type( '.editor-post-title__input', 'A published post' );
- await insertBlock( 'Paragraph' );
- await page.keyboard.type( 'Hello there!' );
- await publishPost();
-
- // Publish a post with the latest posts dynamic block.
- await createNewPost();
- await page.type( '.editor-post-title__input', 'Dynamic block test' );
- await insertBlock( 'Latest Posts' );
- await publishPost();
-
- // View the post.
- const viewPostLinks = await page.$x(
- "//a[contains(text(), 'View Post')]"
- );
- await viewPostLinks[ 0 ].click();
- await page.waitForNavigation();
-
- // Check the dynamic block appears.
- const latestPostsBlock = await page.waitForSelector(
- '.wp-block-latest-posts'
- );
-
- expect(
- await latestPostsBlock.evaluate( ( block ) => block.textContent )
- ).toContain( 'A published post' );
-
- expect(
- await latestPostsBlock.evaluate( ( block ) => block.textContent )
- ).toContain( 'Dynamic block test' );
- } );
-
- it( 'Should render the excerpt in meta based on post content if no explicit excerpt exists', async () => {
- await insertBlock( 'Paragraph' );
- await page.keyboard.type( 'Excerpt from content.' );
- await page.type( '.editor-post-title__input', 'A published post' );
- await publishPost();
-
- // View the post.
- const viewPostLinks = await page.$x(
- "//a[contains(text(), 'View Post')]"
- );
- await viewPostLinks[ 0 ].click();
- await page.waitForNavigation();
-
- // Retrieve the excerpt used as meta.
- const metaExcerpt = await page.evaluate( () => {
- return document
- .querySelector( 'meta[property="gutenberg:hello"]' )
- .getAttribute( 'content' );
- } );
-
- expect( metaExcerpt ).toEqual( 'Excerpt from content.' );
- } );
-
- it( 'Should render the explicitly set excerpt in meta instead of the content based one', async () => {
- await insertBlock( 'Paragraph' );
- await page.keyboard.type( 'Excerpt from content.' );
- await page.type( '.editor-post-title__input', 'A published post' );
-
- // Open the excerpt panel.
- await openDocumentSettingsSidebar();
- const excerptButton =
- await findSidebarPanelToggleButtonWithTitle( 'Excerpt' );
- if ( excerptButton ) {
- await excerptButton.click( 'button' );
- }
-
- await page.waitForSelector( '.editor-post-excerpt textarea' );
-
- await page.type(
- '.editor-post-excerpt textarea',
- 'Explicitly set excerpt.'
- );
-
- await publishPost();
-
- // View the post.
- const viewPostLinks = await page.$x(
- "//a[contains(text(), 'View Post')]"
- );
- await viewPostLinks[ 0 ].click();
- await page.waitForNavigation();
-
- // Retrieve the excerpt used as meta.
- const metaExcerpt = await page.evaluate( () => {
- return document
- .querySelector( 'meta[property="gutenberg:hello"]' )
- .getAttribute( 'content' );
- } );
-
- expect( metaExcerpt ).toEqual( 'Explicitly set excerpt.' );
- } );
-} );
diff --git a/packages/edit-post/src/components/start-page-options/index.js b/packages/edit-post/src/components/start-page-options/index.js
index 02473fd4eaa14..77264d27a5e7d 100644
--- a/packages/edit-post/src/components/start-page-options/index.js
+++ b/packages/edit-post/src/components/start-page-options/index.js
@@ -3,7 +3,7 @@
*/
import { Modal } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-import { useState, useEffect, useMemo } from '@wordpress/element';
+import { useState, useMemo } from '@wordpress/element';
import {
store as blockEditorStore,
__experimentalBlockPatternsList as BlockPatternsList,
@@ -62,19 +62,11 @@ function PatternSelection( { blockPatterns, onChoosePattern } ) {
);
}
-function StartPageOptionsModal() {
- const [ modalState, setModalState ] = useState( 'initial' );
+function StartPageOptionsModal( { onClose } ) {
const startPatterns = useStartPatterns();
const hasStartPattern = startPatterns.length > 0;
- const shouldOpenModal = hasStartPattern && modalState === 'initial';
- useEffect( () => {
- if ( shouldOpenModal ) {
- setModalState( 'open' );
- }
- }, [ shouldOpenModal ] );
-
- if ( modalState !== 'open' ) {
+ if ( ! hasStartPattern ) {
return null;
}
@@ -83,12 +75,12 @@ function StartPageOptionsModal() {
className="edit-post-start-page-options__modal"
title={ __( 'Choose a pattern' ) }
isFullScreen
- onRequestClose={ () => setModalState( 'closed' ) }
+ onRequestClose={ onClose }
>
The XYZ Doohickey Company was founded in 1971, and has been providing' .
+ 'quality doohickeys to the public ever since. Located in Gotham City, XYZ employs' .
+ 'over 2,000 people and does all kinds of awesome things for the Gotham community.
' .
+ '
+
' .
+ '' .
+ '' .
+ '
+ ' .
+ '
The XYZ Doohickey Company was founded in 1971, and has been providing' .
+ 'quality doohickeys to the public ever since. Located in Gotham City, XYZ employs' .
+ 'over 2,000 people and does all kinds of awesome things for the Gotham community.