🎨 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/).
## [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
### 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 { date } = useFormatters();
// Sidebar colapsado por padrão em mobile, expandido em desktop
const getInitialSidebarState = () => window.innerWidth < 1024;
const [sidebarCollapsed, setSidebarCollapsed] = useState(getInitialSidebarState);
// Mobile: sidebar oculta por padrão | Desktop: expandida
const isMobile = () => window.innerWidth < 768;
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
useEffect(() => {
const handleResize = () => {
const isMobile = window.innerWidth < 1024;
setSidebarCollapsed(isMobile);
if (!isMobile() && sidebarOpen) {
setSidebarOpen(false); // Fecha sidebar mobile ao mudar para desktop
}
};
window.addEventListener('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({
movements: true,
@ -104,8 +122,13 @@ const Layout = ({ children }) => {
return (
<div className="app-layout">
{/* Mobile Backdrop */}
{sidebarOpen && (
<div className="sidebar-backdrop" onClick={handleBackdropClick}></div>
)}
{/* Sidebar */}
<aside className={`app-sidebar ${sidebarCollapsed ? 'collapsed' : ''}`}>
<aside className={`app-sidebar ${sidebarCollapsed ? 'collapsed' : ''} ${sidebarOpen ? 'mobile-open' : ''}`}>
{/* Logo Section */}
<div className="sidebar-logo">
<div className="logo-content">
@ -117,7 +140,7 @@ const Layout = ({ children }) => {
)}
<button
className="sidebar-toggle"
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
onClick={toggleSidebar}
title={sidebarCollapsed ? t('categories.expand') : t('categories.collapse')}
>
<i className={`bi ${sidebarCollapsed ? 'bi-chevron-right' : 'bi-chevron-left'}`}></i>
@ -229,6 +252,11 @@ const Layout = ({ children }) => {
<main className={`app-main ${sidebarCollapsed ? 'expanded' : ''}`}>
{/* Top Bar */}
<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">
<span className="welcome-label">{t('dashboard.welcome')},</span>
<span className="welcome-name">{user?.name}</span>

View File

@ -661,32 +661,110 @@ a {
}
/* Responsive */
/* ===== MOBILE SIDEBAR (OVERLAY SLIDE-IN) ===== */
@media (max-width: 768px) {
/* Esconde sidebar por padrão em mobile */
.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,
.app-sidebar .sidebar-link-text,
.app-sidebar .user-details {
display: none;
/* Sidebar aberta em mobile */
.app-sidebar.mobile-open {
left: 0 !important;
box-shadow: 4px 0 12px rgba(0, 0, 0, 0.5);
}
.app-sidebar .sidebar-link {
justify-content: center;
padding: 0.625rem;
/* Mostra tudo quando sidebar mobile está aberta */
.app-sidebar.mobile-open .logo-brand,
.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 {
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 {
display: none;
display: none !important;
}
/* Conteúdo principal com padding reduzido */
.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 */
}
/* 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 */
@media (max-width: 576px) {
.card {
@ -2542,15 +2577,6 @@ a.btn,
@media (max-width: 430px) {
/* 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 */
body {
font-size: 10pt !important;
@ -2654,15 +2680,6 @@ a.btn,
/* iPhone em landscape (horizontal) */
@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 */
.card-body {
padding: 0.5rem !important;
@ -2742,19 +2759,20 @@ a,
body {
padding-top: max(env(safe-area-inset-top), 0px) !important;
padding-bottom: max(env(safe-area-inset-bottom), 0px) !important;
padding-left: max(env(safe-area-inset-left), 0px) !important;
padding-right: max(env(safe-area-inset-right), 0px) !important;
padding-left: 0 !important; /* Remove lateral para permitir sidebar overlay */
padding-right: 0 !important;
}
/* Ajusta sidebar fixa com safe area */
/* Ajusta sidebar com safe area */
.app-sidebar {
padding-top: env(safe-area-inset-top) !important;
padding-bottom: env(safe-area-inset-bottom) !important;
}
/* Ajusta content com safe area */
.app-main {
padding-bottom: env(safe-area-inset-bottom) !important;
.app-content {
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 */
@ -2772,11 +2790,10 @@ a,
-webkit-overflow-scrolling: touch !important;
}
/* Garante que sidebar não cause overflow */
/* Garante que sidebar funcione no PWA */
.app-sidebar {
position: fixed !important;
top: 0 !important;
left: 0 !important;
height: 100vh !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch !important;