Files
pinkudex/components/grid/FilterBarClient.tsx
T
2026-05-26 22:46:00 +02:00

97 lines
3.4 KiB
TypeScript

"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { useTransition } from "react";
import Link from "next/link";
import { cn } from "@/lib/utils";
import type { FilterCriteria, FilterTabKey } from "@/lib/filters";
import { writeFilterCriteria, anyActive, EMPTY_STATUS } from "@/lib/filters";
import { MultiFilterPopover, type FilterOption } from "./MultiFilterPopover";
import { MergedFilterPopover } from "./MergedFilterPopover";
import { MarkActionPopover } from "./MarkActionPopover";
import { ActiveCriteriaStrip } from "./ActiveCriteriaStrip";
import { GridSearchInput } from "./GridSearchInput";
import { SortMenu } from "./SortMenu";
import { ViewToggle, type LibraryView } from "./ViewToggle";
import { InfiniteScrollToggle } from "./InfiniteScrollToggle";
import type { SortKey } from "@/lib/sort";
export function FilterBarClient({
criteria,
options,
isHome,
showSort,
sort,
view,
}: {
criteria: FilterCriteria;
options: Record<FilterTabKey, FilterOption[]>;
isHome: boolean;
showSort: boolean;
sort?: SortKey;
view?: LibraryView;
}) {
const router = useRouter();
const params = useSearchParams();
const [, start] = useTransition();
function pushCriteria(next: FilterCriteria) {
const sp = new URLSearchParams(params.toString());
writeFilterCriteria(sp, next);
const y = typeof window !== "undefined" ? window.scrollY : 0;
start(() => {
router.push(`?${sp.toString()}`, { scroll: false });
// Defensive: restore scroll on the next two frames in case Next still resets.
requestAnimationFrame(() => window.scrollTo({ top: y, left: 0, behavior: "instant" as ScrollBehavior }));
requestAnimationFrame(() => window.scrollTo({ top: y, left: 0, behavior: "instant" as ScrollBehavior }));
});
}
const active = anyActive(criteria);
return (
<>
<div className="flex items-center gap-2 mb-3 flex-wrap">
{isHome ? (
<button
type="button"
onClick={() => pushCriteria({
ids: { actresses: [], studios: [], series: [], genres: [], collections: [], tags: [], categories: [] },
mode: criteria.mode,
status: { ...EMPTY_STATUS },
marks: [],
})}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-full border text-sm transition-colors",
!active
? "bg-[var(--color-cyan)]/15 border-[var(--color-cyan)]/40 text-[var(--color-cyan)]"
: "glass glass-hover text-[var(--color-fg-dim)]",
)}
>
ALL
</button>
) : (
<Link
href="/"
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full border text-sm glass glass-hover text-[var(--color-fg-dim)]"
>
ALL
</Link>
)}
<MultiFilterPopover criteria={criteria} options={options} onChange={pushCriteria} />
<MergedFilterPopover criteria={criteria} onChange={pushCriteria} />
<MarkActionPopover />
<div className="ml-auto flex items-center gap-2">
<GridSearchInput />
{showSort && sort && <SortMenu activeSort={sort} />}
{isHome && <InfiniteScrollToggle />}
{view && <ViewToggle current={view} />}
</div>
</div>
<ActiveCriteriaStrip criteria={criteria} options={options} onChange={pushCriteria} />
</>
);
}