import { generateGuid } from '@vev/utils'; import { isBoolean, isFunction } from 'lodash'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { SilkeBox } from '../silke-box'; import { ButtonContext, SilkeButton, SilkeButtonProps } from '../silke-button'; import { SilkePopover, SilkePopoverProps } from '../silke-popover'; import styles from './silke-overflow-menu.scss'; import { SilkeDivider } from '../silke-divider'; export type SilkeOverflowMenuProps = { children?: React.ReactNode; open?: boolean; /** * What triggers the opening of the overflow menu * @default "click" */ trigger?: 'click' | 'right-click'; onToggleOpen?: (open: boolean) => void; /** Pass items or children */ items?: | (SilkeOverflowMenuItem | React.ReactElement | 'divider')[] | (() => (SilkeOverflowMenuItem | React.ReactElement | 'divider')[]); } & SilkeButtonProps & Omit; export function SilkeOverflowMenu({ children, label, open: externalOpen, icon, items = [], offsetX, offsetY, trigger = 'click', targetOrigin = 'top-right', anchorOrigin = 'bottom-right', onClick, onMouseDown, onToggleOpen, ...rest }: SilkeOverflowMenuProps) { const [open, setOpen] = useState(false); const ref = useRef(null); if (!label && !icon) icon = 'menuMore'; const isOpen = isBoolean(externalOpen) ? externalOpen : open; const buttonId = useMemo(() => { return 'button-' + generateGuid(); }, []); const handleToggleOpen = useCallback( (e: any) => { e?.stopPropagation(); if (onMouseDown) onMouseDown(e); if (isBoolean(externalOpen) && onToggleOpen) { // Handle open/close externally onToggleOpen(!isOpen); } else if ( isOpen || (trigger === 'click' && !e?.button) || (trigger === 'right-click' && e?.button === 2) ) { // Handle open/close internally setOpen(!isOpen); } }, [externalOpen, trigger, isOpen, onToggleOpen], ); const boxRef = useRef(null); if (anchorOrigin?.includes('top')) offsetY = (offsetY || 0) - 1; else offsetY = (offsetY || 0) + 1; return ( <> e.preventDefault() : undefined} onClick={(e) => { e.stopPropagation(); e.preventDefault(); if (onClick) onClick(e); // Ugly hack to separate mouse and keyboard events if (!e.screenX && !e.screenY) handleToggleOpen(e); }} {...rest} /> {isOpen && ( {(isFunction(items) || items?.length > 0) && ( setOpen(false)} ref={boxRef} aria-labelledby={buttonId} > {(isFunction(items) ? items() : items)?.map((item, index) => { return React.isValidElement(item) ? ( item ) : item === 'divider' ? ( ) : ( { e.stopPropagation(); (item as SilkeOverflowMenuItem).onClick?.(e); handleToggleOpen(e); }} /> ); })} )} {children && (
{children}
)}
)} ); } export type SilkeOverflowMenuItem = { label: React.ReactNode; kind?: 'default' | 'danger' | 'selected'; groupLabel?: string; } & Omit; export const SilkeOverflowMenuItem = React.forwardRef( ({ label, kind, groupLabel: _groupLabel, ...rest }, ref) => ( ), ); SilkeOverflowMenuItem.displayName = 'SilkeOverflowMenuItem';