| author | |
| committer | |
| log | 669168d08aa36c3b41f05d78605cdedb0bb1fce1 |
| tree | 0f0cb5a00adab90cec16c2fce6f8408491f3d36d |
| parent | 9bd663be7fc67ebde62ea0268b53b2e9e48ef3a1 |
- Standardize quick action icon sizing and label spacing in chat and git controls
- Replace manual popover hover state handling with `openOnHover` triggers
- Add reusable terminal action button wrapper with hover tooltips for split/new/close controls3 files changed, 73 insertions(+), 66 deletions(-)
apps/web/src/components/ChatView.tsx+2-2| ... | ... | @@ -1476,8 +1476,8 @@ function OpenInPicker() { |
| 1476 | 1476 | return ( |
| 1477 | 1477 | <Group aria-label="Subscription actions"> |
| 1478 | 1478 | <Button size="xs" variant="outline" onClick={() => openInEditor(lastEditor)}> |
| 1479 | {primaryOption?.Icon && <primaryOption.Icon aria-hidden="true" className="size-3" />} | |
| 1480 | Open | |
| 1479 | {primaryOption?.Icon && <primaryOption.Icon aria-hidden="true" className="size-3.5" />} | |
| 1480 | <span className="ml-0.5">Open</span> | |
| 1481 | 1481 | </Button> |
| 1482 | 1482 | <GroupSeparator /> |
| 1483 | 1483 | <Menu> |
apps/web/src/components/GitActionsControl.tsx+16-34| ... | ... | @@ -122,15 +122,16 @@ function GitActionItemIcon({ icon }: { icon: GitActionIconName }) { |
| 122 | 122 | } |
| 123 | 123 | |
| 124 | 124 | function GitQuickActionIcon({ quickAction }: { quickAction: GitQuickAction }) { |
| 125 | if (quickAction.kind === "open_pr") return <GitHubIcon className="size-4" />; | |
| 126 | if (quickAction.kind === "run_pull") return <InfoIcon className="size-4" />; | |
| 125 | const iconClassName = "size-3.5"; | |
| 126 | if (quickAction.kind === "open_pr") return <GitHubIcon className={iconClassName} />; | |
| 127 | if (quickAction.kind === "run_pull") return <InfoIcon className={iconClassName} />; | |
| 127 | 128 | if (quickAction.kind === "run_action") { |
| 128 | if (quickAction.action === "commit") return <GitCommitIcon className="size-4" />; | |
| 129 | if (quickAction.action === "commit_push") return <CloudUploadIcon className="size-4" />; | |
| 130 | return <GitHubIcon className="size-4" />; | |
| 129 | if (quickAction.action === "commit") return <GitCommitIcon className={iconClassName} />; | |
| 130 | if (quickAction.action === "commit_push") return <CloudUploadIcon className={iconClassName} />; | |
| 131 | return <GitHubIcon className={iconClassName} />; | |
| 131 | 132 | } |
| 132 | if (quickAction.label === "Commit") return <GitCommitIcon className="size-4" />; | |
| 133 | return <InfoIcon className="size-4" />; | |
| 133 | if (quickAction.label === "Commit") return <GitCommitIcon className={iconClassName} />; | |
| 134 | return <InfoIcon className={iconClassName} />; | |
| 134 | 135 | } |
| 135 | 136 | |
| 136 | 137 | function buildGitActionProgressStages(input: { |
| ... | ... | @@ -192,10 +193,6 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 192 | 193 | const quickActionDisabledReason = quickAction.disabled |
| 193 | 194 | ? (quickAction.hint ?? "This action is currently unavailable.") |
| 194 | 195 | : null; |
| 195 | const [isQuickActionPopoverOpen, setIsQuickActionPopoverOpen] = useState(false); | |
| 196 | const [openDisabledMenuItemId, setOpenDisabledMenuItemId] = useState< | |
| 197 | GitActionMenuItem["id"] | null | |
| 198 | >(null); | |
| 199 | 196 | |
| 200 | 197 | const refreshGitStatus = useCallback(async () => { |
| 201 | 198 | if (!api || !gitCwd) return; |
| ... | ... | @@ -451,21 +448,20 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 451 | 448 | ) : ( |
| 452 | 449 | <Group aria-label="Git actions"> |
| 453 | 450 | {quickActionDisabledReason ? ( |
| 454 | <Popover open={isQuickActionPopoverOpen} onOpenChange={setIsQuickActionPopoverOpen}> | |
| 451 | <Popover> | |
| 455 | 452 | <PopoverTrigger |
| 453 | openOnHover | |
| 456 | 454 | render={ |
| 457 | 455 | <Button |
| 458 | 456 | variant="outline" |
| 459 | 457 | size="xs" |
| 460 | 458 | disabled={isGitActionRunning || quickAction.disabled} |
| 461 | 459 | className="cursor-not-allowed disabled:cursor-not-allowed disabled:pointer-events-auto" |
| 462 | onMouseEnter={() => setIsQuickActionPopoverOpen(true)} | |
| 463 | onMouseLeave={() => setIsQuickActionPopoverOpen(false)} | |
| 464 | 460 | /> |
| 465 | 461 | } |
| 466 | 462 | > |
| 467 | 463 | <GitQuickActionIcon quickAction={quickAction} /> |
| 468 | {quickAction.label} | |
| 464 | <span className="ml-0.5">{quickAction.label}</span> | |
| 469 | 465 | </PopoverTrigger> |
| 470 | 466 | <PopoverPopup tooltipStyle side="bottom" align="start"> |
| 471 | 467 | {quickActionDisabledReason} |
| ... | ... | @@ -479,16 +475,11 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 479 | 475 | onClick={runQuickAction} |
| 480 | 476 | > |
| 481 | 477 | <GitQuickActionIcon quickAction={quickAction} /> |
| 482 | {quickAction.label} | |
| 478 | <span className="ml-0.5">{quickAction.label}</span> | |
| 483 | 479 | </Button> |
| 484 | 480 | )} |
| 485 | 481 | <GroupSeparator /> |
| 486 | <Menu | |
| 487 | onOpenChange={(open) => { | |
| 488 | if (open) void refreshGitStatus(); | |
| 489 | if (!open) setOpenDisabledMenuItemId(null); | |
| 490 | }} | |
| 491 | > | |
| 482 | <Menu> | |
| 492 | 483 | <MenuTrigger |
| 493 | 484 | render={<Button aria-label="Git action options" size="icon-xs" variant="outline" />} |
| 494 | 485 | disabled={isGitActionRunning} |
| ... | ... | @@ -504,19 +495,10 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 504 | 495 | ); |
| 505 | 496 | if (item.disabled && disabledReason) { |
| 506 | 497 | return ( |
| 507 | <Popover | |
| 508 | key={`${item.id}-${item.label}`} | |
| 509 | open={openDisabledMenuItemId === item.id} | |
| 510 | onOpenChange={(open) => setOpenDisabledMenuItemId(open ? item.id : null)} | |
| 511 | > | |
| 498 | <Popover key={`${item.id}-${item.label}`}> | |
| 512 | 499 | <PopoverTrigger |
| 513 | render={ | |
| 514 | <span | |
| 515 | className="inline-flex w-full cursor-not-allowed" | |
| 516 | onMouseEnter={() => setOpenDisabledMenuItemId(item.id)} | |
| 517 | onMouseLeave={() => setOpenDisabledMenuItemId(null)} | |
| 518 | /> | |
| 519 | } | |
| 500 | openOnHover | |
| 501 | render={<span className="inline-flex w-full cursor-not-allowed" />} | |
| 520 | 502 | > |
| 521 | 503 | <MenuItem disabled> |
| 522 | 504 | <GitActionItemIcon icon={item.icon} /> |
apps/web/src/components/ThreadTerminalDrawer.tsx+55-30| ... | ... | @@ -4,12 +4,14 @@ import { type NativeApi } from "@t3tools/contracts"; |
| 4 | 4 | import { Terminal, type ITheme } from "@xterm/xterm"; |
| 5 | 5 | import { |
| 6 | 6 | type PointerEvent as ReactPointerEvent, |
| 7 | type ReactNode, | |
| 7 | 8 | useCallback, |
| 8 | 9 | useEffect, |
| 9 | 10 | useMemo, |
| 10 | 11 | useRef, |
| 11 | 12 | useState, |
| 12 | 13 | } from "react"; |
| 14 | import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover"; | |
| 13 | 15 | import { |
| 14 | 16 | extractTerminalLinks, |
| 15 | 17 | isTerminalLinkActivation, |
| ... | ... | @@ -419,6 +421,41 @@ interface ThreadTerminalDrawerProps { |
| 419 | 421 | onHeightChange: (height: number) => void; |
| 420 | 422 | } |
| 421 | 423 | |
| 424 | interface TerminalActionButtonProps { | |
| 425 | label: string; | |
| 426 | className: string; | |
| 427 | onClick: () => void; | |
| 428 | children: ReactNode; | |
| 429 | } | |
| 430 | ||
| 431 | function TerminalActionButton({ label, className, onClick, children }: TerminalActionButtonProps) { | |
| 432 | return ( | |
| 433 | <Popover> | |
| 434 | <PopoverTrigger openOnHover | |
| 435 | render={ | |
| 436 | <button | |
| 437 | type="button" | |
| 438 | className={className} | |
| 439 | onClick={onClick} | |
| 440 | aria-label={label} | |
| 441 | /> | |
| 442 | } | |
| 443 | > | |
| 444 | {children} | |
| 445 | </PopoverTrigger> | |
| 446 | <PopoverPopup | |
| 447 | tooltipStyle | |
| 448 | side="bottom" | |
| 449 | sideOffset={6} | |
| 450 | align="center" | |
| 451 | className="pointer-events-none select-none" | |
| 452 | > | |
| 453 | {label} | |
| 454 | </PopoverPopup> | |
| 455 | </Popover> | |
| 456 | ); | |
| 457 | } | |
| 458 | ||
| 422 | 459 | export default function ThreadTerminalDrawer({ |
| 423 | 460 | api, |
| 424 | 461 | threadId, |
| ... | ... | @@ -654,35 +691,29 @@ export default function ThreadTerminalDrawer({ |
| 654 | 691 | {!hasTerminalSidebar && ( |
| 655 | 692 | <div className="pointer-events-none absolute right-2 top-2 z-20"> |
| 656 | 693 | <div className="pointer-events-auto inline-flex items-center overflow-hidden rounded-md border border-border/80 bg-background/70"> |
| 657 | <button | |
| 658 | type="button" | |
| 694 | <TerminalActionButton | |
| 659 | 695 | className="p-1 text-foreground/90 transition-colors hover:bg-accent" |
| 660 | 696 | onClick={onSplitTerminal} |
| 661 | aria-label="Split Terminal" | |
| 662 | title="Split Terminal" | |
| 697 | label="Split Terminal" | |
| 663 | 698 | > |
| 664 | 699 | <SquareSplitHorizontal className="size-3.25" /> |
| 665 | </button> | |
| 700 | </TerminalActionButton> | |
| 666 | 701 | <div className="h-4 w-px bg-border/80" /> |
| 667 | <button | |
| 668 | type="button" | |
| 702 | <TerminalActionButton | |
| 669 | 703 | className="p-1 text-foreground/90 transition-colors hover:bg-accent" |
| 670 | 704 | onClick={onNewTerminal} |
| 671 | aria-label="New Terminal" | |
| 672 | title="New Terminal" | |
| 705 | label="New Terminal" | |
| 673 | 706 | > |
| 674 | 707 | <Plus className="size-3.25" /> |
| 675 | </button> | |
| 708 | </TerminalActionButton> | |
| 676 | 709 | <div className="h-4 w-px bg-border/80" /> |
| 677 | <button | |
| 678 | type="button" | |
| 710 | <TerminalActionButton | |
| 679 | 711 | className="p-1 text-foreground/90 transition-colors hover:bg-accent" |
| 680 | 712 | onClick={() => onCloseTerminal(resolvedActiveTerminalId)} |
| 681 | aria-label="Close Terminal" | |
| 682 | title="Close Terminal" | |
| 713 | label="Close Terminal" | |
| 683 | 714 | > |
| 684 | 715 | <Trash2 className="size-3.25" /> |
| 685 | </button> | |
| 716 | </TerminalActionButton> | |
| 686 | 717 | </div> |
| 687 | 718 | </div> |
| 688 | 719 | )} |
| ... | ... | @@ -745,33 +776,27 @@ export default function ThreadTerminalDrawer({ |
| 745 | 776 | <aside className="flex w-36 min-w-36 flex-col border border-border/70 bg-muted/10"> |
| 746 | 777 | <div className="flex h-[22px] items-stretch justify-end border-b border-border/70"> |
| 747 | 778 | <div className="inline-flex h-full items-stretch"> |
| 748 | <button | |
| 749 | type="button" | |
| 779 | <TerminalActionButton | |
| 750 | 780 | className="inline-flex h-full items-center px-1 text-foreground/90 transition-colors hover:bg-accent/70" |
| 751 | 781 | onClick={onSplitTerminal} |
| 752 | aria-label="Split Terminal" | |
| 753 | title="Split Terminal" | |
| 782 | label="Split Terminal" | |
| 754 | 783 | > |
| 755 | 784 | <SquareSplitHorizontal className="size-3.25" /> |
| 756 | </button> | |
| 757 | <button | |
| 758 | type="button" | |
| 785 | </TerminalActionButton> | |
| 786 | <TerminalActionButton | |
| 759 | 787 | className="inline-flex h-full items-center border-l border-border/70 px-1 text-foreground/90 transition-colors hover:bg-accent/70" |
| 760 | 788 | onClick={onNewTerminal} |
| 761 | aria-label="New Terminal" | |
| 762 | title="New Terminal" | |
| 789 | label="New Terminal" | |
| 763 | 790 | > |
| 764 | 791 | <Plus className="size-3.25" /> |
| 765 | </button> | |
| 766 | <button | |
| 767 | type="button" | |
| 792 | </TerminalActionButton> | |
| 793 | <TerminalActionButton | |
| 768 | 794 | className="inline-flex h-full items-center border-l border-border/70 px-1 text-foreground/90 transition-colors hover:bg-accent/70" |
| 769 | 795 | onClick={() => onCloseTerminal(resolvedActiveTerminalId)} |
| 770 | aria-label="Close Terminal" | |
| 771 | title="Close Terminal" | |
| 796 | label="Close Terminal" | |
| 772 | 797 | > |
| 773 | 798 | <Trash2 className="size-3.25" /> |
| 774 | </button> | |
| 799 | </TerminalActionButton> | |
| 775 | 800 | </div> |
| 776 | 801 | </div> |
| 777 | 802 |