updated more simple modern layout

This commit is contained in:
2025-10-16 20:41:51 +02:00
parent 7b2256c0ed
commit b86040dc04
13 changed files with 1167 additions and 226 deletions

View File

@@ -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

View File

@@ -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',
},
};

View File

@@ -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>

View 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);
}

View 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;

View File

@@ -0,0 +1,3 @@
// frontend/src/components/PillNav/index.ts
export { default } from './PillNav';
export type { PillNavProps, PillNavItem } from './PillNav';