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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user