Refactor: Split Sidebar.tsx into smaller components

Refactored the Sidebar component into smaller, more manageable files to improve code organization and maintainability.

Fix sidebar auto-expansion issue
Ensured the sidebar's collapsed state is maintained when navigating between pages.
This commit is contained in:
Tola Leng
2025-06-08 21:56:09 +08:00
parent da0808cefe
commit f6592d331f
17 changed files with 552 additions and 290 deletions
@@ -0,0 +1,27 @@
import React from "react";
import { MenuItem } from "./MenuItem";
import { mainMenuItems } from "./navigationData";
interface MainNavigationProps {
collapsed: boolean;
}
export const MainNavigation: React.FC<MainNavigationProps> = ({ collapsed }) => {
return (
<nav className="my-2 mx-1 py-1 px-1">
{mainMenuItems.map((item) => (
<MenuItem
key={item.id}
id={item.id}
path={item.path}
icon={item.icon}
translationKey={item.translationKey}
color={item.color}
hasNavigation={item.hasNavigation}
collapsed={collapsed}
/>
))}
</nav>
);
};
@@ -0,0 +1,54 @@
import React from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useTheme } from "@/contexts/ThemeContext";
import { useLanguage } from "@/contexts/LanguageContext";
import { LucideIcon } from "lucide-react";
interface MenuItemProps {
id: string;
path: string | null;
icon: LucideIcon;
translationKey: string;
color: string;
hasNavigation: boolean;
collapsed: boolean;
}
export const MenuItem: React.FC<MenuItemProps> = ({
id,
path,
icon: Icon,
translationKey,
color,
hasNavigation,
collapsed
}) => {
const { theme } = useTheme();
const { t } = useLanguage();
const location = useLocation();
const navigate = useNavigate();
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (hasNavigation && path) {
// Use navigate instead of window.location to prevent full page reload
navigate(path, { replace: false });
}
};
const isActive = path && location.pathname === path;
const mainIconSize = "h-6 w-6";
return (
<div
className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg ${isActive ? theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'}`} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200 cursor-pointer`}
onClick={handleClick}
>
<Icon className={`${mainIconSize} ${color}`} />
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t(translationKey)}</span>}
</div>
);
};
@@ -0,0 +1,100 @@
import React, { useState, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useTheme } from "@/contexts/ThemeContext";
import { useLanguage } from "@/contexts/LanguageContext";
import { Settings, ChevronDown } from "lucide-react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { ScrollArea } from "@/components/ui/scroll-area";
import { settingsMenuItems } from "./navigationData";
interface SettingsPanelProps {
collapsed: boolean;
}
export const SettingsPanel: React.FC<SettingsPanelProps> = ({ collapsed }) => {
const { theme } = useTheme();
const { t } = useLanguage();
const location = useLocation();
const navigate = useNavigate();
const [activeSettingsItem, setActiveSettingsItem] = useState<string | null>("general");
const [settingsPanelOpen, setSettingsPanelOpen] = useState(false);
// Update active settings item based on URL
useEffect(() => {
if (location.pathname === '/settings') {
const params = new URLSearchParams(location.search);
const panel = params.get('panel');
if (panel) {
setActiveSettingsItem(panel);
}
}
}, [location]);
const handleSettingsItemClick = (item: string) => {
setActiveSettingsItem(item);
};
const handleMenuItemClick = (path: string, event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation();
// Use navigate instead of window.location to prevent full page reload
navigate(path, { replace: false });
};
const getMenuItemClasses = (isActive: boolean) => {
return `p-2 ${isActive ? theme === 'dark' ? 'bg-[#1a1a1a]' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-[#1a1a1a]' : 'bg-sidebar-accent'}`} rounded-lg flex items-center`;
};
if (collapsed) {
const mainIconSize = "h-6 w-6";
return (
<div className={`border-t ${theme === 'dark' ? 'border-[#1e1e1e] bg-[#121212]' : 'border-sidebar-border bg-sidebar'} p-4 flex justify-center`}>
<div
onClick={(e) => handleMenuItemClick('/settings', e)}
className="cursor-pointer"
>
<Settings className={`${mainIconSize} text-purple-400`} />
</div>
</div>
);
}
return (
<div className={`flex-1 flex flex-col border-t ${theme === 'dark' ? 'border-[#1e1e1e] bg-[#121212]' : 'border-sidebar-border bg-sidebar'} p-4`}>
<Collapsible open={settingsPanelOpen} onOpenChange={setSettingsPanelOpen} className="w-full flex flex-col flex-1">
<CollapsibleTrigger className={`flex items-center justify-between w-full mb-4 px-2 py-2 rounded-lg ${theme === 'dark' ? 'hover:bg-[#1a1a1a]' : 'hover:bg-sidebar-accent'}`}>
<div className="flex items-center">
<span className="font-medium tracking-wide">{t("settingPanel")}</span>
</div>
<div className="flex items-center">
<Settings className="h-4 w-4 mr-1" />
<ChevronDown className={`h-4 w-4 transition-transform duration-200 ${settingsPanelOpen ? 'rotate-180' : ''}`} />
</div>
</CollapsibleTrigger>
<CollapsibleContent className={`${theme === 'dark' ? 'bg-[#121212]' : 'bg-sidebar'} flex-1 flex flex-col`}>
<div className="max-h-[300px] overflow-y-auto custom-scrollbar relative pr-1">
<ScrollArea className="h-full">
<div className="space-y-2 pr-4">
{settingsMenuItems.map((item) => (
<div
key={item.id}
className={getMenuItemClasses(activeSettingsItem === item.id)}
onClick={(e) => {
handleMenuItemClick(`/settings?panel=${item.id}`, e);
handleSettingsItemClick(item.id);
}}
>
<item.icon className="h-4 w-4 mr-2" />
<span className="text-sm">{t(item.translationKey)}</span>
</div>
))}
</div>
</ScrollArea>
</div>
</CollapsibleContent>
</Collapsible>
</div>
);
};
@@ -0,0 +1,20 @@
import React from "react";
import { useTheme } from "@/contexts/ThemeContext";
interface SidebarHeaderProps {
collapsed: boolean;
}
export const SidebarHeader: React.FC<SidebarHeaderProps> = ({ collapsed }) => {
const { theme } = useTheme();
return (
<div className={`p-4 ${theme === 'dark' ? 'border-[#1e1e1e]' : 'border-sidebar-border'} border-b flex items-center ${collapsed ? 'justify-center' : ''}`}>
<div className="h-8 w-8 bg-green-500 rounded flex items-center justify-center mr-2">
<span className="text-white font-bold">C</span>
</div>
{!collapsed && <h1 className="text-xl font-semibold">CheckCle App</h1>}
</div>
);
};
@@ -0,0 +1,6 @@
export { SidebarHeader } from './SidebarHeader';
export { MainNavigation } from './MainNavigation';
export { MenuItem } from './MenuItem';
export { SettingsPanel } from './SettingsPanel';
export { mainMenuItems, settingsMenuItems } from './navigationData';
@@ -0,0 +1,94 @@
import { Globe, Boxes, Radar, Calendar, BarChart2, LineChart, FileText, Settings, User, Bell, Database, Info, BookOpen } from "lucide-react";
export const mainMenuItems = [
{
id: 'uptime-monitoring',
path: '/dashboard',
icon: Globe,
translationKey: 'uptimeMonitoring',
color: 'text-purple-400',
hasNavigation: true
},
{
id: 'instance-monitoring',
path: null,
icon: Boxes,
translationKey: 'instanceMonitoring',
color: 'text-blue-400',
hasNavigation: false
},
{
id: 'ssl-domain',
path: '/ssl-domain',
icon: Radar,
translationKey: 'sslDomain',
color: 'text-cyan-400',
hasNavigation: true
},
{
id: 'schedule-incident',
path: '/schedule-incident',
icon: Calendar,
translationKey: 'scheduleIncident',
color: 'text-emerald-400',
hasNavigation: true
},
{
id: 'operational-page',
path: '/operational-page',
icon: BarChart2,
translationKey: 'operationalPage',
color: 'text-amber-400',
hasNavigation: true
},
{
id: 'reports',
path: null,
icon: LineChart,
translationKey: 'reports',
color: 'text-rose-400',
hasNavigation: false
},
{
id: 'api-documentation',
path: null,
icon: FileText,
translationKey: 'apiDocumentation',
color: 'text-indigo-400',
hasNavigation: false
}
];
export const settingsMenuItems = [
{
id: 'general',
icon: Settings,
translationKey: 'generalSettings'
},
{
id: 'users',
icon: User,
translationKey: 'userManagement'
},
{
id: 'notifications',
icon: Bell,
translationKey: 'notificationSettings'
},
{
id: 'templates',
icon: BookOpen,
translationKey: 'alertsTemplates'
},
{
id: 'data-retention',
icon: Database,
translationKey: 'dataRetention'
},
{
id: 'about',
icon: Info,
translationKey: 'aboutSystem'
}
];