feat: implement dynamic theme support across chat components, enhancing UI consistency with reactive color updates and gradient functionalities
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
import ChatList from './ChatList.svelte';
|
||||
import ChatWindow from './ChatWindow.svelte';
|
||||
import { MessageSquare, Minus, Maximize2, X, Bell } from 'lucide-svelte';
|
||||
import { obterCoresDoTema, obterTemaPersistidoNoLocalStorage } from '$lib/utils/temas';
|
||||
|
||||
const count = useQuery(api.chat.contarNotificacoesNaoLidas, {});
|
||||
|
||||
@@ -955,6 +956,80 @@
|
||||
window.removeEventListener('touchend', handleTouchEnd);
|
||||
};
|
||||
});
|
||||
|
||||
// Obter cores do tema atual (reativo)
|
||||
let coresTema = $state(obterCoresDoTema());
|
||||
|
||||
// Atualizar cores quando o tema mudar
|
||||
$effect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const atualizarCores = () => {
|
||||
coresTema = obterCoresDoTema();
|
||||
};
|
||||
|
||||
// Atualizar cores inicialmente
|
||||
atualizarCores();
|
||||
|
||||
// Escutar mudanças de tema
|
||||
window.addEventListener('themechange', atualizarCores);
|
||||
|
||||
// Observar mudanças no atributo data-theme do HTML
|
||||
const observer = new MutationObserver(atualizarCores);
|
||||
const htmlElement = document.documentElement;
|
||||
observer.observe(htmlElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['data-theme']
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('themechange', atualizarCores);
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
|
||||
// Função para obter gradiente do tema
|
||||
function obterGradienteTema() {
|
||||
const primary = coresTema.primary;
|
||||
// Criar variações da cor primária para o gradiente
|
||||
return `linear-gradient(135deg, ${primary} 0%, ${primary}dd 50%, ${primary}bb 100%)`;
|
||||
}
|
||||
|
||||
// Função para obter rgba da cor primária
|
||||
function obterPrimariaRgba(alpha: number = 1) {
|
||||
const primary = coresTema.primary.trim();
|
||||
// Se já for rgba, extrair os valores
|
||||
if (primary.startsWith('rgba')) {
|
||||
const match = primary.match(/rgba?\(([^)]+)\)/);
|
||||
if (match) {
|
||||
const values = match[1].split(',').map(v => v.trim());
|
||||
if (values.length >= 3) {
|
||||
return `rgba(${values[0]}, ${values[1]}, ${values[2]}, ${alpha})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Se for hex, converter
|
||||
if (primary.startsWith('#')) {
|
||||
const hex = primary.replace('#', '');
|
||||
if (hex.length === 6) {
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
}
|
||||
// Se for hsl, converter para hsla
|
||||
if (primary.startsWith('hsl')) {
|
||||
const match = primary.match(/hsl\(([^)]+)\)/);
|
||||
if (match) {
|
||||
return `hsla(${match[1]}, ${alpha})`;
|
||||
}
|
||||
// Fallback: tentar adicionar alpha
|
||||
return primary.replace(/\)$/, `, ${alpha})`).replace('hsl', 'hsla');
|
||||
}
|
||||
// Fallback padrão
|
||||
return `rgba(102, 126, 234, ${alpha})`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Botão flutuante MODERNO E ARRASTÁVEL -->
|
||||
@@ -975,10 +1050,10 @@
|
||||
bottom: {bottomPos};
|
||||
right: {rightPos};
|
||||
position: fixed !important;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
|
||||
background: {obterGradienteTema()};
|
||||
box-shadow:
|
||||
0 20px 60px -10px rgba(102, 126, 234, 0.5),
|
||||
0 10px 30px -5px rgba(118, 75, 162, 0.4),
|
||||
0 20px 60px -10px {obterPrimariaRgba(0.5)},
|
||||
0 10px 30px -5px {obterPrimariaRgba(0.4)},
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1) inset;
|
||||
border-radius: 50%;
|
||||
cursor: {isDragging ? 'grabbing' : 'grab'};
|
||||
@@ -1058,17 +1133,17 @@
|
||||
strokeWidth={2}
|
||||
/>
|
||||
|
||||
<!-- Badge ULTRA PREMIUM com gradiente e brilho -->
|
||||
<!-- Badge ULTRA PREMIUM com gradiente e brilho usando cores do tema -->
|
||||
{#if count?.data && count.data > 0}
|
||||
<span
|
||||
class="absolute -top-1 -right-1 z-20 flex h-8 w-8 items-center justify-center rounded-full text-xs font-black text-white"
|
||||
style="
|
||||
background: linear-gradient(135deg, #ff416c, #ff4b2b);
|
||||
background: {coresTema.error ? `linear-gradient(135deg, ${coresTema.error}, ${coresTema.error}dd)` : 'linear-gradient(135deg, #ff416c, #ff4b2b)'};
|
||||
box-shadow:
|
||||
0 8px 24px -4px rgba(255, 65, 108, 0.6),
|
||||
0 4px 12px -2px rgba(255, 75, 43, 0.4),
|
||||
0 8px 24px -4px {coresTema.error ? obterPrimariaRgba(0.6).replace(coresTema.primary, coresTema.error) : 'rgba(255, 65, 108, 0.6)'},
|
||||
0 4px 12px -2px {coresTema.error ? obterPrimariaRgba(0.4).replace(coresTema.primary, coresTema.error) : 'rgba(255, 75, 43, 0.4)'},
|
||||
0 0 0 3px rgba(255, 255, 255, 0.3),
|
||||
0 0 0 5px rgba(255, 65, 108, 0.2);
|
||||
0 0 0 5px {coresTema.error ? obterPrimariaRgba(0.2).replace(coresTema.primary, coresTema.error) : 'rgba(255, 65, 108, 0.2)'};
|
||||
animation: badge-bounce 2s ease-in-out infinite;
|
||||
"
|
||||
>
|
||||
@@ -1121,8 +1196,8 @@
|
||||
<div
|
||||
class="relative flex items-center justify-between overflow-hidden px-6 py-5 text-white"
|
||||
style="
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
|
||||
box-shadow: 0 8px 32px -4px rgba(102, 126, 234, 0.3);
|
||||
background: {obterGradienteTema()};
|
||||
box-shadow: 0 8px 32px -4px {obterPrimariaRgba(0.3)};
|
||||
cursor: {isDragging ? 'grabbing' : 'grab'};
|
||||
"
|
||||
onmousedown={handleMouseDown}
|
||||
@@ -1239,7 +1314,8 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Redimensionar janela pela borda superior"
|
||||
class="hover:bg-primary/20 absolute top-0 right-0 left-0 z-50 h-2 cursor-ns-resize transition-colors"
|
||||
class="absolute top-0 right-0 left-0 z-50 h-2 cursor-ns-resize transition-colors"
|
||||
style="--hover-bg: {obterPrimariaRgba(0.2)}"
|
||||
onmousedown={(e) => handleResizeStart(e, 'n')}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'n')}
|
||||
style="border-radius: 24px 24px 0 0;"
|
||||
@@ -1249,7 +1325,8 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Redimensionar janela pela borda inferior"
|
||||
class="hover:bg-primary/20 absolute right-0 bottom-0 left-0 z-50 h-2 cursor-ns-resize transition-colors"
|
||||
class="absolute right-0 bottom-0 left-0 z-50 h-2 cursor-ns-resize transition-colors"
|
||||
style="--hover-bg: {obterPrimariaRgba(0.2)}"
|
||||
onmousedown={(e) => handleResizeStart(e, 's')}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 's')}
|
||||
style="border-radius: 0 0 24px 24px;"
|
||||
@@ -1259,7 +1336,8 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Redimensionar janela pela borda esquerda"
|
||||
class="hover:bg-primary/20 absolute top-0 bottom-0 left-0 z-50 w-2 cursor-ew-resize transition-colors"
|
||||
class="absolute top-0 bottom-0 left-0 z-50 w-2 cursor-ew-resize transition-colors"
|
||||
style="--hover-bg: {obterPrimariaRgba(0.2)}"
|
||||
onmousedown={(e) => handleResizeStart(e, 'w')}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'w')}
|
||||
style="border-radius: 24px 0 0 24px;"
|
||||
@@ -1269,7 +1347,8 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Redimensionar janela pela borda direita"
|
||||
class="hover:bg-primary/20 absolute top-0 right-0 bottom-0 z-50 w-2 cursor-ew-resize transition-colors"
|
||||
class="absolute top-0 right-0 bottom-0 z-50 w-2 cursor-ew-resize transition-colors"
|
||||
style="--hover-bg: {obterPrimariaRgba(0.2)}"
|
||||
onmousedown={(e) => handleResizeStart(e, 'e')}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'e')}
|
||||
style="border-radius: 0 24px 24px 0;"
|
||||
@@ -1279,7 +1358,8 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Redimensionar janela pelo canto superior esquerdo"
|
||||
class="hover:bg-primary/20 absolute top-0 left-0 z-50 h-4 w-4 cursor-nwse-resize transition-colors"
|
||||
class="absolute top-0 left-0 z-50 h-4 w-4 cursor-nwse-resize transition-colors"
|
||||
style="--hover-bg: {obterPrimariaRgba(0.2)}"
|
||||
onmousedown={(e) => handleResizeStart(e, 'nw')}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'nw')}
|
||||
style="border-radius: 24px 0 0 0;"
|
||||
@@ -1288,7 +1368,8 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Redimensionar janela pelo canto superior direito"
|
||||
class="hover:bg-primary/20 absolute top-0 right-0 z-50 h-4 w-4 cursor-nesw-resize transition-colors"
|
||||
class="absolute top-0 right-0 z-50 h-4 w-4 cursor-nesw-resize transition-colors"
|
||||
style="--hover-bg: {obterPrimariaRgba(0.2)}"
|
||||
onmousedown={(e) => handleResizeStart(e, 'ne')}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'ne')}
|
||||
style="border-radius: 0 24px 0 0;"
|
||||
@@ -1297,7 +1378,8 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Redimensionar janela pelo canto inferior esquerdo"
|
||||
class="hover:bg-primary/20 absolute bottom-0 left-0 z-50 h-4 w-4 cursor-nesw-resize transition-colors"
|
||||
class="absolute bottom-0 left-0 z-50 h-4 w-4 cursor-nesw-resize transition-colors"
|
||||
style="--hover-bg: {obterPrimariaRgba(0.2)}"
|
||||
onmousedown={(e) => handleResizeStart(e, 'sw')}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'sw')}
|
||||
style="border-radius: 0 0 0 24px;"
|
||||
@@ -1306,7 +1388,8 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Redimensionar janela pelo canto inferior direito"
|
||||
class="hover:bg-primary/20 absolute right-0 bottom-0 z-50 h-4 w-4 cursor-nwse-resize transition-colors"
|
||||
class="absolute right-0 bottom-0 z-50 h-4 w-4 cursor-nwse-resize transition-colors"
|
||||
style="--hover-bg: {obterPrimariaRgba(0.2)}"
|
||||
onmousedown={(e) => handleResizeStart(e, 'se')}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'se')}
|
||||
style="border-radius: 0 0 24px 0;"
|
||||
@@ -1324,8 +1407,8 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Abrir conversa: Nova mensagem de {notificationMsg.remetente}"
|
||||
class="bg-base-100 border-primary/20 fixed top-4 right-4 z-1000 max-w-sm cursor-pointer rounded-lg border p-4 shadow-2xl"
|
||||
style="box-shadow: 0 10px 40px -10px rgba(0,0,0,0.3); animation: slideInRight 0.3s ease-out;"
|
||||
class="bg-base-100 fixed top-4 right-4 z-1000 max-w-sm cursor-pointer rounded-lg border p-4 shadow-2xl"
|
||||
style="border-color: {obterPrimariaRgba(0.2)}; box-shadow: 0 10px 40px -10px rgba(0,0,0,0.3); animation: slideInRight 0.3s ease-out;"
|
||||
onclick={() => {
|
||||
const conversaIdToOpen = notificationMsg?.conversaId;
|
||||
showGlobalNotificationPopup = false;
|
||||
@@ -1356,8 +1439,8 @@
|
||||
}}
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="bg-primary/20 flex h-10 w-10 shrink-0 items-center justify-center rounded-full">
|
||||
<Bell class="text-primary h-5 w-5" strokeWidth={2} />
|
||||
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full" style="background-color: {obterPrimariaRgba(0.2)}">
|
||||
<Bell class="h-5 w-5" style="color: {coresTema.primary}" strokeWidth={2} />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-base-content mb-1 text-sm font-semibold">
|
||||
@@ -1366,7 +1449,7 @@
|
||||
<p class="text-base-content/70 line-clamp-2 text-xs">
|
||||
{notificationMsg.conteudo}
|
||||
</p>
|
||||
<p class="text-primary mt-1 text-xs">Clique para abrir</p>
|
||||
<p class="mt-1 text-xs" style="color: {coresTema.primary}">Clique para abrir</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1414,18 +1497,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Ondas de pulso para o botão flutuante */
|
||||
/* Ondas de pulso para o botão flutuante - cores dinâmicas */
|
||||
@keyframes pulse-ring {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.5);
|
||||
box-shadow: 0 0 0 0 var(--pulse-color, rgba(102, 126, 234, 0.5));
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 15px rgba(102, 126, 234, 0);
|
||||
box-shadow: 0 0 0 15px transparent;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0);
|
||||
box-shadow: 0 0 0 0 var(--pulse-color, rgba(102, 126, 234, 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
/* Estilos para handles de redimensionamento com hover dinâmico */
|
||||
[style*="--hover-bg"]:hover {
|
||||
background-color: var(--hover-bg) !important;
|
||||
}
|
||||
|
||||
/* Rotação para anel de brilho - suavizada */
|
||||
@keyframes rotate {
|
||||
|
||||
Reference in New Issue
Block a user