Skip to content

Commit

Permalink
Add AA/AAA filter to Color Contrast matrix
Browse files Browse the repository at this point in the history
  • Loading branch information
moroshko committed Jan 19, 2020
1 parent 2a14973 commit b1b1500
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 50 deletions.
240 changes: 190 additions & 50 deletions website/src/pages/colors/accessibility.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,182 @@
import React, { useState } from "react";
import React, { useState, useMemo } from "react";
import PropTypes from "prop-types";
import { rgba } from "polished";
import { COLORS } from "basis/components/Text";
import { BACKGROUNDS } from "basis/components/Container";
import { designTokens, useTheme, Grid, Text, Select, Input } from "basis";
import { INTENTS, WEIGHTS } from "basis/components/Text";
import { colorContrast } from "../../utils/color";
import { INTENTS, WEIGHTS, allowedWeights } from "basis/components/Text";
import { colorContrast, accessibleContrast } from "../../utils/color";

const TEXT_COLOR_COLUMN_WIDTH = designTokens.sizes[18];
const CELL_WIDTH = designTokens.sizes[18];
const CELL_HEIGHT = designTokens.sizes[14];

const showOptions = [
{
label: "All",
value: "All"
},
{
label: "Pass AA",
value: "Pass AA" // We parse the value to extract AA or AAA.
},
{
label: "Pass AAA",
value: "Pass AAA"
},
{
label: "Fail AA",
value: "Fail AA"
},
{
label: "Fail AAA",
value: "Fail AAA"
}
];
const intentOptions = INTENTS.map(intent => ({
label: intent,
value: intent
}));
const SIZES = ["1", "2", "3", "4", "5", "6"];
const sizeOptions = SIZES.map(size => ({
label: size,
value: size
}));
const weightOptions = WEIGHTS.map(weight => ({
label: weight,
value: weight
}));
const notApplicableOptions = [
{
label: "Not applicable",
value: ""
}
];

function isHeading(intent) {
return ["h1", "h2", "h3", "h4", "h5", "h6"].includes(intent);
}

function isBoldAllowedForIntent(intent) {
for (let i = 0; i < allowedWeights.length; i++) {
if (allowedWeights[i].intent.includes(intent)) {
return allowedWeights[i].allowedWeights.length > 1;
}
}

return false;
}

function MatrixCell({
color,
backgroundColor,
intent,
weight,
size,
text,
minContrast,
shouldPass
}) {
const theme = useTheme();
const contrast = useMemo(
() => colorContrast(theme.getColor(color), theme.getColor(backgroundColor)),
[theme, color, backgroundColor]
);
const isVisible =
minContrast === null ||
(shouldPass && contrast >= minContrast) ||
(!shouldPass && contrast < minContrast);

return (
<div
css={{
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
width: CELL_WIDTH,
height: CELL_HEIGHT,
marginLeft: designTokens.space[1],
border:
backgroundColor === "white"
? `1px solid ${designTokens.colors.grey.t05}`
: null,
boxSizing: "border-box",
backgroundColor: theme.getColor(backgroundColor),
overflow: "hidden",
position: "relative",
opacity: isVisible ? 1 : 0,
transition: "opacity .2s"
}}
aria-hidden={isVisible ? null : "true"}
>
<Text intent={intent} weight={weight} size={size} color={color}>
{text}
</Text>
<div
css={{
position: "absolute",
right: 0,
borderTopLeftRadius: "4px",
bottom: 0,
width: designTokens.sizes[9],
padding: `0 ${designTokens.space[1]}`,
backgroundColor: rgba(designTokens.colors.black, 0.6)
}}
>
<Text intent="body2" color="white" align="center">
{contrast.toFixed(2)}
</Text>
</div>
</div>
);
}

MatrixCell.propTypes = {
color: PropTypes.oneOf(COLORS).isRequired,
backgroundColor: PropTypes.oneOf(BACKGROUNDS).isRequired,
intent: PropTypes.oneOf(INTENTS).isRequired,
weight: PropTypes.oneOf(WEIGHTS).isRequired,
size: PropTypes.oneOf(SIZES),
text: PropTypes.string.isRequired,
minContrast: PropTypes.number,
shouldPass: PropTypes.bool
};

function AccessibilityPage() {
const theme = useTheme();
const [show, setShow] = useState({
value: "Pass AA"
});
const [intent, setIntent] = useState({
value: "body1"
});
const [size, setSize] = useState({
value: ""
});
const [weight, setWeight] = useState({
value: "regular"
});
const [text, setText] = useState({
value: "Text"
});
const isIntentHeading = isHeading(intent.value);
const isBoldAllowed = isBoldAllowedForIntent(intent.value);
const { fontSize, fontWeight } = {
...theme[`text.${intent.value}`],
...(weight.value === "bold" && theme[`text.${intent.value}.bold`]),
...(isIntentHeading && theme[`text.size${size.value}`])
};
const showParts = show.value.split(" ");
const shouldPass = showParts[0] === "All" ? null : showParts[0] === "Pass";
const accessibilityLevel = showParts[1] || null;
const minContrast = accessibilityLevel
? accessibleContrast(
accessibilityLevel,
parseInt(fontSize, 10),
fontWeight > 400
)
: null;

return (
<div css={{ display: "flex", height: "100%" }}>
Expand All @@ -39,6 +186,7 @@ function AccessibilityPage() {
</Text>
<div
css={{
marginBottom: designTokens.space[6],
padding: `0 ${designTokens.space[6]}`,
width: "min-content" // Otherwise, right padding is not visible when there is an overflow.
}}
Expand Down Expand Up @@ -86,52 +234,17 @@ function AccessibilityPage() {
</Text>
</div>
{BACKGROUNDS.map(backgroundColor => (
<div
css={{
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
width: CELL_WIDTH,
height: CELL_HEIGHT,
marginLeft: designTokens.space[1],
border:
backgroundColor === "white"
? `1px solid ${designTokens.colors.grey.t05}`
: null,
boxSizing: "border-box",
backgroundColor: theme.getColor(backgroundColor),
overflow: "hidden",
position: "relative"
}}
<MatrixCell
color={color}
backgroundColor={backgroundColor}
intent={intent.value}
weight={weight.value}
size={size.value || undefined}
text={text.value}
minContrast={minContrast}
shouldPass={shouldPass}
key={backgroundColor}
>
<Text
intent={intent.value}
weight={weight.value}
color={color}
>
{text.value}
</Text>
<div
css={{
position: "absolute",
right: 0,
borderTopLeftRadius: "4px",
bottom: 0,
width: designTokens.sizes[9],
padding: `0 ${designTokens.space[1]}`,
backgroundColor: rgba(designTokens.colors.black, 0.6)
}}
>
<Text intent="body2" color="white" align="center">
{colorContrast(
theme.getColor(color),
theme.getColor(backgroundColor)
).toFixed(2)}
</Text>
</div>
</div>
/>
))}
</div>
))}
Expand All @@ -148,17 +261,44 @@ function AccessibilityPage() {
}}
>
<Grid rowsGutter="7">
<Select
label="Show"
options={showOptions}
placeholder={null}
helpText={
minContrast
? `${shouldPass ? "Min" : "Max"} contrast: ${minContrast}`
: "Filter for AA or AAA here"
}
data={show}
onChange={setShow}
/>
<Select
label="Intent"
options={intentOptions}
placeholder={null}
data={intent}
onChange={setIntent}
onChange={data => {
setIntent(data);
setSize({
...size,
value: isHeading(data.value) ? data.value[1] : ""
});
}}
/>
<Select
label="Size"
placeholder={null}
options={isIntentHeading ? sizeOptions : notApplicableOptions}
isDisabled={!isIntentHeading}
data={size}
onChange={setSize}
/>
<Select
label="Weight"
options={weightOptions}
placeholder={null}
options={isBoldAllowed ? weightOptions : notApplicableOptions}
isDisabled={!isBoldAllowed}
data={weight}
onChange={setWeight}
/>
Expand Down
3 changes: 3 additions & 0 deletions website/src/themes/website/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ export default {
lineHeight: tokens.lineHeights[1],
color: tokens.colors.grey.t65,
marginBottom: tokens.space[1]
},
"field.helpText": {
padding: `${tokens.space[1]} ${tokens.space[1]} 0`
}
};
21 changes: 21 additions & 0 deletions website/src/utils/color.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,24 @@ export function colorContrast(str1, str2) {

return (L1 + 0.05) / (L2 + 0.05);
}

// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#larger-scaledef
function isLargeScale(fontSize, isBold) {
const points = fontSize / 1.333; // 1pt = 1.333px according to http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html#visual-audio-contrast-contrast-73-head

return isBold ? points >= 14 : points >= 18;
}

export function accessibleContrast(accessibilityLevel, fontSize, isBold) {
switch (accessibilityLevel) {
// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast
case "AA":
return isLargeScale(fontSize, isBold) ? 3 : 4.5;

// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast7
case "AAA":
return isLargeScale(fontSize, isBold) ? 4.5 : 7;
}

return null;
}

1 comment on commit b1b1500

@vercel
Copy link

@vercel vercel bot commented on b1b1500 Jan 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.