Skip to content

g1t/apps/web/app/components/pin-button.tsx

67 lines2,648 bytesCodeBlame
1/**
2 * Pinning a project, so it stays at the top of the workspace's sidebar:
3 * a button on the project's header and on each row of the Projects page.
4 * Posts to the workspace's pins route (routes/workspace/pins.ts); while it
5 * is on its way, it shows as done.
6 */
7import { Pin } from "lucide-react";
8import { useFetcher } from "react-router";
9
10import { cn } from "../lib/cn";
11import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
12
13export function PinButton({
14 workspace,
15 slug,
16 name,
17 pinned,
18 compact,
19 small,
20 className,
21}: {
22 workspace: string;
23 slug: string;
24 /** For what the button says to a screen reader. */
25 name: string;
26 pinned: boolean;
27 /** Only the pin, as on a row; the header says it in words too. */
28 compact?: boolean;
29 /** Row-sized, for the sidebar. */
30 small?: boolean;
31 className?: string;
32}) {
33 const fetcher = useFetcher<{ error: string | null }>({ key: `pin:${workspace}/${slug}` });
34 const asked = fetcher.formData?.get("intent");
35 const shown = asked === "pin" ? true : asked === "unpin" ? false : pinned;
36 const label = shown ? `Unpin ${name}` : `Pin ${name}`;
37 const error = fetcher.state === "idle" ? fetcher.data?.error : null;
38 return (
39 <fetcher.Form method="post" action={`/${workspace}/-/pins`} className={cn("relative flex", className)}>
40 <input type="hidden" name="intent" value={shown ? "unpin" : "pin"} />
41 <input type="hidden" name="slug" value={slug} />
42 <Tooltip>
43 <TooltipTrigger asChild>
44 <button
45 type="submit"
46 aria-label={label}
47 aria-pressed={shown}
48 className={cn(
49 "inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md text-[0.8125rem] transition-colors outline-none focus-visible:ring-2 focus-visible:ring-accent",
50 small
51 ? "relative z-10 size-6 hover:bg-raised"
52 : compact
53 ? "relative z-10 size-8 hover:bg-raised"
54 : "h-8 border border-line px-2.5 text-fg/80 hover:border-line-strong hover:bg-surface hover:text-fg",
55 shown ? "text-accent" : compact || small ? "text-faint hover:text-fg" : "",
56 error && "text-danger",
57 )}
58 >
59 <Pin size={small ? 12 : 14} className={shown ? "fill-current" : undefined} />
60 {!compact && !small && <span className="hidden sm:inline">{shown ? "Unpin" : "Pin"}</span>}
61 </button>
62 </TooltipTrigger>
63 <TooltipContent>{error ?? (shown ? "Unpin from the sidebar" : "Pin to the sidebar")}</TooltipContent>
64 </Tooltip>
65 </fetcher.Form>
66 );
67}