mirror of
https://github.com/makeplane/plane.git
synced 2026-02-25 04:35:21 +01:00
145 lines
4.8 KiB
TypeScript
145 lines
4.8 KiB
TypeScript
export type TRgb = { r: number; g: number; b: number };
|
|
|
|
export const hexToRgb = (hex: string): TRgb => {
|
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
const b = parseInt(hex.slice(5, 7), 16);
|
|
|
|
return { r, g, b };
|
|
};
|
|
|
|
export const rgbToHex = (rgb: TRgb): string => {
|
|
const { r, g, b } = rgb;
|
|
|
|
const hexR = r.toString(16).padStart(2, "0");
|
|
const hexG = g.toString(16).padStart(2, "0");
|
|
const hexB = b.toString(16).padStart(2, "0");
|
|
|
|
return `#${hexR}${hexG}${hexB}`;
|
|
};
|
|
|
|
/**
|
|
* Calculate relative luminance of a color according to WCAG
|
|
* @param {Object} rgb - RGB color object with r, g, b properties
|
|
* @returns {number} Relative luminance value
|
|
*/
|
|
export function getLuminance({ r, g, b }: { r: number; g: number; b: number }) {
|
|
// Convert RGB to sRGB
|
|
const sR = r / 255;
|
|
const sG = g / 255;
|
|
const sB = b / 255;
|
|
|
|
// Convert sRGB to linear RGB with gamma correction
|
|
const R = sR <= 0.03928 ? sR / 12.92 : Math.pow((sR + 0.055) / 1.055, 2.4);
|
|
const G = sG <= 0.03928 ? sG / 12.92 : Math.pow((sG + 0.055) / 1.055, 2.4);
|
|
const B = sB <= 0.03928 ? sB / 12.92 : Math.pow((sB + 0.055) / 1.055, 2.4);
|
|
|
|
// Calculate luminance
|
|
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
|
}
|
|
|
|
/**
|
|
* Calculate contrast ratio between two colors
|
|
* @param {Object} rgb1 - First RGB color object
|
|
* @param {Object} rgb2 - Second RGB color object
|
|
* @returns {number} Contrast ratio between the colors
|
|
*/
|
|
export function getContrastRatio(rgb1: { r: number; g: number; b: number }, rgb2: { r: number; g: number; b: number }) {
|
|
const luminance1 = getLuminance(rgb1);
|
|
const luminance2 = getLuminance(rgb2);
|
|
|
|
const lighter = Math.max(luminance1, luminance2);
|
|
const darker = Math.min(luminance1, luminance2);
|
|
|
|
return (lighter + 0.05) / (darker + 0.05);
|
|
}
|
|
|
|
/**
|
|
* Lighten a color by a specified amount
|
|
* @param {Object} rgb - RGB color object
|
|
* @param {number} amount - Amount to lighten (0-1)
|
|
* @returns {Object} Lightened RGB color
|
|
*/
|
|
export function lightenColor(rgb: { r: number; g: number; b: number }, amount: number) {
|
|
return {
|
|
r: rgb.r + (255 - rgb.r) * amount,
|
|
g: rgb.g + (255 - rgb.g) * amount,
|
|
b: rgb.b + (255 - rgb.b) * amount,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Darken a color by a specified amount
|
|
* @param {Object} rgb - RGB color object
|
|
* @param {number} amount - Amount to darken (0-1)
|
|
* @returns {Object} Darkened RGB color
|
|
*/
|
|
export function darkenColor(rgb: { r: number; g: number; b: number }, amount: number) {
|
|
return {
|
|
r: rgb.r * (1 - amount),
|
|
g: rgb.g * (1 - amount),
|
|
b: rgb.b * (1 - amount),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate appropriate foreground and background colors based on input color
|
|
* @param {string} color - Input color in hex format
|
|
* @returns {Object} Object containing foreground and background colors in hex format
|
|
*/
|
|
export function generateIconColors(color: string) {
|
|
// Parse input color
|
|
const rgbColor = hexToRgb(color);
|
|
const luminance = getLuminance(rgbColor);
|
|
|
|
// Initialize output colors
|
|
let foregroundColor = rgbColor;
|
|
|
|
// Constants for color adjustment
|
|
const MIN_CONTRAST_RATIO = 3.0; // Minimum acceptable contrast ratio
|
|
|
|
// For light colors, use as foreground and darken for background
|
|
if (luminance > 0.5) {
|
|
// Make sure the foreground color is dark enough for visibility
|
|
let adjustedForeground = foregroundColor;
|
|
const whiteContrast = getContrastRatio(foregroundColor, { r: 255, g: 255, b: 255 });
|
|
|
|
if (whiteContrast < MIN_CONTRAST_RATIO) {
|
|
// Darken the foreground color until it has enough contrast
|
|
let darkenAmount = 0.1;
|
|
while (darkenAmount <= 0.9) {
|
|
adjustedForeground = darkenColor(foregroundColor, darkenAmount);
|
|
if (getContrastRatio(adjustedForeground, { r: 255, g: 255, b: 255 }) >= MIN_CONTRAST_RATIO) {
|
|
break;
|
|
}
|
|
darkenAmount += 0.1;
|
|
}
|
|
foregroundColor = adjustedForeground;
|
|
}
|
|
}
|
|
// For dark colors, use as foreground and lighten for background
|
|
else {
|
|
// Make sure the foreground color is light enough for visibility
|
|
let adjustedForeground = foregroundColor;
|
|
const blackContrast = getContrastRatio(foregroundColor, { r: 0, g: 0, b: 0 });
|
|
|
|
if (blackContrast < MIN_CONTRAST_RATIO) {
|
|
// Lighten the foreground color until it has enough contrast
|
|
let lightenAmount = 0.1;
|
|
while (lightenAmount <= 0.9) {
|
|
adjustedForeground = lightenColor(foregroundColor, lightenAmount);
|
|
if (getContrastRatio(adjustedForeground, { r: 0, g: 0, b: 0 }) >= MIN_CONTRAST_RATIO) {
|
|
break;
|
|
}
|
|
lightenAmount += 0.1;
|
|
}
|
|
foregroundColor = adjustedForeground;
|
|
}
|
|
}
|
|
|
|
return {
|
|
foreground: rgbToHex({ r: foregroundColor.r, g: foregroundColor.g, b: foregroundColor.b }),
|
|
background: `rgba(${foregroundColor.r}, ${foregroundColor.g}, ${foregroundColor.b}, 0.25)`,
|
|
};
|
|
}
|