fix: collapsed sidebar
This commit is contained in:
parent
aaf64b869f
commit
78889bf963
7 changed files with 590 additions and 76 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { IoIosArrowDown } from "react-icons/io";
|
import { IoIosArrowDown } from "react-icons/io";
|
||||||
|
|
||||||
|
|
@ -17,6 +17,112 @@ const SidebarItem: React.FC<SidebarItemProps> = React.memo(({
|
||||||
const Icon = item.icon as React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
const Icon = item.icon as React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||||
const hasSubItems = item.submenu && item.submenu.length > 0;
|
const hasSubItems = item.submenu && item.submenu.length > 0;
|
||||||
const isDisabled = item.moduleEnabled === false;
|
const isDisabled = item.moduleEnabled === false;
|
||||||
|
const iconContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Fix SVG dimensions when minimized - react-icons uses 1em which can be invisible
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMinimized && iconContainerRef.current) {
|
||||||
|
const wrapper = iconContainerRef.current;
|
||||||
|
const svg = wrapper.querySelector('svg');
|
||||||
|
if (svg) {
|
||||||
|
// Force explicit pixel dimensions
|
||||||
|
svg.setAttribute('width', '25');
|
||||||
|
svg.setAttribute('height', '25');
|
||||||
|
svg.style.cssText = 'width: 25px !important; height: 25px !important; display: block !important;';
|
||||||
|
|
||||||
|
// Get the actual color from parent li element
|
||||||
|
const parentLi = wrapper.closest('li');
|
||||||
|
const parentColor = parentLi ? window.getComputedStyle(parentLi).color : '#000000';
|
||||||
|
|
||||||
|
// Force color directly - use black for now to ensure visibility
|
||||||
|
const iconColor = '#000000'; // Force black for visibility
|
||||||
|
svg.style.setProperty('color', iconColor, 'important');
|
||||||
|
svg.style.setProperty('fill', iconColor, 'important');
|
||||||
|
svg.style.setProperty('stroke', iconColor, 'important');
|
||||||
|
svg.setAttribute('fill', iconColor);
|
||||||
|
svg.setAttribute('stroke', iconColor);
|
||||||
|
|
||||||
|
// Set fill/stroke on all paths - use black
|
||||||
|
const paths = svg.querySelectorAll('path');
|
||||||
|
paths.forEach(path => {
|
||||||
|
const originalFill = path.getAttribute('fill');
|
||||||
|
const originalStroke = path.getAttribute('stroke');
|
||||||
|
const strokeWidth = path.getAttribute('stroke-width');
|
||||||
|
|
||||||
|
if (originalFill === 'none' || (!originalFill && originalStroke)) {
|
||||||
|
path.removeAttribute('fill');
|
||||||
|
path.setAttribute('stroke', iconColor);
|
||||||
|
if (!strokeWidth || strokeWidth === '0') {
|
||||||
|
path.setAttribute('stroke-width', '2');
|
||||||
|
}
|
||||||
|
path.style.setProperty('stroke', iconColor, 'important');
|
||||||
|
path.style.setProperty('stroke-width', '2px', 'important');
|
||||||
|
path.style.setProperty('fill', 'none', 'important');
|
||||||
|
} else {
|
||||||
|
path.removeAttribute('fill');
|
||||||
|
path.setAttribute('fill', iconColor);
|
||||||
|
path.style.setProperty('fill', iconColor, 'important');
|
||||||
|
path.style.setProperty('stroke', iconColor, 'important');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Debug: Check if wrapper is visible
|
||||||
|
const wrapperRect = wrapper.getBoundingClientRect();
|
||||||
|
const wrapperComputed = window.getComputedStyle(wrapper);
|
||||||
|
const button = wrapper.parentElement?.querySelector('button');
|
||||||
|
const buttonComputed = button ? window.getComputedStyle(button) : null;
|
||||||
|
const svgRect = svg.getBoundingClientRect();
|
||||||
|
const svgComputed = window.getComputedStyle(svg);
|
||||||
|
const firstPath = paths[0];
|
||||||
|
const pathComputed = firstPath ? window.getComputedStyle(firstPath) : null;
|
||||||
|
const parentLiComputed = parentLi ? window.getComputedStyle(parentLi) : null;
|
||||||
|
const parentMenu = wrapper.closest(`.${styles.menu}`);
|
||||||
|
const parentMenuComputed = parentMenu ? window.getComputedStyle(parentMenu) : null;
|
||||||
|
|
||||||
|
// Check what's actually at the icon position
|
||||||
|
const centerX = wrapperRect.left + wrapperRect.width / 2;
|
||||||
|
const centerY = wrapperRect.top + wrapperRect.height / 2;
|
||||||
|
const elementsAtPoint = document.elementsFromPoint(centerX, centerY);
|
||||||
|
const wrapperInElements = elementsAtPoint.includes(wrapper);
|
||||||
|
|
||||||
|
console.log(`[${item.name}] Icon wrapper check:`, {
|
||||||
|
hasSubItems,
|
||||||
|
wrapperVisible: wrapperComputed.visibility === 'visible',
|
||||||
|
wrapperOpacity: wrapperComputed.opacity,
|
||||||
|
wrapperZIndex: wrapperComputed.zIndex,
|
||||||
|
wrapperDisplay: wrapperComputed.display,
|
||||||
|
wrapperBackgroundColor: wrapperComputed.backgroundColor,
|
||||||
|
wrapperRect: wrapperRect ? `width: ${wrapperRect.width}, height: ${wrapperRect.height}, top: ${wrapperRect.top}, left: ${wrapperRect.left}` : 'no rect',
|
||||||
|
svgExists: !!svg,
|
||||||
|
svgRect: svgRect ? `width: ${svgRect.width}, height: ${svgRect.height}, top: ${svgRect.top}, left: ${svgRect.left}` : 'no rect',
|
||||||
|
svgDisplay: svgComputed.display,
|
||||||
|
svgVisibility: svgComputed.visibility,
|
||||||
|
svgOpacity: svgComputed.opacity,
|
||||||
|
svgColor: svgComputed.color,
|
||||||
|
pathsCount: paths.length,
|
||||||
|
firstPathFill: pathComputed?.fill,
|
||||||
|
firstPathOpacity: pathComputed?.opacity,
|
||||||
|
buttonExists: !!button,
|
||||||
|
buttonZIndex: buttonComputed?.zIndex,
|
||||||
|
buttonPosition: buttonComputed?.position,
|
||||||
|
buttonRect: button ? button.getBoundingClientRect() : null,
|
||||||
|
elementsAtIconCenter: elementsAtPoint.slice(0, 5).map(el => ({
|
||||||
|
tag: el.tagName,
|
||||||
|
class: el.className?.split(' ')[0] || 'no-class',
|
||||||
|
zIndex: window.getComputedStyle(el).zIndex
|
||||||
|
})),
|
||||||
|
wrapperInElements: wrapperInElements,
|
||||||
|
parentLiOverflow: parentLiComputed?.overflow,
|
||||||
|
parentLiClipPath: parentLiComputed?.clipPath,
|
||||||
|
parentMenuOverflow: parentMenuComputed?.overflow,
|
||||||
|
parentMenuClipPath: parentMenuComputed?.clipPath
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(`[${item.name}] SVG NOT FOUND in wrapper!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isMinimized, isActive, item.name, hasSubItems]);
|
||||||
|
|
||||||
|
|
||||||
const toggleSubmenu = (e: React.MouseEvent) => {
|
const toggleSubmenu = (e: React.MouseEvent) => {
|
||||||
if (isDisabled) {
|
if (isDisabled) {
|
||||||
|
|
@ -45,9 +151,16 @@ const SidebarItem: React.FC<SidebarItemProps> = React.memo(({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.menu} ${isMinimized ? styles.minimized : ''} ${isDisabled ? styles.disabled : ''}`}>
|
<div className={`${styles.menu} ${isMinimized ? styles.minimized : ''} ${isDisabled ? styles.disabled : ''}`}>
|
||||||
<li className={`${isActive ? styles.active : ""} ${isDisabled ? styles.disabledItem : ""}`}>
|
<li
|
||||||
{/* Icon - always visible */}
|
className={`${isActive ? styles.active : ""} ${isDisabled ? styles.disabledItem : ""}`}
|
||||||
{Icon && <Icon className={`${styles.icon} ${isDisabled ? styles.disabledIcon : ''}`} />}
|
data-item-name={item.name}
|
||||||
|
>
|
||||||
|
{/* Icon - always render, CSS handles positioning */}
|
||||||
|
{Icon && !isMinimized && (
|
||||||
|
<Icon
|
||||||
|
className={`${styles.icon} ${isDisabled ? styles.disabledIcon : ''}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Text and arrow - hidden when minimized */}
|
{/* Text and arrow - hidden when minimized */}
|
||||||
{!isMinimized && (
|
{!isMinimized && (
|
||||||
|
|
@ -85,6 +198,38 @@ const SidebarItem: React.FC<SidebarItemProps> = React.memo(({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
{/* Icon for minimized state - render directly as child of li */}
|
||||||
|
{Icon && isMinimized && (
|
||||||
|
<div
|
||||||
|
ref={iconContainerRef}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: '25px',
|
||||||
|
height: '25px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className={`${styles.icon} ${styles.iconMinimized} ${isDisabled ? styles.disabledIcon : ''}`}
|
||||||
|
size={25}
|
||||||
|
style={{
|
||||||
|
width: '25px',
|
||||||
|
height: '25px',
|
||||||
|
color: '#000000',
|
||||||
|
fill: '#000000'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Clickable overlay for items without submenu */}
|
||||||
{isMinimized && !isDisabled && !hasSubItems && (
|
{isMinimized && !isDisabled && !hasSubItems && (
|
||||||
<Link
|
<Link
|
||||||
to={item.link || "#"}
|
to={item.link || "#"}
|
||||||
|
|
@ -93,8 +238,18 @@ const SidebarItem: React.FC<SidebarItemProps> = React.memo(({
|
||||||
onClick={handleLinkClick}
|
onClick={handleLinkClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Clickable overlay for items with submenu */}
|
||||||
|
{isMinimized && hasSubItems && !isDisabled && (
|
||||||
|
<button
|
||||||
|
onClick={toggleSubmenu}
|
||||||
|
className={styles.minimizedSubmenuToggle}
|
||||||
|
title={`Toggle ${item.name} submenu`}
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
{hasSubItems && !isMinimized && !isDisabled && <SidebarSubmenu item={item} isOpen={isOpen} />}
|
{hasSubItems && !isDisabled && <SidebarSubmenu item={item} isOpen={isOpen} isMinimized={isMinimized} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebarContainer.minimized .sidebar {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
.logoContainer {
|
.logoContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
|
@ -124,11 +128,15 @@
|
||||||
/* Minimized Sidebar Styles */
|
/* Minimized Sidebar Styles */
|
||||||
.sidebarContainer.minimized {
|
.sidebarContainer.minimized {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarContainer.minimized .sidebar {
|
.sidebarContainer.minimized .sidebar {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: visible !important;
|
||||||
|
overflow-y: visible !important;
|
||||||
|
overflow-x: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarContainer.minimized .logoContainer {
|
.sidebarContainer.minimized .logoContainer {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu.minimized {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
.menu li {
|
.menu li {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
|
|
@ -125,6 +129,15 @@
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure icon is visible when minimized */
|
||||||
|
.menu.minimized li .icon.iconMinimized {
|
||||||
|
display: flex !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
position: absolute !important;
|
||||||
|
z-index: 99999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.hassubmenu {
|
.hassubmenu {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
|
@ -147,7 +160,24 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 10;
|
z-index: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimizedSubmenuToggle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Minimized Menu Styles */
|
/* Minimized Menu Styles */
|
||||||
|
|
@ -155,12 +185,106 @@
|
||||||
width: 46px;
|
width: 46px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
align-items: center;
|
||||||
|
position: relative !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
clip-path: none !important;
|
||||||
|
clip: none !important;
|
||||||
|
contain: none !important;
|
||||||
|
isolation: auto !important;
|
||||||
|
/* Don't create stacking context that interferes */
|
||||||
|
transform: none !important;
|
||||||
}
|
}
|
||||||
.menu.minimized li a{
|
.menu.minimized li a{
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure icons are never hidden when minimized */
|
||||||
|
.menu.minimized li .iconMinimized,
|
||||||
|
.menu.minimized li [data-debug="icon-minimized"] {
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
display: flex !important;
|
||||||
|
position: absolute !important;
|
||||||
|
z-index: 99999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconMinimizedWrapper {
|
||||||
|
position: absolute !important;
|
||||||
|
top: 50% !important;
|
||||||
|
left: 50% !important;
|
||||||
|
transform: translate(-50%, -50%) !important;
|
||||||
|
font-size: 25px !important;
|
||||||
|
line-height: 25px !important;
|
||||||
|
width: 25px !important;
|
||||||
|
height: 25px !important;
|
||||||
|
min-width: 25px !important;
|
||||||
|
min-height: 25px !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
z-index: 2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconMinimized {
|
||||||
|
margin: 0 !important;
|
||||||
|
display: flex !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
align-items: center !important;
|
||||||
|
width: 25px !important;
|
||||||
|
height: 25px !important;
|
||||||
|
padding: 2.292px 2.3px 2.508px 2.292px !important;
|
||||||
|
color: var(--color-text) !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
font-size: 25px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconMinimized svg {
|
||||||
|
width: 25px !important;
|
||||||
|
height: 25px !important;
|
||||||
|
min-width: 25px !important;
|
||||||
|
min-height: 25px !important;
|
||||||
|
max-width: 25px !important;
|
||||||
|
max-height: 25px !important;
|
||||||
|
display: block !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconMinimized svg path {
|
||||||
|
fill: inherit !important;
|
||||||
|
stroke: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu.minimized li.active .iconMinimized svg,
|
||||||
|
.menu.minimized li:hover .iconMinimized svg {
|
||||||
|
color: white !important;
|
||||||
|
fill: white !important;
|
||||||
|
stroke: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu.minimized li.active .iconMinimized svg path,
|
||||||
|
.menu.minimized li:hover .iconMinimized svg path {
|
||||||
|
fill: white !important;
|
||||||
|
stroke: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure submenu can expand below minimized items */
|
||||||
|
.menu.minimized {
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
overflow: visible !important;
|
||||||
|
min-height: auto !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -170,6 +294,23 @@
|
||||||
color: var(--color-bg);
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu.minimized li.active .iconMinimized,
|
||||||
|
.menu.minimized li:hover .iconMinimized {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu.minimized li.active .iconMinimized svg,
|
||||||
|
.menu.minimized li:hover .iconMinimized svg {
|
||||||
|
color: white !important;
|
||||||
|
fill: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu.minimized li.active .iconMinimized svg path,
|
||||||
|
.menu.minimized li:hover .iconMinimized svg path {
|
||||||
|
fill: white !important;
|
||||||
|
stroke: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Disabled item styles */
|
/* Disabled item styles */
|
||||||
.menu.disabled,
|
.menu.disabled,
|
||||||
.menu li.disabledItem {
|
.menu li.disabledItem {
|
||||||
|
|
|
||||||
|
|
@ -68,3 +68,152 @@
|
||||||
color: #181818;
|
color: #181818;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Horizontal layout for minimized sidebar submenus */
|
||||||
|
.submenu.minimized {
|
||||||
|
width: 100% !important;
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px 0;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 0;
|
||||||
|
overflow: hidden !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
z-index: 10 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalContainer {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalList {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalItem {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
list-style: none;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalLink {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #181818 !important;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalLink:hover {
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalLink:hover .submenuHorizontalIcon {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalLink:hover .submenuHorizontalIcon svg {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalLink:hover .submenuHorizontalIcon svg path {
|
||||||
|
fill: currentColor !important;
|
||||||
|
stroke: currentColor !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalIcon {
|
||||||
|
display: block !important;
|
||||||
|
width: 16px !important;
|
||||||
|
height: 16px !important;
|
||||||
|
padding: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
color: #181818 !important;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative !important;
|
||||||
|
top: auto !important;
|
||||||
|
left: auto !important;
|
||||||
|
transform: none !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalIcon svg {
|
||||||
|
width: 16px !important;
|
||||||
|
height: 16px !important;
|
||||||
|
min-width: 16px !important;
|
||||||
|
min-height: 16px !important;
|
||||||
|
max-width: 16px !important;
|
||||||
|
max-height: 16px !important;
|
||||||
|
display: block !important;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
color: #181818 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenuHorizontalIcon svg path {
|
||||||
|
color: inherit !important;
|
||||||
|
fill: currentColor !important;
|
||||||
|
stroke: currentColor !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure submenu content is visible when sidebar is minimized */
|
||||||
|
.menu.minimized .submenu.minimized,
|
||||||
|
.menu.minimized .submenu.minimized * {
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
color: #181818 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu.minimized .submenuHorizontalLink,
|
||||||
|
.menu.minimized .submenuHorizontalLink * {
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
color: #181818 !important;
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState } from 'react';
|
||||||
import { SidebarSubmenuProps } from './sidebarTypes';
|
import { SidebarSubmenuProps } from './sidebarTypes';
|
||||||
|
|
||||||
const SidebarSubmenu: React.FC<SidebarSubmenuProps> = ({ item, isOpen }) => {
|
const SidebarSubmenu: React.FC<SidebarSubmenuProps> = ({ item, isOpen, isMinimized = false }) => {
|
||||||
if (!item.submenu) return null;
|
if (!item.submenu) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -12,12 +12,71 @@ const SidebarSubmenu: React.FC<SidebarSubmenuProps> = ({ item, isOpen }) => {
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ height: 0, opacity: 0 }}
|
initial={{ height: 0, opacity: 0 }}
|
||||||
animate={{ height: "auto", opacity: 1 }}
|
animate={{
|
||||||
exit={{ height: 0, opacity: 0 }}
|
height: "auto",
|
||||||
transition={{ duration: 0.3, ease: "easeInOut" }}
|
opacity: 1,
|
||||||
className={styles.submenu}
|
transition: {
|
||||||
|
height: { duration: 0.3, ease: "easeInOut", delay: 0 },
|
||||||
|
opacity: { duration: 0.25, ease: "easeInOut", delay: 0.3 }
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
exit={{
|
||||||
|
height: 0,
|
||||||
|
opacity: 0,
|
||||||
|
transition: {
|
||||||
|
opacity: { duration: 0.25, ease: "easeInOut", delay: 0 },
|
||||||
|
height: { duration: 0.3, ease: "easeInOut", delay: 0.25 }
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ overflow: "hidden" }}
|
||||||
|
className={`${styles.submenu} ${isMinimized ? styles.minimized : ''}`}
|
||||||
|
>
|
||||||
|
{isMinimized ? (
|
||||||
|
// Horizontal layout for minimized sidebar
|
||||||
|
<motion.div
|
||||||
|
className={styles.submenuHorizontalContainer}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1, transition: { delay: 0.3, duration: 0.25 } }}
|
||||||
|
exit={{ opacity: 0, transition: { duration: 0.25, delay: 0 } }}
|
||||||
|
>
|
||||||
|
<ul className={styles.submenuHorizontalList}>
|
||||||
|
{item.submenu.map(subitem => {
|
||||||
|
const SubIcon = subitem.icon as React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={subitem.id} className={styles.submenuHorizontalItem}>
|
||||||
|
<Link
|
||||||
|
to={subitem.link || '#'}
|
||||||
|
title={subitem.name}
|
||||||
|
className={styles.submenuHorizontalLink}
|
||||||
|
>
|
||||||
|
{SubIcon && (
|
||||||
|
<SubIcon
|
||||||
|
className={styles.submenuHorizontalIcon}
|
||||||
|
size={16}
|
||||||
|
style={{
|
||||||
|
width: '16px',
|
||||||
|
height: '16px',
|
||||||
|
color: '#181818',
|
||||||
|
display: 'block'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</motion.div>
|
||||||
|
) : (
|
||||||
|
// Vertical layout for expanded sidebar
|
||||||
|
<motion.div
|
||||||
|
className={styles.submenuLineContainer}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1, transition: { delay: 0.3, duration: 0.25 } }}
|
||||||
|
exit={{ opacity: 0, transition: { duration: 0.25, delay: 0 } }}
|
||||||
>
|
>
|
||||||
<div className={styles.submenuLineContainer}>
|
|
||||||
<ul className={styles.submenuList}>
|
<ul className={styles.submenuList}>
|
||||||
{item.submenu.map(subitem => {
|
{item.submenu.map(subitem => {
|
||||||
const textRef = useRef<HTMLSpanElement>(null);
|
const textRef = useRef<HTMLSpanElement>(null);
|
||||||
|
|
@ -82,7 +141,8 @@ const SidebarSubmenu: React.FC<SidebarSubmenuProps> = ({ item, isOpen }) => {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</motion.div>
|
||||||
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export const useSidebarLogic = (): SidebarContextType => {
|
||||||
setState(prevState => ({
|
setState(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
isMinimized: true,
|
isMinimized: true,
|
||||||
openItemId: null, // Close any open submenu when minimizing
|
// Keep submenus open when minimizing - submenu state is independent
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ export interface SidebarItemProps {
|
||||||
export interface SidebarSubmenuProps {
|
export interface SidebarSubmenuProps {
|
||||||
item: SidebarItemData;
|
item: SidebarItemData;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
isMinimized?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SidebarUserProps {
|
export interface SidebarUserProps {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue