Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions frontend/src/components/navigations/side-nav-bar/SideNavBar.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,61 @@
.side-bar {
background-color: #0d3a63 !important;
overflow: hidden;
height: 100%;
}

/* Ant Design's internal wrapper - needs flex layout for scroll */
.side-bar .ant-layout-sider-children {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}

/* Wrapper for scrollable content */
.sidebar-content-wrapper {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
}

/* Hide scrollbar when sidebar is collapsed */
.side-bar.ant-layout-sider-collapsed .sidebar-content-wrapper {
overflow-y: hidden;
}

/* Pin container - fixed at bottom, centered */
.sidebar-pin-container.ant-btn {
display: flex;
justify-content: center;
align-items: center;
padding: 8px;
height: auto;
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0;
flex-shrink: 0;
background: transparent;
width: 100%;
}

.sidebar-pin-container.ant-btn:hover {
background: rgba(255, 255, 255, 0.1);
}

.sidebar-pin-icon {
color: rgba(255, 255, 255, 0.6);
font-size: 16px;
transition: color 0.2s;
}

.sidebar-pin-icon:hover {
color: white;
}

.sidebar-pin-icon.pinned {
color: #1890ff;
}

.secondary-list-wrapper {
Expand Down
252 changes: 164 additions & 88 deletions frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { useMemo } from "react";
import { BranchesOutlined } from "@ant-design/icons";
import { useMemo, useState, useEffect, useRef } from "react";
import {
BranchesOutlined,
PushpinOutlined,
PushpinFilled,
} from "@ant-design/icons";
import {
Button,
Divider,
Image,
Layout,
Expand All @@ -13,6 +18,10 @@ import PropTypes from "prop-types";
import { useNavigate } from "react-router-dom";

import { useSessionStore } from "../../../store/session-store";
import {
getLocalStorageValue,
setLocalStorageValue,
} from "../../../helpers/localStorage";
import Workflows from "../../../assets/Workflows.svg";
import apiDeploy from "../../../assets/api-deployments.svg";
import CustomTools from "../../../assets/custom-tools-icon.svg";
Expand Down Expand Up @@ -150,11 +159,51 @@ SettingsPopoverContent.propTypes = {
navigate: PropTypes.func.isRequired,
};

const SideNavBar = ({ collapsed }) => {
const SideNavBar = ({ collapsed, setCollapsed }) => {
const navigate = useNavigate();
const { sessionDetails } = useSessionStore();
const { orgName, flags } = sessionDetails;

const [isPinned, setIsPinned] = useState(() =>
getLocalStorageValue("sidebarPinned", false)
);
const collapseTimeoutRef = useRef(null);

const clearCollapseTimeout = () => {
if (collapseTimeoutRef.current) {
clearTimeout(collapseTimeoutRef.current);
collapseTimeoutRef.current = null;
}
};

useEffect(() => {
setLocalStorageValue("sidebarPinned", isPinned);
if (isPinned) {
clearCollapseTimeout();
setCollapsed(false);
}
return clearCollapseTimeout;
}, [isPinned, setCollapsed]);

const handleMouseEnter = () => {
clearCollapseTimeout();
if (!isPinned) setCollapsed(false);
};

const handleMouseLeave = () => {
if (!isPinned) {
collapseTimeoutRef.current = setTimeout(() => setCollapsed(true), 300);
}
};

const togglePin = () => {
const newPinned = !isPinned;
setIsPinned(newPinned);
if (newPinned) {
setCollapsed(false);
}
};

try {
if (unstractSubscriptionPlanStore?.useUnstractSubscriptionPlanStore) {
unstractSubscriptionPlan =
Expand Down Expand Up @@ -354,8 +403,8 @@ const SideNavBar = ({ collapsed }) => {
return unstractSubscriptionPlan?.remainingDays < 0;
}, [unstractSubscriptionPlan]);

data.forEach((mainMenuItem) => {
mainMenuItem.subMenu.forEach((subMenuItem) => {
data?.forEach((mainMenuItem) => {
mainMenuItem?.subMenu?.forEach((subMenuItem) => {
subMenuItem.disable = shouldDisableAll;
});
});
Expand All @@ -368,33 +417,98 @@ const SideNavBar = ({ collapsed }) => {
className="side-bar"
width={240}
collapsedWidth={65}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div className="main-slider">
<div className="slider-wrap">
{data?.map((item, index) => (
<div key={item?.id}>
{!collapsed && (
<Typography className="sidebar-main-heading">
{item.mainTitle}
</Typography>
)}
<Space direction="vertical" className="menu-item-body">
{item.subMenu.map((el) => {
// Platform item has a hover menu and click navigates to platform settings
if (el.id === 3.6) {
const handlePlatformClick = () => {
if (!el.disable) {
navigate(el.path);
<div className="sidebar-content-wrapper">
<div className="main-slider">
<div className="slider-wrap">
{data?.map((item, index) => (
<div key={item?.id}>
{!collapsed && (
<Typography className="sidebar-main-heading">
{item?.mainTitle}
</Typography>
)}
<Space direction="vertical" className="menu-item-body">
{item?.subMenu?.map((el) => {
// Platform item has a hover menu and click navigates to platform settings
if (el.id === 3.6) {
const handlePlatformClick = () => {
if (!el.disable) {
navigate(el.path);
}
};

const platformContent = (
<Tooltip title={collapsed ? el.title : ""}>
<Space
className={`space-styles ${
el.active ? "space-styles-active" : ""
} ${el.disable ? "space-styles-disable" : ""}`}
onClick={handlePlatformClick}
data-testid={`sidebar-${el.title
?.toLowerCase()
?.replace(/\s+/g, "-")}`}
>
<Image
src={el.image}
alt="side_icon"
className="menu-item-icon"
preview={false}
/>
{!collapsed && (
<div>
<Typography className="sidebar-item-text fs-14">
{el.title}
</Typography>
<Typography className="sidebar-item-text fs-11">
{el.description}
</Typography>
</div>
)}
</Space>
</Tooltip>
);

// Don't show popover when disabled
if (el.disable) {
return <div key={el.id}>{platformContent}</div>;
}
};

const platformContent = (
<Tooltip title={collapsed ? el.title : ""}>
return (
<Popover
key={el.id}
content={
<SettingsPopoverContent
orgName={orgName}
navigate={navigate}
/>
}
trigger="hover"
placement="rightTop"
arrow={false}
overlayClassName="settings-popover-overlay"
>
{platformContent}
</Popover>
);
}

return (
<Tooltip key={el.id} title={collapsed ? el.title : ""}>
<Space
className={`space-styles ${
el.active ? "space-styles-active" : ""
} ${el.disable ? "space-styles-disable" : ""}`}
onClick={handlePlatformClick}
onClick={() => {
if (!el.disable) {
navigate(el.path);
}
}}
data-testid={`sidebar-${el.title
?.toLowerCase()
?.replace(/\s+/g, "-")}`}
>
<Image
src={el.image}
Expand All @@ -415,77 +529,39 @@ const SideNavBar = ({ collapsed }) => {
</Space>
</Tooltip>
);

// Don't show popover when disabled
if (el.disable) {
return <div key={el.id}>{platformContent}</div>;
}

return (
<Popover
key={el.id}
content={
<SettingsPopoverContent
orgName={orgName}
navigate={navigate}
/>
}
trigger="hover"
placement="rightTop"
arrow={false}
overlayClassName="settings-popover-overlay"
>
{platformContent}
</Popover>
);
}

return (
<Tooltip key={el.id} title={collapsed ? el.title : ""}>
<Space
className={`space-styles ${
el.active ? "space-styles-active" : ""
} ${el.disable ? "space-styles-disable" : ""}`}
onClick={() => {
if (!el.disable) {
navigate(el.path);
}
}}
>
<Image
src={el.image}
alt="side_icon"
className="menu-item-icon"
preview={false}
/>
{!collapsed && (
<div>
<Typography className="sidebar-item-text fs-14">
{el.title}
</Typography>
<Typography className="sidebar-item-text fs-11">
{el.description}
</Typography>
</div>
)}
</Space>
</Tooltip>
);
})}
</Space>
{index < data.length - 1 && (
<Divider className="sidebar-divider" />
)}
</div>
))}
})}
</Space>
{index < data.length - 1 && (
<Divider className="sidebar-divider" />
)}
</div>
))}
</div>
</div>
</div>
<Tooltip title={isPinned ? "Unpin sidebar" : "Keep expanded"}>
<Button
type="text"
className="sidebar-pin-container"
onClick={togglePin}
aria-pressed={isPinned}
aria-label={isPinned ? "Unpin sidebar" : "Keep expanded"}
icon={
isPinned ? (
<PushpinFilled className="sidebar-pin-icon pinned" />
) : (
<PushpinOutlined className="sidebar-pin-icon" />
)
}
/>
</Tooltip>
</Sider>
);
};

SideNavBar.propTypes = {
collapsed: PropTypes.bool.isRequired,
setCollapsed: PropTypes.func.isRequired,
};

export default SideNavBar;
Loading