| ... | @@ -6,11 +6,11 @@ import { | ... | @@ -6,11 +6,11 @@ import { |
| 6 | import { type TerminalSessionState } from "@t3tools/client-runtime/state/terminal"; | 6 | import { type TerminalSessionState } from "@t3tools/client-runtime/state/terminal"; |
| 7 | import { | 7 | import { |
| 8 | Plus, | 8 | Plus, |
| | 9 | Square, |
| 9 | SquareSplitHorizontal, | 10 | SquareSplitHorizontal, |
| 10 | SquareSplitVertical, | 11 | SquareSplitVertical, |
| 11 | TerminalSquare, | 12 | TerminalSquare, |
| 12 | Trash2, | 13 | Trash2, |
| 13 | XIcon, | | |
| 14 | } from "lucide-react"; | 14 | } from "lucide-react"; |
| 15 | import { | 15 | import { |
| 16 | type ContextMenuItem, | 16 | type ContextMenuItem, |
| ... | @@ -33,6 +33,7 @@ import { | ... | @@ -33,6 +33,7 @@ import { |
| 33 | } from "react"; | 33 | } from "react"; |
| 34 | import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover"; | 34 | import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover"; |
| 35 | import { Button } from "~/components/ui/button"; | 35 | import { Button } from "~/components/ui/button"; |
| | 36 | import { PanelTabCloseButton } from "~/components/ui/panel-tab-close-button"; |
| 36 | import { readTextFromClipboard, writeTextToClipboard } from "~/hooks/useCopyToClipboard"; | 37 | import { readTextFromClipboard, writeTextToClipboard } from "~/hooks/useCopyToClipboard"; |
| 37 | import { cn } from "~/lib/utils"; | 38 | import { cn } from "~/lib/utils"; |
| 38 | import { type TerminalContextSelection } from "~/lib/terminalContext"; | 39 | import { type TerminalContextSelection } from "~/lib/terminalContext"; |
| ... | @@ -1622,85 +1623,76 @@ export default function ThreadTerminalDrawer({ | ... | @@ -1622,85 +1623,76 @@ export default function ThreadTerminalDrawer({ |
| 1622 | </div> | 1623 | </div> |
| 1623 | | 1624 | |
| 1624 | <div className="min-h-0 flex-1 overflow-y-auto px-1 py-1"> | 1625 | <div className="min-h-0 flex-1 overflow-y-auto px-1 py-1"> |
| 1625 | {resolvedTerminalGroups.map((terminalGroup, groupIndex) => { | 1626 | {resolvedTerminalGroups.map((terminalGroup) => { |
| 1626 | const isGroupActive = | 1627 | const isGroupActive = |
| 1627 | terminalGroup.terminalIds.includes(resolvedActiveTerminalId); | 1628 | terminalGroup.terminalIds.includes(resolvedActiveTerminalId); |
| 1628 | const groupActiveTerminalId = isGroupActive | 1629 | const groupActiveTerminalId = isGroupActive |
| 1629 | ? resolvedActiveTerminalId | 1630 | ? resolvedActiveTerminalId |
| 1630 | : (terminalGroup.terminalIds[0] ?? resolvedActiveTerminalId); | 1631 | : (terminalGroup.terminalIds[0] ?? resolvedActiveTerminalId); |
| | 1632 | const terminalCount = terminalGroup.terminalIds.length; |
| | 1633 | const isSplitGroup = terminalCount > 1; |
| | 1634 | const groupLabel = !isSplitGroup |
| | 1635 | ? "Single" |
| | 1636 | : terminalGroup.splitDirection === "vertical" |
| | 1637 | ? "Stacked" |
| | 1638 | : "Side by side"; |
| | 1639 | const GroupIcon = !isSplitGroup |
| | 1640 | ? Square |
| | 1641 | : terminalGroup.splitDirection === "vertical" |
| | 1642 | ? SquareSplitVertical |
| | 1643 | : SquareSplitHorizontal; |
| 1631 | | 1644 | |
| 1632 | return ( | 1645 | return ( |
| 1633 | <div key={terminalGroup.id} className="pb-0.5"> | 1646 | <div key={terminalGroup.id} className="pb-0.5"> |
| 1634 | {showGroupHeaders && ( | 1647 | {showGroupHeaders && ( |
| 1635 | <button | 1648 | <button |
| 1636 | type="button" | 1649 | type="button" |
| 1637 | className={`flex w-full items-center rounded px-1 py-0.5 text-[10px] uppercase tracking-[0.08em] ${ | 1650 | className={`flex h-[22px] w-full cursor-pointer items-center gap-1 rounded px-1.5 text-[11px] ${ |
| 1638 | isGroupActive | 1651 | isGroupActive |
| 1639 | ? "bg-accent/70 text-foreground" | 1652 | ? "bg-accent/50 text-foreground" |
| 1640 | : "text-muted-foreground hover:bg-accent/50 hover:text-foreground" | 1653 | : "text-muted-foreground hover:bg-accent/40 hover:text-foreground" |
| 1641 | }`} | 1654 | }`} |
| 1642 | onClick={() => onActiveTerminalChange(groupActiveTerminalId)} | 1655 | onClick={() => onActiveTerminalChange(groupActiveTerminalId)} |
| 1643 | > | 1656 | > |
| 1644 | Group {groupIndex + 1} | 1657 | <GroupIcon className="size-3 shrink-0" /> |
| | 1658 | <span className="min-w-0 flex-1 truncate text-left">{groupLabel}</span> |
| | 1659 | <span className="text-muted-foreground/70 text-[10px] tabular-nums"> |
| | 1660 | {terminalCount} |
| | 1661 | </span> |
| 1645 | </button> | 1662 | </button> |
| 1646 | )} | 1663 | )} |
| 1647 | | 1664 | |
| 1648 | <div | 1665 | <div className="flex flex-col gap-0.5"> |
| 1649 | className={showGroupHeaders ? "ml-1 border-l border-border/60 pl-1.5" : ""} | | |
| 1650 | > | | |
| 1651 | {terminalGroup.terminalIds.map((terminalId) => { | 1666 | {terminalGroup.terminalIds.map((terminalId) => { |
| 1652 | const isActive = terminalId === resolvedActiveTerminalId; | 1667 | const isActive = terminalId === resolvedActiveTerminalId; |
| 1653 | const closeTerminalLabel = `Close ${ | 1668 | const terminalLabel = terminalLabelById.get(terminalId) ?? "Terminal"; |
| 1654 | terminalLabelById.get(terminalId) ?? "terminal" | 1669 | const closeTerminalLabel = `Close ${terminalLabel}${ |
| 1655 | }${isActive && closeShortcutLabel ? ` (${closeShortcutLabel})` : ""}`; | 1670 | isActive && closeShortcutLabel ? ` (${closeShortcutLabel})` : "" |
| | 1671 | }`; |
| 1656 | return ( | 1672 | return ( |
| 1657 | <div | 1673 | <div |
| 1658 | key={terminalId} | 1674 | key={terminalId} |
| 1659 | className={`group flex items-center gap-1 rounded px-1 py-0.5 text-[11px] ${ | 1675 | className={cn( |
| | 1676 | "group/tab flex h-6 w-full items-center gap-0.5 rounded-md pr-2 pl-1.5 text-xs", |
| 1660 | isActive | 1677 | isActive |
| 1661 | ? "bg-accent text-foreground" | 1678 | ? "bg-accent text-foreground" |
| 1662 | : "text-muted-foreground hover:bg-accent/50 hover:text-foreground" | 1679 | : "text-muted-foreground hover:bg-accent/60 hover:text-foreground", |
| 1663 | }`} | | |
| 1664 | > | | |
| 1665 | {showGroupHeaders && ( | | |
| 1666 | <span className="text-[10px] text-muted-foreground/80">└</span> | | |
| 1667 | )} | 1680 | )} |
| | 1681 | > |
| | 1682 | <PanelTabCloseButton |
| | 1683 | label={closeTerminalLabel} |
| | 1684 | onClick={() => confirmCloseTerminal(terminalId)} |
| | 1685 | tooltip={closeTerminalLabel} |
| | 1686 | > |
| | 1687 | <TerminalSquare className="size-3 shrink-0" /> |
| | 1688 | </PanelTabCloseButton> |
| 1668 | <button | 1689 | <button |
| 1669 | type="button" | 1690 | type="button" |
| 1670 | className="flex min-w-0 flex-1 items-center gap-1 text-left" | 1691 | className="flex min-w-0 flex-1 cursor-pointer items-center gap-1 text-left" |
| 1671 | onClick={() => onActiveTerminalChange(terminalId)} | 1692 | onClick={() => onActiveTerminalChange(terminalId)} |
| 1672 | > | 1693 | > |
| 1673 | <TerminalSquare className="size-3 shrink-0" /> | 1694 | <span className="truncate">{terminalLabel}</span> |
| 1674 | <span className="truncate"> | | |
| 1675 | {terminalLabelById.get(terminalId) ?? "Terminal"} | | |
| 1676 | </span> | | |
| 1677 | </button> | 1695 | </button> |
| 1678 | {normalizedTerminalIds.length > 1 && ( | | |
| 1679 | <Popover> | | |
| 1680 | <PopoverTrigger | | |
| 1681 | openOnHover | | |
| 1682 | render={ | | |
| 1683 | <button | | |
| 1684 | type="button" | | |
| 1685 | className="inline-flex size-3.5 items-center justify-center rounded text-xs font-medium leading-none text-muted-foreground opacity-0 transition hover:bg-accent hover:text-foreground group-hover:opacity-100" | | |
| 1686 | onClick={() => confirmCloseTerminal(terminalId)} | | |
| 1687 | aria-label={closeTerminalLabel} | | |
| 1688 | /> | | |
| 1689 | } | | |
| 1690 | > | | |
| 1691 | <XIcon className="size-2.5" /> | | |
| 1692 | </PopoverTrigger> | | |
| 1693 | <PopoverPopup | | |
| 1694 | tooltipStyle | | |
| 1695 | side="bottom" | | |
| 1696 | sideOffset={6} | | |
| 1697 | align="center" | | |
| 1698 | className="pointer-events-none select-none" | | |
| 1699 | > | | |
| 1700 | {closeTerminalLabel} | | |
| 1701 | </PopoverPopup> | | |
| 1702 | </Popover> | | |
| 1703 | )} | | |
| 1704 | </div> | 1696 | </div> |
| 1705 | ); | 1697 | ); |
| 1706 | })} | 1698 | })} |