diff --git a/css/src/structured-data-blocks.scss b/css/src/structured-data-blocks.scss index c0fa1cf300e..b495918c0ae 100644 --- a/css/src/structured-data-blocks.scss +++ b/css/src/structured-data-blocks.scss @@ -1,21 +1,15 @@ -.schema-how-to-duration .schema-how-to-duration-input[type="number"] { - width: 40px; - margin: 0 2px; - padding: 6px 4px; - text-align: center; - -moz-appearance: textfield; - - &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } -} - -.schema-how-to-duration-time-input { - white-space: nowrap; +// Common styles. +.schema-how-to-step, +.schema-faq-section { + position: relative; + padding: 8px 4px 8px 32px; + margin: 4px 0; + border: 1px solid rgba(#9197a2, 0.25); + list-style-type: none; } -.schema-how-to-buttons { +.schema-how-to-buttons, +.schema-faq-buttons { text-align: center; button.components-icon-button:not(:disabled):not([aria-disabled=true]):not(.is-default):hover { @@ -24,55 +18,17 @@ } } -button.schema-how-to-duration-button.components-icon-button { - position: relative; - top: 5px; - padding: 8px 0; - - &:not(:disabled):not([aria-disabled=true]):not(.is-default):hover { - box-shadow: none; - color: #007cba; - } -} - -input + button.schema-how-to-duration-button.components-icon-button { - padding: 8px; -} - -.schema-how-to-description { - margin: 8px 0; -} - -legend.schema-how-to-duration-legend { - float: left; - margin-top: 8px; - margin-right: 4px; -} - -.schema-how-to-step { - position: relative; - padding: 8px 4px 8px 32px; - margin: 4px 0; - border: 1px lightgrey solid; - list-style-type: none; -} - -.schema-how-to-step-mover { +.schema-how-to-step-mover, +.schema-faq-section-mover { display: inline-block; -} - -.schema-how-to-step-button { - margin: 0; -} -.schema-how-to-step-number { - position: absolute; - left: 4px; - width: 24px; - text-align: right; + .editor-block-mover__control { + display: inline-flex; + width: 36px; + height: 36px; + } } -// TinyMCE fields and placeholders .schema-how-to-step-name, .schema-faq-question { font-weight: 600; @@ -86,76 +42,93 @@ legend.schema-how-to-duration-legend { line-height: inherit; } -.schema-how-to-step-button-container { +.schema-how-to-step-button-container, +.schema-faq-section-button-container { display: inline-block; text-align: right; - .schema-how-to-step-add-media { - float: left; - } - button.components-icon-button:not(:disabled):not([aria-disabled=true]):not(.is-default):hover { box-shadow: none; color: #007cba; } } -#schema-how-to-duration-days { - margin-right: 8px; +.schema-how-to-step-controls-container, +.schema-faq-section-controls-container { + text-align: right; + margin-left: -28px; // compensate the 32px wrapper padding and keep a 4px space + + .dashicons-arrow-up-alt2 { + position: relative; + top: -1px; // fix dashicon shape vertical centering + } } -// FAQ styling -.schema-faq-section { - position: relative; - padding: 8px 4px 8px 32px; - margin: 4px 0; - border: 1px lightgrey solid; - list-style-type: none; +.schema-how-to-duration-button, +.how-to-step-add-media, +.schema-how-to-add-step, +.faq-section-add-media, +.schema-faq-add-question { + .dashicon { + margin-right: 4px; + } } -.schema-faq-section-mover { - display: inline-block; +// How-to block specific styles. +.schema-how-to { + padding-top: 4px; } -.schema-faq-section-button-container { - display: inline-block; +.schema-how-to-step-number { + position: absolute; + left: 4px; + width: 24px; text-align: right; +} - .schema-faq-section-add-media { - float: left; - } +.schema-how-to-duration-flex-container { + display: flex; + align-items: center; +} - button.components-icon-button:not(:disabled):not([aria-disabled=true]):not(.is-default):hover { - box-shadow: none; - color: #007cba; - } +.schema-how-to-duration-time-input { + display: inline-flex; + align-items: center; + flex-wrap: nowrap; } -.schema-faq-buttons { - text-align: center; +legend.schema-how-to-duration-legend { + margin-right: 4px; +} - button.components-icon-button:not(:disabled):not([aria-disabled=true]):not(.is-default):hover { - box-shadow: none; - color: #007cba; - } +#schema-how-to-duration-days { + margin-right: 8px; } -.schema-how-to-step-mover, -.schema-faq-section-mover { - .editor-block-mover__control { - display: inline-flex; - width: 36px; - height: 36px; +.schema-how-to-duration .schema-how-to-duration-input[type="number"] { + width: 40px; + margin: 0 2px; + padding: 6px 4px; + text-align: center; + -moz-appearance: textfield; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; } } -.schema-how-to-step-controls-container, -.schema-faq-section-controls-container { - text-align: right; - margin-left: -28px; // compensate the 32px wrapper padding and keep a 4px space +.schema-how-to-duration-button.components-icon-button { + margin-left: -8px; + vertical-align: top; // Remove descender space from inline-flex. - .dashicons-arrow-up-alt2 { - position: relative; - top: -1px; // fix dashicon shape vertical centering + &:not(:disabled):not([aria-disabled=true]):not(.is-default):hover { + box-shadow: none; + color: #007cba; } } + +.schema-how-to-description { + margin: 8px 0; +} diff --git a/js/src/structured-data-blocks/faq/components/FAQ.js b/js/src/structured-data-blocks/faq/components/FAQ.js index 4de1e35fefd..67af5daf2f6 100644 --- a/js/src/structured-data-blocks/faq/components/FAQ.js +++ b/js/src/structured-data-blocks/faq/components/FAQ.js @@ -1,8 +1,7 @@ /* External dependencies */ -import React from "react"; import PropTypes from "prop-types"; -import isUndefined from "lodash/isUndefined"; import { __ } from "@wordpress/i18n"; +import { speak } from "@wordpress/a11y"; /* Internal dependencies */ import Question from "./Question"; @@ -63,7 +62,7 @@ export default class FAQ extends Component { * @returns {void} */ onAddQuestionButtonClick() { - this.insertQuestion(); + this.insertQuestion( null, [], [], false ); } /** @@ -120,17 +119,17 @@ export default class FAQ extends Component { /** * Inserts an empty Question into a FAQ block at the given index. * - * @param {number} [index] The index of the Question after which a new Question should be added. - * @param {array|string} [question] The question of the new Question. - * @param {array|string} [answer] The answer of the new Question. - * @param {bool} [focus=true] Whether or not to focus the new Question. + * @param {number} [index] Optional. The index of the Question after which a new Question should be added. + * @param {array|string} [question] Optional. The question of the new Question. Default: empty. + * @param {array|string} [answer] Optional. The answer of the new Question. Default: empty. + * @param {bool} [focus=true] Optional. Whether or not to focus the new Question. Default: true. * * @returns {void} */ - insertQuestion( index, question = [], answer = [], focus = true ) { + insertQuestion( index = null, question = [], answer = [], focus = true ) { const questions = this.props.attributes.questions ? this.props.attributes.questions.slice() : []; - if ( isUndefined( index ) ) { + if ( index === null ) { index = questions.length - 1; } @@ -153,7 +152,11 @@ export default class FAQ extends Component { if ( focus ) { setTimeout( this.setFocus.bind( this, `${ index + 1 }:question` ) ); + // When moving focus to a newly created question, return and don't use the speak() message. + return; } + + speak( __( "New question added", "wordpress-seo" ) ); } /** @@ -269,16 +272,16 @@ export default class FAQ extends Component { } /** - * Retrieves a button to add a step to the front of the list. + * Retrieves a button to add a question at the end of the FAQ list. * - * @returns {Component} The button for adding add a step. + * @returns {Component} The button to add a question. */ getAddQuestionButton() { return ( { __( "Add question", "wordpress-seo" ) } diff --git a/js/src/structured-data-blocks/how-to/components/HowTo.js b/js/src/structured-data-blocks/how-to/components/HowTo.js index 749c0fe9b1e..920d0eb19d9 100644 --- a/js/src/structured-data-blocks/how-to/components/HowTo.js +++ b/js/src/structured-data-blocks/how-to/components/HowTo.js @@ -1,20 +1,20 @@ /* External dependencies */ import PropTypes from "prop-types"; -import HowToStep from "./HowToStep"; -import isUndefined from "lodash/isUndefined"; import styled from "styled-components"; import { __ } from "@wordpress/i18n"; +import { speak } from "@wordpress/a11y"; import toString from "lodash/toString"; import get from "lodash/get"; /* Internal dependencies */ +import HowToStep from "./HowToStep"; import { stripHTML } from "../../../helpers/stringHelpers"; import buildDurationString from "../utils/buildDurationString"; import appendSpace from "../../../components/higherorder/appendSpace"; const { RichText, InspectorControls } = window.wp.editor; -const { IconButton, PanelBody, TextControl, ToggleControl } = window.wp.components; -const { Component, renderToString } = window.wp.element; +const { Button, IconButton, Dashicon, PanelBody, TextControl, ToggleControl } = window.wp.components; +const { Component, renderToString, createRef } = window.wp.element; const RichTextWithAppendedSpace = appendSpace( RichText.Content ); @@ -62,14 +62,13 @@ export default class HowTo extends Component { this.setDescriptionRef = this.setDescriptionRef.bind( this ); this.addDuration = this.addDuration.bind( this ); this.removeDuration = this.removeDuration.bind( this ); - this.onFocusDaysField = this.onFocusDaysField.bind( this ); - this.onFocusMinutesField = this.onFocusMinutesField.bind( this ); - this.onFocusHoursField = this.onFocusHoursField.bind( this ); this.onChangeDescription = this.onChangeDescription.bind( this ); this.onChangeDays = this.onChangeDays.bind( this ); this.onChangeHours = this.onChangeHours.bind( this ); this.onChangeMinutes = this.onChangeMinutes.bind( this ); this.onAddStepButtonClick = this.onAddStepButtonClick.bind( this ); + this.daysInput = createRef(); + this.addDurationButton = createRef(); const defaultDurationText = this.getDefaultDurationText(); this.setDefaultDurationText( defaultDurationText ); @@ -124,7 +123,7 @@ export default class HowTo extends Component { * @returns {void} */ onAddStepButtonClick() { - this.insertStep(); + this.insertStep( null, [], [], false ); } /** @@ -188,19 +187,19 @@ export default class HowTo extends Component { } /** - * Inserts an empty step into a how-to block at the given index. + * Inserts an empty Step into a how-to block at the given index. * - * @param {number} [index] The index of the step after which a new step should be added. - * @param {string} [name] The name of the new step. - * @param {string} [text] The text of the new step. - * @param {bool} [focus=true] Whether or not to focus the new step. + * @param {number} [index] Optional. The index of the Step after which a new Step should be added. + * @param {array|string} [name] Optional. The title of the new Step. Default: empty. + * @param {array|string} [text] Optional. The description of the new Step. Default: empty. + * @param {bool} [focus=true] Optional. Whether or not to focus the new Step. Default: true. * * @returns {void} */ - insertStep( index, name = [], text = [], focus = true ) { + insertStep( index = null, name = [], text = [], focus = true ) { const steps = this.props.attributes.steps ? this.props.attributes.steps.slice() : []; - if ( isUndefined( index ) ) { + if ( index === null ) { index = steps.length - 1; } @@ -223,7 +222,11 @@ export default class HowTo extends Component { if ( focus ) { setTimeout( this.setFocus.bind( this, `${ index + 1 }:name` ) ); + // When moving focus to a newly created step, return and don't use the speak() messaage. + return; } + + speak( __( "New step added", "wordpress-seo" ) ); } /** @@ -497,16 +500,16 @@ export default class HowTo extends Component { } /** - * A button to add a step to the front of the list. + * Retrieves a button to add a step at the end of the How-to list. * - * @returns {Component} a button to add a step + * @returns {Component} The button to add a step. */ getAddStepButton() { return ( { __( "Add step", "wordpress-seo" ) } @@ -583,48 +586,23 @@ export default class HowTo extends Component { } /** - * Enable the duration fields. + * Enables the duration fields and manages focus. * * @returns {void} */ addDuration() { this.props.setAttributes( { hasDuration: true } ); + setTimeout( () => this.daysInput.current.focus() ); } /** - * Disable the duration fields. + * Disables the duration fields and manages focus. * * @returns {void} */ removeDuration() { this.props.setAttributes( { hasDuration: false } ); - } - - /** - * Focus the days input. - * - * @returns {void} - */ - onFocusDaysField() { - this.setFocus( "days" ); - } - - /** - * Focus the days input. - * - * @returns {void} - */ - onFocusHoursField() { - this.setFocus( "hours" ); - } - - /** - * Focus the days input. - * - * @returns {void} - */ - onFocusMinutesField() { - this.setFocus( "minutes" ); + setTimeout( () => this.addDurationButton.current.focus() ); } /** @@ -673,77 +651,78 @@ export default class HowTo extends Component { if ( ! attributes.hasDuration ) { return ( - + { __( "Add total time", "wordpress-seo" ) } - + ); } return (
- - { attributes.durationText || this.getDefaultDurationText() } - - - - - - - - - - + { attributes.durationText || this.getDefaultDurationText() } + + + + + + + + + + +
); @@ -780,7 +759,7 @@ export default class HowTo extends Component { /> diff --git a/js/src/structured-data-blocks/how-to/components/HowToStep.js b/js/src/structured-data-blocks/how-to/components/HowToStep.js index 0fb5bc8a6ea..8cf1d53b060 100644 --- a/js/src/structured-data-blocks/how-to/components/HowToStep.js +++ b/js/src/structured-data-blocks/how-to/components/HowToStep.js @@ -399,6 +399,9 @@ HowToStep.propTypes = { isSelected: PropTypes.bool.isRequired, isFirst: PropTypes.bool.isRequired, isLast: PropTypes.bool.isRequired, - isUnorderedList: PropTypes.bool.isRequired, + isUnorderedList: PropTypes.bool, }; +HowToStep.defaultProps = { + isUnorderedList: false, +};