Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contrast Checker: check link color #38100

Merged
merged 11 commits into from
Feb 7, 2022
194 changes: 104 additions & 90 deletions packages/block-editor/src/components/contrast-checker/index.js
Original file line number Diff line number Diff line change
@@ -1,125 +1,139 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { speak } from '@wordpress/a11y';
import { Notice } from '@wordpress/components';

aaronrobertshaw marked this conversation as resolved.
Show resolved Hide resolved
/**
* External dependencies
*/
import { colord, extend } from 'colord';
import namesPlugin from 'colord/plugins/names';
import a11yPlugin from 'colord/plugins/a11y';

/**
* WordPress dependencies
*/
import { speak } from '@wordpress/a11y';
import { __ } from '@wordpress/i18n';
import { Notice } from '@wordpress/components';
import { useEffect } from '@wordpress/element';

extend( [ namesPlugin, a11yPlugin ] );

function ContrastCheckerMessage( {
colordBackgroundColor,
colordTextColor,
backgroundColor,
textColor,
shouldShowTransparencyWarning,
} ) {
let msg = '';
if ( shouldShowTransparencyWarning ) {
msg = __( 'Transparent text may be hard for people to read.' );
} else {
msg =
colordBackgroundColor.brightness() < colordTextColor.brightness()
? __(
'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.'
);
}

// Note: The `Notice` component can speak messages via its `spokenMessage`
// prop, but the contrast checker requires granular control over when the
// announcements are made. Notably, the message will be re-announced if a
// new color combination is selected and the contrast is still insufficient.
useEffect( () => {
const speakMsg = shouldShowTransparencyWarning
? __( 'Transparent text may be hard for people to read.' )
: __( 'This color combination may be hard for people to read.' );
speak( speakMsg );
}, [ backgroundColor, textColor ] );

return (
<div className="block-editor-contrast-checker">
<Notice
spokenMessage={ null }
status="warning"
isDismissible={ false }
>
{ msg }
</Notice>
</div>
);
}

function ContrastChecker( {
backgroundColor,
fallbackBackgroundColor,
fallbackTextColor,
fallbackLinkColor,
fontSize, // font size value in pixels
isLargeText,
textColor,
linkColor,
enableAlphaChecker = false,
} ) {
if (
! ( backgroundColor || fallbackBackgroundColor ) ||
! ( textColor || fallbackTextColor )
) {
const currentBackgroundColor = backgroundColor || fallbackBackgroundColor;

// Must have a background color.
if ( ! currentBackgroundColor ) {
return null;
}
const colordBackgroundColor = colord(
backgroundColor || fallbackBackgroundColor
);
const colordTextColor = colord( textColor || fallbackTextColor );
const textColorHasTransparency = colordTextColor.alpha() < 1;

const currentTextColor = textColor || fallbackTextColor;
const currentLinkColor = linkColor || fallbackLinkColor;

// Must have at least one text color.
if ( ! currentTextColor && ! currentLinkColor ) {
return null;
}

const textColors = [
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
{
color: currentTextColor,
description: __( 'text color' ),
},
{
color: currentLinkColor,
description: __( 'link color' ),
},
];
const colordBackgroundColor = colord( currentBackgroundColor );
const backgroundColorHasTransparency = colordBackgroundColor.alpha() < 1;
const hasTransparency =
textColorHasTransparency || backgroundColorHasTransparency;
const isReadable = colordTextColor.isReadable( colordBackgroundColor, {
const backgroundColorBrightness = colordBackgroundColor.brightness();
const isReadableOptions = {
level: 'AA',
size:
isLargeText || ( isLargeText !== false && fontSize >= 24 )
? 'large'
: 'small',
} );
};

// Don't show the message if the text is readable AND there's no transparency.
// This is the default.
if ( isReadable && ! hasTransparency ) {
return null;
}
let message = '';
let speakMessage = '';
for ( const item of textColors ) {
// If there is no color, go no further.
if ( ! item.color ) {
continue;
}
const colordTextColor = colord( item.color );
const isColordTextReadable = colordTextColor.isReadable(
colordBackgroundColor,
isReadableOptions
);
const textHasTransparency = colordTextColor.alpha() < 1;

if ( hasTransparency ) {
if (
// If there's transparency, don't show the message if the alpha checker is disabled.
! enableAlphaChecker ||
// If the alpha checker is enabled, we only show the warning if the text has transparency.
( isReadable && ! textColorHasTransparency )
) {
return null;
// If the contrast is not readable.
if ( ! isColordTextReadable ) {
// Don't show the message if the background or text is transparent.
if ( backgroundColorHasTransparency || textHasTransparency ) {
continue;
}
message =
backgroundColorBrightness < colordTextColor.brightness()
? sprintf(
// translators: %s is a type of text color, e.g., "text color" or "link color"
__(
'This color combination may be hard for people to read. Try using a darker background color and/or a brighter %s.'
),
item.description
)
: sprintf(
// translators: %s is a type of text color, e.g., "text color" or "link color"
__(
'This color combination may be hard for people to read. Try using a brighter background color and/or a darker %s.'
),
item.description
);
speakMessage = __(
'This color combination may be hard for people to read.'
);
// Break from the loop when we have a contrast warning.
// These messages take priority over the transparency warning.
break;
}

// If the text color is readable, but transparent, show the transparent warning.
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
if ( textHasTransparency && true === enableAlphaChecker ) {
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
message = __( 'Transparent text may be hard for people to read.' );
speakMessage = __(
'Transparent text may be hard for people to read.'
);
}
}

if ( ! message ) {
return null;
}

// Note: The `Notice` component can speak messages via its `spokenMessage`
// prop, but the contrast checker requires granular control over when the
// announcements are made. Notably, the message will be re-announced if a
// new color combination is selected and the contrast is still insufficient.
speak( speakMessage );

return (
<ContrastCheckerMessage
backgroundColor={ backgroundColor }
textColor={ textColor }
colordBackgroundColor={ colordBackgroundColor }
colordTextColor={ colordTextColor }
// Flag to warn about transparency only if the text is otherwise readable according to colord
// to ensure the readability warnings take precedence.
shouldShowTransparencyWarning={
isReadable && textColorHasTransparency
}
/>
<div className="block-editor-contrast-checker">
<Notice
spokenMessage={ null }
status="warning"
isDismissible={ false }
>
{ message }
</Notice>
</div>
);
}

Expand Down
Loading