From a2495f54d6680888fed773fbc32b106497791ed5 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 12 May 2017 17:04:06 -0400 Subject: [PATCH 1/2] Move toggled button styles to button component --- components/button/style.scss | 10 ++++++++++ editor/header/tools/style.scss | 12 ------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/components/button/style.scss b/components/button/style.scss index f408ba7ee27aef..fe25c011b5cd4c 100644 --- a/components/button/style.scss +++ b/components/button/style.scss @@ -4,6 +4,16 @@ outline: none; text-decoration: none; + &.is-toggled { + background: $dark-gray-500; + color: $white; + } + + &.is-toggled:hover { + color: $white; + background: $dark-gray-400; + } + &:disabled { opacity: 0.6; } diff --git a/editor/header/tools/style.scss b/editor/header/tools/style.scss index b4cf0b1dda1940..4ef5de10130085 100644 --- a/editor/header/tools/style.scss +++ b/editor/header/tools/style.scss @@ -59,16 +59,4 @@ margin-right: 4px; } } - - .components-button { - &.is-toggled { - background: $dark-gray-500; - color: $white; - } - - &.is-toggled:hover { - color: $white; - background: $dark-gray-400; - } - } } From f36e1de11d59b072c2e43fce58ac2358f3a56f96 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 18 May 2017 20:57:32 -0400 Subject: [PATCH 2/2] Display tooltip on button hover and focus --- components/button/index.js | 131 +++++++++++++++++++++--- components/button/style.scss | 87 +++++++++++++++- components/icon-button/index.js | 4 +- components/icon-button/style.scss | 14 +-- components/toolbar/index.js | 2 +- components/toolbar/style.scss | 7 -- editor/assets/stylesheets/_z-index.scss | 1 + editor/block-switcher/index.js | 2 +- editor/block-switcher/style.scss | 7 -- editor/header/tools/index.js | 4 +- editor/inserter/index.js | 2 +- 11 files changed, 210 insertions(+), 51 deletions(-) diff --git a/components/button/index.js b/components/button/index.js index a9eed61a1d89b4..9d15e9c2399009 100644 --- a/components/button/index.js +++ b/components/button/index.js @@ -3,23 +3,122 @@ */ import './style.scss'; import classnames from 'classnames'; +import { compact, over } from 'lodash'; -function Button( { href, isPrimary, isLarge, isToggled, className, ...additionalProps } ) { - const classes = classnames( 'components-button', className, { - button: ( isPrimary || isLarge ), - 'button-primary': isPrimary, - 'button-large': isLarge, - 'is-toggled': isToggled, - } ); - - const tag = href !== undefined ? 'a' : 'button'; - const tagProps = tag === 'a' ? { href } : { type: 'button' }; - - return wp.element.createElement( tag, { - ...tagProps, - ...additionalProps, - className: classes, - } ); +/** + * WordPress dependencies + */ +import { Component } from 'element'; + +class Button extends Component { + constructor() { + super( ...arguments ); + + this.setFocused = this.setFocused.bind( this ); + this.checkTooltipBounds = this.checkTooltipBounds.bind( this ); + this.resetBoundedTooltip = this.resetBoundedTooltip.bind( this ); + + this.state = { + hasFocus: false, + bounded: [], + }; + } + + setFocused() { + this.setState( { hasFocus: true } ); + } + + checkTooltipBounds( event ) { + // Prevent calculating bounded restrictions if the button has no title + // or if bounds have already been calculated in current hover/focus + if ( ! this.props.title || this.state.bounded.length ) { + return; + } + + const { currentTarget } = event; + + // TODO: Find a better way to determine bounds of the content area, + // This is implemented with padding on the content element, meaning + // it's not as simple as referencing the root or body nodes. A better + // solution would not be tied to specific element IDs but instead + // offet values calculated from the editor's root layout element. + const contentTop = document.getElementById( 'wpbody-content' ).getBoundingClientRect().top; + + // Because tooltip is rendered as a pseudo-element, positional offsets + // are calculated relative the button + const targetRect = currentTarget.getBoundingClientRect(); + const targetCenter = targetRect.left + ( targetRect.width / 2 ); + const tooltipStyles = window.getComputedStyle( currentTarget, ':after' ); + const tooltipWidth = parseInt( tooltipStyles.width, 10 ); + const tooltipTop = targetRect.top + parseInt( tooltipStyles.top, 10 ); + const tooltipLeft = targetCenter - ( tooltipWidth / 2 ); + const tooltipRight = targetCenter + ( tooltipWidth / 2 ); + + this.setState( { + // If the default position of the tooltip exceeds any bounds of the + // page content, assign into state so it can be flipped by styling + bounded: compact( [ + tooltipTop < contentTop ? 'top' : null, + tooltipLeft < 0 ? 'left' : null, + tooltipRight > document.documentElement.clientWidth ? 'right' : null, + ] ), + } ); + } + + resetBoundedTooltip( event ) { + // If button currently has focus, only reset flipped state when focus + // leaves, not on mouse out. + if ( this.state.hasFocus && 'blur' !== event.type ) { + return; + } + + this.setState( { + bounded: [], + hasFocus: false, + } ); + } + + render() { + const { href, isPrimary, isLarge, isToggled, disabled, title, className, ...additionalProps } = this.props; + const { bounded } = this.state; + const classes = classnames( 'components-button', className, { + button: ( isPrimary || isLarge ), + 'button-primary': isPrimary, + 'button-large': isLarge, + 'is-toggled': isToggled, + 'is-disabled': disabled, + }, bounded.map( ( bound ) => 'is-tooltip-bounded-' + bound ) ); + + let tag; + if ( href ) { + tag = 'a'; + } else if ( disabled ) { + // Treat disabled button as styled static element, to avoid needing + // to handle focus events since we can't assign disabled attribute + tag = 'span'; + } else { + tag = 'button'; + } + + const tagProps = tag === 'a' ? { href } : { type: 'button' }; + + return wp.element.createElement( tag, { + ...tagProps, + ...additionalProps, + 'aria-label': title, + // We can't assign a disabled attribute, both because it's invalid + // for anchor elements, and also because it interferes with mouse + // events on tooltip bounds check + 'aria-disabled': disabled, + role: 'button', + className: classes, + onClick: disabled ? null : this.props.onClick, + onFocus: over( this.setFocused, this.checkTooltipBounds, this.props.onFocus ), + onBlur: over( this.resetBoundedTooltip, this.props.onBlur ), + onMouseEnter: over( this.checkTooltipBounds, this.props.onMouseEnter ), + onMouseLeave: over( this.resetBoundedTooltip, this.props.onMouseLeave ), + } ); + } } export default Button; diff --git a/components/button/style.scss b/components/button/style.scss index fe25c011b5cd4c..62e649760e9e51 100644 --- a/components/button/style.scss +++ b/components/button/style.scss @@ -1,4 +1,6 @@ .components-button { + display: inline-block; + position: relative; background: none; border: none; outline: none; @@ -14,7 +16,90 @@ background: $dark-gray-400; } - &:disabled { + &.is-disabled { opacity: 0.6; + cursor: default; + } + + &:focus { + box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba( 30, 140, 190, .8 ); + } + + &[aria-label]:focus, + &[aria-label]:hover { + &:before, + &:after { + box-sizing: border-box; + z-index: z-index( '.components-button[aria-label]:after' ); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX( -50% ); + pointer-events: none; + text-shadow: none; + font-family: $default-font; + font-size: $default-font-size; + } + + &:before { + content: ''; + width: 0; + height: 0; + margin-bottom: -6px; + border: 6px solid transparent; + border-bottom-width: 0; + border-top-color: $dark-gray-400; + } + + &:after { + content: attr( aria-label ); + padding: 4px 12px; + background: $dark-gray-400; + white-space: nowrap; + font-size: $default-font-size; + color: white; + } + } + + &.is-tooltip-bounded-top { + &[aria-label]:focus, + &[aria-label]:hover { + &:before, + &:after { + top: 100%; + bottom: auto; + } + + &:before { + margin-top: -6px; + margin-bottom: 0; + border-top-width: 0; + border-bottom-width: 6px; + border-top-color: transparent; + border-bottom-color: $dark-gray-400; + } + } + } + + &.is-tooltip-bounded-right { + &[aria-label]:focus, + &[aria-label]:hover { + &:after { + left: auto; + right: 50%; + margin-right: -12px; + transform: none; + } + } + } + + &.is-tooltip-bounded-left { + &[aria-label]:focus, + &[aria-label]:hover { + &:after { + margin-left: -12px; + transform: none; + } + } } } diff --git a/components/icon-button/index.js b/components/icon-button/index.js index e4856ec8de2c6d..6184f7b75beee0 100644 --- a/components/icon-button/index.js +++ b/components/icon-button/index.js @@ -10,11 +10,11 @@ import './style.scss'; import Button from '../button'; import Dashicon from '../dashicon'; -function IconButton( { icon, children, label, className, ...additionalProps } ) { +function IconButton( { icon, children, className, ...additionalProps } ) { const classes = classnames( 'components-icon-button', className ); return ( - diff --git a/components/icon-button/style.scss b/components/icon-button/style.scss index 8e893402d6292d..11e3ef49325fbf 100644 --- a/components/icon-button/style.scss +++ b/components/icon-button/style.scss @@ -7,21 +7,9 @@ color: $dark-gray-500; position: relative; - &:not( :disabled ) { - cursor: pointer; - + &:not( .is-disabled ) { &:hover { color: $blue-medium; } } - - &:focus:before { - content: ''; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8); - } } diff --git a/components/toolbar/index.js b/components/toolbar/index.js index 7ec91b9579f101..6a94b55cbfaeed 100644 --- a/components/toolbar/index.js +++ b/components/toolbar/index.js @@ -20,7 +20,7 @@ function Toolbar( { controls } ) { { event.stopPropagation(); diff --git a/components/toolbar/style.scss b/components/toolbar/style.scss index c54cb535b8cb60..c9521277452255 100644 --- a/components/toolbar/style.scss +++ b/components/toolbar/style.scss @@ -21,13 +21,6 @@ margin-left: 3px; } - &:focus:before { - top: -4px; - right: -4px; - bottom: -4px; - left: -4px; - } - &.is-active, &:hover, &:not(:disabled):hover { diff --git a/editor/assets/stylesheets/_z-index.scss b/editor/assets/stylesheets/_z-index.scss index 06224f7b6c9448..9f5bb4e48f4674 100644 --- a/editor/assets/stylesheets/_z-index.scss +++ b/editor/assets/stylesheets/_z-index.scss @@ -7,6 +7,7 @@ $z-layers: ( '.editor-visual-editor__block {core/image aligned left or right}': 10, '.editor-visual-editor__block-controls': 1, '.editor-header': 20, + '.components-button[aria-label]:before': 30, ); @function z-index( $key ) { diff --git a/editor/block-switcher/index.js b/editor/block-switcher/index.js index 052b9dd4257b63..06f4926d4cd3a7 100644 --- a/editor/block-switcher/index.js +++ b/editor/block-switcher/index.js @@ -77,7 +77,7 @@ class BlockSwitcher extends wp.element.Component { onClick={ this.toggleMenu } aria-haspopup="true" aria-expanded={ this.state.open } - label={ wp.i18n.__( 'Change block type' ) } + title={ wp.i18n.__( 'Change block type' ) } > diff --git a/editor/block-switcher/style.scss b/editor/block-switcher/style.scss index 7a0757046e2f25..e29746f95c5f16 100644 --- a/editor/block-switcher/style.scss +++ b/editor/block-switcher/style.scss @@ -12,13 +12,6 @@ width: auto; margin: 3px; padding: 6px; - - &:focus:before { - top: -3px; - right: -3px; - bottom: -3px; - left: -3px; - } } .editor-block-switcher__menu { diff --git a/editor/header/tools/index.js b/editor/header/tools/index.js index a20f071370f310..3e7b0c6f8feab6 100644 --- a/editor/header/tools/index.js +++ b/editor/header/tools/index.js @@ -25,13 +25,13 @@ function Tools( { undo, redo, hasUndo, hasRedo, isSidebarOpened, toggleSidebar } diff --git a/editor/inserter/index.js b/editor/inserter/index.js index 81ca096d19fd2f..c3d253065e06d2 100644 --- a/editor/inserter/index.js +++ b/editor/inserter/index.js @@ -71,7 +71,7 @@ class Inserter extends wp.element.Component {