From 9865a32e99a17d23413b8e192b1b34fa77310e50 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Sun, 1 Mar 2026 13:46:21 +0100
Subject: [PATCH] fix(mobile): stabilize responsive layouts and action wrapping
Improve mobile and tablet usability by fixing viewport and scroll behavior, adding a mobile sidebar flow, and ensuring header action buttons wrap instead of clipping on narrow screens.
Made-with: Cursor
---
.../Navigation/UserSection.module.css | 14 +++
.../NotificationBell.module.css | 10 ++
.../MessageOverlay.module.css | 10 +-
src/index.css | 11 +-
src/layouts/FeatureLayout.module.css | 11 ++
src/layouts/MainLayout.module.css | 107 +++++++++++++++++-
src/layouts/MainLayout.tsx | 45 +++++++-
src/pages/FeatureView.module.css | 11 ++
src/pages/admin/Admin.module.css | 21 ++++
src/pages/basedata/BasedataPages.module.css | 12 ++
src/styles/pages.module.css | 66 +++++++++--
11 files changed, 300 insertions(+), 18 deletions(-)
diff --git a/src/components/Navigation/UserSection.module.css b/src/components/Navigation/UserSection.module.css
index 868f779..0ee90e2 100644
--- a/src/components/Navigation/UserSection.module.css
+++ b/src/components/Navigation/UserSection.module.css
@@ -8,6 +8,7 @@
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
+ padding-bottom: max(0.5rem, env(safe-area-inset-bottom));
border-top: 1px solid var(--border-color, #e0e0e0);
}
@@ -94,6 +95,19 @@
z-index: 100;
}
+@media (max-width: 1024px) {
+ .menu {
+ left: 0.25rem;
+ right: 0.25rem;
+ max-height: min(60dvh, 420px);
+ overflow-y: auto;
+ }
+
+ .userButton {
+ min-height: 44px;
+ }
+}
+
.menuItem {
display: flex;
align-items: center;
diff --git a/src/components/NotificationBell/NotificationBell.module.css b/src/components/NotificationBell/NotificationBell.module.css
index 671bf6f..34b5a53 100644
--- a/src/components/NotificationBell/NotificationBell.module.css
+++ b/src/components/NotificationBell/NotificationBell.module.css
@@ -366,3 +366,13 @@
.content::-webkit-scrollbar-thumb:hover {
background: var(--text-muted, #999);
}
+
+@media (max-width: 1024px) {
+ .dropdown {
+ left: 0.75rem;
+ right: 0.75rem;
+ width: auto;
+ bottom: calc(76px + env(safe-area-inset-bottom));
+ max-height: min(70dvh, 520px);
+ }
+}
diff --git a/src/components/UiComponents/InfoMessageOverlay/MessageOverlay.module.css b/src/components/UiComponents/InfoMessageOverlay/MessageOverlay.module.css
index 3b0499b..4dc813f 100644
--- a/src/components/UiComponents/InfoMessageOverlay/MessageOverlay.module.css
+++ b/src/components/UiComponents/InfoMessageOverlay/MessageOverlay.module.css
@@ -5,7 +5,7 @@
right: 0;
bottom: 0;
width: 100vw;
- height: 100vh;
+ height: 100dvh;
z-index: 9999;
backdrop-filter: blur(8px);
background-color: rgba(0, 0, 0, 0.1);
@@ -13,6 +13,12 @@
animation: fadeIn 0.3s ease-out forwards;
}
+@supports not (height: 100dvh) {
+ .messageOverlay {
+ height: 100vh;
+ }
+}
+
.messageOverlay.closing {
animation: fadeOut 0.3s ease-out forwards;
pointer-events: none;
@@ -64,6 +70,8 @@
align-items: flex-start;
padding: 30px;
pointer-events: none;
+ overflow-y: auto;
+ max-height: 100%;
}
.messageContent {
diff --git a/src/index.css b/src/index.css
index c07a664..f4923b5 100644
--- a/src/index.css
+++ b/src/index.css
@@ -6,14 +6,17 @@
html, body {
margin: 0;
padding: 0;
- height: 100%;
- overflow: hidden;
+ min-height: 100%;
+ width: 100%;
+ overflow-x: hidden;
+ overflow-y: auto;
font-family: var(--font-family, "DM Sans", sans-serif);
}
#root {
- height: 100vh;
- width: 100vw;
+ min-height: 100vh;
+ min-height: 100dvh;
+ width: 100%;
margin: 0;
padding: 0;
font-family: var(--font-family, "DM Sans", sans-serif);
diff --git a/src/layouts/FeatureLayout.module.css b/src/layouts/FeatureLayout.module.css
index aeca795..c639a07 100644
--- a/src/layouts/FeatureLayout.module.css
+++ b/src/layouts/FeatureLayout.module.css
@@ -144,6 +144,7 @@
/* Maintain flex chain for child components */
display: flex;
flex-direction: column;
+ min-height: 0;
}
/* Dark Theme */
@@ -176,3 +177,13 @@
:global(.dark-theme) .errorContainer p {
color: var(--text-secondary-dark, #aaa);
}
+
+@media (max-width: 1024px) {
+ .featureHeader {
+ padding: 0.75rem 1rem;
+ }
+
+ .featureContent {
+ padding: 1rem;
+ }
+}
diff --git a/src/layouts/MainLayout.module.css b/src/layouts/MainLayout.module.css
index ac71c8b..3516819 100644
--- a/src/layouts/MainLayout.module.css
+++ b/src/layouts/MainLayout.module.css
@@ -4,12 +4,20 @@
.mainLayout {
display: flex;
- height: 100vh;
- width: 100vw;
+ height: 100dvh;
+ min-height: 100dvh;
+ width: 100%;
overflow: hidden;
background: var(--bg-primary, #ffffff);
}
+@supports not (height: 100dvh) {
+ .mainLayout {
+ height: 100vh;
+ min-height: 100vh;
+ }
+}
+
/* Sidebar */
.sidebar {
display: flex;
@@ -19,7 +27,8 @@
height: 100%;
background: var(--surface-color, #f8f9fa);
border-right: 1px solid var(--border-color, #e0e0e0);
- overflow: visible;
+ overflow: hidden;
+ z-index: 1200;
}
/* Logo */
@@ -81,11 +90,39 @@
/* Content */
.content {
flex: 1;
+ min-width: 0;
+ min-height: 0;
/* Let child components handle their own scrolling for sticky headers */
overflow: hidden;
background: var(--bg-primary, #ffffff);
}
+.mobileTopBar {
+ display: none;
+}
+
+.mobileMenuButton {
+ border: 1px solid var(--border-color, #e0e0e0);
+ background: var(--bg-primary, #ffffff);
+ color: var(--text-primary, #1a1a1a);
+ width: 40px;
+ height: 40px;
+ border-radius: 10px;
+ font-size: 1.2rem;
+ line-height: 1;
+ cursor: pointer;
+}
+
+.mobileLogo {
+ height: 32px;
+ width: auto;
+ object-fit: contain;
+}
+
+.mobileBackdrop {
+ display: none;
+}
+
/* Dark Theme */
:global(.dark-theme) .mainLayout {
background: var(--bg-dark, #0a0a0a);
@@ -116,6 +153,12 @@
background: var(--bg-dark, #0a0a0a);
}
+:global(.dark-theme) .mobileMenuButton {
+ border-color: var(--border-dark, #333);
+ background: var(--surface-dark, #1a1a1a);
+ color: var(--text-primary-dark, #ffffff);
+}
+
/* Scrollbar Styling */
.navigation::-webkit-scrollbar {
width: 6px;
@@ -141,3 +184,61 @@
:global(.dark-theme) .navigation::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary-dark, #666);
}
+
+@media (max-width: 1024px) {
+ .mainLayout {
+ position: relative;
+ }
+
+ .sidebar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ height: 100dvh;
+ transform: translateX(-100%);
+ transition: transform 0.2s ease-in-out;
+ box-shadow: 0 18px 32px rgba(0, 0, 0, 0.2);
+ border-right: 1px solid var(--border-color, #e0e0e0);
+ }
+
+ @supports not (height: 100dvh) {
+ .sidebar {
+ height: 100vh;
+ }
+ }
+
+ .sidebarOpen {
+ transform: translateX(0);
+ }
+
+ .content {
+ overflow: auto;
+ }
+
+ .mobileTopBar {
+ position: sticky;
+ top: 0;
+ z-index: 1100;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.75rem 1rem;
+ background: var(--bg-primary, #ffffff);
+ border-bottom: 1px solid var(--border-color, #e0e0e0);
+ }
+
+ .mobileBackdrop {
+ display: block;
+ position: fixed;
+ inset: 0;
+ border: none;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.35);
+ z-index: 1150;
+ cursor: pointer;
+ }
+}
diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx
index c19321b..02ea999 100644
--- a/src/layouts/MainLayout.tsx
+++ b/src/layouts/MainLayout.tsx
@@ -5,8 +5,8 @@
* Enthält den FeatureProvider für das Multi-Tenant-System.
*/
-import React, { useEffect } from 'react';
-import { Outlet } from 'react-router-dom';
+import React, { useEffect, useState } from 'react';
+import { Outlet, useLocation } from 'react-router-dom';
import { FeatureProvider, useFeatureStore } from '../stores/featureStore';
import { MandateNavigation } from '../components/Navigation/MandateNavigation';
import { UserSection } from '../components/Navigation/UserSection';
@@ -18,6 +18,8 @@ import styles from './MainLayout.module.css';
const MainLayoutInner: React.FC = () => {
const { loadFeatures, initialized, loading, error } = useFeatureStore();
+ const location = useLocation();
+ const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false);
// Features laden beim Mount
useEffect(() => {
@@ -25,11 +27,34 @@ const MainLayoutInner: React.FC = () => {
loadFeatures();
}
}, [initialized, loading, loadFeatures]);
+
+ useEffect(() => {
+ setIsMobileSidebarOpen(false);
+ }, [location.pathname]);
+
+ useEffect(() => {
+ const handleResize = () => {
+ if (window.innerWidth > 1024) {
+ setIsMobileSidebarOpen(false);
+ }
+ };
+
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, []);
return (
+ {isMobileSidebarOpen && (
+