Organize/components/board/category-chips.tsx

82 lines
2.7 KiB
TypeScript

"use client";
import { useEffect, useRef } from "react";
import { cn } from "@/lib/utils";
import type { CategoryDTO } from "@/types/board";
/**
* Mobile (< md) category rail: one chip per lane, shown under the top bar.
* Doubles as the board's position indicator while swiping between lanes
* (the active chip is highlighted, driven by the board's
* IntersectionObserver) and as quick navigation -- tapping a chip glides
* the lane pager to that lane.
*/
export function CategoryChips({
categories,
activeCategoryId,
onSelect,
}: {
categories: CategoryDTO[];
activeCategoryId: string | null;
onSelect: (id: string) => void;
}) {
const chipRefs = useRef(new Map<string, HTMLButtonElement>());
// Keep the active chip in view if the user swipes several lanes at once
// (or a chip gets added/removed out of the strip's view).
useEffect(() => {
if (!activeCategoryId) return;
chipRefs.current
.get(activeCategoryId)
?.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" });
}, [activeCategoryId]);
return (
<div
// -mx-3/px-3 bleeds the strip to the screen edges (the board's own
// p-3) so chips start and end flush with the viewport, the way a
// native tab strip does.
className="-mx-3 flex gap-2 overflow-x-auto px-3 pb-1 overscroll-x-contain"
>
{categories.map((category) => {
const active = category.id === activeCategoryId;
const openTodos = category.groups.reduce(
(n, g) => n + g.todos.filter((t) => !t.completed).length,
0
);
return (
<button
key={category.id}
ref={(el) => {
if (el) chipRefs.current.set(category.id, el);
else chipRefs.current.delete(category.id);
}}
type="button"
onClick={() => onSelect(category.id)}
aria-pressed={active}
className={cn(
"flex shrink-0 items-center gap-1.5 rounded-full border px-3 py-1.5 text-[13px] font-medium transition-colors",
active
? "border-transparent bg-primary text-primary-foreground shadow-sm"
: "border-border bg-lane text-muted-foreground active:bg-accent active:text-accent-foreground"
)}
>
{category.name}
<span
className={cn(
"rounded-full px-1.5 text-[11px] font-semibold tabular-nums",
active ? "bg-primary-foreground/20" : "bg-foreground/8"
)}
aria-label={`${openTodos} open to-dos`}
>
{openTodos}
</span>
</button>
);
})}
</div>
);
}