import { Link, type LinkProps } from "@tanstack/react-router" import { Check, type LucideIcon } from "lucide-react" import { type ReactNode, useEffect, useRef, useState } from "react" import { cn } from "@/lib/utils" /** How long a press has to last before it means "select this one". */ const LONG_PRESS = 450 /** * Which of the listed documents are picked, and what turns picking on. * * Selection mode is simply "something is selected": a long press picks the * first one and clearing the last one puts the list back to normal. So there * is no mode to be stuck in, nothing to cancel, and no control on screen that * exists only to turn selecting on. * * @param names everything currently listed. A document that is deleted, or * filtered out by the search, cannot stay picked — deleting therefore clears * the selection on its own, once the list comes back without it. */ export function useSelection(names: string[]) { const [picked, setPicked] = useState([]) const selected = picked.filter((name) => names.includes(name)) // Escape is the way out for anyone who started this by accident. useEffect(() => { if (selected.length === 0) return const onKey = (event: KeyboardEvent) => { if (event.key === "Escape") setPicked([]) } window.addEventListener("keydown", onKey) return () => window.removeEventListener("keydown", onKey) }, [selected.length]) return { selected, selecting: selected.length > 0, toggle: (name: string) => setPicked((current) => current.includes(name) ? current.filter((other) => other !== name) : [...current, name], ), clear: () => setPicked([]), } } /** * One entry of the flows or dashboards overview. * * Stays a link in both modes rather than swapping element: while selecting it * carries `role="checkbox"` instead, which is what it now behaves as. Ctrl or * Cmd click is the same gesture for a mouse, and reaches the keyboard for * free — a focused link answers Ctrl+Enter with a click carrying the modifier. */ export function OverviewCard({ link, icon: Icon, title, detail, draft, selecting, selected, onToggle, testId, }: { link: LinkProps icon: LucideIcon title: ReactNode detail: ReactNode /** Has unpublished changes. */ draft?: boolean selecting: boolean selected: boolean onToggle: () => void testId?: string }) { const timer = useRef | null>(null) const held = useRef(false) const stop = () => { if (timer.current) clearTimeout(timer.current) timer.current = null } return ( { // Right-click is the browser's own gesture; leave it alone. if (event.button !== 0) return held.current = false timer.current = setTimeout(() => { held.current = true onToggle() }, LONG_PRESS) }} onPointerUp={stop} onPointerLeave={stop} onPointerCancel={stop} onClick={(event) => { // The press already picked it; the click that ends the press must not // undo it, and must certainly not navigate. if (held.current) { held.current = false event.preventDefault() return } if (selecting || event.ctrlKey || event.metaKey) { event.preventDefault() onToggle() } }} onContextMenu={(event) => { // A touch hold raises this a moment after the press has been taken as // a selection; the link preview on top of it would be a second answer // to one gesture. if (held.current) event.preventDefault() }} > {title} {draft ? ( Unpublished changes ) : null} {detail} {selecting ? ( {selected ? : null} ) : null} ) }