import type { ReactNode, RefObject } from 'react' import React, { useRef } from 'react' import type { BaseProps } from '@toptal/picasso-shared' import { useIsomorphicLayoutEffect } from '@toptal/picasso-utils' import { Menu } from '@toptal/picasso-menu' import { twJoin, twMerge } from '@toptal/picasso-tailwind-merge' export interface Props extends BaseProps { children: React.ReactNode selectedIndex?: number | null onBlur?: (event: React.FocusEvent) => void fixedHeader?: ReactNode fixedFooter?: ReactNode role?: 'listbox' | 'menu' testIds?: { root?: string list?: string } } const getMenuSelectedNode = ( menuRef: RefObject, selectedIndex?: number | null ) => typeof selectedIndex === 'number' ? menuRef.current?.children[selectedIndex] : undefined export const scrollToSelection = ( menuRef: RefObject, selectedIndex?: number | null ) => { const menuNode = menuRef.current const selectedNode = getMenuSelectedNode(menuRef, selectedIndex) if (!menuNode || !selectedNode) { return } const menuRect = menuNode.getBoundingClientRect() const selectedRect = selectedNode.getBoundingClientRect() if (selectedRect.top < menuRect.top) { menuNode.scrollTop -= menuRect.top - selectedRect.top } else if (selectedRect.bottom > menuRect.bottom) { menuNode.scrollTop += selectedRect.bottom - menuRect.bottom } } const preventClick = (e: React.MouseEvent) => { // ScrollMenu is used in dropdowns. // When clicking on the scrollView, the dropdown should not close. e.preventDefault() } const ScrollMenu = ({ role = 'menu', ...props }: Props) => { const { selectedIndex, onBlur, children, style, fixedHeader, fixedFooter, className, testIds, 'data-testid': dataTestId, ...rest } = props const menuRef = useRef(null) useIsomorphicLayoutEffect( () => scrollToSelection(menuRef, selectedIndex), [selectedIndex] ) return ( {fixedHeader}
{children}
{fixedFooter}
) } ScrollMenu.displayName = 'ScrollMenu' export default ScrollMenu