|
1 | | -import React from 'react'; |
2 | | -import { Flex, Box, Text, IconButton } from '@radix-ui/themes'; |
| 1 | +import React, { useCallback } from 'react'; |
| 2 | +import { Flex, Box, Text, IconButton, Kbd } from '@radix-ui/themes'; |
3 | 3 | import { Cross2Icon } from '@radix-ui/react-icons'; |
4 | 4 | import { useTabStore } from '../stores/tabStore'; |
5 | 5 | import { useHotkeys } from 'react-hotkeys-hook'; |
6 | 6 |
|
7 | 7 | export function TabBar() { |
8 | 8 | const { tabs, activeTabId, setActiveTab, closeTab } = useTabStore(); |
9 | 9 |
|
10 | | - // Keyboard shortcuts for tab navigation |
11 | | - useHotkeys('cmd+shift+[, ctrl+shift+[', () => { |
| 10 | + // Keyboard navigation handlers |
| 11 | + const handlePrevTab = useCallback(() => { |
12 | 12 | const currentIndex = tabs.findIndex(tab => tab.id === activeTabId); |
13 | 13 | if (currentIndex > 0) { |
14 | 14 | setActiveTab(tabs[currentIndex - 1].id); |
15 | 15 | } |
16 | 16 | }, [tabs, activeTabId, setActiveTab]); |
17 | 17 |
|
18 | | - useHotkeys('cmd+shift+], ctrl+shift+]', () => { |
| 18 | + const handleNextTab = useCallback(() => { |
19 | 19 | const currentIndex = tabs.findIndex(tab => tab.id === activeTabId); |
20 | 20 | if (currentIndex < tabs.length - 1) { |
21 | 21 | setActiveTab(tabs[currentIndex + 1].id); |
22 | 22 | } |
23 | 23 | }, [tabs, activeTabId, setActiveTab]); |
24 | 24 |
|
25 | | - useHotkeys('cmd+w, ctrl+w', () => { |
| 25 | + const handleCloseTab = useCallback(() => { |
| 26 | + console.log('Closing tab'); |
26 | 27 | if (tabs.length > 1) { |
27 | 28 | closeTab(activeTabId); |
28 | 29 | } |
29 | 30 | }, [tabs, activeTabId, closeTab]); |
30 | 31 |
|
| 32 | + // Tab switching by number handlers |
| 33 | + const handleSwitchToTab = useCallback((index: number) => { |
| 34 | + if (tabs[index]) { |
| 35 | + setActiveTab(tabs[index].id); |
| 36 | + } |
| 37 | + }, [tabs, setActiveTab]); |
| 38 | + |
| 39 | + const HOTKEYS = { |
| 40 | + CTRL_PREV_TAB: 'ctrl+shift+[', |
| 41 | + MOD_PREV_TAB: 'mod+shift+[', |
| 42 | + CTRL_NEXT_TAB: 'ctrl+shift+]', |
| 43 | + MOD_NEXT_TAB: 'mod+shift+]', |
| 44 | + CTRL_CLOSE_TAB: 'ctrl+w', |
| 45 | + MOD_CLOSE_TAB: 'mod+w', |
| 46 | + // Cmd/Ctrl+1 through Cmd/Ctrl+9 |
| 47 | + ...Object.fromEntries( |
| 48 | + Array.from({ length: 9 }, (_, i) => [`TAB_${i + 1}`, `mod+${i + 1}, ctrl+${i + 1}`]) |
| 49 | + ), |
| 50 | + }; |
| 51 | + |
| 52 | + useHotkeys(Object.values(HOTKEYS), (event, { hotkey }) => { |
| 53 | + switch (hotkey) { |
| 54 | + case HOTKEYS.CTRL_PREV_TAB: |
| 55 | + case HOTKEYS.MOD_PREV_TAB: |
| 56 | + handlePrevTab(); |
| 57 | + break; |
| 58 | + case HOTKEYS.CTRL_NEXT_TAB: |
| 59 | + case HOTKEYS.MOD_NEXT_TAB: |
| 60 | + handleNextTab(); |
| 61 | + break; |
| 62 | + case HOTKEYS.MOD_CLOSE_TAB: |
| 63 | + case HOTKEYS.CTRL_CLOSE_TAB: |
| 64 | + handleCloseTab(); |
| 65 | + break; |
| 66 | + default: { |
| 67 | + // Check if it's a tab switching shortcut |
| 68 | + const tabMatch = hotkey.match(/[1-9]/); |
| 69 | + if (tabMatch) { |
| 70 | + const tabIndex = parseInt(tabMatch[0], 10) - 1; |
| 71 | + handleSwitchToTab(tabIndex); |
| 72 | + } |
| 73 | + break; |
| 74 | + } |
| 75 | + } |
| 76 | + }, [handlePrevTab, handleNextTab, handleCloseTab, handleSwitchToTab]); |
| 77 | + |
31 | 78 | return ( |
32 | 79 | <Flex className="drag border-b border-gray-6" height="40px"> |
33 | 80 | {/* Spacer for macOS window controls */} |
34 | 81 | <Box width="80px" flexShrink="0" /> |
35 | 82 |
|
36 | | - {tabs.map((tab) => ( |
| 83 | + {tabs.map((tab, index) => ( |
37 | 84 | <Flex |
38 | 85 | key={tab.id} |
39 | 86 | className={`no-drag cursor-pointer border-r border-gray-6 transition-colors group ${tab.id === activeTabId |
40 | | - ? 'bg-accent-3 text-accent-12 border-b-2 border-b-accent-8 font-medium' |
41 | | - : 'text-gray-11 hover:bg-gray-3 hover:text-gray-12' |
| 87 | + ? 'bg-accent-3 text-accent-12 border-b-2 border-b-accent-8 font-medium' |
| 88 | + : 'text-gray-11 hover:bg-gray-3 hover:text-gray-12' |
42 | 89 | }`} |
43 | 90 | align="center" |
44 | 91 | px="4" |
45 | 92 | onClick={() => setActiveTab(tab.id)} |
46 | 93 | > |
| 94 | + {index < 9 && ( |
| 95 | + <Kbd size="1" className="mr-2 opacity-70"> |
| 96 | + {navigator.platform.includes('Mac') ? '⌘' : 'Ctrl+'}{index + 1} |
| 97 | + </Kbd> |
| 98 | + )} |
| 99 | + |
47 | 100 | <Text |
48 | 101 | size="2" |
49 | 102 | className={`max-w-[200px] overflow-hidden select-none text-ellipsis whitespace-nowrap ${tab.id === activeTabId ? 'font-medium' : '' |
|
0 commit comments