🎨 v1.40.0 - Sidebar Mobile Overlay: UX Reimaginada

Substituída sidebar comprimida por overlay slide-in moderna:

MOBILE (<768px):
- Sidebar ESCONDIDA por padrão (left: -100%)
- Conteúdo em tela cheia (app-main margin-left: 0, width: 100%)
- Hamburger menu (☰) 44x44px no header
- Click hamburger: sidebar slide-in de 280px sobre conteúdo
- Backdrop escuro (rgba(0,0,0,0.5)) para fechar ao clicar fora
- Animação: cubic-bezier(0.4, 0, 0.2, 1) 0.3s
- Shadow: 4px 0 12px rgba(0,0,0,0.5) quando aberta
- Classe .mobile-open para estado aberto
- Auto-close ao redimensionar para desktop

DESKTOP (>=769px):
- Mantém comportamento atual (sidebar fixa colapsável)
- Hamburger e backdrop escondidos
- Toggle colapsa/expande (não esconde)

COMPONENTE (Layout.jsx):
- Estado sidebarOpen (mobile) separado de sidebarCollapsed (desktop)
- isMobile() helper para detectar <768px
- toggleSidebar() inteligente (mobile: open/close | desktop: collapse/expand)
- handleBackdropClick() fecha sidebar mobile
- useEffect cleanup ao resize

CSS:
- @media (max-width: 768px) com sidebar overlay
- @media (min-width: 769px) esconde mobile-only elements
- Removidas regras conflitantes de sidebar 56px/50px
- Safe area: padding-left/right 0 em body para permitir overlay
- Touch target hamburger: 44x44px com hover effect

Deploy: frontend/dist
This commit is contained in:
marcoitaloesp-ai 2025-12-16 08:35:10 +00:00 committed by GitHub
parent db82316461
commit 3ba4bed1c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 153 additions and 88 deletions

View File

@ -5,6 +5,26 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/).
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/). Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
## [1.40.0] - 2025-12-16
### Changed
- **SIDEBAR MOBILE OVERLAY** - UX completamente reimaginada para mobile
- Sidebar escondida por padrão em mobile (<768px) - conteúdo em tela cheia
- Hamburger menu (☰) no header para abrir sidebar
- Sidebar slide-in overlay (280px) sobre o conteúdo
- Backdrop escuro (rgba(0,0,0,0.5)) para fechar ao clicar fora
- Animação suave: cubic-bezier(0.4, 0, 0.2, 1) 0.3s
- Shadow: 4px 0 12px quando aberta
- App-main: margin-left 0 em mobile (100% width)
- Fechamento automático ao redimensionar para desktop
- Mantém comportamento desktop: sidebar fixa colapsável
### Fixed
- **Layout mobile** - Agora conteúdo usa 100% da largura da tela
- **Touch target** - Hamburger menu 44x44px (Apple HIG)
- **Safe areas** - Removido padding-left/right do body para permitir overlay
- **Backdrop** - Escondido em desktop, visível apenas em mobile
## [1.39.2] - 2025-12-16 ## [1.39.2] - 2025-12-16
### Fixed ### Fixed

View File

@ -1 +1 @@
1.39.2 1.40.0

View File

@ -13,20 +13,38 @@ const Layout = ({ children }) => {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const { date } = useFormatters(); const { date } = useFormatters();
// Sidebar colapsado por padrão em mobile, expandido em desktop // Mobile: sidebar oculta por padrão | Desktop: expandida
const getInitialSidebarState = () => window.innerWidth < 1024; const isMobile = () => window.innerWidth < 768;
const [sidebarCollapsed, setSidebarCollapsed] = useState(getInitialSidebarState); const [sidebarOpen, setSidebarOpen] = useState(false); // Mobile: inicia fechada
const [sidebarCollapsed, setSidebarCollapsed] = useState(false); // Desktop: colapsado ou não
// Atualizar estado do sidebar quando a tela for redimensionada // Atualizar estado do sidebar quando a tela for redimensionada
useEffect(() => { useEffect(() => {
const handleResize = () => { const handleResize = () => {
const isMobile = window.innerWidth < 1024; if (!isMobile() && sidebarOpen) {
setSidebarCollapsed(isMobile); setSidebarOpen(false); // Fecha sidebar mobile ao mudar para desktop
}
}; };
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize);
}, []); }, [sidebarOpen]);
// Fecha sidebar mobile ao clicar fora (backdrop)
const handleBackdropClick = () => {
if (isMobile()) {
setSidebarOpen(false);
}
};
// Toggle sidebar (mobile: abre/fecha | desktop: expande/colapsa)
const toggleSidebar = () => {
if (isMobile()) {
setSidebarOpen(!sidebarOpen);
} else {
setSidebarCollapsed(!sidebarCollapsed);
}
};
const [expandedGroups, setExpandedGroups] = useState({ const [expandedGroups, setExpandedGroups] = useState({
movements: true, movements: true,
@ -104,8 +122,13 @@ const Layout = ({ children }) => {
return ( return (
<div className="app-layout"> <div className="app-layout">
{/* Mobile Backdrop */}
{sidebarOpen && (
<div className="sidebar-backdrop" onClick={handleBackdropClick}></div>
)}
{/* Sidebar */} {/* Sidebar */}
<aside className={`app-sidebar ${sidebarCollapsed ? 'collapsed' : ''}`}> <aside className={`app-sidebar ${sidebarCollapsed ? 'collapsed' : ''} ${sidebarOpen ? 'mobile-open' : ''}`}>
{/* Logo Section */} {/* Logo Section */}
<div className="sidebar-logo"> <div className="sidebar-logo">
<div className="logo-content"> <div className="logo-content">
@ -117,7 +140,7 @@ const Layout = ({ children }) => {
)} )}
<button <button
className="sidebar-toggle" className="sidebar-toggle"
onClick={() => setSidebarCollapsed(!sidebarCollapsed)} onClick={toggleSidebar}
title={sidebarCollapsed ? t('categories.expand') : t('categories.collapse')} title={sidebarCollapsed ? t('categories.expand') : t('categories.collapse')}
> >
<i className={`bi ${sidebarCollapsed ? 'bi-chevron-right' : 'bi-chevron-left'}`}></i> <i className={`bi ${sidebarCollapsed ? 'bi-chevron-right' : 'bi-chevron-left'}`}></i>
@ -229,6 +252,11 @@ const Layout = ({ children }) => {
<main className={`app-main ${sidebarCollapsed ? 'expanded' : ''}`}> <main className={`app-main ${sidebarCollapsed ? 'expanded' : ''}`}>
{/* Top Bar */} {/* Top Bar */}
<header className="app-header"> <header className="app-header">
{/* Mobile Hamburger Menu */}
<button className="mobile-menu-toggle" onClick={toggleSidebar}>
<i className="bi bi-list"></i>
</button>
<div className="header-welcome"> <div className="header-welcome">
<span className="welcome-label">{t('dashboard.welcome')},</span> <span className="welcome-label">{t('dashboard.welcome')},</span>
<span className="welcome-name">{user?.name}</span> <span className="welcome-name">{user?.name}</span>

View File

@ -661,32 +661,110 @@ a {
} }
/* Responsive */ /* Responsive */
/* ===== MOBILE SIDEBAR (OVERLAY SLIDE-IN) ===== */
@media (max-width: 768px) { @media (max-width: 768px) {
/* Esconde sidebar por padrão em mobile */
.app-sidebar { .app-sidebar {
width: 60px; position: fixed;
left: -100%;
width: 280px !important;
height: 100vh;
z-index: 1050;
transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: none;
} }
.app-sidebar .logo-brand, /* Sidebar aberta em mobile */
.app-sidebar .sidebar-link-text, .app-sidebar.mobile-open {
.app-sidebar .user-details { left: 0 !important;
display: none; box-shadow: 4px 0 12px rgba(0, 0, 0, 0.5);
} }
.app-sidebar .sidebar-link { /* Mostra tudo quando sidebar mobile está aberta */
justify-content: center; .app-sidebar.mobile-open .logo-brand,
padding: 0.625rem; .app-sidebar.mobile-open .sidebar-link-text,
.app-sidebar.mobile-open .user-details {
display: flex !important;
opacity: 1 !important;
visibility: visible !important;
} }
/* Backdrop para fechar sidebar */
.sidebar-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1040;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Conteúdo principal ocupa toda largura em mobile */
.app-main { .app-main {
margin-left: 60px; margin-left: 0 !important;
width: 100% !important;
}
/* Hamburger menu button */
.mobile-menu-toggle {
display: flex !important;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
background: transparent;
border: none;
color: #f1f5f9;
font-size: 1.5rem;
cursor: pointer;
transition: all 0.2s ease;
border-radius: 0.5rem;
margin-right: 0.75rem;
}
.mobile-menu-toggle:hover {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}
.mobile-menu-toggle:active {
transform: scale(0.95);
}
/* Ajusta header mobile */
.app-header {
padding: 0.75rem 1rem !important;
}
.header-welcome {
font-size: 0.9rem !important;
} }
.header-date { .header-date {
display: none; display: none !important;
} }
/* Conteúdo principal com padding reduzido */
.app-content { .app-content {
padding: 1rem; padding: 0.75rem !important;
}
}
/* Desktop: esconde hamburger e backdrop */
@media (min-width: 769px) {
.mobile-menu-toggle {
display: none !important;
}
.sidebar-backdrop {
display: none !important;
} }
} }
@ -2450,49 +2528,6 @@ a.btn,
touch-action: manipulation; /* Desabilita zoom duplo-toque */ touch-action: manipulation; /* Desabilita zoom duplo-toque */
} }
/* Forçar sidebar colapsada em mobile (<768px) */
@media (max-width: 768px) {
.app-sidebar {
width: 60px !important;
}
.app-main {
margin-left: 60px !important;
}
/* Forçar esconder texto em mobile */
.app-sidebar .logo-brand,
.app-sidebar .sidebar-link-text,
.app-sidebar .user-details,
.app-sidebar .user-name,
.app-sidebar .user-email {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
/* Centralizar ícones */
.app-sidebar .sidebar-link {
justify-content: center !important;
padding: 0.75rem !important;
}
.app-sidebar .sidebar-group-toggle {
justify-content: center !important;
padding: 0.75rem !important;
}
/* Ajustar conteúdo principal */
.app-content {
padding: 0.75rem !important;
}
.container-fluid {
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
}
}
/* Cards e containers em telas pequenas */ /* Cards e containers em telas pequenas */
@media (max-width: 576px) { @media (max-width: 576px) {
.card { .card {
@ -2542,15 +2577,6 @@ a.btn,
@media (max-width: 430px) { @media (max-width: 430px) {
/* iPhone 14 Pro Max e menores */ /* iPhone 14 Pro Max e menores */
/* Forçar sidebar ainda mais compacta */
.app-sidebar {
width: 56px !important;
}
.app-main {
margin-left: 56px !important;
}
/* Ajusta tamanho de fonte para melhor legibilidade */ /* Ajusta tamanho de fonte para melhor legibilidade */
body { body {
font-size: 10pt !important; font-size: 10pt !important;
@ -2654,15 +2680,6 @@ a.btn,
/* iPhone em landscape (horizontal) */ /* iPhone em landscape (horizontal) */
@media (max-height: 430px) and (orientation: landscape) { @media (max-height: 430px) and (orientation: landscape) {
/* Sidebar ainda mais compacta em landscape */
.app-sidebar {
width: 50px !important;
}
.app-main {
margin-left: 50px !important;
}
/* Reduz altura de elementos */ /* Reduz altura de elementos */
.card-body { .card-body {
padding: 0.5rem !important; padding: 0.5rem !important;
@ -2742,19 +2759,20 @@ a,
body { body {
padding-top: max(env(safe-area-inset-top), 0px) !important; padding-top: max(env(safe-area-inset-top), 0px) !important;
padding-bottom: max(env(safe-area-inset-bottom), 0px) !important; padding-bottom: max(env(safe-area-inset-bottom), 0px) !important;
padding-left: max(env(safe-area-inset-left), 0px) !important; padding-left: 0 !important; /* Remove lateral para permitir sidebar overlay */
padding-right: max(env(safe-area-inset-right), 0px) !important; padding-right: 0 !important;
} }
/* Ajusta sidebar fixa com safe area */ /* Ajusta sidebar com safe area */
.app-sidebar { .app-sidebar {
padding-top: env(safe-area-inset-top) !important; padding-top: env(safe-area-inset-top) !important;
padding-bottom: env(safe-area-inset-bottom) !important; padding-bottom: env(safe-area-inset-bottom) !important;
} }
/* Ajusta content com safe area */ /* Ajusta content com safe area */
.app-main { .app-content {
padding-bottom: env(safe-area-inset-bottom) !important; padding-left: max(env(safe-area-inset-left), 0.75rem) !important;
padding-right: max(env(safe-area-inset-right), 0.75rem) !important;
} }
/* Ajusta modais com safe area */ /* Ajusta modais com safe area */
@ -2772,11 +2790,10 @@ a,
-webkit-overflow-scrolling: touch !important; -webkit-overflow-scrolling: touch !important;
} }
/* Garante que sidebar não cause overflow */ /* Garante que sidebar funcione no PWA */
.app-sidebar { .app-sidebar {
position: fixed !important; position: fixed !important;
top: 0 !important; top: 0 !important;
left: 0 !important;
height: 100vh !important; height: 100vh !important;
overflow-y: auto !important; overflow-y: auto !important;
-webkit-overflow-scrolling: touch !important; -webkit-overflow-scrolling: touch !important;