Mobile user menu as bottom sheet instead of dropdown
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

On mobile (≤768px), user menu now slides up from bottom as a sheet:
- Full name displayed in header with avatar
- Larger touch targets (14px padding)
- Dark overlay behind (tap to close)
- Smooth slide-up animation (translateY)
- Safe area inset for iPhone notch/home indicator
- Handle bar at top (standard bottom sheet pattern)
Desktop behavior unchanged (absolute dropdown).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-25 15:31:26 +01:00
parent b28d3c1879
commit abd2a8a95c

View File

@ -946,6 +946,73 @@
display: block;
}
/* Mobile: bottom sheet */
@media (max-width: 768px) {
.user-menu-overlay {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.4);
z-index: 9998;
}
.user-menu-overlay.show { display: block; }
.user-menu {
position: fixed;
top: auto;
bottom: 0;
left: 0;
right: 0;
min-width: 100%;
border-radius: 16px 16px 0 0;
border: none;
box-shadow: 0 -4px 20px rgba(0,0,0,0.15);
z-index: 9999;
padding-bottom: env(safe-area-inset-bottom, 16px);
transform: translateY(100%);
transition: transform 0.25s ease-out;
}
.user-menu.show {
display: block;
transform: translateY(0);
}
.user-menu-handle {
display: block;
width: 36px;
height: 4px;
background: #d1d5db;
border-radius: 2px;
margin: 10px auto 4px;
}
.user-menu-mobile-header {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 16px 12px;
border-bottom: 1px solid var(--border);
}
.user-menu-mobile-header .user-avatar {
width: 40px;
height: 40px;
font-size: 16px;
}
.user-menu-mobile-name {
font-weight: 600;
font-size: 15px;
color: var(--text-primary);
}
.user-menu-item {
padding: 14px 16px;
font-size: 15px;
}
}
@media (min-width: 769px) {
.user-menu-overlay { display: none !important; }
.user-menu-handle { display: none; }
.user-menu-mobile-header { display: none; }
}
.user-menu-item {
display: flex;
align-items: center;
@ -1552,7 +1619,13 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div class="user-menu-overlay" id="userMenuOverlay" onclick="closeUserMenu()"></div>
<div class="user-menu" id="userMenu">
<div class="user-menu-handle"></div>
<div class="user-menu-mobile-header">
<span class="user-avatar">{{ current_user.name[:1].upper() }}</span>
<span class="user-menu-mobile-name">{{ current_user.name }}</span>
</div>
<a href="{{ url_for('dashboard') }}" class="user-menu-item">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
@ -2002,23 +2075,36 @@
event.stopPropagation();
const userDropdown = document.querySelector('.user-dropdown');
const userMenu = document.getElementById('userMenu');
const overlay = document.getElementById('userMenuOverlay');
// Close notifications if open
const notificationsMenu = document.getElementById('notificationsMenu');
if (notificationsMenu) notificationsMenu.classList.remove('show');
userDropdown.classList.toggle('active');
userMenu.classList.toggle('show');
const isOpen = userMenu.classList.contains('show');
if (isOpen) {
closeUserMenu();
} else {
userDropdown.classList.add('active');
userMenu.classList.add('show');
if (overlay) overlay.classList.add('show');
}
}
// Close user menu when clicking outside
document.addEventListener('click', function(event) {
function closeUserMenu() {
const userDropdown = document.querySelector('.user-dropdown');
const userMenu = document.getElementById('userMenu');
const overlay = document.getElementById('userMenuOverlay');
if (userDropdown) userDropdown.classList.remove('active');
if (userMenu) userMenu.classList.remove('show');
if (overlay) overlay.classList.remove('show');
}
if (userDropdown && userMenu && !userDropdown.contains(event.target)) {
userDropdown.classList.remove('active');
userMenu.classList.remove('show');
// Close user menu when clicking outside (desktop)
document.addEventListener('click', function(event) {
const userDropdown = document.querySelector('.user-dropdown');
if (userDropdown && !userDropdown.contains(event.target)) {
closeUserMenu();
}
});