mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
updated more simple modern layout
This commit is contained in:
@@ -1,46 +1,51 @@
|
||||
// frontend/src/components/Layout/Footer.tsx
|
||||
// frontend/src/components/Layout/Footer.tsx - ELEGANT WHITE DESIGN
|
||||
import React from 'react';
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const styles = {
|
||||
footer: {
|
||||
background: '#2c3e50',
|
||||
background: 'linear-gradient(135deg, #1a1325 0%, #24163a 100%)',
|
||||
color: 'white',
|
||||
marginTop: 'auto',
|
||||
borderTop: '1px solid rgba(251, 250, 246, 0.1)',
|
||||
},
|
||||
footerContent: {
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
padding: '2rem 20px',
|
||||
padding: '3rem 2rem 2rem',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
|
||||
gap: '2rem',
|
||||
gap: '3rem',
|
||||
},
|
||||
footerSection: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
},
|
||||
footerSectionH3: {
|
||||
marginBottom: '1rem',
|
||||
color: '#ecf0f1',
|
||||
fontSize: '1.2rem',
|
||||
marginBottom: '1.5rem',
|
||||
color: '#FBFAF6',
|
||||
fontSize: '1.1rem',
|
||||
fontWeight: 600,
|
||||
},
|
||||
footerSectionH4: {
|
||||
marginBottom: '1rem',
|
||||
color: '#ecf0f1',
|
||||
fontSize: '1.1rem',
|
||||
color: '#FBFAF6',
|
||||
fontSize: '1rem',
|
||||
fontWeight: 600,
|
||||
},
|
||||
footerLink: {
|
||||
color: '#bdc3c7',
|
||||
color: 'rgba(251, 250, 246, 0.7)',
|
||||
textDecoration: 'none',
|
||||
marginBottom: '0.5rem',
|
||||
transition: 'color 0.3s ease',
|
||||
marginBottom: '0.75rem',
|
||||
transition: 'all 0.2s ease',
|
||||
fontSize: '0.9rem',
|
||||
},
|
||||
footerBottom: {
|
||||
borderTop: '1px solid #34495e',
|
||||
padding: '1rem 20px',
|
||||
borderTop: '1px solid rgba(251, 250, 246, 0.1)',
|
||||
padding: '1.5rem 2rem',
|
||||
textAlign: 'center' as const,
|
||||
color: '#95a5a6',
|
||||
color: 'rgba(251, 250, 246, 0.6)',
|
||||
fontSize: '0.9rem',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,9 +54,9 @@ const Footer: React.FC = () => {
|
||||
<div style={styles.footerContent}>
|
||||
<div style={styles.footerSection}>
|
||||
<h3 style={styles.footerSectionH3}>Schichtenplaner</h3>
|
||||
<p style={{color: '#bdc3c7', margin: 0}}>
|
||||
<p style={{color: 'rgba(251, 250, 246, 0.7)', margin: 0, lineHeight: 1.6}}>
|
||||
Professionelle Schichtplanung für Ihr Team.
|
||||
Effiziente Personalplanung für optimale Abläufe.
|
||||
Effiziente Personalplanung für optimale Abläube.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -61,10 +66,12 @@ const Footer: React.FC = () => {
|
||||
href="/help"
|
||||
style={styles.footerLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#3498db';
|
||||
e.currentTarget.style.color = '#FBFAF6';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = '#bdc3c7';
|
||||
e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
Hilfe & Anleitungen
|
||||
@@ -73,10 +80,12 @@ const Footer: React.FC = () => {
|
||||
href="/contact"
|
||||
style={styles.footerLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#3498db';
|
||||
e.currentTarget.style.color = '#FBFAF6';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = '#bdc3c7';
|
||||
e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
Kontakt & Support
|
||||
@@ -85,10 +94,12 @@ const Footer: React.FC = () => {
|
||||
href="/faq"
|
||||
style={styles.footerLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#3498db';
|
||||
e.currentTarget.style.color = '#FBFAF6';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = '#bdc3c7';
|
||||
e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
Häufige Fragen
|
||||
@@ -101,10 +112,12 @@ const Footer: React.FC = () => {
|
||||
href="/privacy"
|
||||
style={styles.footerLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#3498db';
|
||||
e.currentTarget.style.color = '#FBFAF6';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = '#bdc3c7';
|
||||
e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
Datenschutzerklärung
|
||||
@@ -113,10 +126,12 @@ const Footer: React.FC = () => {
|
||||
href="/imprint"
|
||||
style={styles.footerLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#3498db';
|
||||
e.currentTarget.style.color = '#FBFAF6';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = '#bdc3c7';
|
||||
e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
Impressum
|
||||
@@ -125,10 +140,12 @@ const Footer: React.FC = () => {
|
||||
href="/terms"
|
||||
style={styles.footerLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#3498db';
|
||||
e.currentTarget.style.color = '#FBFAF6';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = '#bdc3c7';
|
||||
e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
Allgemeine Geschäftsbedingungen
|
||||
@@ -141,10 +158,12 @@ const Footer: React.FC = () => {
|
||||
href="/about"
|
||||
style={styles.footerLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#3498db';
|
||||
e.currentTarget.style.color = '#FBFAF6';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = '#bdc3c7';
|
||||
e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
Über uns
|
||||
@@ -153,10 +172,12 @@ const Footer: React.FC = () => {
|
||||
href="/features"
|
||||
style={styles.footerLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#3498db';
|
||||
e.currentTarget.style.color = '#FBFAF6';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = '#bdc3c7';
|
||||
e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
Funktionen
|
||||
@@ -165,10 +186,12 @@ const Footer: React.FC = () => {
|
||||
href="/pricing"
|
||||
style={styles.footerLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#3498db';
|
||||
e.currentTarget.style.color = '#FBFAF6';
|
||||
e.currentTarget.style.transform = 'translateX(4px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = '#bdc3c7';
|
||||
e.currentTarget.style.color = 'rgba(251, 250, 246, 0.7)';
|
||||
e.currentTarget.style.transform = 'translateX(0)';
|
||||
}}
|
||||
>
|
||||
Preise
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// frontend/src/components/Layout/Layout.tsx - KORRIGIERT
|
||||
// frontend/src/components/Layout/Layout.tsx - ELEGANT WHITE DESIGN
|
||||
import React from 'react';
|
||||
import Navigation from './Navigation';
|
||||
import Footer from './Footer';
|
||||
@@ -13,16 +13,20 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
background: '#FBFAF6',
|
||||
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
||||
lineHeight: 1.6,
|
||||
color: '#161718',
|
||||
},
|
||||
mainContent: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f8f9fa',
|
||||
minHeight: 'calc(100vh - 140px)',
|
||||
paddingTop: '80px',
|
||||
},
|
||||
contentContainer: {
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
padding: '2rem 20px',
|
||||
padding: '3rem 2rem',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
// frontend/src/components/Layout/Navigation.tsx
|
||||
import React, { useState } from 'react';
|
||||
// frontend/src/components/Layout/Navigation.tsx - ELEGANT WHITE DESIGN
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import PillNav from '../PillNav/PillNav';
|
||||
|
||||
const Navigation: React.FC = () => {
|
||||
const { user, logout, hasRole } = useAuth();
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [activePath, setActivePath] = useState('/');
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setActivePath(window.location.pathname);
|
||||
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 10);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
@@ -16,160 +30,194 @@ const Navigation: React.FC = () => {
|
||||
};
|
||||
|
||||
const navigationItems = [
|
||||
{ path: '/', label: '📊 Dashboard', roles: ['admin', 'instandhalter', 'user'] },
|
||||
{ path: '/shift-plans', label: '📅 Schichtpläne', roles: ['admin', 'instandhalter', 'user'] },
|
||||
{ path: '/employees', label: '👥 Mitarbeiter', roles: ['admin', 'instandhalter'] },
|
||||
{ path: '/help', label: '❓ Hilfe & Support', roles: ['admin', 'instandhalter', 'user'] },
|
||||
{ path: '/settings', label: '⚙️ Einstellungen', roles: ['admin', 'instandhalter', 'user'] },
|
||||
{ path: '/', label: 'Dashboard', roles: ['admin', 'instandhalter', 'user'] },
|
||||
{ path: '/shift-plans', label: 'Schichtpläne', roles: ['admin', 'instandhalter', 'user'] },
|
||||
{ path: '/employees', label: 'Mitarbeiter', roles: ['admin', 'instandhalter'] },
|
||||
{ path: '/help', label: 'Hilfe', roles: ['admin', 'instandhalter', 'user'] },
|
||||
{ path: '/settings', label: 'Einstellungen', roles: ['admin', 'instandhalter', 'user'] },
|
||||
];
|
||||
|
||||
const filteredNavigation = navigationItems.filter(item =>
|
||||
hasRole(item.roles)
|
||||
);
|
||||
|
||||
const handlePillChange = (path: string) => {
|
||||
setActivePath(path);
|
||||
window.location.href = path;
|
||||
};
|
||||
|
||||
const pillNavItems = filteredNavigation.map(item => ({
|
||||
id: item.path,
|
||||
label: item.label
|
||||
}));
|
||||
|
||||
const styles = {
|
||||
header: {
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
color: 'white',
|
||||
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
|
||||
position: 'sticky' as const,
|
||||
background: isScrolled
|
||||
? 'rgba(251, 250, 246, 0.95)'
|
||||
: '#FBFAF6',
|
||||
backdropFilter: isScrolled ? 'blur(10px)' : 'none',
|
||||
borderBottom: isScrolled
|
||||
? '1px solid rgba(22, 23, 24, 0.08)'
|
||||
: '1px solid transparent',
|
||||
color: '#161718',
|
||||
position: 'fixed' as const,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 1000,
|
||||
transition: 'all 0.3s ease-in-out',
|
||||
boxShadow: isScrolled
|
||||
? '0 2px 20px rgba(22, 23, 24, 0.06)'
|
||||
: 'none',
|
||||
},
|
||||
headerContent: {
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
padding: '0 20px',
|
||||
padding: '0 2rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
height: '70px',
|
||||
transition: 'all 0.3s ease',
|
||||
},
|
||||
logo: {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
logoH1: {
|
||||
margin: 0,
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 700,
|
||||
color: '#161718',
|
||||
letterSpacing: '-0.02em',
|
||||
},
|
||||
desktopNav: {
|
||||
pillNavWrapper: {
|
||||
display: 'flex',
|
||||
gap: '2rem',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flex: 2,
|
||||
minWidth: 0,
|
||||
},
|
||||
navLink: {
|
||||
color: 'white',
|
||||
textDecoration: 'none',
|
||||
padding: '0.5rem 1rem',
|
||||
borderRadius: '6px',
|
||||
transition: 'all 0.3s ease',
|
||||
fontWeight: 500,
|
||||
pillNavContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
maxWidth: '600px',
|
||||
width: '100%',
|
||||
margin: '0 auto',
|
||||
},
|
||||
userMenu: {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1rem',
|
||||
marginLeft: '2rem',
|
||||
justifyContent: 'flex-end',
|
||||
gap: '1.5rem',
|
||||
},
|
||||
userInfo: {
|
||||
fontWeight: 500,
|
||||
color: '#666',
|
||||
fontSize: '0.9rem',
|
||||
textAlign: 'right' as const,
|
||||
},
|
||||
logoutBtn: {
|
||||
background: 'rgba(255, 255, 255, 0.1)',
|
||||
color: 'white',
|
||||
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||
padding: '0.5rem 1rem',
|
||||
borderRadius: '6px',
|
||||
background: 'transparent',
|
||||
color: '#161718',
|
||||
border: '1.5px solid #51258f',
|
||||
padding: '0.5rem 1.25rem',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease',
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
fontWeight: 500,
|
||||
fontSize: '0.9rem',
|
||||
whiteSpace: 'nowrap' as const,
|
||||
},
|
||||
mobileMenuBtn: {
|
||||
display: 'none',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'white',
|
||||
color: '#161718',
|
||||
fontSize: '1.5rem',
|
||||
cursor: 'pointer',
|
||||
padding: '0.5rem',
|
||||
borderRadius: '4px',
|
||||
transition: 'background-color 0.2s ease',
|
||||
},
|
||||
mobileNav: {
|
||||
display: isMobileMenuOpen ? 'flex' : 'none',
|
||||
flexDirection: 'column' as const,
|
||||
background: 'white',
|
||||
padding: '1rem',
|
||||
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
|
||||
background: '#FBFAF6',
|
||||
padding: '1rem 0',
|
||||
borderTop: '1px solid rgba(22, 23, 24, 0.1)',
|
||||
boxShadow: '0 4px 20px rgba(22, 23, 24, 0.08)',
|
||||
},
|
||||
mobileNavLink: {
|
||||
color: '#333',
|
||||
color: '#161718',
|
||||
textDecoration: 'none',
|
||||
padding: '1rem',
|
||||
borderBottom: '1px solid #eee',
|
||||
transition: 'background-color 0.3s ease',
|
||||
padding: '1rem 2rem',
|
||||
borderBottom: '1px solid rgba(22, 23, 24, 0.05)',
|
||||
transition: 'all 0.2s ease',
|
||||
fontWeight: 500,
|
||||
},
|
||||
mobileUserInfo: {
|
||||
padding: '1rem',
|
||||
borderTop: '1px solid #eee',
|
||||
marginTop: '1rem',
|
||||
color: '#333',
|
||||
padding: '1.5rem 2rem',
|
||||
borderTop: '1px solid rgba(22, 23, 24, 0.1)',
|
||||
marginTop: '0.5rem',
|
||||
color: '#666',
|
||||
},
|
||||
mobileLogoutBtn: {
|
||||
background: '#667eea',
|
||||
background: '#51258f',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
padding: '0.5rem 1rem',
|
||||
borderRadius: '6px',
|
||||
padding: '0.75rem 1.5rem',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
marginTop: '0.5rem',
|
||||
marginTop: '1rem',
|
||||
width: '100%',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.2s ease',
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<header style={styles.header}>
|
||||
<div style={styles.headerContent}>
|
||||
{/* Logo - Links */}
|
||||
<div style={styles.logo}>
|
||||
<h1 style={styles.logoH1}>🔄 Schichtenplaner</h1>
|
||||
<h1 style={styles.logoH1}>Schichtenplaner</h1>
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav style={styles.desktopNav}>
|
||||
{filteredNavigation.map((item) => (
|
||||
<a
|
||||
key={item.path}
|
||||
href={item.path}
|
||||
style={styles.navLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.1)';
|
||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'none';
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.location.href = item.path;
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
{/* PillNav - Zentriert */}
|
||||
<div style={styles.pillNavWrapper}>
|
||||
<div style={styles.pillNavContainer}>
|
||||
<PillNav
|
||||
items={pillNavItems}
|
||||
activeId={activePath}
|
||||
onChange={handlePillChange}
|
||||
variant="solid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* User Menu */}
|
||||
{/* User Menu - Rechts */}
|
||||
<div style={styles.userMenu}>
|
||||
<span style={styles.userInfo}>
|
||||
{user?.name} ({user?.role})
|
||||
{user?.name} <span style={{color: '#999'}}>({user?.role})</span>
|
||||
</span>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
style={styles.logoutBtn}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.2)';
|
||||
e.currentTarget.style.background = '#51258f';
|
||||
e.currentTarget.style.color = 'white';
|
||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||
e.currentTarget.style.borderColor = '#51258f';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.1)';
|
||||
e.currentTarget.style.background = 'transparent';
|
||||
e.currentTarget.style.color = '#161718';
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.borderColor = '#51258f';
|
||||
}}
|
||||
>
|
||||
Abmelden
|
||||
@@ -180,6 +228,12 @@ const Navigation: React.FC = () => {
|
||||
<button
|
||||
style={styles.mobileMenuBtn}
|
||||
onClick={toggleMobileMenu}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'rgba(81, 37, 143, 0.08)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
}}
|
||||
>
|
||||
☰
|
||||
</button>
|
||||
@@ -194,10 +248,12 @@ const Navigation: React.FC = () => {
|
||||
href={item.path}
|
||||
style={styles.mobileNavLink}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f5f5f5';
|
||||
e.currentTarget.style.backgroundColor = 'rgba(81, 37, 143, 0.08)';
|
||||
e.currentTarget.style.color = '#51258f';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
e.currentTarget.style.color = '#161718';
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -209,10 +265,21 @@ const Navigation: React.FC = () => {
|
||||
</a>
|
||||
))}
|
||||
<div style={styles.mobileUserInfo}>
|
||||
<span>{user?.name} ({user?.role})</span>
|
||||
<div style={{marginBottom: '0.5rem'}}>
|
||||
<span style={{fontWeight: 500}}>{user?.name}</span>
|
||||
<span style={{color: '#999', marginLeft: '0.5rem'}}>({user?.role})</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
style={styles.mobileLogoutBtn}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#642ab5';
|
||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#51258f';
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
}}
|
||||
>
|
||||
Abmelden
|
||||
</button>
|
||||
|
||||
88
frontend/src/components/PillNav/PillNav.module.css
Normal file
88
frontend/src/components/PillNav/PillNav.module.css
Normal file
@@ -0,0 +1,88 @@
|
||||
/* frontend/src/components/PillNav/PillNav.module.css */
|
||||
.pillNavContainer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
padding: 4px;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.pillNavContainer::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pill {
|
||||
padding: 8px 16px;
|
||||
border-radius: 9999px;
|
||||
border: 1px solid;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
white-space: nowrap;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.pill:focus-visible {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Solid Variant */
|
||||
.pillSolid {
|
||||
background-color: transparent;
|
||||
color: #6b7280;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
.pillSolidActive {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
border-color: #2563eb;
|
||||
}
|
||||
|
||||
.pillSolid:hover:not(.pillSolidActive) {
|
||||
background-color: #f3f4f6;
|
||||
color: #374151;
|
||||
border-color: #9ca3af;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Outline Variant */
|
||||
.pillOutline {
|
||||
background-color: transparent;
|
||||
color: #6b7280;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
.pillOutlineActive {
|
||||
color: #2563eb;
|
||||
border-color: #2563eb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pillOutline:hover:not(.pillOutlineActive) {
|
||||
background-color: #f3f4f6;
|
||||
color: #374151;
|
||||
border-color: #9ca3af;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Ghost Variant */
|
||||
.pillGhost {
|
||||
background-color: transparent;
|
||||
color: #6b7280;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.pillGhostActive {
|
||||
background-color: #f3f4f6;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.pillGhost:hover:not(.pillGhostActive) {
|
||||
background-color: #f9fafb;
|
||||
color: #374151;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
181
frontend/src/components/PillNav/PillNav.tsx
Normal file
181
frontend/src/components/PillNav/PillNav.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
// frontend/src/components/PillNav/PillNav.tsx - ELEGANT WHITE DESIGN
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
export interface PillNavItem {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface PillNavProps {
|
||||
items: PillNavItem[];
|
||||
activeId: string;
|
||||
onChange: (id: string) => void;
|
||||
className?: string;
|
||||
variant?: 'solid' | 'outline' | 'ghost';
|
||||
}
|
||||
|
||||
const PillNav: React.FC<PillNavProps> = ({
|
||||
items,
|
||||
activeId,
|
||||
onChange,
|
||||
className = '',
|
||||
variant = 'solid'
|
||||
}) => {
|
||||
const pillRefs = useRef<Array<HTMLButtonElement | null>>([]);
|
||||
|
||||
const baseStyles = {
|
||||
container: {
|
||||
display: 'flex',
|
||||
gap: '4px',
|
||||
overflowX: 'auto' as const,
|
||||
padding: '4px',
|
||||
scrollbarWidth: 'none' as const,
|
||||
msOverflowStyle: 'none' as const,
|
||||
background: 'rgba(22, 23, 24, 0.02)',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid rgba(22, 23, 24, 0.06)',
|
||||
},
|
||||
pill: {
|
||||
padding: '10px 20px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
whiteSpace: 'nowrap' as const,
|
||||
outline: 'none',
|
||||
flexShrink: 0,
|
||||
}
|
||||
};
|
||||
|
||||
const getVariantStyles = (isActive: boolean) => {
|
||||
const variants = {
|
||||
solid: {
|
||||
active: {
|
||||
backgroundColor: '#51258f',
|
||||
color: '#FBFAF6',
|
||||
boxShadow: '0 2px 8px rgba(81, 37, 143, 0.2)',
|
||||
},
|
||||
inactive: {
|
||||
backgroundColor: 'transparent',
|
||||
color: '#666',
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
active: {
|
||||
backgroundColor: '#51258f',
|
||||
color: '#FBFAF6',
|
||||
boxShadow: '0 2px 8px rgba(81, 37, 143, 0.2)',
|
||||
},
|
||||
inactive: {
|
||||
backgroundColor: 'transparent',
|
||||
color: '#666',
|
||||
border: '1px solid rgba(22, 23, 24, 0.2)',
|
||||
}
|
||||
},
|
||||
ghost: {
|
||||
active: {
|
||||
backgroundColor: 'rgba(81, 37, 143, 0.1)',
|
||||
color: '#51258f',
|
||||
fontWeight: 600,
|
||||
},
|
||||
inactive: {
|
||||
backgroundColor: 'transparent',
|
||||
color: '#666',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return variants[variant][isActive ? 'active' : 'inactive'];
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent, index: number) => {
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft': {
|
||||
event.preventDefault();
|
||||
const prevIndex = (index - 1 + items.length) % items.length;
|
||||
onChange(items[prevIndex].id);
|
||||
pillRefs.current[prevIndex]?.focus();
|
||||
break;
|
||||
}
|
||||
case 'ArrowRight': {
|
||||
event.preventDefault();
|
||||
const nextIndex = (index + 1) % items.length;
|
||||
onChange(items[nextIndex].id);
|
||||
pillRefs.current[nextIndex]?.focus();
|
||||
break;
|
||||
}
|
||||
case 'Home': {
|
||||
event.preventDefault();
|
||||
onChange(items[0].id);
|
||||
pillRefs.current[0]?.focus();
|
||||
break;
|
||||
}
|
||||
case 'End': {
|
||||
event.preventDefault();
|
||||
onChange(items[items.length - 1].id);
|
||||
pillRefs.current[items.length - 1]?.focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize refs array
|
||||
useEffect(() => {
|
||||
pillRefs.current = pillRefs.current.slice(0, items.length);
|
||||
}, [items.length]);
|
||||
|
||||
const containerStyle = {
|
||||
...baseStyles.container,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label="Navigation tabs"
|
||||
style={containerStyle}
|
||||
className={className}
|
||||
>
|
||||
{items.map((item, index) => {
|
||||
const isActive = item.id === activeId;
|
||||
const pillStyle = {
|
||||
...baseStyles.pill,
|
||||
...getVariantStyles(isActive),
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
ref={el => {
|
||||
pillRefs.current[index] = el;
|
||||
}}
|
||||
role="tab"
|
||||
aria-selected={isActive}
|
||||
aria-controls={`tabpanel-${item.id}`}
|
||||
tabIndex={isActive ? 0 : -1}
|
||||
style={pillStyle}
|
||||
onClick={() => onChange(item.id)}
|
||||
onKeyDown={(e) => handleKeyDown(e, index)}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isActive) {
|
||||
e.currentTarget.style.backgroundColor = 'rgba(81, 37, 143, 0.08)';
|
||||
e.currentTarget.style.color = '#51258f';
|
||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isActive) {
|
||||
Object.assign(e.currentTarget.style, pillStyle);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PillNav;
|
||||
3
frontend/src/components/PillNav/index.ts
Normal file
3
frontend/src/components/PillNav/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// frontend/src/components/PillNav/index.ts
|
||||
export { default } from './PillNav';
|
||||
export type { PillNavProps, PillNavItem } from './PillNav';
|
||||
Reference in New Issue
Block a user