feat: dynamische icon-kleuring gegenereerd voor elke .lucide-<naam>

This commit is contained in:
mo
2026-07-21 22:32:02 +02:00
parent 2e0284f372
commit 07c600242b
3 changed files with 208 additions and 143 deletions
+94 -31
View File
@@ -1,9 +1,20 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { api } from './api';
// Legacy standaard-icoonkleuren (alle Lucide-iconen op de pagina een unieke kleur)
function hashCode(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
return hash;
}
function generateColor(name) {
const hue = Math.abs(hashCode(name)) % 360;
return `hsl(${hue}, 65%, 55%)`;
}
export const DEFAULT_ICON_COLORS = {
// Navigation & Core Domains
'layout-dashboard': '#58a6ff',
'users': '#3fb950',
'briefcase': '#d29922',
@@ -27,13 +38,9 @@ export const DEFAULT_ICON_COLORS = {
'radar': '#d29922',
'sparkles': '#bc8cff',
'settings': '#8b9bb8',
// Legacy & Platform
'shield-check': '#3fb950',
'pen-tool': '#bc8cff',
'file-search': '#d29922',
// Actions & Tools
'plus': '#00d4ff',
'pencil': '#79c0ff',
'trash-2': '#f85149',
@@ -48,15 +55,11 @@ export const DEFAULT_ICON_COLORS = {
'star': '#d29922',
'bot': '#bc8cff',
'filter': '#8b9bb8',
// Infra & Entities
'building-2': '#bc8cff',
'factory': '#bc8cff',
'map-pin': '#58a6ff',
'cable': '#58a6ff',
'workflow': '#bc8cff',
// Status & Info
'bell': '#d29922',
'file-text': '#f778ba',
'lightbulb': '#d29922',
@@ -69,43 +72,104 @@ export const DEFAULT_ICON_COLORS = {
'external-link': '#8b9bb8',
'log-out': '#f85149',
'menu': '#8b9bb8',
'x': '#8b9bb8'
'x': '#8b9bb8',
'arrow-left': '#79c0ff',
'arrow-right': '#79c0ff',
'chevron-left': '#8b9bb8',
'chevron-right': '#8b9bb8',
'globe': '#58a6ff',
'phone': '#3fb950',
'loader-2': '#8b9bb8'
};
// Injecteert globale CSS-regels voor alle .lucide-<name> elementen op de pagina
export function injectIconStyles(customColors) {
let styleEl = document.getElementById('mek-icon-colors');
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = 'mek-icon-colors';
document.head.appendChild(styleEl);
let processedIcons = new Set();
function getStyleEl() {
let el = document.getElementById('mek-icon-colors');
if (!el) {
el = document.createElement('style');
el.id = 'mek-icon-colors';
document.head.appendChild(el);
}
const merged = { ...DEFAULT_ICON_COLORS, ...(customColors || {}) };
let css = '/* Dynamic Icon Colors */\n';
return el;
}
function scanAndInject(styleEl, merged) {
let css = '/* Icon Colors */\n';
for (const [name, color] of Object.entries(merged)) {
if (!color) continue;
css += `.lucide-${name}, [data-lucide="${name}"] { color: ${color}; }\n`;
css += `.lucide-${name}, [data-lucide="${name}"] { color: ${color} }\n`;
processedIcons.add(name);
}
const els = document.querySelectorAll('[class*="lucide-"]');
for (const el of els) {
for (const cls of el.classList) {
if (cls.startsWith('lucide-') && cls !== 'lucide') {
const name = cls.replace('lucide-', '');
if (!processedIcons.has(name)) {
const c = generateColor(name);
css += `.lucide-${name}, [data-lucide="${name}"] { color: ${c} }\n`;
processedIcons.add(name);
}
}
}
el.setAttribute('data-ic', '1');
}
styleEl.textContent = css;
}
export function injectIconStyles(customColors) {
processedIcons.clear();
const merged = { ...DEFAULT_ICON_COLORS, ...(customColors || {}) };
scanAndInject(getStyleEl(), merged);
}
let observer = null;
function startObserver() {
if (observer) return;
observer = new MutationObserver(() => {
const els = document.querySelectorAll('[class*="lucide-"]:not([data-ic])');
if (!els.length) return;
const styleEl = getStyleEl();
let extra = '';
for (const el of els) {
for (const cls of el.classList) {
if (cls.startsWith('lucide-') && cls !== 'lucide') {
const name = cls.replace('lucide-', '');
if (!processedIcons.has(name)) {
const c = generateColor(name);
extra += `.lucide-${name}, [data-lucide="${name}"] { color: ${c} }\n`;
processedIcons.add(name);
}
}
}
el.setAttribute('data-ic', '1');
}
if (extra) styleEl.textContent += extra;
});
observer.observe(document.body || document.documentElement, { childList: true, subtree: true });
}
function stopObserver() {
if (observer) { observer.disconnect(); observer = null; }
}
const SettingsContext = createContext({ settings: null, loading: true });
export function SettingsProvider({ children }) {
const [settings, setSettings] = useState(null);
useEffect(() => {
// Injecteer meteen de standaardkleuren voor snelle weergave
injectIconStyles(null);
startObserver();
api.get('/settings')
.then(s => {
setSettings(s);
if (s?.icon_colors) {
injectIconStyles(s.icon_colors);
}
if (s?.icon_colors) injectIconStyles(s.icon_colors);
})
.catch(() => setSettings(null));
return stopObserver;
}, []);
return (
@@ -115,13 +179,12 @@ export function SettingsProvider({ children }) {
);
}
export function useSettings() {
return useContext(SettingsContext);
}
export function useSettings() { return useContext(SettingsContext); }
export function iconColor(name, settings) {
if (settings?.icon_colors && settings.icon_colors[name]) return settings.icon_colors[name];
return DEFAULT_ICON_COLORS[name] || null;
if (settings?.icon_colors?.[name]) return settings.icon_colors[name];
if (DEFAULT_ICON_COLORS[name]) return DEFAULT_ICON_COLORS[name];
return generateColor(name);
}
export function brandName(settings) {
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mek-Tech — Consultancy Platform</title>
<script type="module" crossorigin src="/app/assets/index-zN5ia3Gj.js"></script>
<script type="module" crossorigin src="/app/assets/index-BsMRi0iE.js"></script>
<link rel="stylesheet" crossorigin href="/app/assets/index-Cmof3-vz.css">
</head>
<body>