aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
132 lines (129 loc) • 3.84 kB
JavaScript
'use client';
import { useEffect } from 'react';
// Parse rgb/rgba/hsl/hsla/hex color strings to [r,g,b,a]
function parseColor(input) {
if (!input) return null;
const s = input.trim();
const rgba = s.match(/^rgba?\(([^)]+)\)/i);
if (rgba) {
const parts = rgba[1].split(',').map(v => v.trim());
const r = parseFloat(parts[0]);
const g = parseFloat(parts[1]);
const b = parseFloat(parts[2]);
const a = parts[3] !== undefined ? parseFloat(parts[3]) : 1;
return [r, g, b, isNaN(a) ? 1 : a];
}
const hsla = s.match(/^hsla?\(([^)]+)\)/i);
if (hsla) {
const parts = hsla[1].split(',').map(v => v.trim().replace('%', ''));
const h = parseFloat(parts[0]);
const ss = parseFloat(parts[1]) / 100;
const l = parseFloat(parts[2]) / 100;
const a = parts[3] !== undefined ? parseFloat(parts[3]) : 1;
// hsl -> rgb
const c = (1 - Math.abs(2 * l - 1)) * ss;
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
const m = l - c / 2;
let r1 = 0,
g1 = 0,
b1 = 0;
if (0 <= h && h < 60) {
r1 = c;
g1 = x;
b1 = 0;
} else if (60 <= h && h < 120) {
r1 = x;
g1 = c;
b1 = 0;
} else if (120 <= h && h < 180) {
r1 = 0;
g1 = c;
b1 = x;
} else if (180 <= h && h < 240) {
r1 = 0;
g1 = x;
b1 = c;
} else if (240 <= h && h < 300) {
r1 = x;
g1 = 0;
b1 = c;
} else {
r1 = c;
g1 = 0;
b1 = x;
}
const r = Math.round((r1 + m) * 255);
const g = Math.round((g1 + m) * 255);
const b = Math.round((b1 + m) * 255);
return [r, g, b, isNaN(a) ? 1 : a];
}
const hex = s.match(/^#([0-9a-f]{3,8})$/i);
if (hex) {
let v = hex[1];
if (v.length === 3) v = v.split('').map(ch => ch + ch).join('');
if (v.length === 6) v += 'ff';
const num = parseInt(v, 16);
const r = num >> 24 & 0xff;
const g = num >> 16 & 0xff;
const b = num >> 8 & 0xff;
const a = (num & 0xff) / 255;
return [r, g, b, a];
}
return null;
}
function relativeLuminance([r, g, b, _a]) {
// sRGB → linear
const srgb = [r, g, b].map(v => v / 255);
const lin = srgb.map(c => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2];
}
function extractFirstColorFromBackground(bg) {
if (!bg) return null;
const re = /(rgba?\([^\)]+\)|hsla?\([^\)]+\)|#[0-9a-fA-F]{3,8})/g;
const match = re.exec(bg);
if (match) return parseColor(match[1]);
return null;
}
function useAutoTextContrast(ref, options = {}) {
const {
threshold = 0.55,
observe = true
} = options;
useEffect(() => {
const el = ref.current;
if (!el) return;
const decide = () => {
const cs = getComputedStyle(el);
let color = parseColor(cs.backgroundColor);
if (!color || color[3] === 0) {
// try background-image
const bg = cs.backgroundImage;
const c2 = extractFirstColorFromBackground(bg);
if (c2) color = c2;
}
if (!color) return; // bail if unknown
const lum = relativeLuminance(color);
const mode = lum < threshold ? 'dark' : 'light';
if (el.getAttribute('data-bg') !== mode) {
el.setAttribute('data-bg', mode);
}
};
decide();
if (!observe) return;
const ro = new ResizeObserver(() => decide());
ro.observe(el);
const mo = new MutationObserver(() => decide());
mo.observe(el, {
attributes: true,
attributeFilter: ['style', 'class']
});
const id = window.setInterval(decide, 1000);
return () => {
ro.disconnect();
mo.disconnect();
window.clearInterval(id);
};
}, [ref, threshold, observe]);
}
export { useAutoTextContrast as default, useAutoTextContrast };
//# sourceMappingURL=useAutoTextContrast.js.map