import { useEffect, useMemo, useRef, useState } from 'react' import * as RadixMenu from '@radix-ui/react-dropdown-menu' import styled, { CSSObject } from 'styled-components' import { ChevronRightIcon, Search } from 'lucide-react' import Block from '../Primitives/Block/Block' import PrimitiveText from '../Primitives/PrimitiveText/PrimitiveText' import SearchInput from '../Inputs/SearchInput/SearchInput' import MenuV2Item from './MenuV2Item' import type { MenuV2ItemType, MenuV2SelectionMode, MenuV2SelectionStyle, } from './menuV2.types' import type { MenuV2TokensType } from './menuV2.tokens' import { getMenuItemBackgroundColor, getMenuItemOptionColor, getMenuItemDescriptionColor, filterMenuV2Item, getItemSlots, defaultSearchSortFn, } from './menuV2.utils' import { menuV2SubmenuContentAnimations } from './menuV2.animations' import { useResponsiveTokens } from '../../hooks/useResponsiveTokens' import { addPxToValue } from '../../global-utils/GlobalUtils' type SubContentStyledProps = { $zIndex: number | string } const SubContent = styled(RadixMenu.SubContent)` z-index: ${(p) => p.$zIndex}; overflow-y: auto; overflow-x: hidden; scrollbar-width: none; scrollbar-color: transparent transparent; ${menuV2SubmenuContentAnimations} ` type MenuV2SubMenuProps = { item: MenuV2ItemType index: number maxHeight?: CSSObject['maxHeight'] selectionStyle?: MenuV2SelectionStyle selectionMode?: MenuV2SelectionMode } const SlotWrapper = ({ slot }: { slot: React.ReactNode }) => ( {slot} ) const MenuV2SubMenu = ({ item, index, maxHeight, selectionStyle, selectionMode, }: MenuV2SubMenuProps) => { const menuTokens = useResponsiveTokens('MENU_V2') const itemTokens = menuTokens.group.item const [slot1] = getItemSlots(item) const [searchText, setSearchText] = useState('') const searchInputRef = useRef(null) const filteredSubMenuItems = useMemo(() => { if (!item.subMenu) return [] if (!searchText.trim()) return item.subMenu const lower = searchText.toLowerCase() const matched = item.subMenu.reduce((acc, sub) => { const result = filterMenuV2Item(sub, lower) if (result) acc.push(result) return acc }, []) const sortFn = item.subMenuSearchSortFn ?? defaultSearchSortFn return sortFn(matched, searchText) }, [item.subMenu, searchText, item.subMenuSearchSortFn]) const { bgDefault, bgHover, bgFocus, bgActive, bgFocusVisible, labelColor, subLabelColor, } = useMemo(() => { const defaultBg = getMenuItemBackgroundColor( 'default', itemTokens, item ) const hoverBg = getMenuItemBackgroundColor('hover', itemTokens, item) const focusBg = getMenuItemBackgroundColor('focus', itemTokens, item) const activeBg = getMenuItemBackgroundColor('active', itemTokens, item) const focusVisibleBg = getMenuItemBackgroundColor( 'focusVisible', itemTokens, item ) const defaultLabelColor = getMenuItemOptionColor( 'default', itemTokens, item ) const defaultSubLabelColor = getMenuItemDescriptionColor( 'default', itemTokens, item ) return { bgDefault: defaultBg, bgHover: hoverBg, bgFocus: focusBg, bgActive: activeBg, bgFocusVisible: focusVisibleBg, labelColor: defaultLabelColor, subLabelColor: defaultSubLabelColor, } }, [itemTokens, item]) const contentPadding = menuTokens const borderStyle = contentPadding.border const borderRadius = contentPadding.borderRadius useEffect(() => { if (item.enableSubMenuSearch) { searchInputRef.current?.focus() } }, [item.enableSubMenuSearch]) return ( {slot1 != null && } {item.label.text} {item.subLabel && ( {item.subLabel} )} {item.enableSubMenuSearch && ( } placeholder={ item.subMenuSearchPlaceholder ?? 'Search...' } value={searchText} onChange={(e) => setSearchText(e.target.value)} onKeyDown={(e) => { e.stopPropagation() if (e.key === 'Enter') { e.preventDefault() item.onSubMenuSearchEnter?.( searchText, filteredSubMenuItems ) } }} aria-label="Search submenu" /> )} {filteredSubMenuItems.map((subItem, subIdx) => ( ))} ) } MenuV2SubMenu.displayName = 'MenuV2SubMenu' export default MenuV2SubMenu