diff --git a/src/index.css b/src/index.css index e340f75..d3c4dce 100644 --- a/src/index.css +++ b/src/index.css @@ -2,4 +2,5 @@ @import './styles/global.css'; @import './styles/reset.css'; @import './styles/typography.css'; -@import './styles/tableCommons.css' \ No newline at end of file +@import './styles/tableCommons.css'; +@import './styles/dark-mode.css'; \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx index 16ac828..eb536fb 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -6,6 +6,7 @@ import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/js/bootstrap.bundle.min.js'; import 'bootstrap-icons/font/bootstrap-icons.css'; import App from './App.jsx' +import './scripts/darkModeToggle.js' createRoot(document.getElementById('root')).render( diff --git a/src/scripts/darkModeToggle.js b/src/scripts/darkModeToggle.js new file mode 100644 index 0000000..d525609 --- /dev/null +++ b/src/scripts/darkModeToggle.js @@ -0,0 +1,146 @@ +/** + * Dark Mode Toggle - Sistema Independente + * + * Script vanilla JavaScript que: + * - Cria um botão no navbar com ícone Bootstrap Icons + * - Gerencia o estado do tema (light/dark) + * - Persiste a escolha no localStorage + * - Detecta preferência do sistema + * - Não interfere com código React existente + */ + +(function() { + 'use strict'; + + // Chave para localStorage + const STORAGE_KEY = 'theme-preference'; + + // Função para obter o tema inicial + function getInitialTheme() { + // Primeiro, verifica localStorage + const saved = localStorage.getItem(STORAGE_KEY); + if (saved) { + return saved; + } + + // Depois, verifica preferência do sistema + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + return 'dark'; + } + + // Padrão: light + return 'light'; + } + + // Função para aplicar o tema + function applyTheme(theme) { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem(STORAGE_KEY, theme); + updateToggleButton(theme); + } + + // Função para alternar tema + function toggleTheme() { + const current = document.documentElement.getAttribute('data-theme') || 'light'; + const next = current === 'light' ? 'dark' : 'light'; + applyTheme(next); + } + + // Função para criar o botão de toggle + function createToggleButton() { + // Verifica se já existe + if (document.getElementById('dark-mode-toggle')) { + return; + } + + // Encontra o navbar + const navbar = document.querySelector('.navbar-nav:last-child'); + if (!navbar) { + console.warn('Navbar não encontrado, botão não foi criado'); + return; + } + + // Cria o item de lista + const li = document.createElement('li'); + li.className = 'nav-item'; + + // Cria o botão + const button = document.createElement('button'); + button.id = 'dark-mode-toggle'; + button.setAttribute('aria-label', 'Alternar modo escuro'); + button.innerHTML = ''; + + // Event listener + button.addEventListener('click', toggleTheme); + + // Adiciona ao navbar + li.appendChild(button); + navbar.appendChild(li); + } + + // Função para atualizar o ícone do botão + function updateToggleButton(theme) { + const button = document.getElementById('dark-mode-toggle'); + if (button) { + const icon = theme === 'light' + ? '' + : ''; + const title = theme === 'light' ? 'Ativar modo escuro' : 'Ativar modo claro'; + button.innerHTML = icon; + button.setAttribute('title', title); + } + } + + // Listener para mudanças na preferência do sistema + function watchSystemTheme() { + if (window.matchMedia) { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + mediaQuery.addEventListener('change', (e) => { + // Só aplica se não houver preferência salva + if (!localStorage.getItem(STORAGE_KEY)) { + applyTheme(e.matches ? 'dark' : 'light'); + } + }); + } + } + + // Inicialização + function init() { + // Aplica tema inicial imediatamente (antes do DOM carregar) + const initialTheme = getInitialTheme(); + document.documentElement.setAttribute('data-theme', initialTheme); + + // Função para tentar criar o botão (com retry) + function tryCreateButton(attempts = 0) { + const navbar = document.querySelector('.navbar-nav:last-child'); + if (navbar) { + createToggleButton(); + updateToggleButton(initialTheme); + } else if (attempts < 20) { + // Tenta novamente após 100ms + setTimeout(() => tryCreateButton(attempts + 1), 100); + } + } + + // Quando o DOM estiver pronto, tenta criar o botão + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + tryCreateButton(); + watchSystemTheme(); + }); + } else { + tryCreateButton(); + watchSystemTheme(); + } + } + + // Executa inicialização + init(); + + // Expõe funções globalmente (opcional, para debug) + window.darkMode = { + toggle: toggleTheme, + set: applyTheme, + get: () => document.documentElement.getAttribute('data-theme') + }; +})(); diff --git a/src/styles/dark-mode.css b/src/styles/dark-mode.css new file mode 100644 index 0000000..7ccba1c --- /dev/null +++ b/src/styles/dark-mode.css @@ -0,0 +1,281 @@ +/* + * Dark Mode - Implementação Independente + * Não modifica os estilos existentes. Apenas adiciona regras quando [data-theme="dark"] está ativo. + * Usa apenas as cores da paleta oficial do sistema. + */ + +/* Variáveis auxiliares (não utilizadas atualmente, podem ser removidas) */ +[data-theme="dark"] { + --bg-main: var(--base-black); + --bg-card: var(--black-700); + --bg-hover: var(--black-500); + --txt-primary: var(--base-white); + --txt-secondary: var(--white-500); + --txt-tertiary: var(--white-700); +} + +/* Background principal */ +[data-theme="dark"] body, +[data-theme="dark"] div#root { + background-color: var(--base-black) !important; + color: var(--base-white) !important; +} + +/* Navbar */ +[data-theme="dark"] nav.navbar { + background-color: var(--black-700) !important; + color: var(--base-white) !important; +} + +[data-theme="dark"] nav.navbar img, +[data-theme="dark"] .navbar-brand img { + filter: brightness(0) invert(1); +} + +[data-theme="dark"] .nav-link { + color: var(--white-500) !important; +} + +[data-theme="dark"] .nav-link:hover { + color: var(--base-white) !important; +} + +[data-theme="dark"] .navbar-toggler-icon { + filter: invert(1); +} + +/* Footer */ +[data-theme="dark"] footer#footer { + background-color: var(--base-navy) !important; +} + +/* Tabelas */ +[data-theme="dark"] table.table > thead * { + background-color: var(--black-700) !important; + color: var(--white-500) !important; +} + +[data-theme="dark"] table.table > tbody tr, +[data-theme="dark"] table.table > tbody tr th, +[data-theme="dark"] table.table > tbody tr td { + background-color: var(--base-black) !important; + color: var(--white-700) !important; +} + +[data-theme="dark"] table.table > tbody tr:hover th, +[data-theme="dark"] table.table > tbody tr:hover td { + background-color: var(--black-700) !important; +} + +/* Inputs e Formulários */ +[data-theme="dark"] input, +[data-theme="dark"] textarea, +[data-theme="dark"] select { + background-color: var(--black-700) !important; + color: var(--base-white) !important; + border-color: var(--black-500) !important; +} + +[data-theme="dark"] input::placeholder, +[data-theme="dark"] textarea::placeholder { + color: var(--white-700) !important; +} + +/* Labels */ +[data-theme="dark"] label { + color: var(--base-white) !important; +} + +/* Cards e Containers */ +[data-theme="dark"] .card, +[data-theme="dark"] div[class*="card"] { + background-color: var(--black-700) !important; + border-color: var(--black-500) !important; +} + +[data-theme="dark"] .card:hover, +[data-theme="dark"] div[class*="card"]:hover { + background-color: var(--black-700) !important; + border-color: var(--royal-500) !important; + box-shadow: 0 4px 12px rgba(35, 111, 189, 0.3) !important; +} + +[data-theme="dark"] .card-body, +[data-theme="dark"] div[class*="card-content"] { + background-color: transparent !important; +} + +[data-theme="dark"] .card-title, +[data-theme="dark"] .card h1, +[data-theme="dark"] .card h2, +[data-theme="dark"] .card h3, +[data-theme="dark"] .card h4, +[data-theme="dark"] .card h5, +[data-theme="dark"] .card h6, +[data-theme="dark"] div[class*="card"] h1, +[data-theme="dark"] div[class*="card"] h2, +[data-theme="dark"] div[class*="card"] h3 { + color: var(--base-white) !important; +} + +[data-theme="dark"] .card-text, +[data-theme="dark"] .card p, +[data-theme="dark"] div[class*="card"] p { + color: var(--white-500) !important; +} + +/* Ícone dentro dos cards */ +[data-theme="dark"] .card-icon, +[data-theme="dark"] div[class*="card-icon"] { + background-color: var(--black-500) !important; +} + +[data-theme="dark"] .card:hover .card-icon, +[data-theme="dark"] div[class*="card"]:hover div[class*="card-icon"] { + background-color: var(--black-500) !important; +} + +[data-theme="dark"] .card svg, +[data-theme="dark"] .card i, +[data-theme="dark"] div[class*="card"] svg, +[data-theme="dark"] div[class*="card"] i { + filter: brightness(1) !important; +} + +[data-theme="dark"] .container, +[data-theme="dark"] .container-sm, +[data-theme="dark"] .container-fluid, +[data-theme="dark"] [class*="container"] > div { + background-color: transparent !important; + color: var(--base-white) !important; +} + +/* Títulos */ +[data-theme="dark"] h1, +[data-theme="dark"] h2, +[data-theme="dark"] h3, +[data-theme="dark"] h4, +[data-theme="dark"] h5, +[data-theme="dark"] h6 { + color: var(--base-white) !important; +} + +/* Parágrafos */ +[data-theme="dark"] p { + color: var(--white-500) !important; +} + +/* Links */ +[data-theme="dark"] a { + color: var(--royal-300) !important; +} + +[data-theme="dark"] a:hover { + color: var(--royal-500) !important; +} + +/* Dropzone (DragDrop) */ +[data-theme="dark"] [class*="dropzone"], +[data-theme="dark"] [class*="drop-zone"] { + background-color: var(--black-700) !important; + color: var(--base-white) !important; +} + +/* Step Map */ +[data-theme="dark"] [class*="step-map-circle"] { + background-color: var(--black-700) !important; + color: var(--base-white) !important; +} + +[data-theme="dark"] [class*="step-map-count"], +[data-theme="dark"] [class*="step-map-label"] { + color: var(--base-white) !important; +} + +[data-theme="dark"] [class*="step-map-load"], +[data-theme="dark"] [class*="loading"], +[data-theme="dark"] .fa-spinner, +[data-theme="dark"] .fa-circle-notch, +[data-theme="dark"] .fa-arrow-rotate-right { + color: var(--base-white) !important; +} + +/* Dropdown Options */ +[data-theme="dark"] [class*="option"] { + background-color: var(--black-700) !important; + color: var(--base-white) !important; +} + +[data-theme="dark"] [class*="option"]:hover { + background-color: var(--black-500) !important; +} + +/* Modals e Overlays */ +[data-theme="dark"] .modal-content { + background-color: var(--black-700) !important; + color: var(--base-white) !important; +} + +/* Borders gerais */ +[data-theme="dark"] [class*="border"], +[data-theme="dark"] .border { + border-color: var(--black-500) !important; +} + +/* Botão de Toggle - Minimalista com Bootstrap Icons */ +#dark-mode-toggle { + background: transparent; + border: none; + cursor: pointer; + font-size: 1.1rem; + padding: 8px 10px; + margin-left: 0.5rem; + display: inline-flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + color: var(--base-navy) !important; +} + +#dark-mode-toggle:hover { + color: var(--royal-700) !important; + transform: scale(1.15); +} + +[data-theme="dark"] #dark-mode-toggle { + color: var(--base-white) !important; +} + +[data-theme="dark"] #dark-mode-toggle:hover { + color: var(--white-300) !important; +} + +/* Homepage - Elementos destacados (Descriptum e PDF) */ +[data-theme="dark"] b { + background: var(--black-500) !important; + color: var(--base-white) !important; +} + +[data-theme="dark"] abbr { + background: var(--black-500) !important; + color: var(--base-white) !important; +} + +/* Botões outlined no modo escuro */ +[data-theme="dark"] button[class*="outlined"], +[data-theme="dark"] a[class*="outlined"] { + border-color: var(--white-500) !important; + color: var(--base-white) !important; +} + +[data-theme="dark"] button[class*="outlined"]:hover, +[data-theme="dark"] a[class*="outlined"]:hover { + background-color: var(--black-500) !important; + border-color: var(--base-white) !important; + color: var(--base-white) !important; +} + +/* Transições suaves */ +[data-theme="dark"] * { + transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; +}