diff --git a/packages/block-library/src/details/block.json b/packages/block-library/src/details/block.json new file mode 100644 index 00000000000000..ee8cbd0ab8aac6 --- /dev/null +++ b/packages/block-library/src/details/block.json @@ -0,0 +1,13 @@ +{ + "name": "core/details", + "category": "layout", + "attributes": { + "initialOpen": { + "type": "boolean", + "default": false + }, + "summaryContent": { + "type": "string" + } + } +} diff --git a/packages/block-library/src/details/edit.js b/packages/block-library/src/details/edit.js new file mode 100644 index 00000000000000..962045ee397eb6 --- /dev/null +++ b/packages/block-library/src/details/edit.js @@ -0,0 +1,91 @@ +/** + * WordPress dependencies + */ +import { + InnerBlocks, + RichText, + InspectorControls, +} from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { + ToggleControl, + Panel, + PanelBody, + PanelRow, +} from '@wordpress/components'; +import { useEffect, useRef } from '@wordpress/element'; +import { SPACE } from '@wordpress/keycodes'; + +export default function DetalsEdit( { + attributes, + className, + clientId, + isSelected, + setAttributes, +} ) { + const summaryRef = useRef( null ); + + const keyUpListener = ( e ) => { + if ( e.keyCode === SPACE ) { + e.preventDefault(); + } + }; + + const clickListener = ( e ) => e.preventDefault(); + + useEffect( () => { + if ( ! summaryRef.current ) { + return; + } + + summaryRef.current.addEventListener( 'keyup', keyUpListener ); + summaryRef.current.addEventListener( 'click', clickListener ); + return () => { + summaryRef.current.removeEventListener( 'keyup', keyUpListener ); + summaryRef.current.removeEventListener( 'click', clickListener ); + }; + }, [ summaryRef.current ] ); + + const isInnerBlockSelected = useSelect( + ( select ) => + select( 'core/block-editor' ).hasSelectedInnerBlock( clientId ), + [ clientId ] + ); + + const showInnerBlocks = + attributes.initialOpen || isSelected || isInnerBlockSelected; + + return ( + <> + + + + + + setAttributes( { initialOpen } ) + } + checked={ attributes.initialOpen } + /> + + + + +
+ + setAttributes( { summaryContent } ) + } + ref={ summaryRef } + placeholder={ __( 'Write a summary…' ) } + aria-label={ __( 'Summary text' ) } + /> + +
+ + ); +} diff --git a/packages/block-library/src/details/editor.scss b/packages/block-library/src/details/editor.scss new file mode 100644 index 00000000000000..5c06909f94520f --- /dev/null +++ b/packages/block-library/src/details/editor.scss @@ -0,0 +1,5 @@ +.wp-block-details { + summary .rich-text { + display: inline; + } +} diff --git a/packages/block-library/src/details/index.js b/packages/block-library/src/details/index.js new file mode 100644 index 00000000000000..a439035fd61880 --- /dev/null +++ b/packages/block-library/src/details/index.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { details as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import edit from './edit'; +import metadata from './block.json'; +import save from './save'; + +const { name, category, attributes } = metadata; + +export { metadata, name }; + +export const settings = { + title: __( 'Details' ), + description: __( 'Create a toggle to show and hide content.' ), + icon, + category, + edit, + save, + // details and summary are not translated because they are the HTML tags + keywords: [ 'details', 'summary', __( 'faq' ) ], + attributes, +}; diff --git a/packages/block-library/src/details/save.js b/packages/block-library/src/details/save.js new file mode 100644 index 00000000000000..c3c1d2d03108d5 --- /dev/null +++ b/packages/block-library/src/details/save.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks, RichText } from '@wordpress/block-editor'; + +export default ( { attributes } ) => { + return ( +
+ + +
+ ); +}; diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index c7c23d390dca10..e01d42131994b4 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -12,6 +12,7 @@ @import "./code/editor.scss"; @import "./columns/editor.scss"; @import "./cover/editor.scss"; +@import "./details/editor.scss"; @import "./embed/editor.scss"; @import "./file/editor.scss"; @import "./classic/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index d92ab8f3a05429..1ce85cc737c643 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -31,6 +31,7 @@ import * as code from './code'; import * as columns from './columns'; import * as column from './column'; import * as cover from './cover'; +import * as details from './details'; import * as embed from './embed'; import * as file from './file'; import * as html from './html'; @@ -133,6 +134,7 @@ export const registerCoreBlocks = () => { columns, column, cover, + details, embed, file, group, diff --git a/packages/e2e-tests/fixtures/blocks/core__details.html b/packages/e2e-tests/fixtures/blocks/core__details.html new file mode 100644 index 00000000000000..fa943245021d17 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__details.html @@ -0,0 +1,12 @@ + +
+ This is the summary + +

More here

+ + + +

+ +
+ diff --git a/packages/e2e-tests/fixtures/blocks/core__details.json b/packages/e2e-tests/fixtures/blocks/core__details.json new file mode 100644 index 00000000000000..03beb29ac4a817 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__details.json @@ -0,0 +1,36 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/details", + "isValid": true, + "attributes": { + "initialOpen": false, + "summaryContent": "This is the summary " + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/heading", + "isValid": true, + "attributes": { + "content": "More here", + "level": 2 + }, + "innerBlocks": [], + "originalContent": "

More here

" + }, + { + "clientId": "_clientId_1", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "content": "", + "dropCap": false + }, + "innerBlocks": [], + "originalContent": "

" + } + ], + "originalContent": "
\n\tThis is the summary \n\t\n\n\t\n
" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__details.parsed.json b/packages/e2e-tests/fixtures/blocks/core__details.parsed.json new file mode 100644 index 00000000000000..4c8575353bb0d8 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__details.parsed.json @@ -0,0 +1,46 @@ +[ + { + "blockName": "core/details", + "attrs": { + "summaryContent": "This is the summary ", + "initialOpen": false + }, + "innerBlocks": [ + { + "blockName": "core/heading", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\t

More here

\n\t", + "innerContent": [ + "\n\t

More here

\n\t" + ] + }, + { + "blockName": "core/paragraph", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\t

\n\t", + "innerContent": [ + "\n\t

\n\t" + ] + } + ], + "innerHTML": "\n
\n\tThis is the summary \n\t\n\n\t\n
\n", + "innerContent": [ + "\n
\n\tThis is the summary \n\t", + null, + "\n\n\t", + null, + "\n
\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__details.serialized.html b/packages/e2e-tests/fixtures/blocks/core__details.serialized.html new file mode 100644 index 00000000000000..8fa6ac9c91cf5e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__details.serialized.html @@ -0,0 +1,9 @@ + +
This is the summary +

More here

+ + + +

+
+ diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js index 9054f43a6c4286..f0fcca0acbdbb0 100644 --- a/packages/icons/src/index.js +++ b/packages/icons/src/index.js @@ -49,6 +49,7 @@ export { default as currencyDollar } from './library/currency-dollar'; export { default as currencyEuro } from './library/currency-euro'; export { default as currencyPound } from './library/currency-pound'; export { default as desktop } from './library/desktop'; +export { default as details } from './library/details'; export { default as edit } from './library/edit'; export { default as external } from './library/external'; export { default as file } from './library/file'; diff --git a/packages/icons/src/library/details.js b/packages/icons/src/library/details.js new file mode 100644 index 00000000000000..ef9d3c95051077 --- /dev/null +++ b/packages/icons/src/library/details.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const details = ( + + + +); + +export default details; diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 76d8fc64d13268..ad70861aa0c549 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -527,6 +527,11 @@ function RichText( { function handleSpace( event ) { const { keyCode, shiftKey, altKey, metaKey, ctrlKey } = event; + if ( keyCode === SPACE && TagName === 'summary' ) { + handleChange( insert( createRecord(), ' ' ) ); + return; + } + if ( // Only override when no modifiers are pressed. shiftKey ||