Files
BetiX/resources/js/composables/usePrimaryColor.ts
Dolo 0280278978
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
Initialer Laravel Commit für BetiX
2026-04-04 18:01:50 +02:00

130 lines
4.3 KiB
TypeScript

import { ref, onMounted } from 'vue';
export type UsePrimaryColorReturn = {
primaryColor: ReturnType<typeof ref<string>>;
updatePrimaryColor: (value: string) => void;
};
const STORAGE_KEY = 'primaryColor';
function setCookie(name: string, value: string, days = 365) {
if (typeof document === 'undefined') return;
const maxAge = days * 24 * 60 * 60;
document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;
}
function getStoredPrimaryColor(): string | null {
if (typeof window === 'undefined') return null;
return localStorage.getItem(STORAGE_KEY);
}
function parseColorToRGB(color: string): { r: number; g: number; b: number } | null {
// Supports #rgb, #rrggbb, rgb(), hsl()
color = color.trim().toLowerCase();
// Hex
if (color.startsWith('#')) {
let hex = color.slice(1);
if (hex.length === 3) {
hex = hex.split('').map((c) => c + c).join('');
}
if (hex.length === 6) {
const num = parseInt(hex, 16);
return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 };
}
}
// rgb/rgba
if (color.startsWith('rgb')) {
const m = color.match(/rgba?\(([^)]+)\)/);
if (m) {
const [r, g, b] = m[1]
.split(',')
.slice(0, 3)
.map((v) => parseFloat(v.trim()));
return { r, g, b };
}
}
// hsl/hsla
if (color.startsWith('hsl')) {
const m = color.match(/hsla?\(([^)]+)\)/);
if (m) {
const [hStr, sStr, lStr] = m[1].split(',').map((v) => v.trim());
const h = parseFloat(hStr);
const s = parseFloat(sStr) / 100;
const l = parseFloat(lStr) / 100;
// convert HSL to RGB
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m2 = l - c / 2;
let r1 = 0, g1 = 0, b1 = 0;
if (0 <= h && h < 60) [r1, g1, b1] = [c, x, 0];
else if (60 <= h && h < 120) [r1, g1, b1] = [x, c, 0];
else if (120 <= h && h < 180) [r1, g1, b1] = [0, c, x];
else if (180 <= h && h < 240) [r1, g1, b1] = [0, x, c];
else if (240 <= h && h < 300) [r1, g1, b1] = [x, 0, c];
else [r1, g1, b1] = [c, 0, x];
return {
r: Math.round((r1 + m2) * 255),
g: Math.round((g1 + m2) * 255),
b: Math.round((b1 + m2) * 255),
};
}
}
return null;
}
function getContrastColor(bg: string): '#000000' | '#FFFFFF' {
const rgb = parseColorToRGB(bg) || { r: 0, g: 0, b: 0 };
// Relative luminance
const srgb = [rgb.r, rgb.g, rgb.b].map((v) => {
const c = v / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
const L = 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
return L > 0.179 ? '#000000' : '#FFFFFF';
}
export function applyPrimaryColor(value: string): void {
if (typeof document === 'undefined') return;
const root = document.documentElement;
// Apply to global primary variables used by Tailwind theme tokens
root.style.setProperty('--primary', value);
root.style.setProperty('--primary-foreground', getContrastColor(value));
// Align commonly used accent variables in user layout to the same main color
root.style.setProperty('--magenta', value);
root.style.setProperty('--sidebar-primary', value);
root.style.setProperty('--sidebar-primary-foreground', getContrastColor(value));
}
export function initializePrimaryColor(): void {
const saved = getStoredPrimaryColor();
if (saved) applyPrimaryColor(saved);
}
const primaryColorRef = ref<string>(getStoredPrimaryColor() || '#ff007a');
export function usePrimaryColor(): UsePrimaryColorReturn {
onMounted(() => {
const saved = getStoredPrimaryColor();
if (saved) {
primaryColorRef.value = saved;
}
});
function updatePrimaryColor(value: string) {
primaryColorRef.value = value;
if (typeof window !== 'undefined') {
localStorage.setItem(STORAGE_KEY, value);
}
setCookie(STORAGE_KEY, value);
applyPrimaryColor(value);
}
return { primaryColor: primaryColorRef, updatePrimaryColor };
}