| ... | @@ -11,7 +11,7 @@ import { | ... | @@ -11,7 +11,7 @@ import { |
| 11 | MoreHorizontalIcon, | 11 | MoreHorizontalIcon, |
| 12 | PlusIcon, | 12 | PlusIcon, |
| 13 | } from "lucide-react"; | 13 | } from "lucide-react"; |
| 14 | import { useCallback, useMemo } from "react"; | 14 | import { useCallback, useMemo, useState } from "react"; |
| 15 | | 15 | |
| 16 | import { writeTextToClipboard } from "~/hooks/useCopyToClipboard"; | 16 | import { writeTextToClipboard } from "~/hooks/useCopyToClipboard"; |
| 17 | import { useOpenPrLink } from "~/lib/openPullRequestLink"; | 17 | import { useOpenPrLink } from "~/lib/openPullRequestLink"; |
| ... | @@ -84,6 +84,15 @@ function LinkRow({ | ... | @@ -84,6 +84,15 @@ function LinkRow({ |
| 84 | onSetWatching: ((link: ThreadPullRequestLink, watching: boolean) => void) | null; | 84 | onSetWatching: ((link: ThreadPullRequestLink, watching: boolean) => void) | null; |
| 85 | }) { | 85 | }) { |
| 86 | const openPrLink = useOpenPrLink(threadRef); | 86 | const openPrLink = useOpenPrLink(threadRef); |
| | 87 | const [menuOpen, setMenuOpen] = useState(false); |
| | 88 | const [menuPosition, setMenuPosition] = useState<{ x: number; y: number } | null>(null); |
| | 89 | const menuAnchor = useMemo( |
| | 90 | () => |
| | 91 | menuPosition |
| | 92 | ? { getBoundingClientRect: () => new DOMRect(menuPosition.x, menuPosition.y, 0, 0) } |
| | 93 | : undefined, |
| | 94 | [menuPosition], |
| | 95 | ); |
| 87 | const { link, depth, stack } = line; | 96 | const { link, depth, stack } = line; |
| 88 | const snapshot = link.snapshot; | 97 | const snapshot = link.snapshot; |
| 89 | const open = snapshot === null || snapshot.state === "open"; | 98 | const open = snapshot === null || snapshot.state === "open"; |
| ... | @@ -91,6 +100,12 @@ function LinkRow({ | ... | @@ -91,6 +100,12 @@ function LinkRow({ |
| 91 | return ( | 100 | return ( |
| 92 | <div | 101 | <div |
| 93 | className={cn(PULL_REQUEST_ROW_CLASS, "relative hover:bg-accent/60")} | 102 | className={cn(PULL_REQUEST_ROW_CLASS, "relative hover:bg-accent/60")} |
| | 103 | onContextMenu={(event) => { |
| | 104 | event.preventDefault(); |
| | 105 | event.stopPropagation(); |
| | 106 | setMenuPosition({ x: event.clientX, y: event.clientY }); |
| | 107 | setMenuOpen(true); |
| | 108 | }} |
| 94 | // Each layer steps in under the one it targets. The step is capped: beyond a few layers | 109 | // Each layer steps in under the one it targets. The step is capped: beyond a few layers |
| 95 | // the indent only says "still in the stack", which the connector line already does, and | 110 | // the indent only says "still in the stack", which the connector line already does, and |
| 96 | // a sixteen-layer stack would otherwise stair-step off the right edge. | 111 | // a sixteen-layer stack would otherwise stair-step off the right edge. |
| ... | @@ -221,7 +236,13 @@ function LinkRow({ | ... | @@ -221,7 +236,13 @@ function LinkRow({ |
| 221 | )} | 236 | )} |
| 222 | > | 237 | > |
| 223 | <span aria-hidden className="absolute inset-0 bg-accent/60" /> | 238 | <span aria-hidden className="absolute inset-0 bg-accent/60" /> |
| 224 | <Menu> | 239 | <Menu |
| | 240 | open={menuOpen} |
| | 241 | onOpenChange={(open) => { |
| | 242 | setMenuOpen(open); |
| | 243 | if (!open) setMenuPosition(null); |
| | 244 | }} |
| | 245 | > |
| 225 | <MenuTrigger | 246 | <MenuTrigger |
| 226 | render={ | 247 | render={ |
| 227 | <Button | 248 | <Button |
| ... | @@ -234,7 +255,12 @@ function LinkRow({ | ... | @@ -234,7 +255,12 @@ function LinkRow({ |
| 234 | </Button> | 255 | </Button> |
| 235 | } | 256 | } |
| 236 | /> | 257 | /> |
| 237 | <MenuPopup align="end" side="bottom"> | 258 | <MenuPopup |
| | 259 | anchor={menuAnchor} |
| | 260 | align={menuPosition ? "start" : "end"} |
| | 261 | side="bottom" |
| | 262 | sideOffset={menuPosition ? 0 : 4} |
| | 263 | > |
| 238 | <MenuItem onClick={() => void writeTextToClipboard(link.url, "link")}> | 264 | <MenuItem onClick={() => void writeTextToClipboard(link.url, "link")}> |
| 239 | <LinkIcon className="size-3.5" /> | 265 | <LinkIcon className="size-3.5" /> |
| 240 | Copy link | 266 | Copy link |