-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(portrait): allows customers to provide a custom background colou…
…r for the Portrait component
- Loading branch information
1 parent
0e9595d
commit fb61bcd
Showing
14 changed files
with
768 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import getColoursForPortrait from "./utils"; | ||
|
||
test("returns the default string if no arguments are passed", () => { | ||
const result = getColoursForPortrait(undefined); | ||
expect(result).toBe( | ||
`background-color: var(--colorsUtilityReadOnly400); color: var(--colorsUtilityYin090);`, | ||
); | ||
}); | ||
|
||
test("returns a fixed string if only the `backgroundColor` argument is set to true", () => { | ||
const result = getColoursForPortrait("#FF0000"); | ||
expect(result).toBe( | ||
"background-color: #FF0000; color: var(--colorsUtilityYin090);", | ||
); | ||
}); | ||
|
||
test("returns a fixed string if the `darkBackground` argument is set to true", () => { | ||
const result = getColoursForPortrait(undefined, true); | ||
expect(result).toBe( | ||
"background-color: var(--colorsUtilityYin090); color: var(--colorsUtilityReadOnly600);", | ||
); | ||
}); | ||
|
||
test("returns a fixed string if neither `darkBackground` nor `backgroundColor` argument are defined", () => { | ||
const result = getColoursForPortrait(undefined, false); | ||
expect(result).toBe( | ||
`background-color: var(--colorsUtilityReadOnly400); color: var(--colorsUtilityYin090);`, | ||
); | ||
}); | ||
|
||
test("returns a string with the custom background color if the `backgroundColor` argument is defined", () => { | ||
const result = getColoursForPortrait("#FF0000", false); | ||
expect(result).toBe( | ||
"background-color: #FF0000; color: var(--colorsUtilityYin090);", | ||
); | ||
}); | ||
|
||
test("returns a string with the custom background color if only the `backgroundColor` argument is defined and all others are false", () => { | ||
const result = getColoursForPortrait("#FF0000", false, false, false); | ||
expect(result).toBe( | ||
"background-color: #FF0000; color: var(--colorsUtilityYin090);", | ||
); | ||
}); | ||
|
||
test("returns a string with the custom background color if the `backgroundColor` argument is defined and `largeText` argument is true", () => { | ||
const result = getColoursForPortrait("#FF0000", false, true); | ||
expect(result).toBe( | ||
"background-color: #FF0000; color: var(--colorsUtilityYin090);", | ||
); | ||
}); | ||
|
||
test("returns a string with the custom background color if the `backgroundColor` and `largeText` arguments are defined and `strict` argument is false", () => { | ||
const result = getColoursForPortrait("#FF0000", false, true, false); | ||
expect(result).toBe( | ||
"background-color: #FF0000; color: var(--colorsUtilityYin090);", | ||
); | ||
}); | ||
|
||
test("returns a string with the custom background color if the `backgroundColor` and `largeText` arguments are defined and `strict` argument is true", () => { | ||
const result = getColoursForPortrait("#FF0000", false, true, true); | ||
expect(result).toBe( | ||
"background-color: #FF0000; color: var(--colorsUtilityYin090);", | ||
); | ||
}); | ||
|
||
describe("Contrast ratio tests", () => { | ||
it("uses a white foreground colour if the white contrast ratio meets the minimum contrast threshold and is higher than the black contrast ratio", () => { | ||
const result = getColoursForPortrait("#0000FF"); | ||
expect(result).toBe("background-color: #0000FF; color: #FFFFFF;"); | ||
}); | ||
|
||
it("uses a black foreground colour if the black contrast ratio meets the minimum contrast threshold", () => { | ||
const result = getColoursForPortrait("#FFFF00"); | ||
expect(result).toBe( | ||
"background-color: #FFFF00; color: var(--colorsUtilityYin090);", | ||
); | ||
}); | ||
}); | ||
|
||
test("returns a string with the custom background color and light text if the `backgroundColor` argument is set to a colour with poor contrast ratios (higher white contrast)", () => { | ||
const result = getColoursForPortrait("#0000FF"); | ||
expect(result).toBe("background-color: #0000FF; color: #FFFFFF;"); | ||
}); | ||
|
||
test("returns a string with the custom colors if the `backgroundColor` and `foregroundColor` arguments are provided and all others are false", () => { | ||
const result = getColoursForPortrait( | ||
"#FF0000", | ||
false, | ||
false, | ||
false, | ||
"#00FF00", | ||
); | ||
expect(result).toBe("background-color: #FF0000; color: #00FF00;"); | ||
}); | ||
|
||
test("returns a string with the custom foreground color if `foregroundColor` argument is present but `backgroundColor` is omitted", () => { | ||
const result = getColoursForPortrait( | ||
undefined, | ||
false, | ||
false, | ||
false, | ||
"#00FF00", | ||
); | ||
expect(result).toBe( | ||
`background-color: var(--colorsUtilityReadOnly400); color: #00FF00;`, | ||
); | ||
}); | ||
|
||
test("returns a string with the custom colors if the `darkBackground`, `foregroundColor` and `backgroundColor` props are set", () => { | ||
const result = getColoursForPortrait( | ||
"#FF0000", | ||
true, | ||
false, | ||
false, | ||
"#00FF00", | ||
); | ||
expect(result).toBe("background-color: #FF0000; color: #00FF00;"); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
const getContrastRatio = (luminance1: number, luminance2: number): number => { | ||
const [L1, L2] = | ||
luminance1 > luminance2 | ||
? [luminance1, luminance2] | ||
: [luminance2, luminance1]; | ||
return (L1 + 0.05) / (L2 + 0.05); | ||
}; | ||
|
||
const calculateLuminance = (hexColor: string): number => { | ||
const hex = hexColor.replace("#", ""); | ||
const r = parseInt(hex.slice(0, 2), 16); | ||
const g = parseInt(hex.slice(2, 4), 16); | ||
const b = parseInt(hex.slice(4, 6), 16); | ||
|
||
const normalize = (value: number): number => { | ||
const v = value / 255; | ||
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; | ||
}; | ||
|
||
const normalizedR = normalize(r); | ||
const normalizedG = normalize(g); | ||
const normalizedB = normalize(b); | ||
|
||
const luminance = | ||
0.2126 * normalizedR + 0.7152 * normalizedG + 0.0722 * normalizedB; | ||
|
||
return luminance; | ||
}; | ||
|
||
function getAccessibleForegroundColor( | ||
backgroundColor: string, | ||
largeText: boolean, | ||
strict: boolean, | ||
): string { | ||
const bgLuminance = calculateLuminance(backgroundColor); | ||
const whiteLuminance = calculateLuminance("#FFFFFF"); | ||
const blackLuminance = calculateLuminance("#000000"); | ||
|
||
const whiteContrast = getContrastRatio(bgLuminance, whiteLuminance); | ||
const blackContrast = getContrastRatio(bgLuminance, blackLuminance); | ||
|
||
const strictThreshold = largeText ? 4.5 : 7.0; | ||
const nonStrictThreshold = largeText ? 3.0 : 4.5; | ||
const minContrast = strict ? strictThreshold : nonStrictThreshold; | ||
|
||
/* istanbul ignore else */ | ||
if (whiteContrast >= minContrast && whiteContrast > blackContrast) { | ||
return "#FFFFFF"; | ||
} | ||
|
||
/* istanbul ignore else */ | ||
if (blackContrast >= minContrast) { | ||
return "var(--colorsUtilityYin090)"; | ||
} | ||
|
||
// If no color meets the contrast ratio, return the color with the highest contrast | ||
// In theory this is possible only if the background color is a shade of grey, but | ||
// this is a fallback mechanism as finding a colour which fails both contrast ratios | ||
// is highly unlikely. | ||
/* istanbul ignore next */ | ||
return whiteContrast > blackContrast | ||
? "#FFFFFF" | ||
: "var(--colorsUtilityYin090)"; | ||
} | ||
|
||
const getColoursForPortrait = ( | ||
// The custom background colour, if any | ||
backgroundColour: string | undefined, | ||
// Whether the portrait is on a dark background | ||
darkBackground = false, | ||
// Whether the text is large | ||
largeText = false, | ||
/** | ||
* Whether to use strict contrast (i.e., WCAG AAA). If this is false, it uses WCAG AA contrast | ||
* ratios (4.5:1 for normal text, 3:1 for large text). If true, it uses 7:1 for normal text and | ||
* 4.5:1 for large text. | ||
*/ | ||
strict = false, | ||
// The custom foreground colour, if any | ||
foregroundColor: string | undefined = undefined, | ||
): string => { | ||
let fgColor = "var(--colorsUtilityYin090)"; | ||
let bgColor = "var(--colorsUtilityReadOnly400)"; | ||
|
||
if (darkBackground && !backgroundColour && !foregroundColor) { | ||
bgColor = "var(--colorsUtilityYin090)"; | ||
fgColor = "var(--colorsUtilityReadOnly600)"; | ||
} | ||
|
||
if (backgroundColour) { | ||
bgColor = backgroundColour; | ||
fgColor = getAccessibleForegroundColor(backgroundColour, largeText, strict); | ||
} | ||
|
||
if (foregroundColor) { | ||
fgColor = foregroundColor; | ||
} | ||
|
||
return `background-color: ${bgColor}; color: ${fgColor};`; | ||
}; | ||
|
||
export default getColoursForPortrait; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.