feat: implement dynamic theme support across chat components, enhancing UI consistency with reactive color updates and gradient functionalities
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
import NewConversationModal from './NewConversationModal.svelte';
|
||||
import { Search, Plus, MessageSquare, Users, UsersRound } from 'lucide-svelte';
|
||||
import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import { obterCoresDoTema } from '$lib/utils/temas';
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
@@ -23,6 +24,57 @@
|
||||
|
||||
let searchQuery = $state('');
|
||||
let activeTab = $state<'usuarios' | 'conversas'>('usuarios');
|
||||
|
||||
// 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();
|
||||
};
|
||||
|
||||
atualizarCores();
|
||||
|
||||
window.addEventListener('themechange', atualizarCores);
|
||||
|
||||
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 rgba da cor primária
|
||||
function obterPrimariaRgba(alpha: number = 1) {
|
||||
const primary = coresTema.primary;
|
||||
if (primary.startsWith('rgba')) {
|
||||
const match = primary.match(/rgba?\(([^)]+)\)/);
|
||||
if (match) {
|
||||
const values = match[1].split(',');
|
||||
return `rgba(${values[0]}, ${values[1]}, ${values[2]}, ${alpha})`;
|
||||
}
|
||||
}
|
||||
if (primary.startsWith('#')) {
|
||||
const hex = primary.replace('#', '');
|
||||
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})`;
|
||||
}
|
||||
if (primary.startsWith('hsl')) {
|
||||
return primary.replace(/\)$/, `, ${alpha})`).replace('hsl', 'hsla');
|
||||
}
|
||||
return `rgba(102, 126, 234, ${alpha})`;
|
||||
}
|
||||
|
||||
// Debug: monitorar carregamento de dados
|
||||
$effect(() => {
|
||||
@@ -263,7 +315,7 @@
|
||||
<!-- Ícone de mensagem -->
|
||||
<div
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl transition-all duration-300 hover:scale-110"
|
||||
style="background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%); border: 1px solid rgba(102, 126, 234, 0.2);"
|
||||
style="background: linear-gradient(135deg, {obterPrimariaRgba(0.1)} 0%, {obterPrimariaRgba(0.1)} 100%); border: 1px solid {obterPrimariaRgba(0.2)};"
|
||||
>
|
||||
<MessageSquare class="text-primary h-5 w-5" strokeWidth={2} />
|
||||
</div>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
//import { getAvatarUrl } from '$lib/utils/avatarGenerator';
|
||||
import { voltarParaLista } from '$lib/stores/chatStore';
|
||||
import { useConvexClient, useQuery } from 'convex-svelte';
|
||||
import { obterCoresDoTema } from '$lib/utils/temas';
|
||||
|
||||
//import { Bell, X, ArrowLeft, LogOut, MoreVertical, Users, Clock, XCircle } from 'lucide-svelte';
|
||||
|
||||
@@ -260,23 +261,84 @@
|
||||
let souAnfitriao = $derived(
|
||||
chamadaAtual && meuPerfil?.data ? chamadaAtual.criadoPor === meuPerfil.data._id : false
|
||||
);
|
||||
|
||||
// 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();
|
||||
};
|
||||
|
||||
atualizarCores();
|
||||
|
||||
window.addEventListener('themechange', atualizarCores);
|
||||
|
||||
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 rgba da cor primária
|
||||
function obterPrimariaRgba(alpha: number = 1) {
|
||||
const primary = coresTema.primary.trim();
|
||||
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})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
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})`;
|
||||
}
|
||||
}
|
||||
if (primary.startsWith('hsl')) {
|
||||
const match = primary.match(/hsl\(([^)]+)\)/);
|
||||
if (match) {
|
||||
return `hsla(${match[1]}, ${alpha})`;
|
||||
}
|
||||
return primary.replace(/\)$/, `, ${alpha})`).replace('hsl', 'hsla');
|
||||
}
|
||||
return `rgba(102, 126, 234, ${alpha})`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col" onclick={() => (showAdminMenu = false)}>
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="border-base-300 bg-base-200 flex items-center gap-3 border-b px-4 py-3"
|
||||
class="border-base-300 flex items-center gap-3 border-b px-4 py-3"
|
||||
style="background-color: {coresTema.base200}; border-color: {coresTema.base300};"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<!-- Botão Voltar -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-circle hover:bg-primary/20 transition-all duration-200 hover:scale-110"
|
||||
class="btn btn-sm btn-circle transition-all duration-200 hover:scale-110"
|
||||
style="--hover-bg: {obterPrimariaRgba(0.2)}"
|
||||
onclick={voltarParaLista}
|
||||
aria-label="Voltar"
|
||||
title="Voltar para lista de conversas"
|
||||
>
|
||||
<ArrowLeft class="text-primary h-6 w-6" strokeWidth={2.5} />
|
||||
<ArrowLeft class="h-6 w-6" style="color: {coresTema.primary}" strokeWidth={2.5} />
|
||||
</button>
|
||||
|
||||
<!-- Avatar e Info -->
|
||||
@@ -289,7 +351,7 @@
|
||||
userId={conversa()?.outroUsuario?._id}
|
||||
/>
|
||||
{:else}
|
||||
<div class="bg-primary/20 flex h-10 w-10 items-center justify-center rounded-full text-xl">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-full text-xl" style="background-color: {obterPrimariaRgba(0.2)}">
|
||||
{getAvatarConversa()}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -380,7 +442,8 @@
|
||||
</div>
|
||||
{#if conversa()?.tipo === 'sala_reuniao' && isAdmin?.data}
|
||||
<span
|
||||
class="text-primary ml-1 text-[10px] font-semibold whitespace-nowrap"
|
||||
class="ml-1 text-[10px] font-semibold whitespace-nowrap"
|
||||
style="color: {coresTema.primary}"
|
||||
title="Você é administrador desta sala">• Admin</span
|
||||
>
|
||||
{/if}
|
||||
@@ -727,10 +790,8 @@
|
||||
{#each searchResults as resultado, index (resultado._id)}
|
||||
<button
|
||||
type="button"
|
||||
class="hover:bg-base-300 flex w-full items-start gap-3 px-4 py-3 text-left transition-colors {index ===
|
||||
selectedSearchResult
|
||||
? 'bg-primary/10'
|
||||
: ''}"
|
||||
class="hover:bg-base-300 flex w-full items-start gap-3 px-4 py-3 text-left transition-colors"
|
||||
style={index === selectedSearchResult ? `background-color: ${obterPrimariaRgba(0.1)}` : ''}
|
||||
onclick={() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('scrollToMessage', {
|
||||
@@ -745,7 +806,8 @@
|
||||
aria-label="Mensagem de {resultado.remetente?.nome || 'Usuário'}"
|
||||
>
|
||||
<div
|
||||
class="bg-primary/20 flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full"
|
||||
class="flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full"
|
||||
style="background-color: {obterPrimariaRgba(0.2)}"
|
||||
>
|
||||
{#if resultado.remetente?.fotoPerfilUrl}
|
||||
<img
|
||||
@@ -840,7 +902,7 @@
|
||||
<div class="modal-box max-w-md" onclick={(e) => e.stopPropagation()}>
|
||||
<div class="border-base-300 flex items-center justify-between border-b px-6 py-4">
|
||||
<h2 class="flex items-center gap-2 text-xl font-semibold">
|
||||
<Bell class="text-primary h-5 w-5" />
|
||||
<Bell class="h-5 w-5" style="color: {coresTema.primary}" />
|
||||
Enviar Notificação
|
||||
</h2>
|
||||
<button
|
||||
@@ -931,3 +993,10 @@
|
||||
details={errorInstructions || errorDetails}
|
||||
onClose={fecharErrorModal}
|
||||
/>
|
||||
|
||||
<style>
|
||||
/* Estilos para hover dinâmico com cores do tema */
|
||||
[style*="--hover-bg"]:hover {
|
||||
background-color: var(--hover-bg) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
type EncryptedMessage
|
||||
} from '$lib/utils/e2eEncryption';
|
||||
import { obterChaveCriptografia, armazenarChaveCriptografia } from '$lib/stores/chatStore';
|
||||
import { obterCoresDoTema } from '$lib/utils/temas';
|
||||
|
||||
interface Props {
|
||||
conversaId: Id<'conversas'>;
|
||||
@@ -88,6 +89,63 @@
|
||||
let mentionStartPos = $state(0);
|
||||
let selectedMentionIndex = $state(0); // Índice do participante selecionado no dropdown
|
||||
let mensagemMuitoLonga = $state(false);
|
||||
|
||||
// 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();
|
||||
};
|
||||
|
||||
atualizarCores();
|
||||
|
||||
window.addEventListener('themechange', atualizarCores);
|
||||
|
||||
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 rgba da cor primária
|
||||
function obterPrimariaRgba(alpha: number = 1) {
|
||||
const primary = coresTema.primary;
|
||||
if (primary.startsWith('rgba')) {
|
||||
const match = primary.match(/rgba?\(([^)]+)\)/);
|
||||
if (match) {
|
||||
const values = match[1].split(',');
|
||||
return `rgba(${values[0]}, ${values[1]}, ${values[2]}, ${alpha})`;
|
||||
}
|
||||
}
|
||||
if (primary.startsWith('#')) {
|
||||
const hex = primary.replace('#', '');
|
||||
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})`;
|
||||
}
|
||||
if (primary.startsWith('hsl')) {
|
||||
return primary.replace(/\)$/, `, ${alpha})`).replace('hsl', 'hsla');
|
||||
}
|
||||
return `rgba(102, 126, 234, ${alpha})`;
|
||||
}
|
||||
|
||||
// Função para obter gradiente do tema
|
||||
function obterGradienteTema() {
|
||||
const primary = coresTema.primary;
|
||||
return `linear-gradient(135deg, ${primary} 0%, ${primary}dd 100%)`;
|
||||
}
|
||||
|
||||
// Emojis mais usados
|
||||
const emojis = [
|
||||
@@ -670,7 +728,7 @@
|
||||
<div class="relative shrink-0">
|
||||
<label
|
||||
class="group relative flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-xl transition-all duration-300"
|
||||
style="background: rgba(102, 126, 234, 0.1); border: 1px solid rgba(102, 126, 234, 0.2);"
|
||||
style="background: {obterPrimariaRgba(0.1)}; border: 1px solid {obterPrimariaRgba(0.2)};"
|
||||
title="Anexar arquivo"
|
||||
aria-label="Anexar arquivo"
|
||||
>
|
||||
@@ -838,7 +896,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="group relative flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-xl transition-all duration-300 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 8px 24px -4px rgba(102, 126, 234, 0.4);"
|
||||
style="background: {obterGradienteTema()}; box-shadow: 0 8px 24px -4px {obterPrimariaRgba(0.4)};"
|
||||
onclick={handleEnviar}
|
||||
disabled={!mensagem.trim() || enviando || uploadingFile}
|
||||
aria-label="Enviar mensagem"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { format } from 'date-fns';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
import { Clock, Trash2, X } from 'lucide-svelte';
|
||||
import { obterCoresDoTema } from '$lib/utils/temas';
|
||||
|
||||
interface Props {
|
||||
conversaId: Id<'conversas'>;
|
||||
@@ -22,6 +23,63 @@
|
||||
let data = $state('');
|
||||
let hora = $state('');
|
||||
let loading = $state(false);
|
||||
|
||||
// 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();
|
||||
};
|
||||
|
||||
atualizarCores();
|
||||
|
||||
window.addEventListener('themechange', atualizarCores);
|
||||
|
||||
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 rgba da cor primária
|
||||
function obterPrimariaRgba(alpha: number = 1) {
|
||||
const primary = coresTema.primary;
|
||||
if (primary.startsWith('rgba')) {
|
||||
const match = primary.match(/rgba?\(([^)]+)\)/);
|
||||
if (match) {
|
||||
const values = match[1].split(',');
|
||||
return `rgba(${values[0]}, ${values[1]}, ${values[2]}, ${alpha})`;
|
||||
}
|
||||
}
|
||||
if (primary.startsWith('#')) {
|
||||
const hex = primary.replace('#', '');
|
||||
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})`;
|
||||
}
|
||||
if (primary.startsWith('hsl')) {
|
||||
return primary.replace(/\)$/, `, ${alpha})`).replace('hsl', 'hsla');
|
||||
}
|
||||
return `rgba(102, 126, 234, ${alpha})`;
|
||||
}
|
||||
|
||||
// Função para obter gradiente do tema
|
||||
function obterGradienteTema() {
|
||||
const primary = coresTema.primary;
|
||||
return `linear-gradient(135deg, ${primary} 0%, ${primary}dd 100%)`;
|
||||
}
|
||||
|
||||
// Rastrear mudanças nas mensagens agendadas
|
||||
$effect(() => {
|
||||
@@ -186,7 +244,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="group relative overflow-hidden rounded-xl px-6 py-3 font-bold text-white transition-all duration-300 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 8px 24px -4px rgba(102, 126, 234, 0.4);"
|
||||
style="background: {obterGradienteTema()}; box-shadow: 0 8px 24px -4px {obterPrimariaRgba(0.4)};"
|
||||
onclick={handleAgendar}
|
||||
disabled={loading || !mensagem.trim() || !data || !hora}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user