reka-ui
Version:
Vue port for Radix UI Primitives.
148 lines (131 loc) • 3.69 kB
text/typescript
/**
* Converts a hex color string to RGB (Red, Green, Blue).
* @param hex Hex color string (e.g., "#ff5733" or "#f53")
* @returns An object containing red, green, and blue values (0-255).
*/
const HASH_PREFIX_RE = /^#/
const HEX6_RE = /^[0-9A-F]{6}$/i
const HEX3_RE = /^[0-9A-F]{3}$/i
export function hexToRGB(hex: string): { r: number, g: number, b: number } {
hex = hex.replace(HASH_PREFIX_RE, '')
// Validate hex format (3 or 6 hex digits)
if (!HEX6_RE.test(hex) && !HEX3_RE.test(hex)) {
throw new Error(`Invalid hex color: ${hex}. Expected format: #RGB or #RRGGBB`)
}
// Handle shorthand hex (e.g., "#FFF" -> "#FFFFFF")
if (hex.length === 3) {
hex = hex.split('').map(c => c + c).join('')
}
const bigint = parseInt(hex, 16)
const r = (bigint >> 16) & 255
const g = (bigint >> 8) & 255
const b = bigint & 255
return { r, g, b }
}
/**
* Converts a hex color string to HSL (Hue, Saturation, Lightness).
* @param hex Hex color string (e.g., "#ff5733")
* @returns An object containing hue (0-360), saturation (0-100), and lightness (0-100) values.
*/
export function hexToHSL(hex: string): { h: number, s: number, l: number } {
let { r, g, b } = hexToRGB(hex)
r /= 255
g /= 255
b /= 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
let h: number
let s: number
let l: number = (max + min) / 2
if (max === min) {
h = s = 0 // achromatic
l *= 100 // Scale l to 0-100 for consistency with chromatic case
}
else {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
if (max === r) {
h = (g - b) / d + (g < b ? 6 : 0)
}
else if (max === g) {
h = (b - r) / d + 2
}
else {
h = (r - g) / d + 4
}
h /= 6
h *= 360
s *= 100
l *= 100
}
return { h, s, l }
}
/**
* Converts a hex color string to a human-readable color name.
* @param hex Hex color string (e.g., "#ff5733")
* @returns A human-readable color name based on the hue, saturation, and lightness.
*/
export function getColorName(hex: string) {
const { h, s, l } = hexToHSL(hex)
// Handle achromatic colors (low saturation)
// Using 0-100 scale for all comparisons
if (s < 10) {
if (l < 10)
return 'black'
if (l > 95)
return 'white'
if (l < 20)
return 'very dark gray'
if (l < 35)
return 'dark gray'
if (l < 65)
return 'gray'
if (l < 80)
return 'light gray'
return 'very light gray'
}
// Determine base color by hue
let baseName
if (h < 15 || h >= 345)
baseName = 'red'
else if (h < 45)
baseName = 'orange'
else if (h < 75)
baseName = 'yellow'
else if (h < 105)
baseName = 'yellow-green'
else if (h < 135)
baseName = 'green'
else if (h < 165)
baseName = 'green-cyan'
else if (h < 195)
baseName = 'cyan'
else if (h < 225)
baseName = 'blue'
else if (h < 255)
baseName = 'blue-violet'
else if (h < 285)
baseName = 'violet'
else if (h < 315)
baseName = 'magenta'
else baseName = 'red-magenta'
// Add descriptors based on saturation and lightness
// Using 0-100 scale for all comparisons
const descriptors = []
if (s > 80)
descriptors.push('vibrant')
else if (s < 30)
descriptors.push('muted')
if (l > 80)
descriptors.push('light')
else if (l < 30)
descriptors.push('dark')
return descriptors.length > 0
? `${descriptors.join(' ')} ${baseName}`
: baseName
}
export function getColorContrast(hex: string): 'light' | 'dark' {
const { r, g, b } = hexToRGB(hex)
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
return luminance > 0.5 ? 'dark' : 'light'
}