feat: implement mobile-responsive tab navigation with select dropdown

- Replace tabs with select dropdown on mobile devices for better UX
- Add responsive Layout.Header for mobile with proper styling
- Implement hybrid CSS approach: static styles in CSS, dynamic in inline
- Add mobile-specific select styling with transparent background
- Remove unused CaretDownOutlined import, use EllipsisOutlined for dropdown
- Optimize mobile header layout with compact zoom controls
- Separate mobile and desktop rendering logic for better maintainability
- Add CSS class for mobile project header with proper positioning
This commit is contained in:
Johannes
2025-09-12 18:07:53 +02:00
parent 1390e35efe
commit 8ab35c79af
2 changed files with 107 additions and 28 deletions

View File

@@ -13,12 +13,12 @@
color: #fff;
}
.ant-tabs > .ant-tabs-nav button.ant-btn-variant-outlined:disabled {
button.ant-btn-variant-outlined:disabled {
background: rgba(255, 255, 255, 0.2);
}
.ant-tabs > .ant-tabs-nav button.ant-tabs-nav-more {
color: #fff;
.ant-tabs-nav button.ant-tabs-nav-more {
color: #fff !important;
}
.ant-layout-header.overview-header {
@@ -41,4 +41,35 @@
.ant-tabs-content {
padding: 0px 16px; /* Reduced padding on mobile */
}
}
/* Mobile project header */
.mobile-project-header {
padding: 0px 16px !important;
position: sticky !important;
top: 0 !important;
z-index: 100 !important;
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
gap: 12px !important;
height: auto !important;
min-height: 64px !important;
}
/* Mobile select styling */
@media (max-width: 768px) {
.ant-select .ant-select-selector {
background: rgba(255, 255, 255, 0.1) !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
color: #fff !important;
}
.ant-select .ant-select-selection-item {
color: #fff !important;
}
.ant-select .ant-select-arrow {
color: #fff !important;
}
}

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Tabs, Button, Spin, Skeleton, Result, Grid } from 'antd';
import { Tabs, Button, Spin, Skeleton, Result, Grid, Select, Layout } from 'antd';
import { ArrowLeftOutlined, LoadingOutlined, LockOutlined, MinusOutlined, PlusOutlined, EllipsisOutlined } from '@ant-design/icons';
import UserMenu from '../components/UserMenu';
import FilePreview, { formatFileSize, formatDuration } from '../components/FilePreview';
@@ -243,45 +243,93 @@ function ProjectDetail({ user, darkMode, onLogout, onToggleDarkMode, overridePar
}
};
// Finde den aktuellen Tab-Content
const activeTabContent = tabs.find(tab => tab.key === activeKey)?.children;
return (
<div>
<Spin spinning={loading} indicator={<LoadingOutlined spin />} size="large" tip="Lade Projektdaten..." fullscreen />
{!loading && (
tabs.length > 0 ? (
<Tabs
items={tabs}
more={{ icon: <EllipsisOutlined />, trigger: 'click' }}
activeKey={activeKey}
onChange={handleTabChange}
tabBarExtraContent={{
left: (
<div style={{ display: 'flex', alignItems: 'center' }}>
isMobile ? (
// Mobile Darstellung mit Select und separatem Content
<Layout>
<Layout.Header className="mobile-project-header" style={{
background: darkMode ? '#001f1e' : '#001f1e', // Dynamisch, falls später unterschiedliche Themes gewünscht
color: darkMode ? '#fff' : '#fff'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flex: 1 }}>
<Button
icon={<ArrowLeftOutlined style={{ color: '#001f1e' }} />}
onClick={() => navigate('/')}
style={{ marginRight: projectLogo ? 12 : 0 }}
size="small"
/>
<Select
value={activeKey}
onChange={handleTabChange}
style={{
flex: 1,
minWidth: 120,
height: 26
}}
size="middle"
suffixIcon={<EllipsisOutlined style={{ color: '#fff' }} />}
options={tabs.map(tab => ({
value: tab.key,
label: tab.label
}))}
/>
{projectLogo && !isMobile && (
<img
src={projectLogo}
alt="Logo"
style={{ height: 38, marginRight: 32, objectFit: 'contain' }}
/>
)}
</div>
),
right: (
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<Button size="small" shape="circle" icon={<MinusOutlined style={{ color: '#001f1e' }} />} onClick={() => handleZoom('out')} disabled={zoom <= ZOOM_LEVELS[0]}></Button>
<span style={{ minWidth: 48, textAlign: 'center' }}>{Math.round(zoom * 100)}%</span>
<span style={{ minWidth: 36, textAlign: 'center', fontSize: 12 }}>{Math.round(zoom * 100)}%</span>
<Button size="small" shape="circle" icon={<PlusOutlined style={{ color: '#001f1e' }} />} onClick={() => handleZoom('in')} disabled={zoom >= ZOOM_LEVELS[ZOOM_LEVELS.length - 1]}></Button>
</div>
<UserMenu user={user} onLogout={onLogout} darkMode={darkMode} onToggleDarkMode={onToggleDarkMode} />
</div>
)
}}
/>
</Layout.Header>
<Layout.Content style={{ padding: '0px 16px' }}>
{activeTabContent}
</Layout.Content>
</Layout>
) : (
// Desktop Darstellung mit Tabs
<Tabs
items={tabs}
more={{ icon: <EllipsisOutlined />, trigger: 'click' }}
activeKey={activeKey}
onChange={handleTabChange}
tabBarExtraContent={{
left: (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Button
icon={<ArrowLeftOutlined style={{ color: '#001f1e' }} />}
onClick={() => navigate('/')}
style={{ marginRight: projectLogo ? 12 : 0 }}
/>
{projectLogo && (
<img
src={projectLogo}
alt="Logo"
style={{ height: 38, marginRight: 32, objectFit: 'contain' }}
/>
)}
</div>
),
right: (
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Button size="small" shape="circle" icon={<MinusOutlined style={{ color: '#001f1e' }} />} onClick={() => handleZoom('out')} disabled={zoom <= ZOOM_LEVELS[0]}></Button>
<span style={{ minWidth: 48, textAlign: 'center' }}>{Math.round(zoom * 100)}%</span>
<Button size="small" shape="circle" icon={<PlusOutlined style={{ color: '#001f1e' }} />} onClick={() => handleZoom('in')} disabled={zoom >= ZOOM_LEVELS[ZOOM_LEVELS.length - 1]}></Button>
</div>
<UserMenu user={user} onLogout={onLogout} darkMode={darkMode} onToggleDarkMode={onToggleDarkMode} />
</div>
)
}}
/>
)
) : error ? (
// Spezifische Fehlerbehandlung
<Result