feat: full mobile responsive optimization for Android/iPhone

- Add bottom navigation bar (6 tabs) for mobile, hidden on desktop
- Compact header with smaller logo and reduced padding on mobile
- Collapsible filter bar with active filter count badge on mobile
- Bottom-sheet style dropdowns with overlay and larger touch targets
- Fullscreen AI chat on mobile, floating window on desktop
- Responsive dashboard cards with always-visible expand button
- Smaller table text and touch-friendly scroll on all data grids
- iPhone safe-area support and thinner scrollbars on mobile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Vidal Wolf
2026-02-16 09:27:46 +01:00
co-authored by Claude Opus 4.6
parent fecc7264c8
commit 9895545910
11 changed files with 172 additions and 72 deletions
+27 -4
View File
@@ -38,6 +38,14 @@ const MultiSelectDropdown: React.FC<MultiSelectDropdownProps> = ({ label, select
}
}, [isOpen]);
// Prevent body scroll when dropdown is open on mobile
useEffect(() => {
if (isOpen && window.innerWidth < 768) {
document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = ''; };
}
}, [isOpen]);
const filteredOptions = useMemo(() => {
if (!searchTerm) return options;
return options.filter(opt => opt.toLowerCase().includes(searchTerm.toLowerCase()));
@@ -139,8 +147,23 @@ const MultiSelectDropdown: React.FC<MultiSelectDropdownProps> = ({ label, select
</svg>
</button>
{/* Mobile overlay backdrop */}
{isOpen && (
<div className="absolute top-[calc(100%+4px)] left-0 w-64 max-h-96 overflow-hidden bg-slate-900 border border-slate-700 rounded-xl shadow-2xl z-[100] animate-fade-in flex flex-col">
<div className="fixed inset-0 bg-black/50 z-[99] md:hidden" onClick={() => setIsOpen(false)} />
)}
{isOpen && (
<div className="fixed inset-x-3 bottom-3 max-h-[75vh] md:absolute md:inset-auto md:top-[calc(100%+4px)] md:left-0 md:bottom-auto md:w-64 md:max-h-96 overflow-hidden bg-slate-900 border border-slate-700 rounded-xl shadow-2xl z-[100] animate-fade-in flex flex-col">
{/* Mobile header with close */}
<div className="flex items-center justify-between p-3 border-b border-slate-800 md:hidden">
<h3 className="text-sm font-bold text-white">{label}</h3>
<button onClick={() => setIsOpen(false)} className="text-slate-400 active:text-white p-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" className="w-5 h-5">
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Search Bar */}
<div className="p-2 border-b border-slate-800 sticky top-0 bg-slate-900 z-10">
@@ -209,14 +232,14 @@ const MultiSelectDropdown: React.FC<MultiSelectDropdownProps> = ({ label, select
</button>
</div>
<div className="space-y-1 overflow-y-auto custom-scrollbar p-2 max-h-60">
<div className="space-y-1 overflow-y-auto custom-scrollbar scroll-touch p-2 max-h-60 md:max-h-60">
{filteredOptions.map((opt) => (
<label key={opt} className="flex items-center space-x-3 p-2 rounded hover:bg-slate-800 cursor-pointer group">
<label key={opt} className="flex items-center space-x-3 p-2.5 md:p-2 rounded hover:bg-slate-800 active:bg-slate-700 cursor-pointer group">
<input
type="checkbox"
checked={selected.includes(opt)}
onChange={() => toggleOption(opt)}
className="form-checkbox h-4 w-4 text-primary rounded border-slate-600 bg-slate-800 focus:ring-primary focus:ring-offset-slate-900 transition duration-150 ease-in-out"
className="form-checkbox h-5 w-5 md:h-4 md:w-4 text-primary rounded border-slate-600 bg-slate-800 focus:ring-primary focus:ring-offset-slate-900 transition duration-150 ease-in-out"
/>
<span className={`text-sm break-all group-hover:text-white ${selected.includes(opt) ? 'text-white' : 'text-slate-400'}`}>
{opt}