|
|
import React, { useEffect, useState } from "react"; |
|
|
import { CaretRight } from "@phosphor-icons/react"; |
|
|
import { Link, useLocation } from "react-router-dom"; |
|
|
|
|
|
export default function MenuOption({ |
|
|
btnText, |
|
|
icon, |
|
|
href, |
|
|
childOptions = [], |
|
|
flex = false, |
|
|
user = null, |
|
|
roles = [], |
|
|
hidden = false, |
|
|
isChild = false, |
|
|
}) { |
|
|
const storageKey = generateStorageKey({ key: btnText }); |
|
|
const location = useLocation(); |
|
|
const hasChildren = childOptions.length > 0; |
|
|
const hasVisibleChildren = hasVisibleOptions(user, childOptions); |
|
|
const { isExpanded, setIsExpanded } = useIsExpanded({ |
|
|
storageKey, |
|
|
hasVisibleChildren, |
|
|
childOptions, |
|
|
location: location.pathname, |
|
|
}); |
|
|
|
|
|
if (hidden) return null; |
|
|
|
|
|
|
|
|
if (!isChild) { |
|
|
|
|
|
if (!hasChildren) { |
|
|
if (!flex && !roles.includes(user?.role)) return null; |
|
|
if (flex && !!user && !roles.includes(user?.role)) return null; |
|
|
} |
|
|
|
|
|
|
|
|
if (hasChildren && !hasVisibleChildren) return null; |
|
|
} else { |
|
|
|
|
|
if (!flex && !roles.includes(user?.role)) return null; |
|
|
if (flex && !!user && !roles.includes(user?.role)) return null; |
|
|
} |
|
|
|
|
|
const isActive = hasChildren |
|
|
? (!isExpanded && |
|
|
childOptions.some((child) => child.href === location.pathname)) || |
|
|
location.pathname === href |
|
|
: location.pathname === href; |
|
|
|
|
|
const handleClick = (e) => { |
|
|
if (hasChildren) { |
|
|
e.preventDefault(); |
|
|
const newExpandedState = !isExpanded; |
|
|
setIsExpanded(newExpandedState); |
|
|
localStorage.setItem(storageKey, JSON.stringify(newExpandedState)); |
|
|
} |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<div> |
|
|
<div |
|
|
className={` |
|
|
flex items-center justify-between w-full |
|
|
transition-all duration-300 |
|
|
rounded-[6px] |
|
|
${ |
|
|
isActive |
|
|
? "bg-theme-sidebar-subitem-selected font-medium border-outline" |
|
|
: "hover:bg-theme-sidebar-subitem-hover" |
|
|
} |
|
|
`} |
|
|
> |
|
|
<Link |
|
|
to={href} |
|
|
className={`flex flex-grow items-center px-[12px] h-[32px] font-medium ${ |
|
|
isChild ? "hover:text-white" : "text-white light:text-black" |
|
|
}`} |
|
|
onClick={hasChildren ? handleClick : undefined} |
|
|
> |
|
|
{icon} |
|
|
<p |
|
|
className={`${ |
|
|
isChild ? "text-xs" : "text-sm" |
|
|
} leading-loose whitespace-nowrap overflow-hidden ml-2 ${ |
|
|
isActive |
|
|
? "text-white font-semibold" |
|
|
: "text-white light:text-black" |
|
|
} ${!icon && "pl-5"}`} |
|
|
> |
|
|
{btnText} |
|
|
</p> |
|
|
</Link> |
|
|
{hasChildren && ( |
|
|
<button onClick={handleClick} className="p-2 text-white"> |
|
|
<CaretRight |
|
|
size={16} |
|
|
weight="bold" |
|
|
// color={isExpanded ? "#000000" : "var(--theme-sidebar-subitem-icon)"} |
|
|
className={`transition-transform text-white light:text-black ${ |
|
|
isExpanded ? "rotate-90" : "" |
|
|
}`} |
|
|
/> |
|
|
</button> |
|
|
)} |
|
|
</div> |
|
|
{isExpanded && hasChildren && ( |
|
|
<div className="mt-1 rounded-r-lg w-full"> |
|
|
{childOptions.map((childOption, index) => ( |
|
|
<MenuOption |
|
|
key={index} |
|
|
{...childOption} // flex and roles go here. |
|
|
user={user} |
|
|
isChild={true} |
|
|
/> |
|
|
))} |
|
|
</div> |
|
|
)} |
|
|
</div> |
|
|
); |
|
|
} |
|
|
|
|
|
function useIsExpanded({ |
|
|
storageKey = "", |
|
|
hasVisibleChildren = false, |
|
|
childOptions = [], |
|
|
location = null, |
|
|
}) { |
|
|
const [isExpanded, setIsExpanded] = useState(() => { |
|
|
if (hasVisibleChildren) { |
|
|
const storedValue = localStorage.getItem(storageKey); |
|
|
if (storedValue !== null) { |
|
|
return JSON.parse(storedValue); |
|
|
} |
|
|
return childOptions.some((child) => child.href === location); |
|
|
} |
|
|
return false; |
|
|
}); |
|
|
|
|
|
useEffect(() => { |
|
|
if (hasVisibleChildren) { |
|
|
const shouldExpand = childOptions.some( |
|
|
(child) => child.href === location |
|
|
); |
|
|
if (shouldExpand && !isExpanded) { |
|
|
setIsExpanded(true); |
|
|
localStorage.setItem(storageKey, JSON.stringify(true)); |
|
|
} |
|
|
} |
|
|
}, [location]); |
|
|
|
|
|
return { isExpanded, setIsExpanded }; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function hasVisibleOptions(user = null, childOptions = []) { |
|
|
if (!Array.isArray(childOptions) || childOptions?.length === 0) return false; |
|
|
|
|
|
function isVisible({ |
|
|
roles = [], |
|
|
user = null, |
|
|
flex = false, |
|
|
hidden = false, |
|
|
}) { |
|
|
if (hidden) return false; |
|
|
if (!flex && !roles.includes(user?.role)) return false; |
|
|
if (flex && !!user && !roles.includes(user?.role)) return false; |
|
|
return true; |
|
|
} |
|
|
|
|
|
return childOptions.some((opt) => |
|
|
isVisible({ roles: opt.roles, user, flex: opt.flex, hidden: opt.hidden }) |
|
|
); |
|
|
} |
|
|
|
|
|
function generateStorageKey({ key = "" }) { |
|
|
const _key = key.replace(/\s+/g, "_").toLowerCase(); |
|
|
return `anything_llm_menu_${_key}_expanded`; |
|
|
} |
|
|
|