chore: WDS color refinement (#31499)

Closes #31284

1. Decreased overall chroma everywhere except for actual `bgAccent`.
2. Couldn't reproduce the too-light heading text issue in Storybook,
`fg*` colors are all appropriately dark. Perhaps, a wrong token is
applied in the editor?

Before / After comparisons for warm and cold seeds:

![image](https://github.com/appsmithorg/appsmith/assets/80973/bdeebbbc-2547-4997-8f0f-24c9c501eb56)

![image](https://github.com/appsmithorg/appsmith/assets/80973/fae51cd8-7c90-434e-aa3c-040a8b88a07b)




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


## Summary by CodeRabbit

- **Style**
- Enhanced contrast in both dark and light mode themes through
adjustments in chroma and lightness values.
- Improved color accuracy in the design system themes for better
representation.
- Updated color values in test scenarios for more precise RGB
representation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
vadim 2024-03-06 09:51:43 +01:00 committed by GitHub
parent 08f8dd2c61
commit cb6e5818fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 98 additions and 69 deletions

View File

@ -145,10 +145,13 @@ export class DarkModeTheme implements ColorModeTheme {
color.oklch.c = 0;
}
if (!this.seedIsAchromatic) {
color.oklch.c = 0.064;
if (!this.seedIsAchromatic && this.seedIsCold) {
color.oklch.c = 0.029;
}
if (!this.seedIsAchromatic && !this.seedIsCold) {
color.oklch.c = 0.012;
}
return color;
}
@ -611,7 +614,7 @@ export class DarkModeTheme implements ColorModeTheme {
private get bgElevation2() {
const color = this.bgElevation1.clone();
color.oklch.l += 0.035;
color.oklch.l += 0.025;
return color;
}
@ -619,7 +622,7 @@ export class DarkModeTheme implements ColorModeTheme {
private get bgElevation3() {
const color = this.bgElevation2.clone();
color.oklch.l += 0.04;
color.oklch.l += 0.03;
return color;
}
@ -635,8 +638,8 @@ export class DarkModeTheme implements ColorModeTheme {
color.alpha = 0.35;
if (color.oklch.c > 0.08) {
color.oklch.c = 0.08;
if (color.oklch.c > 0.06) {
color.oklch.c = 0.06;
}
return color;
@ -672,7 +675,7 @@ export class DarkModeTheme implements ColorModeTheme {
// This ensures harmonious combination with main accents and neutrals.
const color = this.seedColor.clone();
color.oklch.l = 0.965;
color.oklch.l = 0.935;
// If seed color didn't have substantial amount of chroma make sure fg is achromatic.
if (this.seedIsAchromatic) {
@ -680,7 +683,7 @@ export class DarkModeTheme implements ColorModeTheme {
}
if (!this.seedIsAchromatic) {
color.oklch.c = 0.024;
color.oklch.c = 0.012;
}
return color;
@ -692,7 +695,7 @@ export class DarkModeTheme implements ColorModeTheme {
// For light content on dark background APCA contrast is negative. 60 is “The minimum level recommended for content text that is not body, column, or block text. In other words, text you want people to read.” Failure to reach this contrast level is most likely due to low lightness. Lightness and chroma are set to ones that reach the threshold universally regardless of hue.
if (this.bg.contrastAPCA(this.seedColor) >= -60) {
color.oklch.l = 0.79;
color.oklch.l = 0.82;
if (this.seedIsAchromatic) {
color.oklch.c = 0;
@ -800,8 +803,17 @@ export class DarkModeTheme implements ColorModeTheme {
}
// Light and dark derivatives of the seed
tint.oklch.l = 0.94;
shade.oklch.l = 0.27;
tint.oklch.l = 0.92;
shade.oklch.l = 0.23;
// Chroma limits for tint and shade
if (tint.oklch.c >= 0.025) {
tint.oklch.c = 0.025;
}
if (shade.oklch.c >= 0.04) {
shade.oklch.c = 0.04;
}
// Check which of them has better contrast with bgAccent
if (-this.bgAccent.contrastAPCA(tint) < this.bgAccent.contrastAPCA(shade)) {
@ -915,16 +927,16 @@ export class DarkModeTheme implements ColorModeTheme {
private get bdAccent() {
const color = this.seedColor.clone();
// For light content on dark background APCA contrast is negative. 15 is “The absolute minimum for any non-text that needs to be discernible and differentiable, but does not apply to semantic non-text such as icons”. In practice, thin borders are perceptually too subtle when using this as a threshould. 25 is used as the required minimum instead. Failure to reach this contrast level is most likely due to high lightness. Lightness and chroma are set to ones that reach the threshold universally regardless of hue.
// For light content on dark background APCA contrast is negative. 15 is “The absolute minimum for any non-text that needs to be discernible and differentiable, but does not apply to semantic non-text such as icons”. In practice, thin borders are perceptually too subtle when using this as a threshold. 25 is used as the required minimum instead. Failure to reach this contrast level is most likely due to high lightness. Lightness and chroma are set to ones that reach the threshold universally regardless of hue.
if (this.bg.contrastAPCA(this.seedColor) >= -25) {
if (this.seedIsAchromatic) {
color.oklch.l = 0.82;
color.oklch.l = 0.87;
color.oklch.c = 0;
}
if (!this.seedIsAchromatic) {
color.oklch.l = 0.75;
color.oklch.c = 0.15;
color.oklch.l = 0.84;
color.oklch.c = 0.13;
}
}
@ -947,7 +959,7 @@ export class DarkModeTheme implements ColorModeTheme {
// Desatured version of the seed for harmonious combination with backgrounds and accents.
const color = this.bdAccent.clone();
color.oklch.c = 0.035;
color.oklch.c = 0.012;
if (this.seedIsAchromatic) {
color.oklch.c = 0;
@ -957,6 +969,10 @@ export class DarkModeTheme implements ColorModeTheme {
color.oklch.l += 0.15;
}
if (this.bg.contrastAPCA(color) <= -25) {
color.oklch.l += 0.09;
}
return color;
}

View File

@ -141,16 +141,16 @@ export class LightModeTheme implements ColorModeTheme {
}
if (!this.seedIsVeryLight) {
color.oklch.l = 0.96;
color.oklch.l = 0.97;
}
// Cold colors can have a bit more chroma while staying perceptually neutral
if (this.seedIsCold) {
color.oklch.c = 0.009;
color.oklch.c = 0.002;
}
if (!this.seedIsCold) {
color.oklch.c = 0.007;
color.oklch.c = 0.001;
}
// If initial seed had non-substantial amount of chroma, make sure bg is achromatic.
@ -340,11 +340,11 @@ export class LightModeTheme implements ColorModeTheme {
}
if (this.seedIsCold && !this.seedIsAchromatic) {
color.oklch.c = 0.03;
color.oklch.c = 0.002;
}
if (!this.seedIsCold && !this.seedIsAchromatic) {
color.oklch.c = 0.015;
color.oklch.c = 0.001;
}
return color;
@ -413,8 +413,8 @@ export class LightModeTheme implements ColorModeTheme {
color.oklch.l = 0.93;
}
if (this.seedChroma > 0.01) {
color.oklch.c = 0.01;
if (this.seedChroma > 0.001) {
color.oklch.c = 0.001;
}
if (this.seedIsAchromatic) {
@ -668,6 +668,10 @@ export class LightModeTheme implements ColorModeTheme {
color.oklch.l = 0.3;
if (color.oklch.c >= 0.02) {
color.oklch.c = 0.02;
}
color.alpha = 0.1;
return color;
@ -711,7 +715,7 @@ export class LightModeTheme implements ColorModeTheme {
}
if (!this.seedIsAchromatic) {
color.oklch.c = 0.032;
color.oklch.c = 0.006;
}
return color;
@ -743,7 +747,7 @@ export class LightModeTheme implements ColorModeTheme {
// Minimal contrast that we set for fgAccent (60) is too low for a gray color
if (this.bg.contrastAPCA(this.fgAccent) < 75) {
color.oklch.l -= 0.1;
color.oklch.l -= 0.2;
}
if (this.seedIsAchromatic) {
@ -751,11 +755,11 @@ export class LightModeTheme implements ColorModeTheme {
}
if (this.seedIsCold && !this.seedIsAchromatic) {
color.oklch.c = 0.05;
color.oklch.c = 0.003;
}
if (!this.seedIsCold && !this.seedIsAchromatic) {
color.oklch.c = 0.015;
color.oklch.c = 0.001;
}
return color;
@ -834,6 +838,15 @@ export class LightModeTheme implements ColorModeTheme {
tint.oklch.l = 0.96;
shade.oklch.l = 0.23;
// Chroma limits for tint and shade
if (tint.oklch.c >= 0.015) {
tint.oklch.c = 0.015;
}
if (shade.oklch.c >= 0.03) {
shade.oklch.c = 0.03;
}
// Check which of them has better contrast with bgAccent
if (
-this.bgAccent.contrastAPCA(tint) >= this.bgAccent.contrastAPCA(shade)
@ -952,13 +965,13 @@ export class LightModeTheme implements ColorModeTheme {
// For dark content on light background APCA contrast is positive. 15 is “The absolute minimum for any non-text that needs to be discernible and differentiable, but does not apply to semantic non-text such as icons”. In practice, thin borders are perceptually too subtle when using this as a threshould. 25 is used as the required minimum instead. Failure to reach this contrast level is most likely due to high lightness. Lightness and chroma are set to ones that reach the threshold universally regardless of hue.
if (this.bg.contrastAPCA(this.seedColor) <= 25) {
if (this.seedIsAchromatic) {
color.oklch.l = 0.3;
color.oklch.l = 0.25;
color.oklch.c = 0;
}
if (!this.seedIsAchromatic) {
color.oklch.l = 0.55;
color.oklch.c = 0.25;
color.oklch.l = 0.45;
color.oklch.c = 0.15;
}
}
@ -981,7 +994,7 @@ export class LightModeTheme implements ColorModeTheme {
// Desatured version of the seed for harmonious combination with backgrounds and accents.
const color = this.bdAccent.clone();
color.oklch.c = 0.035;
color.oklch.c = 0.001;
if (this.seedIsAchromatic) {
color.oklch.c = 0;

View File

@ -8,7 +8,7 @@ describe("bg color", () => {
it("should return correct color when chroma > 0.04", () => {
const { bg } = new DarkModeTheme("oklch(0.92 0.05 110)").getColors();
expect(bg).toBe("rgb(5.3377% 4.7804% 0%)");
expect(bg).toBe("rgb(4.4523% 4.5607% 2.4575%)");
});
});
@ -539,13 +539,13 @@ describe("fg color", () => {
it("should return correct color when chroma < 0.04", () => {
const { fg } = new DarkModeTheme("oklch(0.45 0.03 60)").getColors();
expect(fg).toEqual("rgb(95.405% 95.405% 95.405%)");
expect(fg).toEqual("rgb(91.499% 91.499% 91.499%)");
});
it("should return correct color when chroma > 0.04", () => {
const { fg } = new DarkModeTheme("oklch(0.45 0.1 60)").getColors();
expect(fg).toEqual("rgb(100% 94.175% 89.331%)");
expect(fg).toEqual("rgb(94.05% 90.903% 88.505%)");
});
});
@ -553,13 +553,13 @@ describe("fgAccent color", () => {
it("should return correct color when chroma < 0.04", () => {
const { fgAccent } = new DarkModeTheme("oklch(0.45 0.03 60)").getColors();
expect(fgAccent).toEqual("rgb(73.075% 73.075% 73.075%)");
expect(fgAccent).toEqual("rgb(76.823% 76.823% 76.823%)");
});
it("should return correct color when chroma > 0.04", () => {
const { fgAccent } = new DarkModeTheme("oklch(0.45 0.1 60)").getColors();
expect(fgAccent).toEqual("rgb(97.93% 64.37% 34.977%)");
expect(fgAccent).toEqual("rgb(100% 68.135% 38.832%)");
});
});
@ -567,19 +567,19 @@ describe("fgNeutral color", () => {
it("should return correct color when chroma < 0.04", () => {
const { fgNeutral } = new DarkModeTheme("oklch(0.45 0.03 60)").getColors();
expect(fgNeutral).toEqual("rgb(78.08% 78.08% 78.08%)");
expect(fgNeutral).toEqual("rgb(81.873% 81.873% 81.873%)");
});
it("should return correct color when chroma > 0.04 and hue is between 120 and 300", () => {
const { fgNeutral } = new DarkModeTheme("oklch(0.45 0.1 150)").getColors();
expect(fgNeutral).toEqual("rgb(73.047% 80.455% 74.211%)");
expect(fgNeutral).toEqual("rgb(76.801% 84.271% 77.971%)");
});
it("should return correct color when chroma > 0.04 and hue is not between 120 and 300", () => {
const { fgNeutral } = new DarkModeTheme("oklch(0.45 0.1 110)").getColors();
expect(fgNeutral).toEqual("rgb(78.185% 78.394% 75.54%)");
expect(fgNeutral).toEqual("rgb(81.979% 82.19% 79.311%)");
});
});
@ -589,7 +589,7 @@ describe("fgNeutralSubtle color", () => {
"oklch(0.45 0.03 60)",
).getColors();
expect(fgNeutralSubtle).toEqual("rgb(59.646% 59.646% 59.646%)");
expect(fgNeutralSubtle).toEqual("rgb(63.258% 63.258% 63.258%)");
});
});
@ -657,13 +657,13 @@ describe("fgOnAccent color ", () => {
it("should return correct color when chroma < 0.04", () => {
const { fgOnAccent } = new DarkModeTheme("oklch(0.45 0.03 60)").getColors();
expect(fgOnAccent).toEqual("rgb(92.148% 92.148% 92.148%)");
expect(fgOnAccent).toEqual("rgb(89.558% 89.558% 89.558%)");
});
it("should return correct color when chroma > 0.04", () => {
const { fgOnAccent } = new DarkModeTheme("oklch(0.45 0.1 60)").getColors();
expect(fgOnAccent).toEqual("rgb(100% 89.256% 79.443%)");
expect(fgOnAccent).toEqual("rgb(94.787% 88.286% 83.298%)");
});
});
@ -720,26 +720,26 @@ describe("fgOnWarning color ", () => {
describe("bd color", () => {
it("should return correct color", () => {
const { bd } = new DarkModeTheme("oklch(0.45 0.5 60)").getColors();
expect(bd).toEqual("rgb(32.033% 27.005% 23.081%)");
expect(bd).toEqual("rgb(30.094% 27.562% 25.617%)");
});
});
describe("bdAccent color", () => {
it("should return correct color when chroma < 0.04", () => {
const { bdAccent } = new DarkModeTheme("oklch(0.45 0.03 60)").getColors();
expect(bdAccent).toEqual("rgb(76.823% 76.823% 76.823%)");
expect(bdAccent).toEqual("rgb(83.144% 83.144% 83.144%)");
});
it("should return correct color when chroma > 0.04", () => {
const { bdAccent } = new DarkModeTheme("oklch(0.45 0.1 60)").getColors();
expect(bdAccent).toEqual("rgb(94.8% 58.165% 23.581%)");
expect(bdAccent).toEqual("rgb(100% 71.253% 43.937%)");
});
});
describe("bdFocus color", () => {
it("should return correct color when lightness < 0.4", () => {
const { bdFocus } = new DarkModeTheme("oklch(0.3 0.4 60)").getColors();
expect(bdFocus).toEqual("rgb(94.8% 58.165% 23.581%)");
expect(bdFocus).toEqual("rgb(100% 71.253% 43.937%)");
});
it("should return correct color when lightness > 0.65", () => {
@ -766,7 +766,7 @@ describe("bdFocus color", () => {
describe("bdNeutral color", () => {
it("should return correct color when chroma < 0.04", () => {
const { bdNeutral } = new DarkModeTheme("oklch(0.45 0.03 60)").getColors();
expect(bdNeutral).toEqual("rgb(76.823% 76.823% 76.823%)");
expect(bdNeutral).toEqual("rgb(94.752% 94.752% 94.752%)");
});
});
@ -775,7 +775,7 @@ describe("bdNeutralHover", () => {
const { bdNeutralHover } = new DarkModeTheme(
"oklch(0.45 0.03 60)",
).getColors();
expect(bdNeutralHover).toEqual("rgb(89.558% 89.558% 89.558%)");
expect(bdNeutralHover).toEqual("rgb(63.258% 63.258% 63.258%)");
});
});

View File

@ -3,27 +3,27 @@ import { LightModeTheme } from "../src/LightModeTheme";
describe("bg color", () => {
it("should return correct color when lightness > 0.93", () => {
const { bg } = new LightModeTheme("oklch(0.95 0.09 231)").getColors();
expect(bg).toBe("rgb(84.831% 87.516% 88.974%)");
expect(bg).toBe("rgb(86.508% 87.102% 87.426%)");
});
it("should return correct color when lightness < 0.93", () => {
const { bg } = new LightModeTheme("oklch(0.92 0.09 231)").getColors();
expect(bg).toBe("rgb(92.567% 95.296% 96.777%)");
expect(bg).toBe("rgb(95.576% 96.181% 96.511%)");
});
it("should return correct color when hue > 120 && hue < 300", () => {
const { bg } = new LightModeTheme("oklch(0.95 0.07 231)").getColors();
expect(bg).toBe("rgb(84.831% 87.516% 88.974%)");
expect(bg).toBe("rgb(86.508% 87.102% 87.426%)");
});
it("should return correct color when hue < 120 or hue > 300", () => {
const { bg } = new LightModeTheme("oklch(0.92 0.07 110)").getColors();
expect(bg).toBe("rgb(94.827% 94.982% 92.913%)");
expect(bg).toBe("rgb(96.069% 96.092% 95.796%)");
});
it("should return correct color when chroma < 0.04", () => {
const { bg } = new LightModeTheme("oklch(0.92 0.02 110)").getColors();
expect(bg).toBe("rgb(94.752% 94.752% 94.752%)");
expect(bg).toBe("rgb(96.059% 96.059% 96.059%)");
});
});
@ -203,7 +203,7 @@ describe("bgNeutral color", () => {
it("should return correct color when lightness is between 0.25 and 0.85", () => {
const { bgNeutral } = new LightModeTheme("oklch(0.5 0.09 231)").getColors();
expect(bgNeutral).toEqual("rgb(21.658% 29.368% 33.367%)");
expect(bgNeutral).toEqual("rgb(27.672% 28.158% 28.423%)");
});
it("should return correct color when chroma < 0.04", () => {
@ -217,12 +217,12 @@ describe("bgNeutral color", () => {
const { bgNeutral } = new LightModeTheme(
"oklch(0.95 0.06 240)",
).getColors();
expect(bgNeutral).toEqual("rgb(87.409% 95.47% 100%)");
expect(bgNeutral).toEqual("rgb(93.666% 94.196% 94.597%)");
});
it("should return correct color when hue is not between 120 and 300 and chroma is not less than 0.04", () => {
const { bgNeutral } = new LightModeTheme("oklch(0.95 0.06 30)").getColors();
expect(bgNeutral).toEqual("rgb(98.083% 92.81% 91.842%)");
expect(bgNeutral).toEqual("rgb(94.37% 94.015% 93.949%)");
});
});
@ -231,7 +231,7 @@ describe("bgNeutralOpacity color", () => {
const { bgNeutralOpacity } = new LightModeTheme(
"oklch(0.51 0.24 279)",
).getColors();
expect(bgNeutralOpacity).toEqual("rgb(27.662% 28.6% 35.606% / 0.5)");
expect(bgNeutralOpacity).toEqual("rgb(29.012% 29.087% 29.559% / 0.5)");
});
});
@ -321,7 +321,7 @@ describe("bgNeutralSubtle color", () => {
const { bgNeutralSubtle } = new LightModeTheme(
"oklch(0.92 0.1 170)",
).getColors();
expect(bgNeutralSubtle).toEqual("rgb(88.517% 91.796% 90.467%)");
expect(bgNeutralSubtle).toEqual("rgb(90.621% 90.946% 90.813%)");
});
it("should return correct color when chroma < 0.04", () => {
@ -338,7 +338,7 @@ describe("bgNeutralSubtleHover color", () => {
"oklch(0.92 0.1 170)",
).getColors();
expect(bgNeutralSubtleHover).toEqual("rgb(91.102% 94.398% 93.061%)");
expect(bgNeutralSubtleHover).toEqual("rgb(93.216% 93.544% 93.409%)");
});
});
@ -348,7 +348,7 @@ describe("bgNeutralSubtleActive color", () => {
"oklch(0.92 0.1 170)",
).getColors();
expect(bgNeutralSubtleActive).toEqual("rgb(87.229% 90.5% 89.174%)");
expect(bgNeutralSubtleActive).toEqual("rgb(89.328% 89.653% 89.519%)");
});
});
@ -575,7 +575,7 @@ describe("fg color", () => {
it("should return correct color when chroma > 0.04", () => {
const { fg } = new LightModeTheme("oklch(0.45 0.1 60)").getColors();
expect(fg).toEqual("rgb(5.4369% 1.2901% 0%)");
expect(fg).toEqual("rgb(2.8027% 2.0973% 1.6301%)");
});
});
@ -603,13 +603,13 @@ describe("fgNeutral color", () => {
it("should return correct color when chroma > 0.04 and hue is between 120 and 300", () => {
const { fgNeutral } = new LightModeTheme("oklch(0.45 0.1 150)").getColors();
expect(fgNeutral).toEqual("rgb(25.52% 36.593% 27.669%)");
expect(fgNeutral).toEqual("rgb(32.964% 33.592% 33.055%)");
});
it("should return correct color when chroma > 0.04 and hue is not between 120 and 300", () => {
const { fgNeutral } = new LightModeTheme("oklch(0.45 0.1 110)").getColors();
expect(fgNeutral).toEqual("rgb(33.531% 33.77% 30.07%)");
expect(fgNeutral).toEqual("rgb(33.392% 33.411% 33.167%)");
});
});
@ -703,7 +703,7 @@ describe("fgOnAccent color ", () => {
it("should return correct color when chroma > 0.04", () => {
const { fgOnAccent } = new LightModeTheme("oklch(0.45 0.1 60)").getColors();
expect(fgOnAccent).toEqual("rgb(100% 92.634% 85.713%)");
expect(fgOnAccent).toEqual("rgb(97.954% 93.998% 90.979%)");
});
});
@ -760,7 +760,7 @@ describe("fgOnWarning color ", () => {
describe("bd color", () => {
it("should return correct color", () => {
const { bd } = new LightModeTheme("oklch(0.45 0.5 60)").getColors();
expect(bd).toEqual("rgb(80.718% 72.709% 66.526%)");
expect(bd).toEqual("rgb(75.553% 74.037% 72.885%)");
});
});
@ -784,22 +784,22 @@ describe("bdFocus color", () => {
it("should return correct color when lightness > 0.8", () => {
const { bdFocus } = new LightModeTheme("oklch(0.85 0.03 60)").getColors();
expect(bdFocus).toEqual("rgb(31.389% 9.8% 0%)");
expect(bdFocus).toEqual("rgb(26.226% 2.7922% 0%)");
});
it("should return correct color when chroma < 0.15", () => {
const { bdFocus } = new LightModeTheme("oklch(0.85 0.1 60)").getColors();
expect(bdFocus).toEqual("rgb(64.667% 36.271% 0%)");
expect(bdFocus).toEqual("rgb(49.321% 26.506% 0%)");
});
it("should return correct color when hue is between 0 and 55", () => {
const { bdFocus } = new LightModeTheme("oklch(0.85 0.1 30)").getColors();
expect(bdFocus).toEqual("rgb(72.468% 27.962% 22.197%)");
expect(bdFocus).toEqual("rgb(59.176% 15.217% 10.565%)");
});
it("should return correct color when hue > 340", () => {
const { bdFocus } = new LightModeTheme("oklch(0.85 0.1 350)").getColors();
expect(bdFocus).toEqual("rgb(68.494% 27.322% 49.304%)");
expect(bdFocus).toEqual("rgb(55.484% 14.961% 37.943%)");
});
});