From f3a8d8f1e5547c14211f0af5134e92b9dd8be79c Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Mon, 25 Sep 2017 14:20:14 +1000 Subject: [PATCH 01/12] Make inline link entry same as image block --- blocks/editable/format-toolbar/index.js | 140 ++++---------------- blocks/editable/format-toolbar/style.scss | 40 ------ blocks/editable/index.js | 2 + blocks/editable/style.scss | 6 + blocks/library/button/index.js | 15 +-- blocks/library/cover-image/index.js | 1 - blocks/library/image/block.js | 4 +- blocks/url-input/button.js | 152 +++++++++++++++++++--- blocks/url-input/index.js | 4 +- blocks/url-input/style.scss | 84 +++++++++++- 10 files changed, 256 insertions(+), 192 deletions(-) diff --git a/blocks/editable/format-toolbar/index.js b/blocks/editable/format-toolbar/index.js index 85f1e5e97fbaf3..1d292c9bf98623 100644 --- a/blocks/editable/format-toolbar/index.js +++ b/blocks/editable/format-toolbar/index.js @@ -3,16 +3,14 @@ */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { IconButton, Toolbar, withSpokenMessages } from '@wordpress/components'; +import { Toolbar, withSpokenMessages } from '@wordpress/components'; import { keycodes } from '@wordpress/utils'; /** * Internal dependencies */ import './style.scss'; -import UrlInput from '../../url-input'; -import { filterURLForDisplay } from '../../../editor/utils/url'; -import ToggleControl from '../../inspector-controls/toggle-control'; +import UrlInputButton from '../../url-input/button'; const { ESCAPE } = keycodes; @@ -40,22 +38,9 @@ const DEFAULT_CONTROLS = [ 'bold', 'italic', 'strikethrough', 'link' ]; class FormatToolbar extends Component { constructor() { super( ...arguments ); - this.state = { - isAddingLink: false, - isEditingLink: false, - settingsVisible: false, - opensInNewWindow: false, - newLinkValue: '', - }; - this.addLink = this.addLink.bind( this ); - this.editLink = this.editLink.bind( this ); - this.dropLink = this.dropLink.bind( this ); - this.submitLink = this.submitLink.bind( this ); this.onKeyDown = this.onKeyDown.bind( this ); - this.onChangeLinkValue = this.onChangeLinkValue.bind( this ); - this.toggleLinkSettingsVisibility = this.toggleLinkSettingsVisibility.bind( this ); - this.setLinkTarget = this.setLinkTarget.bind( this ); + this.onUrlChange = this.onUrlChange.bind( this ); } componentDidMount() { @@ -75,22 +60,6 @@ class FormatToolbar extends Component { } } - componentWillReceiveProps( nextProps ) { - if ( this.props.selectedNodeId !== nextProps.selectedNodeId ) { - this.setState( { - isAddingLink: false, - isEditingLink: false, - settingsVisible: false, - opensInNewWindow: !! nextProps.formats.link && !! nextProps.formats.link.target, - newLinkValue: '', - } ); - } - } - - onChangeLinkValue( value ) { - this.setState( { newLinkValue: value } ); - } - toggleFormat( format ) { return () => { this.props.onChange( { @@ -99,45 +68,20 @@ class FormatToolbar extends Component { }; } - toggleLinkSettingsVisibility() { - this.setState( ( state ) => ( { settingsVisible: ! state.settingsVisible } ) ); - } - - setLinkTarget( event ) { - const opensInNewWindow = event.target.checked; - this.setState( { opensInNewWindow } ); - this.props.onChange( { link: { value: this.props.formats.link.value, target: opensInNewWindow ? '_blank' : '' } } ); - } - - addLink() { - this.setState( { isEditingLink: false, isAddingLink: true, newLinkValue: '' } ); - } + onUrlChange( { url, opensInNewWindow } ) { + if ( !! url ) { + this.props.onChange( { link: { value: url, target: opensInNewWindow ? '_blank' : '' } } ); - dropLink() { - this.props.onChange( { link: undefined } ); - this.setState( { isEditingLink: false, isAddingLink: false, newLinkValue: '' } ); - } - - editLink( event ) { - event.preventDefault(); - this.setState( { isEditingLink: false, isAddingLink: true, newLinkValue: this.props.formats.link.value } ); - } - - submitLink( event ) { - event.preventDefault(); - this.props.onChange( { link: { value: this.state.newLinkValue, target: this.state.opensInNewWindow ? '_blank' : '' } } ); - if ( this.state.isAddingLink ) { - this.props.speak( __( 'Link inserted.' ), 'assertive' ); + if ( ! this.props.format.link ) { + this.props.speak( __( 'Link inserted.' ), 'assertive' ); + } + } else { + this.props.onChange( { link: undefined } ); } } render() { - const { formats, focusPosition, enabledControls = DEFAULT_CONTROLS } = this.props; - const { isAddingLink, isEditingLink, newLinkValue, settingsVisible, opensInNewWindow } = this.state; - const linkStyle = focusPosition - ? { position: 'absolute', ...focusPosition } - : null; - + const { formats, enabledControls = DEFAULT_CONTROLS, extraButtons, selectedNodeId } = this.props; const toolbarControls = FORMATTING_CONTROLS .filter( control => enabledControls.indexOf( control.format ) !== -1 ) .map( ( control ) => ( { @@ -146,56 +90,20 @@ class FormatToolbar extends Component { isActive: !! formats[ control.format ], } ) ); - const linkSettings = settingsVisible && ( -
- -
- ); - - if ( enabledControls.indexOf( 'link' ) !== -1 ) { - toolbarControls.push( { - icon: 'admin-links', - title: __( 'Link' ), - onClick: this.addLink, - isActive: isAddingLink || !! formats.link, - } ); - } + const showLinkControl = enabledControls.indexOf( 'link' ) !== -1; return ( -
- - - { ( isAddingLink || isEditingLink ) && -
- - - - - { linkSettings } - - } - - { !! formats.link && ! isAddingLink && ! isEditingLink && - - } +
+ + {showLinkControl && + + } + { extraButtons } + +
); } diff --git a/blocks/editable/format-toolbar/style.scss b/blocks/editable/format-toolbar/style.scss index e6a80a1f27594b..40c75256585154 100644 --- a/blocks/editable/format-toolbar/style.scss +++ b/blocks/editable/format-toolbar/style.scss @@ -1,43 +1,3 @@ .blocks-format-toolbar { display: inline-flex; } - -.blocks-format-toolbar__link-modal { - position: absolute; - box-shadow: 0px 3px 20px rgba( 18, 24, 30, .1 ), 0px 1px 3px rgba( 18, 24, 30, .1 ); - border: 1px solid #e0e5e9; - background: #fff; - width: 305px; - display: inline-flex; - flex-wrap: wrap; - align-items: center; - font-family: $default-font; - font-size: $default-font-size; - line-height: $default-line-height; - z-index: z-index( '.blocks-format-toolbar__link-modal' ); - - .blocks-url-input { - width: auto; - } -} - -.blocks-format-toolbar__link-value { - padding: 10px; - flex-grow: 1; - overflow: hidden; - position: relative; - - &:after { - @include long-content-fade( $size: 40% ); - } -} - -.blocks-format-toolbar__link-settings { - border-top: 1px solid $light-gray-500; - width: 100%; - padding: 10px 8px; - - .blocks-base-control { - margin: 0; - } -} diff --git a/blocks/editable/index.js b/blocks/editable/index.js index 22679714e659f1..ff900279fc6914 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -594,6 +594,7 @@ export default class Editable extends Component { placeholder, multiline: MultilineTag, keepPlaceholderOnFocus = false, + extraToolbarButtons, } = this.props; // Generating a key that includes `tagName` ensures that if the tag @@ -610,6 +611,7 @@ export default class Editable extends Component { formats={ this.state.formats } onChange={ this.changeFormats } enabledControls={ formattingControls } + extraButtons={ extraToolbarButtons } /> ); diff --git a/blocks/editable/style.scss b/blocks/editable/style.scss index 7fa2bf97d44738..1b66d3a7fe4de7 100644 --- a/blocks/editable/style.scss +++ b/blocks/editable/style.scss @@ -94,4 +94,10 @@ ul.components-toolbar { box-shadow: $shadow-toolbar; } + + .blocks-format-toolbar__link-modal { + position: absolute; + left: 50%; + transform: translateX(-50%); + } } diff --git a/blocks/library/button/index.js b/blocks/library/button/index.js index bdb1b5fb661c61..ef5e66e0f7b5b0 100644 --- a/blocks/library/button/index.js +++ b/blocks/library/button/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { IconButton } from '@wordpress/components'; /** * Internal dependencies @@ -11,7 +10,7 @@ import './editor.scss'; import './style.scss'; import { registerBlockType, source } from '../../api'; import Editable from '../../editable'; -import UrlInput from '../../url-input'; +import UrlInputButton from '../../url-input/button'; import BlockControls from '../../block-controls'; import BlockAlignmentToolbar from '../../block-alignment-toolbar'; import ColorPalette from '../../color-palette'; @@ -75,18 +74,8 @@ registerBlockType( 'core/button', { onChange={ ( value ) => setAttributes( { text: value } ) } formattingControls={ [ 'bold', 'italic', 'strikethrough' ] } keepPlaceholderOnFocus + extraToolbarButtons={ setAttributes( { url: newUrl } ) } /> } /> - { focus && -
event.preventDefault() }> - setAttributes( { url: value } ) } - /> - - - } { focus && setAttributes( { title: value } ) } - inlineToolbar /> ) : null } , diff --git a/blocks/library/image/block.js b/blocks/library/image/block.js index 9e3daf63197ac6..c679342824a112 100644 --- a/blocks/library/image/block.js +++ b/blocks/library/image/block.js @@ -58,8 +58,8 @@ class ImageBlock extends Component { this.fetchMedia( media.id ); } - onSetHref( value ) { - this.props.setAttributes( { href: value } ); + onSetHref( { url } ) { + this.props.setAttributes( { href: url } ); } updateAlt( newAlt ) { diff --git a/blocks/url-input/button.js b/blocks/url-input/button.js index d58b4c153aee87..32a55168575203 100644 --- a/blocks/url-input/button.js +++ b/blocks/url-input/button.js @@ -2,6 +2,7 @@ * External dependencies */ import classnames from 'classnames'; +import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'; /** * WordPress dependencies @@ -15,58 +16,175 @@ import { IconButton } from '@wordpress/components'; * Internal dependencies */ import UrlInput from './'; +import ToggleControl from '../inspector-controls/toggle-control'; +import { filterURLForDisplay } from '../../editor/utils/url'; + +const DisplayStep = 'DISPLAY'; +const EditStep = 'EDIT'; +const SettingsStep = 'SETTINGS'; + +const AllSteps = [ EditStep, SettingsStep, DisplayStep ]; + +const defaultState = { + expanded: false, + currentStep: 0, + isDeleted: false, +}; class UrlInputButton extends Component { constructor() { super( ...arguments ); - this.toggle = this.toggle.bind( this ); - this.submitLink = this.submitLink.bind( this ); + this.state = { - expanded: false, + ...defaultState, + url: this.props.url, + opensInNewWindow: this.props.opensInNewWindow, }; + + this.toggleExpanded = this.toggleExpanded.bind( this ); + this.onSubmit = this.onSubmit.bind( this ); + this.changeLink = this.changeLink.bind( this ); + this.deleteLink = this.deleteLink.bind( this ); + this.onBack = this.onBack.bind( this ); + this.toggleOpensInNewWindow = this.toggleOpensInNewWindow.bind( this ); + } + + componentWillReceiveProps( nextProps ) { + if ( nextProps.selectedNodeId !== this.props.selectedNodeId ) { + this.setState( { + ...defaultState, + url: nextProps.url, + opensInNewWindow: nextProps.opensInNewWindow, + } ); + } } - toggle() { + toggleExpanded() { this.setState( { expanded: ! this.state.expanded } ); } - submitLink( event ) { + onSubmit( event ) { event.preventDefault(); - this.toggle(); + this.changeStep( 1 ); + } + + onBack() { + this.changeStep( -1 ); + } + + changeStep( dir ) { + const nextStep = this.state.currentStep + dir; + + if ( nextStep < 0 ) { + this.setState( { expanded: false, currentStep: 0 } ); + return; + } + + if ( nextStep > ( this.getSteps().length - 1 ) ) { + this.props.onChange( { url: this.state.isDeleted ? null : this.state.url, opensInNewWindow: this.state.opensInNewWindow } ); + this.setState( { expanded: false, currentStep: 0 } ); + return; + } + + this.setState( { currentStep: nextStep } ); + } + + deleteLink() { + this.setState( { url: '', isDeleted: true } ); + } + + changeLink( url ) { + this.setState( { url } ); + } + + toggleOpensInNewWindow() { + this.setState( { opensInNewWindow: ! this.state.opensInNewWindow } ); + } + + getSteps() { + return AllSteps.filter( step => { + if ( step === DisplayStep ) { + return !! this.state.url && ! this.state.isDeleted; + } else if ( step === SettingsStep ) { + return ! this.state.isDeleted; + } + + return true; + } ); + } + + renderStep( steps ) { + const { currentStep, url, isDeleted, opensInNewWindow } = this.state; + + if ( steps[ currentStep ] === EditStep ) { + return [ + , + , + ]; + } else if ( steps[ currentStep ] === DisplayStep ) { + return [ + + { url && filterURLForDisplay( decodeURI( url ) ) } + , + ]; + } else if ( steps[ currentStep ] === SettingsStep ) { + return [ +
+ +
, + ]; + } + return []; } render() { - const { url, onChange } = this.props; - const { expanded } = this.state; + const { expanded, url, currentStep } = this.state; + const steps = this.getSteps(); + const isLastStep = currentStep === ( steps.length - 1 ); return (
  • - { expanded && + + { expanded &&
    + onSubmit={ this.onSubmit }> - + { this.renderStep( steps ) } - } + } +
  • ); } diff --git a/blocks/url-input/index.js b/blocks/url-input/index.js index debe3a7e2832ac..6b60cadfbca906 100644 --- a/blocks/url-input/index.js +++ b/blocks/url-input/index.js @@ -165,7 +165,7 @@ class UrlInput extends Component { } render() { - const { value, instanceId } = this.props; + const { value, instanceId, required = true } = this.props; const { showSuggestions, posts, selectedSuggestion, loading } = this.state; /* eslint-disable jsx-a11y/no-autofocus */ return ( @@ -174,7 +174,7 @@ class UrlInput extends Component { autoFocus type="url" aria-label={ __( 'URL' ) } - required + required={ required } value={ value } onChange={ this.onChange } placeholder={ __( 'Paste URL or type' ) } diff --git a/blocks/url-input/style.scss b/blocks/url-input/style.scss index c2ec88c70641a7..4db64417fd2f4a 100644 --- a/blocks/url-input/style.scss +++ b/blocks/url-input/style.scss @@ -5,7 +5,6 @@ position: relative; input[type=url] { - padding: 10px; font-size: $default-font-size; width: 100%; border: none; @@ -79,7 +78,90 @@ ul.components-toolbar > li.blocks-url-input__button { } } +.blocks-url-input__button .blocks-url-input__forward { + margin-left: 4px; + overflow: visible; + + &::before { + content: ''; + position: absolute; + display: block; + width: 1px; + height: 24px; + left: -1px; + background: $light-gray-500; + } +} + .blocks-url-input__button .blocks-url-input__suggestions { left: -40px; right: -32px; } + + +.blocks-format-toolbar__link-modal { + position: absolute; + top: 0; + left: 0; + box-shadow: 0px 3px 20px rgba( 18, 24, 30, .1 ), 0px 1px 3px rgba( 18, 24, 30, .1 ); + border: 1px solid #e0e5e9; + background: #fff; + width: 305px; + display: inline-flex; + flex-wrap: wrap; + align-items: center; + font-family: $default-font; + font-size: $default-font-size; + line-height: $default-line-height; + z-index: z-index( '.blocks-format-toolbar__link-modal' ); + + .blocks-url-input { + width: auto; + } + + &.is-entering { + opacity: 0; + transform: translate(100%); + } + + &.is-entering-active { + opacity: 1; + transform: translate(0); + transition: opacity 300ms ease-in, transform 300ms ease-in; + } + + &.is-leaving { + opacity: 1; + transition: opacity 300ms ease-out, transform 300ms ease-out; + } + + &.is-leaving-active { + opacity: 0; + transform: translate(100%); + } +} + +.blocks-format-toolbar__link-settings { + width: auto; + flex-grow: 1; + padding: 0 10px; + + .blocks-base-control { + margin: 0; + } + + .blocks-base-control__label { + margin-bottom: 0; + } +} + +.blocks-format-toolbar__link-value { + padding: 8px 10px; + flex-grow: 1; + overflow: hidden; + position: relative; + + &:after { + @include long-content-fade( $size: 40% ); + } +} From f69e1c8bdd1040b47bd192a130242dee5b68c601 Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Mon, 25 Sep 2017 15:39:18 +1000 Subject: [PATCH 02/12] Fix wrapping on long url, dont show settings for image link --- blocks/editable/format-toolbar/index.js | 2 +- blocks/library/button/index.js | 2 +- blocks/url-input/button.js | 6 +++++- blocks/url-input/style.scss | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/blocks/editable/format-toolbar/index.js b/blocks/editable/format-toolbar/index.js index 1d292c9bf98623..667ff6b707cf3b 100644 --- a/blocks/editable/format-toolbar/index.js +++ b/blocks/editable/format-toolbar/index.js @@ -72,7 +72,7 @@ class FormatToolbar extends Component { if ( !! url ) { this.props.onChange( { link: { value: url, target: opensInNewWindow ? '_blank' : '' } } ); - if ( ! this.props.format.link ) { + if ( ! this.props.formats.link ) { this.props.speak( __( 'Link inserted.' ), 'assertive' ); } } else { diff --git a/blocks/library/button/index.js b/blocks/library/button/index.js index ef5e66e0f7b5b0..2583f0d9c5ab7a 100644 --- a/blocks/library/button/index.js +++ b/blocks/library/button/index.js @@ -74,7 +74,7 @@ registerBlockType( 'core/button', { onChange={ ( value ) => setAttributes( { text: value } ) } formattingControls={ [ 'bold', 'italic', 'strikethrough' ] } keepPlaceholderOnFocus - extraToolbarButtons={ setAttributes( { url: newUrl } ) } /> } + extraToolbarButtons={ setAttributes( { url: newUrl } ) } /> } /> { focus && diff --git a/blocks/url-input/button.js b/blocks/url-input/button.js index 32a55168575203..c641d2df1461ca 100644 --- a/blocks/url-input/button.js +++ b/blocks/url-input/button.js @@ -106,7 +106,7 @@ class UrlInputButton extends Component { if ( step === DisplayStep ) { return !! this.state.url && ! this.state.isDeleted; } else if ( step === SettingsStep ) { - return ! this.state.isDeleted; + return this.props.showSettings && ! this.state.isDeleted; } return true; @@ -190,4 +190,8 @@ class UrlInputButton extends Component { } } +UrlInputButton.defaultProps = { + showSettings: true, +}; + export default UrlInputButton; diff --git a/blocks/url-input/style.scss b/blocks/url-input/style.scss index 4db64417fd2f4a..486540a9a114c7 100644 --- a/blocks/url-input/style.scss +++ b/blocks/url-input/style.scss @@ -108,7 +108,7 @@ ul.components-toolbar > li.blocks-url-input__button { background: #fff; width: 305px; display: inline-flex; - flex-wrap: wrap; + flex-wrap: nowrap; align-items: center; font-family: $default-font; font-size: $default-font-size; From e81c4f043ae9420d0dac425f98fde2445bee1851 Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Mon, 25 Sep 2017 17:22:11 +1000 Subject: [PATCH 03/12] Adding tests --- blocks/url-input/button.js | 4 ++- blocks/url-input/test/index.js | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 blocks/url-input/test/index.js diff --git a/blocks/url-input/button.js b/blocks/url-input/button.js index c641d2df1461ca..83fd92e89e550e 100644 --- a/blocks/url-input/button.js +++ b/blocks/url-input/button.js @@ -164,7 +164,9 @@ class UrlInputButton extends Component { enter: 'is-entering', enterActive: 'is-entering-active', leave: 'is-leaving', - leaveActive: 'is-leaving-active' } } > + leaveActive: 'is-leaving-active' } } + transitionEnterTimeout={ 300 } + transitionLeaveTimeout={ 300 } > { expanded &&
    { + it( 'renders `Edit Link` button', () => { + const wrapper = shallow( ); + expect( wrapper.find( IconButton ).length ).toEqual( 1 ); + } ); + it( 'expands when `Edit` clicked', () => { + const wrapper = shallow( ); + wrapper.find( IconButton ).simulate( 'click' ); + expect( wrapper.find( 'form' ).length ).toEqual( 1 ); + } ); + it( 'allows url to be input', () => { + const wrapper = shallow( ); + wrapper.find( IconButton ).simulate( 'click' ); + const input = wrapper.find( UrlInput ); + input.simulate( 'change', 'https://wordpress.org/' ); + expect( wrapper.find( UrlInput ).prop( 'value' ) ).toEqual( 'https://wordpress.org/' ); + } ); + it( 'call `onChange` when url submitted', () => { + let newUrl = ''; + + const wrapper = shallow( { + newUrl = url; + } } /> ); + + wrapper.find( IconButton ).simulate( 'click' ); + const input = wrapper.find( UrlInput ); + input.simulate( 'change', 'https://wordpress.org/' ); + + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // link entry + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // settings + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // display + + expect( newUrl ).toEqual( 'https://wordpress.org/' ); + expect( wrapper.find( 'form' ).length ).toEqual( 0 ); + } ); +} ); From c714b95a2c74b03004443abcda3fbf8f1481271e Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Tue, 26 Sep 2017 11:52:22 +1000 Subject: [PATCH 04/12] Finished tests --- blocks/url-input/button.js | 2 +- blocks/url-input/test/index.js | 48 +++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/blocks/url-input/button.js b/blocks/url-input/button.js index 83fd92e89e550e..4839e1f683c231 100644 --- a/blocks/url-input/button.js +++ b/blocks/url-input/button.js @@ -119,7 +119,7 @@ class UrlInputButton extends Component { if ( steps[ currentStep ] === EditStep ) { return [ , - , + , ]; } else if ( steps[ currentStep ] === DisplayStep ) { return [ diff --git a/blocks/url-input/test/index.js b/blocks/url-input/test/index.js index 3649d16b6e0836..28a76d3b554ad2 100644 --- a/blocks/url-input/test/index.js +++ b/blocks/url-input/test/index.js @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; * Internal dependencies */ import { IconButton } from '@wordpress/components'; +import ToggleControl from '../../inspector-controls/toggle-control'; import UrlInputButton from '../button'; import UrlInput from '../'; @@ -27,7 +28,7 @@ describe( 'UrlInputButton', () => { input.simulate( 'change', 'https://wordpress.org/' ); expect( wrapper.find( UrlInput ).prop( 'value' ) ).toEqual( 'https://wordpress.org/' ); } ); - it( 'call `onChange` when url submitted', () => { + it( '`onChange` called with new url', () => { let newUrl = ''; const wrapper = shallow( { @@ -45,4 +46,49 @@ describe( 'UrlInputButton', () => { expect( newUrl ).toEqual( 'https://wordpress.org/' ); expect( wrapper.find( 'form' ).length ).toEqual( 0 ); } ); + it( '`onChange` called with un-linked url', () => { + let newUrl = ''; + + const wrapper = shallow( { + newUrl = url; + } } /> ); + + wrapper.find( IconButton ).simulate( 'click' ); + wrapper.find( '.blocks-url-input__unlink' ).simulate( 'click' ); + + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // done + + expect( newUrl ).toBeNull(); + expect( wrapper.find( 'form' ).length ).toEqual( 0 ); + } ); + + it( '`onChange` called with new `opensInNewWindow`', () => { + let newOpensInNewWindow = null; + + const wrapper = shallow( { + newOpensInNewWindow = opensInNewWindow; + } } /> ); + + wrapper.find( IconButton ).simulate( 'click' ); // expand link entry + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // link entry + + wrapper.find( ToggleControl ).simulate( 'change' ); + + + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // settings + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // display + + expect( newOpensInNewWindow ).toEqual( false ); + expect( wrapper.find( 'form' ).length ).toEqual( 0 ); // link entry collapsed + } ); + + it( 'displays url', () => { + const wrapper = shallow( ); + wrapper.find( IconButton ).simulate( 'click' ); // expand link entry + + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // move to settings + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // move to display + + expect( wrapper.find( 'a' ).prop( 'href' ) ).toEqual( 'https://wordpress.org/' ); + } ); } ); From ad724e92856d8fd52f57677ab00b076df44aee91 Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Tue, 26 Sep 2017 14:26:30 +1000 Subject: [PATCH 05/12] Fix button border z-index --- blocks/url-input/style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blocks/url-input/style.scss b/blocks/url-input/style.scss index 486540a9a114c7..d315cf67e66d4e 100644 --- a/blocks/url-input/style.scss +++ b/blocks/url-input/style.scss @@ -75,6 +75,7 @@ ul.components-toolbar > li.blocks-url-input__button { height: 24px; right: -1px; background: $light-gray-500; + z-index: -1; } } @@ -90,6 +91,7 @@ ul.components-toolbar > li.blocks-url-input__button { height: 24px; left: -1px; background: $light-gray-500; + z-index: -1; } } From ccda5e36d1e6a8ede56f3ee7006ac2ee092d676b Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Tue, 26 Sep 2017 15:59:25 +1000 Subject: [PATCH 06/12] modify image block toolbars, increase speed of link entry animation --- blocks/library/image/block.js | 36 +++++++++++++++++------------------ blocks/url-input/button.js | 4 ++-- blocks/url-input/style.scss | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/blocks/library/image/block.js b/blocks/library/image/block.js index c679342824a112..73a3dc6d73e539 100644 --- a/blocks/library/image/block.js +++ b/blocks/library/image/block.js @@ -110,23 +110,24 @@ class ImageBlock extends Component { value={ align } onChange={ this.updateAlignment } /> - - -
  • - - - -
  • - -
    + {focus.editable !== 'caption' && + +
  • + + + +
  • + +
    + } ) ); @@ -248,7 +249,6 @@ class ImageBlock extends Component { focus={ focus && focus.editable === 'caption' ? focus : undefined } onFocus={ focusCaption } onChange={ ( value ) => setAttributes( { caption: value } ) } - inlineToolbar /> ) : null } , diff --git a/blocks/url-input/button.js b/blocks/url-input/button.js index 4839e1f683c231..37716cf4e90097 100644 --- a/blocks/url-input/button.js +++ b/blocks/url-input/button.js @@ -165,8 +165,8 @@ class UrlInputButton extends Component { enterActive: 'is-entering-active', leave: 'is-leaving', leaveActive: 'is-leaving-active' } } - transitionEnterTimeout={ 300 } - transitionLeaveTimeout={ 300 } > + transitionEnterTimeout={ 250 } + transitionLeaveTimeout={ 250 } > { expanded && li.blocks-url-input__button { &.is-entering-active { opacity: 1; transform: translate(0); - transition: opacity 300ms ease-in, transform 300ms ease-in; + transition: opacity 250ms ease-in, transform 250ms ease-in; } &.is-leaving { opacity: 1; - transition: opacity 300ms ease-out, transform 300ms ease-out; + transition: opacity 250ms ease-out, transform 250ms ease-out; } &.is-leaving-active { From 00b357cadfb272fbcb050cb5b010c0aa13f8afb2 Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Fri, 29 Sep 2017 10:56:25 +1000 Subject: [PATCH 07/12] Fixed link button URL not being added --- blocks/library/button/index.js | 3 ++- blocks/url-input/button.js | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/blocks/library/button/index.js b/blocks/library/button/index.js index 2583f0d9c5ab7a..2ec13362ffbe25 100644 --- a/blocks/library/button/index.js +++ b/blocks/library/button/index.js @@ -57,6 +57,7 @@ registerBlockType( 'core/button', { edit( { attributes, setAttributes, focus, setFocus, className } ) { const { text, url, title, align, color } = attributes; const updateAlignment = ( nextAlign ) => setAttributes( { align: nextAlign } ); + const updateUrl = ( urlSettings ) => setAttributes( { url: urlSettings.url } ); return [ focus && ( @@ -74,7 +75,7 @@ registerBlockType( 'core/button', { onChange={ ( value ) => setAttributes( { text: value } ) } formattingControls={ [ 'bold', 'italic', 'strikethrough' ] } keepPlaceholderOnFocus - extraToolbarButtons={ setAttributes( { url: newUrl } ) } /> } + extraToolbarButtons={ } /> { focus && diff --git a/blocks/url-input/button.js b/blocks/url-input/button.js index 37716cf4e90097..24645ae62bd1db 100644 --- a/blocks/url-input/button.js +++ b/blocks/url-input/button.js @@ -41,7 +41,7 @@ class UrlInputButton extends Component { opensInNewWindow: this.props.opensInNewWindow, }; - this.toggleExpanded = this.toggleExpanded.bind( this ); + this.onLinkButtonClick = this.onLinkButtonClick.bind( this ); this.onSubmit = this.onSubmit.bind( this ); this.changeLink = this.changeLink.bind( this ); this.deleteLink = this.deleteLink.bind( this ); @@ -59,8 +59,8 @@ class UrlInputButton extends Component { } } - toggleExpanded() { - this.setState( { expanded: ! this.state.expanded } ); + onLinkButtonClick() { + this.setState( { expanded: true, currentStep: 0, isDeleted: false } ); } onSubmit( event ) { @@ -154,7 +154,7 @@ class UrlInputButton extends Component { Date: Fri, 29 Sep 2017 11:16:13 +1000 Subject: [PATCH 08/12] Fix linting --- blocks/url-input/test/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/blocks/url-input/test/index.js b/blocks/url-input/test/index.js index 28a76d3b554ad2..a6c6c0baf9dcc1 100644 --- a/blocks/url-input/test/index.js +++ b/blocks/url-input/test/index.js @@ -74,7 +74,6 @@ describe( 'UrlInputButton', () => { wrapper.find( ToggleControl ).simulate( 'change' ); - wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // settings wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // display From 1ff418ffefdb66ce950e72536365ce53f4aede3d Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Mon, 9 Oct 2017 14:32:35 +1000 Subject: [PATCH 09/12] ImageBlock - differentiate focus between image and caption --- blocks/library/image/block.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/library/image/block.js b/blocks/library/image/block.js index 37842a1d1cd55f..2f50cb2fb3a356 100644 --- a/blocks/library/image/block.js +++ b/blocks/library/image/block.js @@ -153,7 +153,7 @@ class ImageBlock extends Component { const classes = classnames( className, { 'is-transient': 0 === url.indexOf( 'blob:' ), 'is-resized': !! width, - 'is-focused': !! focus, + 'is-focused': !! focus && focus.editable !== 'caption', } ); // Disable reason: Each block can be selected by clicking on it From 75bc17fd231cea1ba32d3367e2f3f795ccf0b57e Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Mon, 9 Oct 2017 14:53:54 +1000 Subject: [PATCH 10/12] Make caption toolbar consistent on video and embed blocks --- blocks/library/embed/index.js | 1 - blocks/library/video/index.js | 1 - 2 files changed, 2 deletions(-) diff --git a/blocks/library/embed/index.js b/blocks/library/embed/index.js index be06689c1789c7..92ba723f153dfa 100644 --- a/blocks/library/embed/index.js +++ b/blocks/library/embed/index.js @@ -208,7 +208,6 @@ function getEmbedBlockSettings( { title, icon, category = 'embed', transforms, k focus={ focus } onFocus={ setFocus } onChange={ ( value ) => setAttributes( { caption: value } ) } - inlineToolbar /> ) : null } , diff --git a/blocks/library/video/index.js b/blocks/library/video/index.js index db99bea5e9e8b4..03cab2dd9f7a90 100644 --- a/blocks/library/video/index.js +++ b/blocks/library/video/index.js @@ -132,7 +132,6 @@ registerBlockType( 'core/video', { focus={ focus && focus.editable === 'caption' ? focus : undefined } onFocus={ focusCaption } onChange={ ( value ) => setAttributes( { caption: value } ) } - inlineToolbar /> ) : null } , From 3d886b22665d0e979c43f9ac6c0399bbead5b918 Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Mon, 9 Oct 2017 16:56:10 +1000 Subject: [PATCH 11/12] Address review issues --- blocks/editable/format-toolbar/index.js | 3 +- blocks/editable/index.js | 2 - blocks/library/button/index.js | 5 +- blocks/url-input/button.js | 69 ++++++++++++++----------- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/blocks/editable/format-toolbar/index.js b/blocks/editable/format-toolbar/index.js index 667ff6b707cf3b..fe0319df6b469a 100644 --- a/blocks/editable/format-toolbar/index.js +++ b/blocks/editable/format-toolbar/index.js @@ -81,7 +81,7 @@ class FormatToolbar extends Component { } render() { - const { formats, enabledControls = DEFAULT_CONTROLS, extraButtons, selectedNodeId } = this.props; + const { formats, enabledControls = DEFAULT_CONTROLS, selectedNodeId } = this.props; const toolbarControls = FORMATTING_CONTROLS .filter( control => enabledControls.indexOf( control.format ) !== -1 ) .map( ( control ) => ( { @@ -101,7 +101,6 @@ class FormatToolbar extends Component { onChange={ this.onUrlChange } url={ formats.link ? formats.link.value : '' } /> } - { extraButtons }
    diff --git a/blocks/editable/index.js b/blocks/editable/index.js index 102c01df17918a..7198db9f50d83a 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -604,7 +604,6 @@ export default class Editable extends Component { placeholder, multiline: MultilineTag, keepPlaceholderOnFocus = false, - extraToolbarButtons, } = this.props; // Generating a key that includes `tagName` ensures that if the tag @@ -621,7 +620,6 @@ export default class Editable extends Component { formats={ this.state.formats } onChange={ this.changeFormats } enabledControls={ formattingControls } - extraButtons={ extraToolbarButtons } /> ); diff --git a/blocks/library/button/index.js b/blocks/library/button/index.js index 9596260ad6379d..faf208dd08bfdb 100644 --- a/blocks/library/button/index.js +++ b/blocks/library/button/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { Toolbar } from '@wordpress/components'; /** * Internal dependencies @@ -74,6 +75,9 @@ registerBlockType( 'core/button', { focus && ( + + + ), @@ -86,7 +90,6 @@ registerBlockType( 'core/button', { onChange={ ( value ) => setAttributes( { text: value } ) } formattingControls={ [ 'bold', 'italic', 'strikethrough' ] } keepPlaceholderOnFocus - extraToolbarButtons={ } /> { focus && diff --git a/blocks/url-input/button.js b/blocks/url-input/button.js index 24645ae62bd1db..7c24525fc72766 100644 --- a/blocks/url-input/button.js +++ b/blocks/url-input/button.js @@ -19,11 +19,11 @@ import UrlInput from './'; import ToggleControl from '../inspector-controls/toggle-control'; import { filterURLForDisplay } from '../../editor/utils/url'; -const DisplayStep = 'DISPLAY'; -const EditStep = 'EDIT'; -const SettingsStep = 'SETTINGS'; +const DISPLAY_STEP = 'DISPLAY'; +const EDIT_STEP = 'EDIT'; +const SETTINGS_STEP = 'SETTINGS'; -const AllSteps = [ EditStep, SettingsStep, DisplayStep ]; +const ALL_STEPS = [ EDIT_STEP, SETTINGS_STEP, DISPLAY_STEP ]; const defaultState = { expanded: false, @@ -102,10 +102,10 @@ class UrlInputButton extends Component { } getSteps() { - return AllSteps.filter( step => { - if ( step === DisplayStep ) { + return ALL_STEPS.filter( step => { + if ( step === DISPLAY_STEP ) { return !! this.state.url && ! this.state.isDeleted; - } else if ( step === SettingsStep ) { + } else if ( step === SETTINGS_STEP ) { return this.props.showSettings && ! this.state.isDeleted; } @@ -116,31 +116,38 @@ class UrlInputButton extends Component { renderStep( steps ) { const { currentStep, url, isDeleted, opensInNewWindow } = this.state; - if ( steps[ currentStep ] === EditStep ) { - return [ - , - , - ]; - } else if ( steps[ currentStep ] === DisplayStep ) { - return [ - - { url && filterURLForDisplay( decodeURI( url ) ) } - , - ]; - } else if ( steps[ currentStep ] === SettingsStep ) { - return [ -
    - -
    , - ]; + switch ( steps[ currentStep ] ) { + case EDIT_STEP: + return [ + , + , + ]; + + case DISPLAY_STEP: + return [ + + { url && filterURLForDisplay( decodeURI( url ) ) } + , + ]; + + case SETTINGS_STEP: + return [ +
    + +
    , + ]; } + return []; } From 218de2f41f3f36bd7a0802afac3a650377aaffaa Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Thu, 12 Oct 2017 14:51:12 +1000 Subject: [PATCH 12/12] Resize link entry based on toolbar group size --- blocks/url-input/ancestor-size.js | 37 ++++ blocks/url-input/button.js | 172 ++++-------------- blocks/url-input/link-settings.js | 166 +++++++++++++++++ blocks/url-input/style.scss | 12 +- blocks/url-input/test/index.js | 52 +++--- .../higher-order/with-focus-return/index.js | 14 +- .../with-focus-return/test/index.js | 10 +- components/index.js | 2 +- 8 files changed, 292 insertions(+), 173 deletions(-) create mode 100644 blocks/url-input/ancestor-size.js create mode 100644 blocks/url-input/link-settings.js diff --git a/blocks/url-input/ancestor-size.js b/blocks/url-input/ancestor-size.js new file mode 100644 index 00000000000000..6c3b4ce2c58686 --- /dev/null +++ b/blocks/url-input/ancestor-size.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { Component } from 'element'; + +export default class AncestorSize extends Component { + constructor() { + super( ...arguments ); + + this.getAncestorSize = this.getAncestorSize.bind( this ); + this.bindContainer = this.bindContainer.bind( this ); + } + bindContainer( ref ) { + this.containerRef = ref; + } + getAncestorSize() { + if ( this.containerRef ) { + const ancestor = this.containerRef.closest( this.props.ancestorSelector ); + + if ( ancestor ) { + const { width, height } = ancestor.getBoundingClientRect(); + return { width, height }; + } + + return { width: 0, height: 0 }; + } + + return { width: 0, height: 0 }; + } + render() { + return ( +
    + { this.props.children( this.getAncestorSize ) } +
    + ); + } +} diff --git a/blocks/url-input/button.js b/blocks/url-input/button.js index 7c24525fc72766..6df5abb01cc674 100644 --- a/blocks/url-input/button.js +++ b/blocks/url-input/button.js @@ -15,44 +15,27 @@ import { IconButton } from '@wordpress/components'; /** * Internal dependencies */ -import UrlInput from './'; -import ToggleControl from '../inspector-controls/toggle-control'; -import { filterURLForDisplay } from '../../editor/utils/url'; - -const DISPLAY_STEP = 'DISPLAY'; -const EDIT_STEP = 'EDIT'; -const SETTINGS_STEP = 'SETTINGS'; - -const ALL_STEPS = [ EDIT_STEP, SETTINGS_STEP, DISPLAY_STEP ]; - -const defaultState = { - expanded: false, - currentStep: 0, - isDeleted: false, -}; +import LinkSettings from './link-settings'; +import AncestorSize from './ancestor-size'; class UrlInputButton extends Component { constructor() { super( ...arguments ); this.state = { - ...defaultState, + expanded: false, url: this.props.url, opensInNewWindow: this.props.opensInNewWindow, }; this.onLinkButtonClick = this.onLinkButtonClick.bind( this ); - this.onSubmit = this.onSubmit.bind( this ); - this.changeLink = this.changeLink.bind( this ); - this.deleteLink = this.deleteLink.bind( this ); - this.onBack = this.onBack.bind( this ); - this.toggleOpensInNewWindow = this.toggleOpensInNewWindow.bind( this ); + this.onSettingsCancel = this.onSettingsCancel.bind( this ); } componentWillReceiveProps( nextProps ) { if ( nextProps.selectedNodeId !== this.props.selectedNodeId ) { this.setState( { - ...defaultState, + expanded: false, url: nextProps.url, opensInNewWindow: nextProps.opensInNewWindow, } ); @@ -60,101 +43,15 @@ class UrlInputButton extends Component { } onLinkButtonClick() { - this.setState( { expanded: true, currentStep: 0, isDeleted: false } ); - } - - onSubmit( event ) { - event.preventDefault(); - this.changeStep( 1 ); - } - - onBack() { - this.changeStep( -1 ); - } - - changeStep( dir ) { - const nextStep = this.state.currentStep + dir; - - if ( nextStep < 0 ) { - this.setState( { expanded: false, currentStep: 0 } ); - return; - } - - if ( nextStep > ( this.getSteps().length - 1 ) ) { - this.props.onChange( { url: this.state.isDeleted ? null : this.state.url, opensInNewWindow: this.state.opensInNewWindow } ); - this.setState( { expanded: false, currentStep: 0 } ); - return; - } - - this.setState( { currentStep: nextStep } ); + this.setState( { expanded: true } ); } - deleteLink() { - this.setState( { url: '', isDeleted: true } ); - } - - changeLink( url ) { - this.setState( { url } ); - } - - toggleOpensInNewWindow() { - this.setState( { opensInNewWindow: ! this.state.opensInNewWindow } ); - } - - getSteps() { - return ALL_STEPS.filter( step => { - if ( step === DISPLAY_STEP ) { - return !! this.state.url && ! this.state.isDeleted; - } else if ( step === SETTINGS_STEP ) { - return this.props.showSettings && ! this.state.isDeleted; - } - - return true; - } ); - } - - renderStep( steps ) { - const { currentStep, url, isDeleted, opensInNewWindow } = this.state; - - switch ( steps[ currentStep ] ) { - case EDIT_STEP: - return [ - , - , - ]; - - case DISPLAY_STEP: - return [ - - { url && filterURLForDisplay( decodeURI( url ) ) } - , - ]; - - case SETTINGS_STEP: - return [ -
    - -
    , - ]; - } - - return []; + onSettingsCancel() { + this.setState( { expanded: false } ); } render() { - const { expanded, url, currentStep } = this.state; - const steps = this.getSteps(); - const isLastStep = currentStep === ( steps.length - 1 ); + const { expanded } = this.state; return (
  • @@ -163,37 +60,32 @@ class UrlInputButton extends Component { label={ __( 'Edit Link' ) } onClick={ this.onLinkButtonClick } className={ classnames( 'components-toolbar__control', { - 'is-active': url, + 'is-active': this.props.url, } ) } /> - - { expanded && - - - { this.renderStep( steps ) } - - + + { ( getAncestorSize ) => + + { expanded && + + } + } - +
  • ); } diff --git a/blocks/url-input/link-settings.js b/blocks/url-input/link-settings.js new file mode 100644 index 00000000000000..ea9ebce98f4740 --- /dev/null +++ b/blocks/url-input/link-settings.js @@ -0,0 +1,166 @@ + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { IconButton, withFocusReturnWrapperProps } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import UrlInput from './'; +import ToggleControl from '../inspector-controls/toggle-control'; +import { filterURLForDisplay } from '../../editor/utils/url'; + +const DISPLAY_STEP = 'DISPLAY'; +const EDIT_STEP = 'EDIT'; +const SETTINGS_STEP = 'SETTINGS'; + +const ALL_STEPS = [ EDIT_STEP, SETTINGS_STEP, DISPLAY_STEP ]; + +export class LinkSettings extends Component { + constructor() { + super( ...arguments ); + + this.state = { + currentStep: 0, + isDeleted: false, + url: this.props.url, + opensInNewWindow: this.props.opensInNewWindow, + }; + + this.onSubmit = this.onSubmit.bind( this ); + this.changeLink = this.changeLink.bind( this ); + this.deleteLink = this.deleteLink.bind( this ); + this.onBack = this.onBack.bind( this ); + this.toggleOpensInNewWindow = this.toggleOpensInNewWindow.bind( this ); + } + + componentWillReceiveProps( nextProps ) { + if ( nextProps.linkId !== this.props.linkId ) { + this.setState( { + currentStep: 0, + isDeleted: false, + url: nextProps.url, + opensInNewWindow: nextProps.opensInNewWindow, + } ); + } + } + + getSteps() { + return ALL_STEPS.filter( step => { + if ( step === DISPLAY_STEP ) { + return !! this.state.url && ! this.state.isDeleted; + } else if ( step === SETTINGS_STEP ) { + return this.props.showOpensInNewWindow && ! this.state.isDeleted; + } + + return true; + } ); + } + + onSubmit( event ) { + event.preventDefault(); + this.changeStep( 1 ); + } + + onBack() { + this.changeStep( -1 ); + } + + changeStep( dir ) { + const nextStep = this.state.currentStep + dir; + + if ( nextStep < 0 ) { + this.setState( { currentStep: 0 } ); + this.props.onCancel(); + return; + } else if ( nextStep > ( this.getSteps().length - 1 ) ) { + this.setState( { currentStep: 0 } ); + this.props.onChange( { url: this.state.isDeleted ? null : this.state.url, opensInNewWindow: this.state.opensInNewWindow } ); + return; + } + + this.setState( { currentStep: nextStep } ); + } + + deleteLink() { + this.setState( { url: '', isDeleted: true } ); + } + + changeLink( url ) { + this.setState( { url } ); + } + + toggleOpensInNewWindow() { + this.setState( { opensInNewWindow: ! this.state.opensInNewWindow } ); + } + + renderStep( steps ) { + const { currentStep, url, isDeleted, opensInNewWindow } = this.state; + + switch ( steps[ currentStep ] ) { + case EDIT_STEP: + return [ + , + , + ]; + + case DISPLAY_STEP: + return [ + + { url && filterURLForDisplay( decodeURI( url ) ) } + , + ]; + + case SETTINGS_STEP: + return [ +
    + +
    , + ]; + } + + return []; + } + + render() { + const { currentStep } = this.state; + const steps = this.getSteps(); + const isLastStep = currentStep === ( steps.length - 1 ); + + return ( +
    + + { this.renderStep( steps ) } + + ); + } + +} + +export default withFocusReturnWrapperProps( { className: 'blocks-format-toolbar__link-modal' } )( LinkSettings ); diff --git a/blocks/url-input/style.scss b/blocks/url-input/style.scss index 37990b853b9f5a..8da7a547fbe634 100644 --- a/blocks/url-input/style.scss +++ b/blocks/url-input/style.scss @@ -108,10 +108,7 @@ ul.components-toolbar > li.blocks-url-input__button { box-shadow: 0px 3px 20px rgba( 18, 24, 30, .1 ), 0px 1px 3px rgba( 18, 24, 30, .1 ); border: 1px solid #e0e5e9; background: #fff; - width: 305px; - display: inline-flex; - flex-wrap: nowrap; - align-items: center; + min-width: 305px; font-family: $default-font; font-size: $default-font-size; line-height: $default-line-height; @@ -141,6 +138,13 @@ ul.components-toolbar > li.blocks-url-input__button { opacity: 0; transform: translate(100%); } + + form { + width: 100%; + display: inline-flex; + flex-wrap: nowrap; + align-items: center; + } } .blocks-format-toolbar__link-settings { diff --git a/blocks/url-input/test/index.js b/blocks/url-input/test/index.js index a6c6c0baf9dcc1..175f39b6aa4ab8 100644 --- a/blocks/url-input/test/index.js +++ b/blocks/url-input/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { shallow } from 'enzyme'; +import { shallow, mount } from 'enzyme'; /** * Internal dependencies @@ -10,6 +10,7 @@ import { IconButton } from '@wordpress/components'; import ToggleControl from '../../inspector-controls/toggle-control'; import UrlInputButton from '../button'; import UrlInput from '../'; +import { LinkSettings } from '../link-settings'; describe( 'UrlInputButton', () => { it( 'renders `Edit Link` button', () => { @@ -17,25 +18,20 @@ describe( 'UrlInputButton', () => { expect( wrapper.find( IconButton ).length ).toEqual( 1 ); } ); it( 'expands when `Edit` clicked', () => { - const wrapper = shallow( ); + const wrapper = mount( ); wrapper.find( IconButton ).simulate( 'click' ); expect( wrapper.find( 'form' ).length ).toEqual( 1 ); } ); - it( 'allows url to be input', () => { - const wrapper = shallow( ); - wrapper.find( IconButton ).simulate( 'click' ); - const input = wrapper.find( UrlInput ); - input.simulate( 'change', 'https://wordpress.org/' ); - expect( wrapper.find( UrlInput ).prop( 'value' ) ).toEqual( 'https://wordpress.org/' ); - } ); +} ); + +describe( 'LinkSettings', () => { it( '`onChange` called with new url', () => { let newUrl = ''; - const wrapper = shallow( { + const wrapper = shallow( {} } onChange={ ( { url } ) => { newUrl = url; } } /> ); - wrapper.find( IconButton ).simulate( 'click' ); const input = wrapper.find( UrlInput ); input.simulate( 'change', 'https://wordpress.org/' ); @@ -44,32 +40,28 @@ describe( 'UrlInputButton', () => { wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // display expect( newUrl ).toEqual( 'https://wordpress.org/' ); - expect( wrapper.find( 'form' ).length ).toEqual( 0 ); } ); it( '`onChange` called with un-linked url', () => { let newUrl = ''; - const wrapper = shallow( { + const wrapper = shallow( {} } onChange={ ( { url } ) => { newUrl = url; } } /> ); - wrapper.find( IconButton ).simulate( 'click' ); wrapper.find( '.blocks-url-input__unlink' ).simulate( 'click' ); wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // done expect( newUrl ).toBeNull(); - expect( wrapper.find( 'form' ).length ).toEqual( 0 ); } ); it( '`onChange` called with new `opensInNewWindow`', () => { let newOpensInNewWindow = null; - const wrapper = shallow( { + const wrapper = shallow( {} } onChange={ ( { opensInNewWindow } ) => { newOpensInNewWindow = opensInNewWindow; } } /> ); - wrapper.find( IconButton ).simulate( 'click' ); // expand link entry wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // link entry wrapper.find( ToggleControl ).simulate( 'change' ); @@ -78,16 +70,34 @@ describe( 'UrlInputButton', () => { wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // display expect( newOpensInNewWindow ).toEqual( false ); - expect( wrapper.find( 'form' ).length ).toEqual( 0 ); // link entry collapsed } ); it( 'displays url', () => { - const wrapper = shallow( ); - wrapper.find( IconButton ).simulate( 'click' ); // expand link entry + const wrapper = shallow( {} } onChange={ () => {} } /> ); wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // move to settings wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // move to display - expect( wrapper.find( 'a' ).prop( 'href' ) ).toEqual( 'https://wordpress.org/' ); + expect( wrapper.find( 'a' ).prop( 'href' ) ).toEqual( 'https://www.ephox.com' ); + } ); + + it( 'doesn`t show settings per prop', () => { + const wrapper = shallow( {} } onChange={ () => {} } /> ); + + wrapper.find( 'form' ).simulate( 'submit', { preventDefault: () => {} } ); // move to display + + expect( wrapper.find( 'a' ).prop( 'href' ) ).toEqual( 'https://www.ephox.com' ); + } ); + + it( '`onCancel` called', () => { + let onCancelCalled = false; + + const wrapper = shallow( {} } onCancel={ () => { + onCancelCalled = true; + } } /> ); + + wrapper.find( '.blocks-url-input__back' ).simulate( 'click' ); + + expect( onCancelCalled ).toEqual( true ); } ); } ); diff --git a/components/higher-order/with-focus-return/index.js b/components/higher-order/with-focus-return/index.js index 615205c75d2474..b52f10f5c20cbc 100644 --- a/components/higher-order/with-focus-return/index.js +++ b/components/higher-order/with-focus-return/index.js @@ -8,12 +8,14 @@ import { Component } from '@wordpress/element'; * When mounting the wrapped component, we track a reference to the current active element * so we know where to restore focus when the component is unmounted * - * @param {WPElement} WrappedComponent The disposable component + * @param {WPElement} wrapperProps props to apply to the wrapper div + * + * @param {WPElement} OriginalComponent The disposable component * * @return {Component} Component with the focus restauration behaviour */ -function withFocusReturn( WrappedComponent ) { - return class extends Component { +export const withFocusReturnWrapperProps = ( wrapperProps ) => ( OriginalComponent ) => + class extends Component { constructor() { super( ...arguments ); @@ -42,12 +44,12 @@ function withFocusReturn( WrappedComponent ) {
    - +
    ); } }; -} -export default withFocusReturn; +export default withFocusReturnWrapperProps( {} ); diff --git a/components/higher-order/with-focus-return/test/index.js b/components/higher-order/with-focus-return/test/index.js index 3226dc65e676c6..5f5dcced277b22 100644 --- a/components/higher-order/with-focus-return/test/index.js +++ b/components/higher-order/with-focus-return/test/index.js @@ -7,7 +7,7 @@ import { Component } from '../../../../element'; /** * Internal dependencies */ -import withFocusReturn from '../'; +import withFocusReturn, { withFocusReturnWrapperProps } from '../'; class Test extends Component { render() { @@ -74,3 +74,11 @@ describe( 'withFocusReturn()', () => { } ); } ); } ); + +describe( 'withFocusReturnWrapperProps()', () => { + it( 'adds props to wrapper div', () => { + const Composite = withFocusReturnWrapperProps( { className: 'foo' } )( Test ); + const renderedComposite = shallow( ); + expect( renderedComposite.find( '.foo' ).length ).toEqual( 1 ); + } ); +} ); diff --git a/components/index.js b/components/index.js index 1eb3dd2516e595..d6501aa9f0e973 100644 --- a/components/index.js +++ b/components/index.js @@ -31,6 +31,6 @@ export { default as Tooltip } from './tooltip'; // Higher-Order Components export { default as withAPIData } from './higher-order/with-api-data'; -export { default as withFocusReturn } from './higher-order/with-focus-return'; +export { default as withFocusReturn, withFocusReturnWrapperProps } from './higher-order/with-focus-return'; export { default as withInstanceId } from './higher-order/with-instance-id'; export { default as withSpokenMessages } from './higher-order/with-spoken-messages';