Polish mobile and web experience
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
const DARK_ACTION_FOREGROUND = "#18181B";
|
||||
const LIGHT_ACTION_FOREGROUND = "#FFFFFF";
|
||||
const MIN_TEXT_CONTRAST = 4.5;
|
||||
|
||||
function parseHexColor(color: string): [number, number, number] | null {
|
||||
const value = color.trim().replace(/^#/, "");
|
||||
const expanded =
|
||||
value.length === 3
|
||||
? value
|
||||
.split("")
|
||||
.map((character) => character.repeat(2))
|
||||
.join("")
|
||||
: value.slice(0, 6);
|
||||
|
||||
if (expanded.length !== 6 || !/^[0-9a-f]+$/i.test(expanded)) return null;
|
||||
|
||||
return [
|
||||
Number.parseInt(expanded.slice(0, 2), 16),
|
||||
Number.parseInt(expanded.slice(2, 4), 16),
|
||||
Number.parseInt(expanded.slice(4, 6), 16),
|
||||
];
|
||||
}
|
||||
|
||||
function luminance(color: string): number | null {
|
||||
const rgb = parseHexColor(color);
|
||||
if (!rgb) return null;
|
||||
|
||||
const channels = rgb.map((channel) => {
|
||||
const value = channel / 255;
|
||||
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
||||
});
|
||||
|
||||
return 0.2126 * channels[0]! + 0.7152 * channels[1]! + 0.0722 * channels[2]!;
|
||||
}
|
||||
|
||||
function contrastRatio(foreground: string, background: string): number | null {
|
||||
const foregroundLuminance = luminance(foreground);
|
||||
const backgroundLuminance = luminance(background);
|
||||
if (foregroundLuminance == null || backgroundLuminance == null) return null;
|
||||
|
||||
const lighter = Math.max(foregroundLuminance, backgroundLuminance);
|
||||
const darker = Math.min(foregroundLuminance, backgroundLuminance);
|
||||
return (lighter + 0.05) / (darker + 0.05);
|
||||
}
|
||||
|
||||
/** Chooses a readable action label/icon color while preserving a valid requested color. */
|
||||
export function resolveActionForeground(background: string, requested: string): string {
|
||||
const requestedContrast = contrastRatio(requested, background);
|
||||
if (requestedContrast == null || requestedContrast >= MIN_TEXT_CONTRAST) {
|
||||
return requested;
|
||||
}
|
||||
|
||||
const darkContrast = contrastRatio(DARK_ACTION_FOREGROUND, background) ?? 0;
|
||||
const lightContrast = contrastRatio(LIGHT_ACTION_FOREGROUND, background) ?? 0;
|
||||
return darkContrast >= lightContrast ? DARK_ACTION_FOREGROUND : LIGHT_ACTION_FOREGROUND;
|
||||
}
|
||||
Reference in New Issue
Block a user