refactor: hamburger for navigation component

This commit is contained in:
Felix Zett 2025-09-20 17:25:36 +02:00
parent 68f2d202f0
commit a61557f0cc

View file

@ -8,6 +8,107 @@ import TagsPage from "./pages/TagsPage";
const APP_TITLE = "da packste dich weg"; const APP_TITLE = "da packste dich weg";
function Navigation() {
const location = useLocation();
const navItems = [
{ to: "/trips", label: "Trips" },
{ to: "/items", label: "Items" },
{ to: "/tags", label: "Tags" },
];
const [menuOpen, setMenuOpen] = useState(false);
const navigate = useNavigate();
async function goToCurrentTrip() {
try {
const id = await getNextTripId();
navigate(`/trips/${id}`);
} catch {
alert("Kein anstehender Trip gefunden");
}
}
const isTripDetail = /^\/trips\/[^/]+$/.test(location.pathname);
return (
<nav className="mb-2 flex items-center gap-2 bg-white/80 backdrop-blur border-b border-gray-200 px-2 py-2 rounded-xl shadow-sm relative">
<button
className="text-2xl font-extrabold text-blue-700 tracking-tight mr-2 select-none hover:underline bg-transparent p-0"
style={{ background: "none", border: "none" }}
onClick={goToCurrentTrip}
title="Zum aktuellen Trip"
>
{APP_TITLE}
</button>
{/* Hamburger für kleine Screens */}
<button
className="sm:hidden ml-auto px-2 py-1 rounded text-blue-700 border border-blue-200"
onClick={() => setMenuOpen((v) => !v)}
aria-label="Menü öffnen"
>
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
<rect y="6" width="28" height="2.5" rx="1.25" fill="#2563eb"/>
<rect y="13" width="28" height="2.5" rx="1.25" fill="#2563eb"/>
<rect y="20" width="28" height="2.5" rx="1.25" fill="#2563eb"/>
</svg>
</button>
{/* Links für große Screens */}
<div className="hidden sm:flex items-center gap-2 ml-auto">
{navItems.map((item) => {
const isActive =
!isTripDetail && location.pathname.startsWith(item.to);
return (
<Link
key={item.to}
to={item.to}
className={
"px-3 py-1 rounded font-medium transition " +
(isActive
? "bg-blue-600 text-white shadow"
: "text-blue-700 hover:bg-blue-100 hover:text-blue-900")
}
style={{
boxShadow: isActive ? "0 2px 8px 0 rgba(37,99,235,0.08)" : undefined,
border: isActive ? "2px solid #2563eb" : undefined,
}}
onClick={() => setMenuOpen(false)}
>
{item.label}
</Link>
);
})}
</div>
{/* Dropdown für kleine Screens */}
{menuOpen && (
<div className="absolute top-full left-0 w-full bg-white border-t border-blue-200 shadow-md z-50 flex flex-col sm:hidden">
{navItems.map((item) => {
const isActive =
!isTripDetail && location.pathname.startsWith(item.to);
return (
<Link
key={item.to}
to={item.to}
className={
"px-4 py-3 border-b font-medium transition " +
(isActive
? "bg-blue-600 text-white shadow"
: "text-blue-700 hover:bg-blue-100 hover:text-blue-900")
}
style={{
boxShadow: isActive ? "0 2px 8px 0 rgba(37,99,235,0.08)" : undefined,
border: isActive ? "2px solid #2563eb" : undefined,
}}
onClick={() => setMenuOpen(false)}
>
{item.label}
</Link>
);
})}
</div>
)}
</nav>
);
}
function NextTripRedirect({ trips }: { trips: any[] }) { function NextTripRedirect({ trips }: { trips: any[] }) {
const [nextTripId, setNextTripId] = React.useState<string | null>(null); const [nextTripId, setNextTripId] = React.useState<string | null>(null);
const [error, setError] = React.useState<string | null>(null); const [error, setError] = React.useState<string | null>(null);
@ -23,74 +124,6 @@ function NextTripRedirect({ trips }: { trips: any[] }) {
return <Navigate to={`/trips/${nextTripId}`} replace />; return <Navigate to={`/trips/${nextTripId}`} replace />;
} }
function Navigation() {
const location = useLocation();
const navItems = [
{ to: "/trips", label: "Trips" },
{ to: "/items", label: "Items" },
{ to: "/tags", label: "Tags" },
];
// Navigation für den Titel-Link
const navigate = useNavigate();
async function goToCurrentTrip() {
try {
const id = await getNextTripId();
navigate(`/trips/${id}`);
} catch {
alert("Kein anstehender Trip gefunden");
}
}
// Prüfe, ob ein einzelner Trip angezeigt wird
const isTripDetail = /^\/trips\/[^/]+$/.test(location.pathname);
return (
<nav className="mb-2 flex items-center gap-4 bg-white/80 backdrop-blur border-b border-gray-200 px-2 py-2 rounded-xl shadow-sm">
<button
className="text-2xl font-extrabold text-blue-700 tracking-tight mr-4 select-none hover:underline bg-transparent p-0"
style={{ background: "none", border: "none" }}
onClick={goToCurrentTrip}
title="Zum aktuellen Trip"
>
{APP_TITLE}
</button>
{navItems.map((item) => {
// Trips ist NICHT aktiv, wenn ein einzelner Trip angezeigt wird
const isActive =
!isTripDetail && location.pathname.startsWith(item.to);
return (
<Link
key={item.to}
to={item.to}
className={
"px-4 py-2 rounded font-medium transition " +
(isActive
? "bg-blue-600 text-white shadow"
: "text-blue-700 hover:bg-blue-100 hover:text-blue-900")
}
style={{
boxShadow: isActive ? "0 2px 8px 0 rgba(37,99,235,0.08)" : undefined,
border: isActive ? "2px solid #2563eb" : undefined,
}}
>
{item.label}
</Link>
);
})}
<button
className="ml-auto bg-gray-100 text-gray-700 px-4 py-2 rounded hover:bg-gray-200 transition"
onClick={async () => {
await getSeed();
window.location.reload();
}}
>
Seed-Daten erzeugen
</button>
</nav>
);
}
export default function App() { export default function App() {
const [trips, setTrips] = useState<any[]>([]); const [trips, setTrips] = useState<any[]>([]);
const [dbError, setDbError] = useState<string | null>(null); const [dbError, setDbError] = useState<string | null>(null);