From a6f5d14955b291b7d63cff376ae4f48168f98095 Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Fri, 20 Sep 2024 15:03:31 +0100 Subject: [PATCH 1/9] feat: adding contrast checker for text and bg --- src/editor/components/StylesColor.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/editor/components/StylesColor.js b/src/editor/components/StylesColor.js index f47cefdd..6274275b 100644 --- a/src/editor/components/StylesColor.js +++ b/src/editor/components/StylesColor.js @@ -2,6 +2,7 @@ import { set } from 'lodash'; import { __ } from '@wordpress/i18n'; import { useContext } from '@wordpress/element'; import { ColorPalette } from '@wordpress/components'; +import { ContrastChecker } from '@wordpress/block-editor'; import { varToHex, hexToVar } from '../../utils/block-helpers'; import getThemeOption from '../../utils/get-theme-option'; @@ -51,6 +52,13 @@ const Color = ( { selector } ) => { { __( 'Color', 'themer' ) } +
{ colorPalettes } From 5c499c7f8c081eb9eeadf19927228d52d0026e9b Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Fri, 20 Sep 2024 17:13:26 +0100 Subject: [PATCH 2/9] fix: avoiding custom palette display issue --- src/editor/components/StylesColor.js | 47 ++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/editor/components/StylesColor.js b/src/editor/components/StylesColor.js index 6274275b..2f5f1d27 100644 --- a/src/editor/components/StylesColor.js +++ b/src/editor/components/StylesColor.js @@ -1,6 +1,6 @@ import { set } from 'lodash'; import { __ } from '@wordpress/i18n'; -import { useContext } from '@wordpress/element'; +import { useContext, useState, useEffect } from '@wordpress/element'; import { ColorPalette } from '@wordpress/components'; import { ContrastChecker } from '@wordpress/block-editor'; @@ -35,6 +35,44 @@ const Color = ( { selector } ) => { setUserConfig( config ); }; + /** + * Define mouse state and related functions + */ + const [ isMouseDown, setIsMouseDown ] = useState( false ); + const handleMouseDown = () => setIsMouseDown( true ); + const handleMouseUp = () => setIsMouseDown( false ); + + /** + * Define colour variables, used to avoid jumping colour picker when ContrastChecker display toggles + */ + const [ textColour, setTextColour ] = useState( colorStyles.text ); + const [ backgroundColour, setBackgroundColour ] = useState( + colorStyles.background + ); + + /** + * When the values are changed, ensure the mouse buttons have been released before updating + */ + useEffect( () => { + if ( ! isMouseDown ) { + setTextColour( colorStyles.text ); + setBackgroundColour( colorStyles.background ); + } + }, [ isMouseDown, colorStyles.text, colorStyles.background ] ); + + /** + * Add and remove the mouse event listeners + */ + useEffect( () => { + window.addEventListener( 'mousedown', handleMouseDown ); + window.addEventListener( 'mouseup', handleMouseUp ); + + return () => { + window.removeEventListener( 'mousedown', handleMouseDown ); + window.removeEventListener( 'mouseup', handleMouseUp ); + }; + }, [] ); + const colorPalettes = [ 'background', 'text' ].map( ( key ) => (
{ key } @@ -53,11 +91,8 @@ const Color = ( { selector } ) => { { __( 'Color', 'themer' ) }
{ colorPalettes } From 340d2256be9c2a247745971d0bcd514d074b9afb Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Mon, 23 Sep 2024 16:57:46 +0100 Subject: [PATCH 3/9] feat: including custom contrast component --- src/editor/components/BBContrastChecker.js | 102 ++++++++++++++++++ src/editor/components/StylesColor.js | 9 +- .../components/bb-contrast-checker.scss | 57 ++++++++++ src/editor/styles/index.scss | 1 + 4 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 src/editor/components/BBContrastChecker.js create mode 100644 src/editor/styles/components/bb-contrast-checker.scss diff --git a/src/editor/components/BBContrastChecker.js b/src/editor/components/BBContrastChecker.js new file mode 100644 index 00000000..19a1c1e4 --- /dev/null +++ b/src/editor/components/BBContrastChecker.js @@ -0,0 +1,102 @@ +import { Notice } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +const BBContrastChecker = ( { background, foreground } ) => { + /** + * If either colour is not provided, don't render anything + */ + if ( ! background || ! foreground ) { + return; + } + + /** + * Get the luminance of a colour + * + * @param {string} colour The colour to convert to luminance value + * + * @return {int} The luminance value + */ + const getLuminance = ( colour ) => { + const rgb = colour + .match( /\w\w/g ) + .map( ( c ) => parseInt( c, 16 ) / 255 ); + const [ r, g, b ] = rgb.map( ( c ) => { + return c <= 0.03928 + ? c / 12.92 + : Math.pow( ( c + 0.055 ) / 1.055, 2.4 ); + } ); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; + }; + + /** + * Compare two colours and return their contrast ratio. + * + * @param {string} colour1 This is an example function/method parameter description. + * @param {string} colour2 This is a second example. + * + * @return {number} The contrast ratio between the colours + */ + const getContrastRatio = ( colour1, colour2 ) => { + const luminance1 = getLuminance( colour1 ); + const luminance2 = getLuminance( colour2 ); + return ( + ( Math.max( luminance1, luminance2 ) + 0.05 ) / + ( Math.min( luminance1, luminance2 ) + 0.05 ) + ); + }; + + /** + * Determine the colour contrast ratio + */ + const contrastRatio = getContrastRatio( background, foreground ); + + /** + * Determine the message to output + */ + const displayMessage = + contrastRatio >= 4.5 + ? __( + 'The selected colours do not meet the colour contrast ratio for AAA (7:1) accessibility standards', + 'themer' + ) + : __( + 'The selected colours do not meet the colour contrast ratio for AA (4.5:1) accessibility standards', + 'themer' + ); + + /** + * Set the notice display level based on the contrast ratio + */ + const displayMessageImportance = contrastRatio >= 4.5 ? 'info' : 'warning'; + + return ( + <> +
+

+ { __( 'WCAG Check:', 'themer' ) } +

+

+ { contrastRatio.toFixed( 1 ) } : 1 +

+
+

= 4.5 ? 'pass' : ' fail' }> + ✓ { __( 'AA', 'themer' ) } +

+

= 7 ? 'pass' : ' fail' }> + ✓ { __( 'AAA ', 'themer' ) } +

+
+
+ { contrastRatio < 7 && ( + + { displayMessage } + + ) } + + ); +}; + +export default BBContrastChecker; diff --git a/src/editor/components/StylesColor.js b/src/editor/components/StylesColor.js index 2f5f1d27..a014a3f5 100644 --- a/src/editor/components/StylesColor.js +++ b/src/editor/components/StylesColor.js @@ -9,6 +9,7 @@ import getThemeOption from '../../utils/get-theme-option'; import EditorContext from '../context/EditorContext'; import StylesContext from '../context/StylesContext'; import Gradient from './StylesGradient'; +import BBContrastChecker from './BBContrastChecker'; /** * Reusable color control style component @@ -62,6 +63,8 @@ const Color = ( { selector } ) => { /** * Add and remove the mouse event listeners + * + * TODO - Restruct this to just clicks within the container as it fires all the time */ useEffect( () => { window.addEventListener( 'mousedown', handleMouseDown ); @@ -90,9 +93,9 @@ const Color = ( { selector } ) => { { __( 'Color', 'themer' ) } -
{ colorPalettes } diff --git a/src/editor/styles/components/bb-contrast-checker.scss b/src/editor/styles/components/bb-contrast-checker.scss new file mode 100644 index 00000000..6a1de6df --- /dev/null +++ b/src/editor/styles/components/bb-contrast-checker.scss @@ -0,0 +1,57 @@ +.contrast-checker { + align-items: center; + border: 1px solid green; + border-radius: 0.5rem; + display: flex; + gap: 1rem; + justify-content: flex-start; + padding: 0.5rem; + + transition: border-color 0.25s ease-in-out; +} + +.contrast-checker:has(p.fail) { + border-color: blue; +} + +.contrast-checker:has(p.fail + p.fail) { + border-color: red; +} + +.contrast-checker p { + margin-block: 0; +} + +.contrast-checker-title { + font-size: 0.75rem; + font-weight: bold; + text-transform: uppercase; +} + +.contrast-checker-ratio { + font-size: 1.25rem; + font-weight: bold; + margin-inline-start: auto; +} + +.contrast-checker-badges { + align-items: center; + display: flex; + gap: 0.5rem; +} + +.contrast-checker-badges > p { + background-color: green; + border-radius: 0.25rem; + color: #fff; + display: block; + padding: 0.25rem 0.5rem; + + transition: background-color 0.25s ease-in-out, + color 0.25s ease-in-out; +} + +.contrast-checker-badges > p.fail { + background-color: red; + color: #000; +} diff --git a/src/editor/styles/index.scss b/src/editor/styles/index.scss index 3b72351e..2b488a1f 100644 --- a/src/editor/styles/index.scss +++ b/src/editor/styles/index.scss @@ -10,3 +10,4 @@ @import "./components/breadcrumbs"; @import "./components/nav-list"; @import "./components/fontPicker.scss"; +@import "./components/bb-contrast-checker"; From 19abcd6eaf89d2f42b1ad0c3006c292cfcf33e62 Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Mon, 23 Sep 2024 17:04:33 +0100 Subject: [PATCH 4/9] fix: doc update --- src/editor/components/BBContrastChecker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/components/BBContrastChecker.js b/src/editor/components/BBContrastChecker.js index 19a1c1e4..d85a51df 100644 --- a/src/editor/components/BBContrastChecker.js +++ b/src/editor/components/BBContrastChecker.js @@ -14,7 +14,7 @@ const BBContrastChecker = ( { background, foreground } ) => { * * @param {string} colour The colour to convert to luminance value * - * @return {int} The luminance value + * @return {number} The luminance value */ const getLuminance = ( colour ) => { const rgb = colour From 84928068359e6e8bc56c27a069e6ee2ffc3c6fa3 Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Mon, 23 Sep 2024 17:16:35 +0100 Subject: [PATCH 5/9] fix: scoping event listeners to palettes --- src/editor/components/StylesColor.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/editor/components/StylesColor.js b/src/editor/components/StylesColor.js index a014a3f5..47adf16d 100644 --- a/src/editor/components/StylesColor.js +++ b/src/editor/components/StylesColor.js @@ -1,8 +1,7 @@ import { set } from 'lodash'; import { __ } from '@wordpress/i18n'; -import { useContext, useState, useEffect } from '@wordpress/element'; +import { useContext, useState, useEffect, useRef } from '@wordpress/element'; import { ColorPalette } from '@wordpress/components'; -import { ContrastChecker } from '@wordpress/block-editor'; import { varToHex, hexToVar } from '../../utils/block-helpers'; import getThemeOption from '../../utils/get-theme-option'; @@ -18,6 +17,7 @@ import BBContrastChecker from './BBContrastChecker'; * @param {string} props.selector Property target selector */ const Color = ( { selector } ) => { + const containerRef = useRef( null ); const { userConfig, themeConfig } = useContext( EditorContext ); const { setUserConfig } = useContext( StylesContext ); const colorStyles = getThemeOption( selector, themeConfig ) || {}; @@ -63,16 +63,20 @@ const Color = ( { selector } ) => { /** * Add and remove the mouse event listeners - * - * TODO - Restruct this to just clicks within the container as it fires all the time */ useEffect( () => { - window.addEventListener( 'mousedown', handleMouseDown ); - window.addEventListener( 'mouseup', handleMouseUp ); + const container = containerRef.current; + + if ( container ) { + container.addEventListener( 'mousedown', handleMouseDown ); + container.addEventListener( 'mouseup', handleMouseUp ); + } return () => { - window.removeEventListener( 'mousedown', handleMouseDown ); - window.removeEventListener( 'mouseup', handleMouseUp ); + if ( container ) { + container.removeEventListener( 'mousedown', handleMouseDown ); + container.removeEventListener( 'mouseup', handleMouseUp ); + } }; }, [] ); @@ -97,7 +101,10 @@ const Color = ( { selector } ) => { background={ varToHex( backgroundColour, themePalette ) } foreground={ varToHex( textColour, themePalette ) } /> -
+
{ colorPalettes }
From d7e20adea861b6fa88482f603eaaf61eaa3de0d7 Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Mon, 23 Sep 2024 17:26:23 +0100 Subject: [PATCH 6/9] feat: adding some container styles --- .../components/bb-contrast-checker.scss | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/editor/styles/components/bb-contrast-checker.scss b/src/editor/styles/components/bb-contrast-checker.scss index 6a1de6df..e90c7e0a 100644 --- a/src/editor/styles/components/bb-contrast-checker.scss +++ b/src/editor/styles/components/bb-contrast-checker.scss @@ -1,3 +1,8 @@ +.themer--styles__item:has(.contrast-checker) { + container-type: inline-size; + container-name: contrast-checker-outer; +} + .contrast-checker { align-items: center; border: 1px solid green; @@ -55,3 +60,23 @@ background-color: red; color: #000; } + + +@container contrast-checker-outer (max-width: 350px) { + + .contrast-checker { + align-items: center; + flex-wrap: wrap; + justify-content: space-between; + } + + .contrast-checker-title { + text-align: center; + width: 100%; + } + + .contrast-checker-ratio { + margin-inline-start: 0; + } + +} From b761f260d3ec50db73196d2bef740e5b08b93fcf Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Tue, 24 Sep 2024 09:25:09 +0100 Subject: [PATCH 7/9] feat: restricting event listeners to container --- src/editor/components/StylesColor.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/editor/components/StylesColor.js b/src/editor/components/StylesColor.js index 2f5f1d27..7754b892 100644 --- a/src/editor/components/StylesColor.js +++ b/src/editor/components/StylesColor.js @@ -1,6 +1,6 @@ import { set } from 'lodash'; import { __ } from '@wordpress/i18n'; -import { useContext, useState, useEffect } from '@wordpress/element'; +import { useContext, useState, useEffect, useRef } from '@wordpress/element'; import { ColorPalette } from '@wordpress/components'; import { ContrastChecker } from '@wordpress/block-editor'; @@ -17,6 +17,7 @@ import Gradient from './StylesGradient'; * @param {string} props.selector Property target selector */ const Color = ( { selector } ) => { + const containerRef = useRef( null ); const { userConfig, themeConfig } = useContext( EditorContext ); const { setUserConfig } = useContext( StylesContext ); const colorStyles = getThemeOption( selector, themeConfig ) || {}; @@ -64,12 +65,18 @@ const Color = ( { selector } ) => { * Add and remove the mouse event listeners */ useEffect( () => { - window.addEventListener( 'mousedown', handleMouseDown ); - window.addEventListener( 'mouseup', handleMouseUp ); + const container = containerRef.current; + + if ( container ) { + container.addEventListener( 'mousedown', handleMouseDown ); + container.addEventListener( 'mouseup', handleMouseUp ); + } return () => { - window.removeEventListener( 'mousedown', handleMouseDown ); - window.removeEventListener( 'mouseup', handleMouseUp ); + if ( container ) { + container.removeEventListener( 'mousedown', handleMouseDown ); + container.removeEventListener( 'mouseup', handleMouseUp ); + } }; }, [] ); @@ -77,8 +84,8 @@ const Color = ( { selector } ) => {
{ key } onChange( value, key ) } value={ varToHex( colorStyles[ key ], themePalette ) } /> @@ -94,7 +101,10 @@ const Color = ( { selector } ) => { textColor={ varToHex( textColour, themePalette ) } backgroundColor={ varToHex( backgroundColour, themePalette ) } /> -
+
{ colorPalettes }
From 45051ed4e7b3d3398aa2997432ed8a987dba601f Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Tue, 24 Sep 2024 15:41:46 +0100 Subject: [PATCH 8/9] feat: switching event listener to debounce --- src/editor/components/StylesColor.js | 69 +++++++++++----------------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/src/editor/components/StylesColor.js b/src/editor/components/StylesColor.js index 7754b892..85f63683 100644 --- a/src/editor/components/StylesColor.js +++ b/src/editor/components/StylesColor.js @@ -1,6 +1,6 @@ -import { set } from 'lodash'; +import { set, debounce } from 'lodash'; import { __ } from '@wordpress/i18n'; -import { useContext, useState, useEffect, useRef } from '@wordpress/element'; +import { useContext, useState, useEffect } from '@wordpress/element'; import { ColorPalette } from '@wordpress/components'; import { ContrastChecker } from '@wordpress/block-editor'; @@ -17,7 +17,6 @@ import Gradient from './StylesGradient'; * @param {string} props.selector Property target selector */ const Color = ( { selector } ) => { - const containerRef = useRef( null ); const { userConfig, themeConfig } = useContext( EditorContext ); const { setUserConfig } = useContext( StylesContext ); const colorStyles = getThemeOption( selector, themeConfig ) || {}; @@ -26,6 +25,20 @@ const Color = ( { selector } ) => { themeConfig ); + /** + * Define colour variables, used to avoid jumping colour picker when ContrastChecker display toggles + */ + const [ textColour, setTextColour ] = useState( colorStyles.text ); + const [ backgroundColour, setBackgroundColour ] = useState( + colorStyles.background + ); + + /** + * Function to handle the colour palette changes + * + * @param {string} newValue The value of the setting + * @param {string} key The key of the setting + */ const onChange = ( newValue, key ) => { let config = structuredClone( userConfig ); config = set( @@ -37,48 +50,23 @@ const Color = ( { selector } ) => { }; /** - * Define mouse state and related functions - */ - const [ isMouseDown, setIsMouseDown ] = useState( false ); - const handleMouseDown = () => setIsMouseDown( true ); - const handleMouseUp = () => setIsMouseDown( false ); - - /** - * Define colour variables, used to avoid jumping colour picker when ContrastChecker display toggles - */ - const [ textColour, setTextColour ] = useState( colorStyles.text ); - const [ backgroundColour, setBackgroundColour ] = useState( - colorStyles.background - ); - - /** - * When the values are changed, ensure the mouse buttons have been released before updating + * Function to debounce the assignment of colours + * This approach addresses an interaction issue with custom colour palettes */ - useEffect( () => { - if ( ! isMouseDown ) { - setTextColour( colorStyles.text ); - setBackgroundColour( colorStyles.background ); - } - }, [ isMouseDown, colorStyles.text, colorStyles.background ] ); + const debouncedUpdateColors = debounce( () => { + setTextColour( colorStyles.text ); + setBackgroundColour( colorStyles.background ); + }, 150 ); /** - * Add and remove the mouse event listeners + * useEffect when colours are updated */ useEffect( () => { - const container = containerRef.current; - - if ( container ) { - container.addEventListener( 'mousedown', handleMouseDown ); - container.addEventListener( 'mouseup', handleMouseUp ); - } - + debouncedUpdateColors(); return () => { - if ( container ) { - container.removeEventListener( 'mousedown', handleMouseDown ); - container.removeEventListener( 'mouseup', handleMouseUp ); - } + debouncedUpdateColors.cancel(); }; - }, [] ); + }, [ colorStyles.text, colorStyles.background, debouncedUpdateColors ] ); const colorPalettes = [ 'background', 'text' ].map( ( key ) => (
@@ -101,10 +89,7 @@ const Color = ( { selector } ) => { textColor={ varToHex( textColour, themePalette ) } backgroundColor={ varToHex( backgroundColour, themePalette ) } /> -
+
{ colorPalettes }
From 63f35ee72e381120b994d727f9cfc56a7b3d52b8 Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Tue, 24 Sep 2024 15:47:38 +0100 Subject: [PATCH 9/9] feat: reducing complexity of approach --- src/editor/components/StylesColor.js | 40 ++++++---------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/src/editor/components/StylesColor.js b/src/editor/components/StylesColor.js index 85f63683..d802a583 100644 --- a/src/editor/components/StylesColor.js +++ b/src/editor/components/StylesColor.js @@ -1,6 +1,6 @@ import { set, debounce } from 'lodash'; import { __ } from '@wordpress/i18n'; -import { useContext, useState, useEffect } from '@wordpress/element'; +import { useContext } from '@wordpress/element'; import { ColorPalette } from '@wordpress/components'; import { ContrastChecker } from '@wordpress/block-editor'; @@ -25,21 +25,13 @@ const Color = ( { selector } ) => { themeConfig ); - /** - * Define colour variables, used to avoid jumping colour picker when ContrastChecker display toggles - */ - const [ textColour, setTextColour ] = useState( colorStyles.text ); - const [ backgroundColour, setBackgroundColour ] = useState( - colorStyles.background - ); - /** * Function to handle the colour palette changes * * @param {string} newValue The value of the setting * @param {string} key The key of the setting */ - const onChange = ( newValue, key ) => { + const onChange = debounce( ( newValue, key ) => { let config = structuredClone( userConfig ); config = set( config, @@ -47,26 +39,7 @@ const Color = ( { selector } ) => { hexToVar( newValue, themePalette ) ?? '' ); setUserConfig( config ); - }; - - /** - * Function to debounce the assignment of colours - * This approach addresses an interaction issue with custom colour palettes - */ - const debouncedUpdateColors = debounce( () => { - setTextColour( colorStyles.text ); - setBackgroundColour( colorStyles.background ); - }, 150 ); - - /** - * useEffect when colours are updated - */ - useEffect( () => { - debouncedUpdateColors(); - return () => { - debouncedUpdateColors.cancel(); - }; - }, [ colorStyles.text, colorStyles.background, debouncedUpdateColors ] ); + }, 50 ); const colorPalettes = [ 'background', 'text' ].map( ( key ) => (
@@ -86,8 +59,11 @@ const Color = ( { selector } ) => { { __( 'Color', 'themer' ) }
{ colorPalettes }