d1635c35f3
Implement Issue #130 suggestion: Add dropdown menu in top tab bar to help users quickly navigate between open hosts/tabs when many are open. Features: - Smart visibility: Only shows when multiple tabs are open - Visual identification: Each tab type has corresponding icon (Home, Terminal, Server, etc.) - Active tab indicator: Current tab highlighted with blue dot - Quick switching: Click any tab to switch instantly - Seamless integration: Positioned in top navbar without disrupting existing UI New Components: - src/components/ui/dropdown-menu.tsx: Radix UI based dropdown component - src/ui/Desktop/Navigation/Tabs/TabDropdown.tsx: Tab navigation dropdown logic - Updated TopNavbar.tsx with integrated dropdown UI/UX Improvements: - Consistent dark theme styling (#18181b background, #303032 borders) - 30x30px button size matching existing toolbar buttons - Right-aligned dropdown to avoid screen boundary issues - Hover effects and smooth transitions - Responsive design for different screen sizes i18n Support: - Added "tabNavigation" key in both English and Chinese translations - Accessible tooltips and labels Technical Implementation: - Zero breaking changes: Pure incremental feature - Reuses existing tab context and switching logic - TypeScript safe with proper type definitions - Performance optimized: Only renders when needed - Follows existing code patterns and conventions This enhancement significantly improves the user experience when managing multiple host connections, providing quick access to any tab without scrolling. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
111 lines
3.8 KiB
TypeScript
111 lines
3.8 KiB
TypeScript
import React from "react";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu.tsx";
|
|
import { Button } from "@/components/ui/button.tsx";
|
|
import {
|
|
ChevronDown,
|
|
Home,
|
|
Terminal as TerminalIcon,
|
|
Server as ServerIcon,
|
|
Folder as FolderIcon,
|
|
Shield as AdminIcon,
|
|
Network as SshManagerIcon
|
|
} from "lucide-react";
|
|
import { useTabs, type Tab } from "@/ui/Desktop/Navigation/Tabs/TabContext.tsx";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
export function TabDropdown(): React.ReactElement {
|
|
const { tabs, currentTab, setCurrentTab } = useTabs();
|
|
const { t } = useTranslation();
|
|
|
|
const getTabIcon = (tabType: Tab['type']) => {
|
|
switch (tabType) {
|
|
case 'home':
|
|
return <Home className="h-4 w-4" />;
|
|
case 'terminal':
|
|
return <TerminalIcon className="h-4 w-4" />;
|
|
case 'server':
|
|
return <ServerIcon className="h-4 w-4" />;
|
|
case 'file_manager':
|
|
return <FolderIcon className="h-4 w-4" />;
|
|
case 'ssh_manager':
|
|
return <SshManagerIcon className="h-4 w-4" />;
|
|
case 'admin':
|
|
return <AdminIcon className="h-4 w-4" />;
|
|
default:
|
|
return <TerminalIcon className="h-4 w-4" />;
|
|
}
|
|
};
|
|
|
|
const getTabDisplayTitle = (tab: Tab) => {
|
|
switch (tab.type) {
|
|
case 'home':
|
|
return t('nav.home');
|
|
case 'server':
|
|
return tab.title || t('nav.serverStats');
|
|
case 'file_manager':
|
|
return tab.title || t('nav.fileManager');
|
|
case 'ssh_manager':
|
|
return tab.title || t('nav.sshManager');
|
|
case 'admin':
|
|
return tab.title || t('nav.admin');
|
|
case 'terminal':
|
|
default:
|
|
return tab.title || t('nav.terminal');
|
|
}
|
|
};
|
|
|
|
const handleTabSwitch = (tabId: number) => {
|
|
setCurrentTab(tabId);
|
|
};
|
|
|
|
// If only one tab (home), don't show dropdown
|
|
if (tabs.length <= 1) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
className="w-[30px] h-[30px] border-[#303032]"
|
|
title={t('nav.tabNavigation', { defaultValue: 'Tab Navigation' })}
|
|
>
|
|
<ChevronDown className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
className="w-56 bg-[#18181b] border-[#303032] text-white"
|
|
>
|
|
{tabs.map((tab) => {
|
|
const isActive = tab.id === currentTab;
|
|
return (
|
|
<DropdownMenuItem
|
|
key={tab.id}
|
|
onClick={() => handleTabSwitch(tab.id)}
|
|
className={`flex items-center gap-2 cursor-pointer px-3 py-2 ${
|
|
isActive
|
|
? 'bg-[#1d1d1f] text-white'
|
|
: 'hover:bg-[#2d2d30] text-gray-300'
|
|
}`}
|
|
>
|
|
{getTabIcon(tab.type)}
|
|
<span className="flex-1 truncate">
|
|
{getTabDisplayTitle(tab)}
|
|
</span>
|
|
{isActive && (
|
|
<div className="w-2 h-2 rounded-full bg-blue-500 flex-shrink-0" />
|
|
)}
|
|
</DropdownMenuItem>
|
|
);
|
|
})}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
} |