187 lines
5.2 KiB
TypeScript
187 lines
5.2 KiB
TypeScript
'use client'
|
|
|
|
// React Imports
|
|
import { forwardRef, useEffect, useState } from 'react'
|
|
import type { AnchorHTMLAttributes, ForwardRefRenderFunction, ReactElement, ReactNode } from 'react'
|
|
|
|
// Next Imports
|
|
import { usePathname } from 'next/navigation'
|
|
|
|
// Third-party Imports
|
|
import classnames from 'classnames'
|
|
import { useUpdateEffect } from 'react-use'
|
|
import type { CSSObject } from '@emotion/styled'
|
|
|
|
// Type Imports
|
|
import type { ChildrenType, MenuItemElement, MenuItemExactMatchUrlProps, RootStylesType } from '../../types'
|
|
|
|
// Component Imports
|
|
import MenuButton from './MenuButton'
|
|
|
|
// Hook Imports
|
|
import useVerticalNav from '../../hooks/useVerticalNav'
|
|
import useVerticalMenu from '../../hooks/useVerticalMenu'
|
|
|
|
// Util Imports
|
|
import { renderMenuIcon } from '../../utils/menuUtils'
|
|
import { menuClasses } from '../../utils/menuClasses'
|
|
|
|
// Styled Component Imports
|
|
import StyledMenuLabel from '../../styles/StyledMenuLabel'
|
|
import StyledMenuPrefix from '../../styles/StyledMenuPrefix'
|
|
import StyledMenuSuffix from '../../styles/StyledMenuSuffix'
|
|
import StyledVerticalMenuItem from '../../styles/vertical/StyledVerticalMenuItem'
|
|
|
|
export type MenuItemProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'prefix'> &
|
|
RootStylesType &
|
|
Partial<ChildrenType> &
|
|
MenuItemExactMatchUrlProps & {
|
|
icon?: ReactElement
|
|
prefix?: ReactNode
|
|
suffix?: ReactNode
|
|
disabled?: boolean
|
|
target?: string
|
|
rel?: string
|
|
onActiveChange?: (active: boolean) => void
|
|
|
|
/**
|
|
* @ignore
|
|
*/
|
|
level?: number
|
|
}
|
|
|
|
const MenuItem: ForwardRefRenderFunction<HTMLLIElement, MenuItemProps> = (props, ref) => {
|
|
// Props
|
|
const {
|
|
children,
|
|
icon,
|
|
className,
|
|
prefix,
|
|
suffix,
|
|
level = 0,
|
|
disabled = false,
|
|
exactMatch = true,
|
|
activeUrl,
|
|
onActiveChange,
|
|
rootStyles,
|
|
...rest
|
|
} = props
|
|
|
|
// States
|
|
const [active, setActive] = useState(false)
|
|
|
|
// Hooks
|
|
const pathname = usePathname()
|
|
const { menuItemStyles, renderExpandedMenuItemIcon, textTruncate } = useVerticalMenu()
|
|
|
|
const { toggleVerticalNav, isToggled, isBreakpointReached } = useVerticalNav()
|
|
|
|
// Get the styles for the specified element.
|
|
const getMenuItemStyles = (element: MenuItemElement): CSSObject | undefined => {
|
|
// If the menuItemStyles prop is provided, get the styles for the specified element.
|
|
if (menuItemStyles) {
|
|
// Define the parameters that are passed to the style functions.
|
|
const params = { level, disabled, active, isSubmenu: false }
|
|
|
|
// Get the style function for the specified element.
|
|
const styleFunction = menuItemStyles[element]
|
|
|
|
if (styleFunction) {
|
|
// If the style function is a function, call it and return the result.
|
|
// Otherwise, return the style function itself.
|
|
return typeof styleFunction === 'function' ? styleFunction(params) : styleFunction
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle the click event.
|
|
const handleClick = () => {
|
|
if (isToggled) {
|
|
toggleVerticalNav()
|
|
}
|
|
}
|
|
|
|
// Change active state when the url changes
|
|
useEffect(() => {
|
|
const href = rest.href
|
|
|
|
if (href) {
|
|
// Check if the current url matches any of the children urls
|
|
if (exactMatch ? pathname === href : activeUrl && pathname.includes(activeUrl)) {
|
|
setActive(true)
|
|
} else {
|
|
setActive(false)
|
|
}
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [pathname])
|
|
|
|
// Call the onActiveChange callback when the active state changes.
|
|
useUpdateEffect(() => {
|
|
onActiveChange?.(active)
|
|
}, [active])
|
|
|
|
return (
|
|
<StyledVerticalMenuItem
|
|
ref={ref}
|
|
className={classnames(
|
|
menuClasses.menuItemRoot,
|
|
{ [menuClasses.disabled]: disabled },
|
|
{ [menuClasses.active]: active },
|
|
className
|
|
)}
|
|
level={level}
|
|
disabled={disabled}
|
|
buttonStyles={getMenuItemStyles('button')}
|
|
menuItemStyles={getMenuItemStyles('root')}
|
|
rootStyles={rootStyles}
|
|
>
|
|
<MenuButton
|
|
className={classnames(menuClasses.button, { [menuClasses.active]: active })}
|
|
tabIndex={disabled ? -1 : 0}
|
|
{...rest}
|
|
onClick={e => {
|
|
handleClick()
|
|
rest.onClick && rest.onClick(e)
|
|
}}
|
|
>
|
|
{/* Menu Item Icon */}
|
|
{renderMenuIcon({
|
|
icon,
|
|
level,
|
|
active,
|
|
disabled,
|
|
renderExpandedMenuItemIcon,
|
|
styles: getMenuItemStyles('icon'),
|
|
isBreakpointReached
|
|
})}
|
|
|
|
{/* Menu Item Prefix */}
|
|
{prefix && (
|
|
<StyledMenuPrefix className={menuClasses.prefix} rootStyles={getMenuItemStyles('prefix')}>
|
|
{prefix}
|
|
</StyledMenuPrefix>
|
|
)}
|
|
|
|
{/* Menu Item Label */}
|
|
<StyledMenuLabel
|
|
className={menuClasses.label}
|
|
rootStyles={getMenuItemStyles('label')}
|
|
textTruncate={textTruncate}
|
|
>
|
|
{children}
|
|
</StyledMenuLabel>
|
|
|
|
{/* Menu Item Suffix */}
|
|
{suffix && (
|
|
<StyledMenuSuffix className={menuClasses.suffix} rootStyles={getMenuItemStyles('suffix')}>
|
|
{suffix}
|
|
</StyledMenuSuffix>
|
|
)}
|
|
</MenuButton>
|
|
</StyledVerticalMenuItem>
|
|
)
|
|
}
|
|
|
|
export default forwardRef(MenuItem)
|