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 { 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 hasSubItems = item.submenu && item.submenu.length > 0;
|
||||
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) => {
|
||||
if (isDisabled) {
|
||||
|
|
@ -45,9 +151,16 @@ const SidebarItem: React.FC<SidebarItemProps> = React.memo(({
|
|||
|
||||
return (
|
||||
<div className={`${styles.menu} ${isMinimized ? styles.minimized : ''} ${isDisabled ? styles.disabled : ''}`}>
|
||||
<li className={`${isActive ? styles.active : ""} ${isDisabled ? styles.disabledItem : ""}`}>
|
||||
{/* Icon - always visible */}
|
||||
{Icon && <Icon className={`${styles.icon} ${isDisabled ? styles.disabledIcon : ''}`} />}
|
||||
<li
|
||||
className={`${isActive ? styles.active : ""} ${isDisabled ? styles.disabledItem : ""}`}
|
||||
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 */}
|
||||
{!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 && (
|
||||
<Link
|
||||
to={item.link || "#"}
|
||||
|
|
@ -93,8 +238,18 @@ const SidebarItem: React.FC<SidebarItemProps> = React.memo(({
|
|||
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>
|
||||
{hasSubItems && !isMinimized && !isDisabled && <SidebarSubmenu item={item} isOpen={isOpen} />}
|
||||
{hasSubItems && !isDisabled && <SidebarSubmenu item={item} isOpen={isOpen} isMinimized={isMinimized} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebarContainer.minimized .sidebar {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
display: flex;
|
||||
height: 80px;
|
||||
|
|
@ -124,11 +128,15 @@
|
|||
/* Minimized Sidebar Styles */
|
||||
.sidebarContainer.minimized {
|
||||
width: 80px;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.sidebarContainer.minimized .sidebar {
|
||||
width: 80px;
|
||||
align-items: center;
|
||||
overflow: visible !important;
|
||||
overflow-y: visible !important;
|
||||
overflow-x: visible !important;
|
||||
}
|
||||
|
||||
.sidebarContainer.minimized .logoContainer {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.menu.minimized {
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.menu li {
|
||||
display: flex;
|
||||
width: 220px;
|
||||
|
|
@ -125,6 +129,15 @@
|
|||
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 {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
|
@ -147,7 +160,24 @@
|
|||
left: 0;
|
||||
width: 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 */
|
||||
|
|
@ -155,12 +185,106 @@
|
|||
width: 46px;
|
||||
padding: 0;
|
||||
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{
|
||||
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);
|
||||
}
|
||||
|
||||
.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 */
|
||||
.menu.disabled,
|
||||
.menu li.disabledItem {
|
||||
|
|
|
|||
|
|
@ -67,4 +67,153 @@
|
|||
height: 16px;
|
||||
color: #181818;
|
||||
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 { SidebarSubmenuProps } from './sidebarTypes';
|
||||
|
||||
const SidebarSubmenu: React.FC<SidebarSubmenuProps> = ({ item, isOpen }) => {
|
||||
const SidebarSubmenu: React.FC<SidebarSubmenuProps> = ({ item, isOpen, isMinimized = false }) => {
|
||||
if (!item.submenu) return null;
|
||||
|
||||
return (
|
||||
|
|
@ -12,77 +12,137 @@ const SidebarSubmenu: React.FC<SidebarSubmenuProps> = ({ item, isOpen }) => {
|
|||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: "auto", opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.3, ease: "easeInOut" }}
|
||||
className={styles.submenu}
|
||||
animate={{
|
||||
height: "auto",
|
||||
opacity: 1,
|
||||
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 : ''}`}
|
||||
>
|
||||
<div className={styles.submenuLineContainer}>
|
||||
<ul className={styles.submenuList}>
|
||||
{item.submenu.map(subitem => {
|
||||
const textRef = useRef<HTMLSpanElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isOverflowing, setIsOverflowing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkOverflow = () => {
|
||||
if (textRef.current && containerRef.current) {
|
||||
const textWidth = textRef.current.scrollWidth;
|
||||
const containerWidth = containerRef.current.clientWidth;
|
||||
setIsOverflowing(textWidth > containerWidth);
|
||||
}
|
||||
};
|
||||
{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>>;
|
||||
|
||||
checkOverflow();
|
||||
// Also check on window resize
|
||||
window.addEventListener('resize', checkOverflow);
|
||||
return () => window.removeEventListener('resize', checkOverflow);
|
||||
}, [subitem.name]);
|
||||
|
||||
const SubIcon = subitem.icon as React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
|
||||
return (
|
||||
<li key={subitem.id}>
|
||||
<Link
|
||||
to={subitem.link || '#'}
|
||||
title={subitem.name}
|
||||
>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={styles.textContainer}
|
||||
return (
|
||||
<li key={subitem.id} className={styles.submenuHorizontalItem}>
|
||||
<Link
|
||||
to={subitem.link || '#'}
|
||||
title={subitem.name}
|
||||
className={styles.submenuHorizontalLink}
|
||||
>
|
||||
<motion.span
|
||||
ref={textRef}
|
||||
style={{
|
||||
display: 'block',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
initial={{ x: 0 }}
|
||||
animate={{ x: 0 }}
|
||||
{...(isOverflowing && {
|
||||
whileHover: {
|
||||
x: -(textRef.current?.scrollWidth || 0) + (containerRef.current?.clientWidth || 153),
|
||||
transition: {
|
||||
duration: 2,
|
||||
ease: "linear"
|
||||
}
|
||||
}
|
||||
})}
|
||||
{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 } }}
|
||||
>
|
||||
<ul className={styles.submenuList}>
|
||||
{item.submenu.map(subitem => {
|
||||
const textRef = useRef<HTMLSpanElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isOverflowing, setIsOverflowing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkOverflow = () => {
|
||||
if (textRef.current && containerRef.current) {
|
||||
const textWidth = textRef.current.scrollWidth;
|
||||
const containerWidth = containerRef.current.clientWidth;
|
||||
setIsOverflowing(textWidth > containerWidth);
|
||||
}
|
||||
};
|
||||
|
||||
checkOverflow();
|
||||
// Also check on window resize
|
||||
window.addEventListener('resize', checkOverflow);
|
||||
return () => window.removeEventListener('resize', checkOverflow);
|
||||
}, [subitem.name]);
|
||||
|
||||
const SubIcon = subitem.icon as React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
|
||||
return (
|
||||
<li key={subitem.id}>
|
||||
<Link
|
||||
to={subitem.link || '#'}
|
||||
title={subitem.name}
|
||||
>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={styles.textContainer}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', paddingRight: '10px' }}>
|
||||
{SubIcon && <SubIcon className={styles.submenuIcon} />}
|
||||
<span style={{ marginLeft: SubIcon ? '8px' : '0' }}>
|
||||
{subitem.name}
|
||||
</span>
|
||||
</div>
|
||||
</motion.span>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<motion.span
|
||||
ref={textRef}
|
||||
style={{
|
||||
display: 'block',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
initial={{ x: 0 }}
|
||||
animate={{ x: 0 }}
|
||||
{...(isOverflowing && {
|
||||
whileHover: {
|
||||
x: -(textRef.current?.scrollWidth || 0) + (containerRef.current?.clientWidth || 153),
|
||||
transition: {
|
||||
duration: 2,
|
||||
ease: "linear"
|
||||
}
|
||||
}
|
||||
})}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', paddingRight: '10px' }}>
|
||||
{SubIcon && <SubIcon className={styles.submenuIcon} />}
|
||||
<span style={{ marginLeft: SubIcon ? '8px' : '0' }}>
|
||||
{subitem.name}
|
||||
</span>
|
||||
</div>
|
||||
</motion.span>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export const useSidebarLogic = (): SidebarContextType => {
|
|||
setState(prevState => ({
|
||||
...prevState,
|
||||
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 {
|
||||
item: SidebarItemData;
|
||||
isOpen: boolean;
|
||||
isMinimized?: boolean;
|
||||
}
|
||||
|
||||
export interface SidebarUserProps {
|
||||
|
|
|
|||
Loading…
Reference in a new issue