| 1 | import { PanelBottomIcon, PanelRightIcon, SquareMenuIcon } from "lucide-react"; |
| 2 | import { Maximize2, Minimize2 } from "lucide"; |
| 3 | import { MorphIcon } from "~/components/MorphIcon"; |
| 4 | import { memo, type ReactElement } from "react"; |
| 5 | |
| 6 | import type { ThreadPanelPresentation } from "../../rightPanelLayout"; |
| 7 | import { PopoverCreateHandle, PopoverTrigger } from "../ui/popover"; |
| 8 | import { Toggle } from "../ui/toggle"; |
| 9 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 10 | |
| 11 | export interface PanelLayoutControlsProps { |
| 12 | showThreadPanelControl?: boolean; |
| 13 | showTerminalControl?: boolean; |
| 14 | showRightPanelControl?: boolean; |
| 15 | terminalAvailable: boolean; |
| 16 | terminalOpen: boolean; |
| 17 | terminalShortcutLabel: string | null; |
| 18 | threadPanelOpen: boolean; |
| 19 | threadPanelPresentation: ThreadPanelPresentation; |
| 20 | threadPanelPopoverHandle?: ReturnType<typeof PopoverCreateHandle>; |
| 21 | threadPanelShortcutLabel: string | null; |
| 22 | rightPanelAvailable: boolean; |
| 23 | rightPanelOpen: boolean; |
| 24 | rightPanelShortcutLabel: string | null; |
| 25 | rightPanelUnavailableLabel?: string; |
| 26 | onToggleTerminal: () => void; |
| 27 | onToggleThreadPanel: () => void; |
| 28 | onToggleRightPanel: () => void; |
| 29 | } |
| 30 | |
| 31 | export const PanelLayoutControls = memo(function PanelLayoutControls({ |
| 32 | showThreadPanelControl = true, |
| 33 | showTerminalControl = true, |
| 34 | showRightPanelControl = true, |
| 35 | terminalAvailable, |
| 36 | terminalOpen, |
| 37 | terminalShortcutLabel, |
| 38 | threadPanelOpen, |
| 39 | threadPanelPresentation, |
| 40 | threadPanelPopoverHandle, |
| 41 | threadPanelShortcutLabel, |
| 42 | rightPanelAvailable, |
| 43 | rightPanelOpen, |
| 44 | rightPanelShortcutLabel, |
| 45 | rightPanelUnavailableLabel = "Right panel is unavailable", |
| 46 | onToggleTerminal, |
| 47 | onToggleThreadPanel, |
| 48 | onToggleRightPanel, |
| 49 | }: PanelLayoutControlsProps) { |
| 50 | const threadPanelToggle = ( |
| 51 | <Toggle |
| 52 | className="relative shrink-0 [-webkit-app-region:no-drag]" |
| 53 | pressed={threadPanelOpen} |
| 54 | aria-label="Toggle thread details panel" |
| 55 | variant="ghost" |
| 56 | size="sm" |
| 57 | > |
| 58 | <SquareMenuIcon className="size-4" /> |
| 59 | </Toggle> |
| 60 | ); |
| 61 | const threadPanelTooltip = (trigger: ReactElement) => ( |
| 62 | <Tooltip> |
| 63 | <TooltipTrigger |
| 64 | render={trigger} |
| 65 | {...(threadPanelPresentation === "popover" ? {} : { onClick: onToggleThreadPanel })} |
| 66 | /> |
| 67 | <TooltipPopup side="bottom"> |
| 68 | Toggle thread details |
| 69 | {threadPanelShortcutLabel ? ` (${threadPanelShortcutLabel})` : ""} |
| 70 | </TooltipPopup> |
| 71 | </Tooltip> |
| 72 | ); |
| 73 | |
| 74 | return ( |
| 75 | <div |
| 76 | className="flex h-full shrink-0 items-center gap-1 [-webkit-app-region:no-drag]" |
| 77 | data-panel-layout-controls |
| 78 | > |
| 79 | {showThreadPanelControl |
| 80 | ? threadPanelPresentation === "popover" |
| 81 | ? threadPanelTooltip( |
| 82 | <PopoverTrigger handle={threadPanelPopoverHandle} render={threadPanelToggle} />, |
| 83 | ) |
| 84 | : threadPanelTooltip(threadPanelToggle) |
| 85 | : null} |
| 86 | {showTerminalControl ? ( |
| 87 | <Tooltip> |
| 88 | <TooltipTrigger render={<span className="flex shrink-0" />}> |
| 89 | <Toggle |
| 90 | className="shrink-0 [-webkit-app-region:no-drag]" |
| 91 | pressed={terminalOpen} |
| 92 | onPressedChange={onToggleTerminal} |
| 93 | aria-label="Toggle terminal" |
| 94 | variant="ghost" |
| 95 | size="sm" |
| 96 | disabled={!terminalAvailable} |
| 97 | > |
| 98 | <PanelBottomIcon className="size-4" /> |
| 99 | </Toggle> |
| 100 | </TooltipTrigger> |
| 101 | <TooltipPopup side="bottom"> |
| 102 | {terminalAvailable |
| 103 | ? `Toggle terminal${terminalShortcutLabel ? ` (${terminalShortcutLabel})` : ""}` |
| 104 | : "Terminal is unavailable"} |
| 105 | </TooltipPopup> |
| 106 | </Tooltip> |
| 107 | ) : null} |
| 108 | {showRightPanelControl ? ( |
| 109 | <Tooltip> |
| 110 | <TooltipTrigger render={<span className="flex shrink-0" />}> |
| 111 | <Toggle |
| 112 | className="shrink-0 [-webkit-app-region:no-drag]" |
| 113 | pressed={rightPanelOpen} |
| 114 | onPressedChange={onToggleRightPanel} |
| 115 | aria-label="Toggle right panel" |
| 116 | variant="ghost" |
| 117 | size="sm" |
| 118 | disabled={!rightPanelAvailable} |
| 119 | > |
| 120 | <PanelRightIcon className="size-4" /> |
| 121 | </Toggle> |
| 122 | </TooltipTrigger> |
| 123 | <TooltipPopup side="bottom"> |
| 124 | {rightPanelAvailable |
| 125 | ? `Toggle right panel${rightPanelShortcutLabel ? ` (${rightPanelShortcutLabel})` : ""}` |
| 126 | : rightPanelUnavailableLabel} |
| 127 | </TooltipPopup> |
| 128 | </Tooltip> |
| 129 | ) : null} |
| 130 | </div> |
| 131 | ); |
| 132 | }); |
| 133 | |
| 134 | export const RightPanelMaximizeControl = memo(function RightPanelMaximizeControl({ |
| 135 | maximized, |
| 136 | onToggle, |
| 137 | }: { |
| 138 | maximized: boolean; |
| 139 | onToggle: () => void; |
| 140 | }) { |
| 141 | const label = maximized ? "Restore panel size" : "Maximize panel"; |
| 142 | return ( |
| 143 | <Tooltip> |
| 144 | <TooltipTrigger |
| 145 | render={ |
| 146 | <Toggle |
| 147 | className="shrink-0 [-webkit-app-region:no-drag]" |
| 148 | pressed={maximized} |
| 149 | onPressedChange={onToggle} |
| 150 | aria-label={label} |
| 151 | variant="ghost" |
| 152 | size="sm" |
| 153 | > |
| 154 | <MorphIcon className="size-4" icon={maximized ? Minimize2 : Maximize2} /> |
| 155 | </Toggle> |
| 156 | } |
| 157 | /> |
| 158 | <TooltipPopup side="bottom">{label}</TooltipPopup> |
| 159 | </Tooltip> |
| 160 | ); |
| 161 | }); |