-
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
ea849e3
commit 1bb5f6f
Showing
14 changed files
with
790 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,121 @@ | ||
// Function provided by ChatGPT | ||
// Calculate the contrast ratio of a pair of luminance values | ||
const getContrastRatio = (luminance1: number, luminance2: number): number => { | ||
// Ensure L1 is the lighter color and L2 is the darker color | ||
const [L1, L2] = | ||
luminance1 > luminance2 | ||
? [luminance1, luminance2] | ||
: [luminance2, luminance1]; | ||
return (L1 + 0.05) / (L2 + 0.05); | ||
}; | ||
|
||
// Function provided by ChatGPT | ||
// Calculates the foreground color based on the background color | ||
const calculateLuminance = (hexColor: string): number => { | ||
// Remove the hash | ||
const hex = hexColor.replace("#", ""); | ||
// Split the hex into RGB components | ||
const r = parseInt(hex.slice(0, 2), 16); | ||
const g = parseInt(hex.slice(2, 4), 16); | ||
const b = parseInt(hex.slice(4, 6), 16); | ||
|
||
// Function to normalize RGB values (0-255) to floating integer values (0-1) | ||
const normalize = (value: number): number => { | ||
// Divide by 255 and apply gamma correction | ||
const v = value / 255; | ||
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; | ||
}; | ||
|
||
// Normalize RGB values | ||
const normalizedR = normalize(r); | ||
const normalizedG = normalize(g); | ||
const normalizedB = normalize(b); | ||
|
||
// Calculate luminance | ||
const luminance = | ||
0.2126 * normalizedR + 0.7152 * normalizedG + 0.0722 * normalizedB; | ||
|
||
// Return black or white based on luminance | ||
return luminance; | ||
}; | ||
|
||
// Function provided by ChatGPT | ||
// Get the accessible foreground color based on the background color | ||
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)"; | ||
} | ||
|
||
// Helper function to return the desired colours for the portrait | ||
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 only dark background is set, set dark background colours | ||
if (darkBackground && !backgroundColour && !foregroundColor) { | ||
bgColor = "var(--colorsUtilityYin090)"; | ||
fgColor = "var(--colorsUtilityReadOnly600)"; | ||
} | ||
|
||
// If custom background colour is set, use it. Calculate foreground colour based on it. | ||
if (backgroundColour) { | ||
bgColor = backgroundColour; | ||
fgColor = getAccessibleForegroundColor(backgroundColour, largeText, strict); | ||
} | ||
|
||
// If custom foreground colour is set, use it | ||
if (foregroundColor) { | ||
fgColor = foregroundColor; | ||
} | ||
|
||
// Return relevant colours | ||
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
Oops, something went wrong.