1import { PanelBottomIcon, PanelRightIcon, SquareMenuIcon } from "lucide-react";
2import { Maximize2, Minimize2 } from "lucide";
3import { MorphIcon } from "~/components/MorphIcon";
4import { memo, type ReactElement } from "react";
5
6import type { ThreadPanelPresentation } from "../../rightPanelLayout";
7import { PopoverCreateHandle, PopoverTrigger } from "../ui/popover";
8import { Toggle } from "../ui/toggle";
9import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
10
11export 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
31export 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
134export 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});