bb-plane-fork/web/helpers/theme.tsx
Anmol Singh Bhatia 99dba80d19
[WEB-3540] dev: color picker component (#6823)
* dev: color picker component added

* chore: helper function added

* chore: code refactor

* chore: code refactor

* chore: code refactor

* chore: code refactor
2025-03-27 17:48:39 +05:30

100 lines
3.2 KiB
TypeScript

import chroma from "chroma-js";
interface HSLColor {
h: number; // hue (0-360)
s: number; // saturation (0-100)
l: number; // lightness (0-100)
}
interface ColorAdjustmentOptions {
targetContrast?: number; // Minimum contrast ratio (4.5 for WCAG AAA, 3 for WCAG AA)
preserveHue?: boolean; // Whether to maintain the original hue
maxTries?: number; // Maximum attempts to find accessible colors
}
// Helper function to ensure color contrast compliance
const ensureAccessibleColors = (
foreground: string,
background: string,
options: ColorAdjustmentOptions = {}
): { foreground: string; background: string } => {
const {
targetContrast = 4.5, // WCAG AAA by default
preserveHue = true,
maxTries = 10,
} = options;
try {
const fg = chroma(foreground);
const bg = chroma(background);
let contrast = chroma.contrast(fg, bg);
// If contrast is already good, return original colors
if (contrast >= targetContrast) {
return { foreground, background };
}
// Adjust colors to meet contrast requirements
let adjustedFg = fg;
let adjustedBg = bg;
let tries = 0;
while (contrast < targetContrast && tries < maxTries) {
if (fg.luminance() > bg.luminance()) {
// Make foreground lighter and background darker
adjustedFg = preserveHue ? fg.luminance(Math.min(fg.luminance() + 0.1, 0.9)) : fg.brighten(0.5);
adjustedBg = preserveHue ? bg.luminance(Math.max(bg.luminance() - 0.1, 0.1)) : bg.darken(0.5);
} else {
// Make foreground darker and background lighter
adjustedFg = preserveHue ? fg.luminance(Math.max(fg.luminance() - 0.1, 0.1)) : fg.darken(0.5);
adjustedBg = preserveHue ? bg.luminance(Math.min(bg.luminance() + 0.1, 0.9)) : bg.brighten(0.5);
}
contrast = chroma.contrast(adjustedFg, adjustedBg);
tries++;
}
return {
foreground: adjustedFg.css(),
background: adjustedBg.css(),
};
} catch (error) {
console.warn("Color adjustment failed:", error);
return { foreground, background };
}
};
// background color
export const createBackgroundColor = (hsl: HSLColor, resolvedTheme: "light" | "dark" = "light"): string => {
const baseColor = chroma.hsl(hsl.h, hsl.s / 100, hsl.l / 100);
// Set base opacity according to theme
const baseOpacity = resolvedTheme === "dark" ? 0.25 : 0.15;
// Create semi-transparent background
let backgroundColor = baseColor.alpha(baseOpacity);
if (hsl.l > 90) {
backgroundColor = baseColor.darken(1).alpha(resolvedTheme === "dark" ? 0.3 : 0.2);
} else if (hsl.l > 70) {
backgroundColor = baseColor.darken(0.5).alpha(resolvedTheme === "dark" ? 0.28 : 0.18);
} else if (hsl.l < 30) {
backgroundColor = baseColor.brighten(0.5).alpha(resolvedTheme === "dark" ? 0.22 : 0.12);
}
return backgroundColor.css();
};
// foreground color
export const getIconColor = (hsl: HSLColor): string => {
const baseColor = chroma.hsl(hsl.h, hsl.s / 100, hsl.l / 100);
const backgroundColor = createBackgroundColor(hsl);
// Adjust colors for accessibility
const { foreground } = ensureAccessibleColors(baseColor.css(), backgroundColor, {
targetContrast: 3, // WCAG AA for UI components
preserveHue: true,
});
return foreground;
};