refactor: hamburger for navigation component
This commit is contained in:
parent
68f2d202f0
commit
a61557f0cc
1 changed files with 101 additions and 68 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue