UNPKG

reka-ui

Version:

Vue port for Radix UI Primitives.

148 lines (131 loc) 3.69 kB
/** * 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' }