🎨 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:
parent
db82316461
commit
3ba4bed1c4
20
CHANGELOG.md
20
CHANGELOG.md
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user