|
1 | 1 | import { Cross2Icon } from "@radix-ui/react-icons"; |
2 | | -import { Box, Flex, IconButton, Kbd, Text } from "@radix-ui/themes"; |
| 2 | +import { |
| 3 | + Box, |
| 4 | + ContextMenu, |
| 5 | + Flex, |
| 6 | + IconButton, |
| 7 | + Kbd, |
| 8 | + Text, |
| 9 | +} from "@radix-ui/themes"; |
3 | 10 | import type React from "react"; |
4 | 11 | import { useCallback, useState } from "react"; |
5 | 12 | import { useHotkeys } from "react-hotkeys-hook"; |
6 | 13 | import { useTabStore } from "../stores/tabStore"; |
7 | 14 |
|
8 | 15 | export function TabBar() { |
9 | | - const { tabs, activeTabId, setActiveTab, closeTab, reorderTabs } = |
10 | | - useTabStore(); |
| 16 | + const { |
| 17 | + tabs, |
| 18 | + activeTabId, |
| 19 | + setActiveTab, |
| 20 | + closeTab, |
| 21 | + closeOtherTabs, |
| 22 | + closeTabsToRight, |
| 23 | + reorderTabs, |
| 24 | + } = useTabStore(); |
11 | 25 | const [draggedTab, setDraggedTab] = useState<string | null>(null); |
12 | 26 | const [dragOverTab, setDragOverTab] = useState<string | null>(null); |
13 | 27 | const [dropPosition, setDropPosition] = useState<"left" | "right" | null>( |
@@ -168,66 +182,83 @@ export function TabBar() { |
168 | 182 | const showRightIndicator = isDragOver && dropPosition === "right"; |
169 | 183 |
|
170 | 184 | return ( |
171 | | - <Flex |
172 | | - key={tab.id} |
173 | | - className={`no-drag group relative cursor-pointer border-gray-6 border-r border-b-2 transition-colors ${ |
174 | | - tab.id === activeTabId |
175 | | - ? "border-b-accent-8 bg-accent-3 text-accent-12" |
176 | | - : "border-b-transparent text-gray-11 hover:bg-gray-3 hover:text-gray-12" |
177 | | - } ${isDragging ? "opacity-50" : ""}`} |
178 | | - align="center" |
179 | | - px="4" |
180 | | - draggable |
181 | | - onClick={() => setActiveTab(tab.id)} |
182 | | - onDragStart={(e) => handleDragStart(e, tab.id)} |
183 | | - onDragOver={(e) => handleDragOver(e, tab.id)} |
184 | | - onDragLeave={handleDragLeave} |
185 | | - onDrop={(e) => handleDrop(e, tab.id)} |
186 | | - onDragEnd={handleDragEnd} |
187 | | - > |
188 | | - {showLeftIndicator && ( |
189 | | - <Box |
190 | | - className="absolute top-0 bottom-0 left-0 z-10 w-0.5 bg-accent-8" |
191 | | - style={{ marginLeft: "-1px" }} |
192 | | - /> |
193 | | - )} |
194 | | - |
195 | | - {showRightIndicator && ( |
196 | | - <Box |
197 | | - className="absolute top-0 right-0 bottom-0 z-10 w-0.5 bg-accent-8" |
198 | | - style={{ marginRight: "-1px" }} |
199 | | - /> |
200 | | - )} |
201 | | - {index < 9 && ( |
202 | | - <Kbd size="1" className="mr-2 opacity-70"> |
203 | | - {navigator.platform.includes("Mac") ? "⌘" : "Ctrl+"} |
204 | | - {index + 1} |
205 | | - </Kbd> |
206 | | - )} |
207 | | - |
208 | | - <Text |
209 | | - size="2" |
210 | | - className="max-w-[200px] select-none overflow-hidden text-ellipsis whitespace-nowrap" |
211 | | - mr="2" |
212 | | - > |
213 | | - {tab.title} |
214 | | - </Text> |
215 | | - |
216 | | - {tabs.length > 1 && ( |
217 | | - <IconButton |
218 | | - size="1" |
219 | | - variant="ghost" |
220 | | - color={tab.id !== activeTabId ? "gray" : undefined} |
221 | | - className="opacity-0 transition-opacity group-hover:opacity-100" |
222 | | - onClick={(e) => { |
223 | | - e.stopPropagation(); |
224 | | - closeTab(tab.id); |
225 | | - }} |
| 185 | + <ContextMenu.Root key={tab.id}> |
| 186 | + <ContextMenu.Trigger> |
| 187 | + <Flex |
| 188 | + className={`no-drag group relative cursor-pointer border-gray-6 border-r border-b-2 transition-colors ${ |
| 189 | + tab.id === activeTabId |
| 190 | + ? "border-b-accent-8 bg-accent-3 text-accent-12" |
| 191 | + : "border-b-transparent text-gray-11 hover:bg-gray-3 hover:text-gray-12" |
| 192 | + } ${isDragging ? "opacity-50" : ""}`} |
| 193 | + align="center" |
| 194 | + px="4" |
| 195 | + draggable |
| 196 | + onClick={() => setActiveTab(tab.id)} |
| 197 | + onDragStart={(e) => handleDragStart(e, tab.id)} |
| 198 | + onDragOver={(e) => handleDragOver(e, tab.id)} |
| 199 | + onDragLeave={handleDragLeave} |
| 200 | + onDrop={(e) => handleDrop(e, tab.id)} |
| 201 | + onDragEnd={handleDragEnd} |
226 | 202 | > |
227 | | - <Cross2Icon /> |
228 | | - </IconButton> |
229 | | - )} |
230 | | - </Flex> |
| 203 | + {showLeftIndicator && ( |
| 204 | + <Box |
| 205 | + className="absolute top-0 bottom-0 left-0 z-10 w-0.5 bg-accent-8" |
| 206 | + style={{ marginLeft: "-1px" }} |
| 207 | + /> |
| 208 | + )} |
| 209 | + |
| 210 | + {showRightIndicator && ( |
| 211 | + <Box |
| 212 | + className="absolute top-0 right-0 bottom-0 z-10 w-0.5 bg-accent-8" |
| 213 | + style={{ marginRight: "-1px" }} |
| 214 | + /> |
| 215 | + )} |
| 216 | + {index < 9 && ( |
| 217 | + <Kbd size="1" className="mr-2 opacity-70"> |
| 218 | + {navigator.platform.includes("Mac") ? "⌘" : "Ctrl+"} |
| 219 | + {index + 1} |
| 220 | + </Kbd> |
| 221 | + )} |
| 222 | + |
| 223 | + <Text |
| 224 | + size="2" |
| 225 | + className="max-w-[200px] select-none overflow-hidden text-ellipsis whitespace-nowrap" |
| 226 | + mr="2" |
| 227 | + > |
| 228 | + {tab.title} |
| 229 | + </Text> |
| 230 | + |
| 231 | + {tabs.length > 1 && ( |
| 232 | + <IconButton |
| 233 | + size="1" |
| 234 | + variant="ghost" |
| 235 | + color={tab.id !== activeTabId ? "gray" : undefined} |
| 236 | + className="opacity-0 transition-opacity group-hover:opacity-100" |
| 237 | + onClick={(e) => { |
| 238 | + e.stopPropagation(); |
| 239 | + closeTab(tab.id); |
| 240 | + }} |
| 241 | + > |
| 242 | + <Cross2Icon /> |
| 243 | + </IconButton> |
| 244 | + )} |
| 245 | + </Flex> |
| 246 | + </ContextMenu.Trigger> |
| 247 | + <ContextMenu.Content> |
| 248 | + <ContextMenu.Item |
| 249 | + disabled={tabs.length === 1} |
| 250 | + onSelect={() => closeOtherTabs(tab.id)} |
| 251 | + > |
| 252 | + Close other tabs |
| 253 | + </ContextMenu.Item> |
| 254 | + <ContextMenu.Item |
| 255 | + disabled={index === tabs.length - 1} |
| 256 | + onSelect={() => closeTabsToRight(tab.id)} |
| 257 | + > |
| 258 | + Close tabs to the right |
| 259 | + </ContextMenu.Item> |
| 260 | + </ContextMenu.Content> |
| 261 | + </ContextMenu.Root> |
231 | 262 | ); |
232 | 263 | })} |
233 | 264 | </Flex> |
|
0 commit comments