updated docs
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
'use client';
|
||||
import { FC, useState, useEffect } from "react";
|
||||
import { MenuProps } from "@/validations/mutual/dashboard/props";
|
||||
import { langGetKey } from "@/lib/langGet";
|
||||
import { parseURlFormString } from "@/lib/menuGet";
|
||||
import FirstLayerDropdown from "./firstLayerComponent";
|
||||
import SecondLayerDropdown from "./secondLayerComponent";
|
||||
import ThirdLayerDropdown from "./thirdLayerComponent";
|
||||
|
||||
// Define types for menu structure
|
||||
type ThirdLayerItem = Record<string, any>;
|
||||
type SecondLayerItems = Record<string, ThirdLayerItem>;
|
||||
type FirstLayerItems = Record<string, SecondLayerItems>;
|
||||
type MenuStructure = FirstLayerItems;
|
||||
|
||||
const MenuComponent: FC<MenuProps> = ({ lang, menuItems, menuTranslationsFlatten, activePageUrl }) => {
|
||||
// State for tracking expanded menu items
|
||||
const [expandedFirstLayer, setExpandedFirstLayer] = useState<string | null>(null);
|
||||
const [expandedSecondLayer, setExpandedSecondLayer] = useState<string | null>(null);
|
||||
const [menuStructure, setMenuStructure] = useState<MenuStructure>({});
|
||||
|
||||
// Parse active URL to determine which menu items should be active
|
||||
const activePathLayers = parseURlFormString(activePageUrl).data;
|
||||
const activeFirstLayer = activePathLayers[0] || null;
|
||||
const activeSecondLayer = activePathLayers[1] || null;
|
||||
const activeThirdLayer = activePathLayers[2] || null;
|
||||
|
||||
// Initialize expanded state based on active path
|
||||
useEffect(() => {
|
||||
if (activeFirstLayer) {
|
||||
setExpandedFirstLayer(activeFirstLayer);
|
||||
if (activeSecondLayer) {
|
||||
setExpandedSecondLayer(activeSecondLayer);
|
||||
}
|
||||
}
|
||||
}, [activeFirstLayer, activeSecondLayer]);
|
||||
|
||||
// Process menu items into a hierarchical structure
|
||||
useEffect(() => {
|
||||
const processedStructure: MenuStructure = {};
|
||||
|
||||
Object.entries(menuItems).forEach(([path, _]: [string, any]) => {
|
||||
const layers = parseURlFormString(path).data;
|
||||
const firstLayer = layers[0];
|
||||
const secondLayer = layers[1];
|
||||
const thirdLayer = layers[2];
|
||||
|
||||
// Create first layer if it doesn't exist
|
||||
if (!processedStructure[firstLayer]) {
|
||||
processedStructure[firstLayer] = {};
|
||||
}
|
||||
|
||||
// Create second layer if it doesn't exist
|
||||
if (!processedStructure[firstLayer][secondLayer]) {
|
||||
processedStructure[firstLayer][secondLayer] = {};
|
||||
}
|
||||
|
||||
// Add third layer
|
||||
processedStructure[firstLayer][secondLayer][thirdLayer] = {};
|
||||
});
|
||||
|
||||
setMenuStructure(processedStructure);
|
||||
}, [menuItems]);
|
||||
|
||||
// Handle click on first layer menu item
|
||||
const handleFirstLayerClick = (key: string) => {
|
||||
if (expandedFirstLayer === key) {
|
||||
// If already expanded, collapse it
|
||||
setExpandedFirstLayer(null);
|
||||
setExpandedSecondLayer(null);
|
||||
} else {
|
||||
// Otherwise expand it
|
||||
setExpandedFirstLayer(key);
|
||||
setExpandedSecondLayer(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle click on second layer menu item
|
||||
const handleSecondLayerClick = (key: string) => {
|
||||
if (expandedSecondLayer === key) {
|
||||
// If already expanded, collapse it
|
||||
setExpandedSecondLayer(null);
|
||||
} else {
|
||||
// Otherwise expand it
|
||||
setExpandedSecondLayer(key);
|
||||
}
|
||||
};
|
||||
|
||||
// Render third layer menu items
|
||||
const renderThirdLayerItems = (firstLayerKey: string, secondLayerKey: string, thirdLayerItems: ThirdLayerItem) => {
|
||||
const baseUrl = `/${firstLayerKey}/${secondLayerKey}`;
|
||||
|
||||
return Object.keys(thirdLayerItems).map(thirdLayerKey => {
|
||||
const isActive =
|
||||
activeFirstLayer === firstLayerKey &&
|
||||
activeSecondLayer === secondLayerKey &&
|
||||
activeThirdLayer === thirdLayerKey;
|
||||
|
||||
const url = `/${lang}${baseUrl}/${thirdLayerKey}`;
|
||||
|
||||
return (
|
||||
<div key={`${thirdLayerKey}-item`} className="ml-2 my-1">
|
||||
<ThirdLayerDropdown
|
||||
isActive={isActive}
|
||||
innerText={langGetKey(menuTranslationsFlatten, thirdLayerKey)}
|
||||
url={url}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Render second layer menu items
|
||||
const renderSecondLayerItems = (firstLayerKey: string, secondLayerItems: SecondLayerItems) => {
|
||||
return Object.entries(secondLayerItems).map(([secondLayerKey, thirdLayerItems]) => {
|
||||
const isActive = activeFirstLayer === firstLayerKey && activeSecondLayer === secondLayerKey;
|
||||
const isExpanded = expandedSecondLayer === secondLayerKey;
|
||||
|
||||
return (
|
||||
<div key={`${secondLayerKey}-item`} className="ml-2 my-1">
|
||||
<SecondLayerDropdown
|
||||
isActive={isActive}
|
||||
isExpanded={isExpanded}
|
||||
innerText={langGetKey(menuTranslationsFlatten, secondLayerKey)}
|
||||
onClick={() => handleSecondLayerClick(secondLayerKey)}
|
||||
/>
|
||||
{isExpanded && (
|
||||
<div className="ml-2 mt-1">
|
||||
{renderThirdLayerItems(firstLayerKey, secondLayerKey, thirdLayerItems)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Render first layer menu items
|
||||
const renderFirstLayerItems = () => {
|
||||
return Object.entries(menuStructure).map(([firstLayerKey, secondLayerItems]) => {
|
||||
const isActive = activeFirstLayer === firstLayerKey;
|
||||
const isExpanded = expandedFirstLayer === firstLayerKey;
|
||||
|
||||
return (
|
||||
<div key={`${firstLayerKey}-item`} className="mb-2">
|
||||
<FirstLayerDropdown
|
||||
isActive={isActive}
|
||||
isExpanded={isExpanded}
|
||||
innerText={langGetKey(menuTranslationsFlatten, firstLayerKey)}
|
||||
onClick={() => handleFirstLayerClick(firstLayerKey)}
|
||||
/>
|
||||
{isExpanded && (
|
||||
<div className="mt-1">
|
||||
{renderSecondLayerItems(firstLayerKey, secondLayerItems)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed top-24 p-5 left-0 right-0 w-80 border-emerald-150 border-r-2 overflow-y-auto h-[calc(100vh-6rem)]">
|
||||
<div className="flex flex-col">
|
||||
{renderFirstLayerItems()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default MenuComponent;
|
||||
@@ -0,0 +1,41 @@
|
||||
'use client';
|
||||
import { FC } from "react";
|
||||
|
||||
interface FirstLayerDropdownProps {
|
||||
isActive: boolean;
|
||||
isExpanded: boolean;
|
||||
innerText: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const FirstLayerDropdown: FC<FirstLayerDropdownProps> = ({ isActive, isExpanded, innerText, onClick }) => {
|
||||
// Base styles
|
||||
const baseClassName = "py-3 px-4 text-sm rounded-xl cursor-pointer transition-colors duration-200 flex justify-between items-center w-full";
|
||||
|
||||
// Determine the appropriate class based on active and expanded states
|
||||
let className = baseClassName;
|
||||
if (isActive) {
|
||||
className += " bg-emerald-700 text-white font-medium";
|
||||
} else if (isExpanded) {
|
||||
className += " bg-emerald-600 text-white";
|
||||
} else {
|
||||
className += " bg-emerald-800 text-white hover:bg-emerald-700";
|
||||
}
|
||||
|
||||
return (
|
||||
<div onClick={onClick} className={className}>
|
||||
<span>{innerText}</span>
|
||||
{isExpanded ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FirstLayerDropdown;
|
||||
@@ -0,0 +1,41 @@
|
||||
'use client';
|
||||
import { FC } from "react";
|
||||
|
||||
interface SecondLayerDropdownProps {
|
||||
isActive: boolean;
|
||||
isExpanded: boolean;
|
||||
innerText: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const SecondLayerDropdown: FC<SecondLayerDropdownProps> = ({ isActive, isExpanded, innerText, onClick }) => {
|
||||
// Base styles
|
||||
const baseClassName = "py-2 my-1 px-3 text-sm rounded-lg cursor-pointer transition-colors duration-200 flex justify-between items-center w-full";
|
||||
|
||||
// Determine the appropriate class based on active and expanded states
|
||||
let className = baseClassName;
|
||||
if (isActive) {
|
||||
className += " bg-emerald-600 text-white font-medium";
|
||||
} else if (isExpanded) {
|
||||
className += " bg-emerald-500 text-white";
|
||||
} else {
|
||||
className += " bg-emerald-700 text-white hover:bg-emerald-600";
|
||||
}
|
||||
|
||||
return (
|
||||
<div onClick={onClick} className={className}>
|
||||
<span>{innerText}</span>
|
||||
{isExpanded ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecondLayerDropdown;
|
||||
@@ -0,0 +1,50 @@
|
||||
'use client'
|
||||
import { FC, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import LoadingContent from "@/components/mutual/loader/component";
|
||||
|
||||
interface ThirdLayerDropdownProps {
|
||||
isActive: boolean,
|
||||
innerText: string,
|
||||
url: string,
|
||||
}
|
||||
|
||||
const ThirdLayerDropdown: FC<ThirdLayerDropdownProps> = ({ isActive, innerText, url }) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
// Base styles
|
||||
const baseClassName = "py-2 my-1 px-3 text-sm rounded-lg bg-black transition-colors duration-200 flex items-center w-full";
|
||||
|
||||
// Determine the appropriate class based on active state
|
||||
let className = baseClassName;
|
||||
if (isActive) {
|
||||
className += " bg-emerald-500 text-white font-medium";
|
||||
} else {
|
||||
className += " bg-emerald-600 text-white hover:bg-emerald-500";
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
return (
|
||||
<div className={`${className} cursor-not-allowed`}>
|
||||
<span className="ml-2">{innerText}</span>
|
||||
</div>
|
||||
);
|
||||
} else if (isLoading) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<LoadingContent height="h-5" size="w-5 h-5" plane="" />
|
||||
<span className="ml-2">{innerText}</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Link href={url} onClick={() => setIsLoading(true)} className="block">
|
||||
<div className={className}>
|
||||
<span className="ml-2">{innerText}</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ThirdLayerDropdown;
|
||||
@@ -0,0 +1,5 @@
|
||||
interface IntrerfaceLayerDropdown {
|
||||
isActive: boolean;
|
||||
innerText: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
Reference in New Issue
Block a user