75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
import { createFileRoute, Link } from "@tanstack/react-router";
|
|
import { AnimatePresence, motion } from "framer-motion";
|
|
import { useRef } from "react";
|
|
import { z } from "zod";
|
|
import { CollectionView } from "../../components/CollectionView";
|
|
import { PlanningView } from "../../components/PlanningView";
|
|
|
|
const searchSchema = z.object({
|
|
tab: z.enum(["gear", "planning"]).catch("gear"),
|
|
});
|
|
|
|
export const Route = createFileRoute("/collection/")({
|
|
validateSearch: searchSchema,
|
|
component: CollectionPage,
|
|
});
|
|
|
|
const TAB_ORDER = ["gear", "planning"] as const;
|
|
const TAB_LABELS: Record<(typeof TAB_ORDER)[number], string> = {
|
|
gear: "Gear",
|
|
planning: "Planning",
|
|
};
|
|
|
|
const slideVariants = {
|
|
enter: (dir: number) => ({ x: `${dir * 15}%`, opacity: 0 }),
|
|
center: { x: 0, opacity: 1 },
|
|
exit: (dir: number) => ({ x: `${dir * -15}%`, opacity: 0 }),
|
|
};
|
|
|
|
function CollectionPage() {
|
|
const { tab } = Route.useSearch();
|
|
const prevTab = useRef(tab);
|
|
|
|
const direction =
|
|
TAB_ORDER.indexOf(tab) >= TAB_ORDER.indexOf(prevTab.current) ? 1 : -1;
|
|
prevTab.current = tab;
|
|
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 overflow-x-hidden">
|
|
{/* Tab navigation */}
|
|
<div className="flex justify-center mb-6">
|
|
<div className="flex bg-gray-100 rounded-full p-0.5 gap-0.5">
|
|
{TAB_ORDER.map((t) => (
|
|
<Link
|
|
key={t}
|
|
to="/collection"
|
|
search={{ tab: t }}
|
|
className={`px-4 py-1.5 text-sm font-medium rounded-full transition-colors ${
|
|
tab === t
|
|
? "bg-gray-700 text-white"
|
|
: "text-gray-600 hover:bg-gray-200"
|
|
}`}
|
|
>
|
|
{TAB_LABELS[t]}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<AnimatePresence mode="wait" initial={false} custom={direction}>
|
|
<motion.div
|
|
key={tab}
|
|
custom={direction}
|
|
variants={slideVariants}
|
|
initial="enter"
|
|
animate="center"
|
|
exit="exit"
|
|
transition={{ duration: 0.12, ease: "easeInOut" }}
|
|
>
|
|
{tab === "gear" ? <CollectionView /> : <PlanningView />}
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}
|