diff --git a/blocks/contrast-checker/index.js b/blocks/contrast-checker/index.js new file mode 100644 index 0000000000000..885936258d8ff --- /dev/null +++ b/blocks/contrast-checker/index.js @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import tinycolor from 'tinycolor2'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Notice } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import './style.scss'; + +function ContrastChecker( { backgroundColor, textColor, isLargeText } ) { + if ( ! backgroundColor || ! textColor ) { + return null; + } + const tinyBackgroundColor = tinycolor( backgroundColor ); + const tinyTextColor = tinycolor( textColor ); + if ( tinycolor.isReadable( + tinyBackgroundColor, + tinyTextColor, + { level: 'AA', size: ( isLargeText ? 'large' : 'small' ) } + ) ) { + return null; + } + const msg = tinyBackgroundColor.getBrightness() < tinyTextColor.getBrightness() ? + __( 'This color combination may be hard for people to read. Try using a darker background color and/or a brighter text color.' ) : + __( 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' ); + return ( +
+ +
+ ); +} + +export default ContrastChecker; diff --git a/blocks/contrast-checker/style.scss b/blocks/contrast-checker/style.scss new file mode 100644 index 0000000000000..2456bd495e4ec --- /dev/null +++ b/blocks/contrast-checker/style.scss @@ -0,0 +1,3 @@ +.blocks-contrast-checker > .notice { + margin: 0; +} diff --git a/blocks/library/button/index.js b/blocks/library/button/index.js index 666e244d4e062..e7edef6b8bd19 100644 --- a/blocks/library/button/index.js +++ b/blocks/library/button/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; import { Dashicon, IconButton, PanelBody } from '@wordpress/components'; /** @@ -16,70 +17,100 @@ import BlockControls from '../../block-controls'; import ToggleControl from '../../inspector-controls/toggle-control'; import BlockAlignmentToolbar from '../../block-alignment-toolbar'; import ColorPalette from '../../color-palette'; +import ContrastChecker from '../../contrast-checker'; import InspectorControls from '../../inspector-controls'; import BlockDescription from '../../block-description'; const { attr, children } = source; +const { getComputedStyle } = window; -registerBlockType( 'core/button', { - title: __( 'Button' ), +class ButtonBlock extends Component { + constructor() { + super( ...arguments ); - icon: 'button', + this.containers = {}; + this.fallbackColors = {}; - category: 'layout', + this.state = { + fallbackBackgroundColor: undefined, + fallbackTextColor: undefined, + }; - attributes: { - url: { - type: 'string', - source: attr( 'a', 'href' ), - }, - title: { - type: 'string', - source: attr( 'a', 'title' ), - }, - text: { - type: 'array', - source: children( 'a' ), - }, - align: { - type: 'string', - default: 'none', - }, - color: { - type: 'string', - }, - textColor: { - type: 'string', - }, - }, + this.updateAlignment = this.updateAlignment.bind( this ); + this.toggleClear = this.toggleClear.bind( this ); + this.bindRef = this.bindRef.bind( this ); + } - getEditWrapperProps( attributes ) { - const { align, clear } = attributes; - const props = {}; + componentDidMount() { + this.grabColors(); + } - if ( 'left' === align || 'right' === align || 'center' === align ) { - props[ 'data-align' ] = align; + componentDidUpdate() { + this.grabColors(); + } + + updateAlignment( nextAlign ) { + this.props.setAttributes( { align: nextAlign } ); + } + + toggleClear() { + const { attributes, setAttributes } = this.props; + setAttributes( { clear: ! attributes.clear } ); + } + + bindRef( node ) { + if ( ! node ) { + return; } - if ( clear ) { - props[ 'data-clear' ] = 'true'; + this.containers.background = node; + this.containers.text = node.querySelector( '[contenteditable="true"]' ); + } + + grabColors() { + const { background, text } = this.containers; + const { textColor, color } = this.props.attributes; + const { fallbackTextColor, fallbackBackgroundColor } = this.state; + + if ( ! color && ! fallbackBackgroundColor && background ) { + this.setState( { fallbackBackgroundColor: getComputedStyle( background ).backgroundColor } ); } - return props; - }, + if ( ! textColor && ! fallbackTextColor && text ) { + this.setState( { fallbackTextColor: getComputedStyle( text ).color } ); + } + } + + render() { + const { + attributes, + setAttributes, + focus, + setFocus, + className, + } = this.props; - edit( { attributes, setAttributes, focus, setFocus, className } ) { - const { text, url, title, align, color, textColor, clear } = attributes; - const updateAlignment = ( nextAlign ) => setAttributes( { align: nextAlign } ); - const toggleClear = () => setAttributes( { clear: ! clear } ); + const { + text, + url, + title, + align, + color, + textColor, + clear, + } = attributes; + const { + fallbackBackgroundColor, + fallbackTextColor, + } = this.state; return [ focus && ( - + ), - + setAttributes( { textColor: colorValue } ) } /> + } , @@ -132,6 +168,58 @@ registerBlockType( 'core/button', { ), ]; + } +} + +registerBlockType( 'core/button', { + title: __( 'Button' ), + + icon: 'button', + + category: 'layout', + + attributes: { + url: { + type: 'string', + source: attr( 'a', 'href' ), + }, + title: { + type: 'string', + source: attr( 'a', 'title' ), + }, + text: { + type: 'array', + source: children( 'a' ), + }, + align: { + type: 'string', + default: 'none', + }, + color: { + type: 'string', + }, + textColor: { + type: 'string', + }, + }, + + getEditWrapperProps( attributes ) { + const { align, clear } = attributes; + const props = {}; + + if ( 'left' === align || 'right' === align || 'center' === align ) { + props[ 'data-align' ] = align; + } + + if ( clear ) { + props[ 'data-clear' ] = 'true'; + } + + return props; + }, + + edit( props ) { + return ; }, save( { attributes } ) { diff --git a/package.json b/package.json index 923bfe17e2dab..d9a050e1aa86f 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "rememo": "2.3.3", "showdown": "1.7.4", "simple-html-tokenizer": "0.4.1", + "tinycolor2": "1.4.1", "uuid": "3.1.0" }, "devDependencies": {