import { RefObject } from 'react'; import React, { FC, useRef, useCallback } from '../../lib/teact/teact'; import { IS_TOUCH_ENV } from '../../util/environment'; import { fastRaf } from '../../util/schedulers'; import buildClassName from '../../util/buildClassName'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import useContextMenuPosition from '../../hooks/useContextMenuPosition'; import useFlag from '../../hooks/useFlag'; import useLang from '../../hooks/useLang'; import RippleEffect from './RippleEffect'; import Menu from './Menu'; import MenuItem from './MenuItem'; import Button from './Button'; import './ListItem.scss'; interface MenuItemContextAction { title: string; icon: string; destructive?: boolean; handler?: () => void; } interface OwnProps { ref?: RefObject; buttonRef?: RefObject; icon?: string; secondaryIcon?: string; className?: string; style?: string; children: any; disabled?: boolean; ripple?: boolean; narrow?: boolean; inactive?: boolean; focus?: boolean; destructive?: boolean; multiline?: boolean; isStatic?: boolean; contextActions?: MenuItemContextAction[]; onMouseDown?: (e: React.MouseEvent) => void; onClick?: (e: React.MouseEvent) => void; onSecondaryIconClick?: (e: React.MouseEvent) => void; } const ListItem: FC = ({ ref, buttonRef, icon, secondaryIcon, className, style, children, disabled, ripple, narrow, inactive, focus, destructive, multiline, isStatic, contextActions, onMouseDown, onClick, onSecondaryIconClick, }) => { // eslint-disable-next-line no-null/no-null let containerRef = useRef(null); if (ref) { containerRef = ref; } const [isTouched, markIsTouched, unmarkIsTouched] = useFlag(); const { isContextMenuOpen, contextMenuPosition, handleBeforeContextMenu, handleContextMenu, handleContextMenuClose, handleContextMenuHide, } = useContextMenuHandlers(containerRef, !contextActions); const getTriggerElement = useCallback(() => containerRef.current, []); const getRootElement = useCallback( () => containerRef.current!.closest('.custom-scroll'), [], ); const getMenuElement = useCallback( () => containerRef.current!.querySelector('.ListItem-context-menu .bubble'), [], ); const { positionX, positionY, transformOriginX, transformOriginY, style: menuStyle, } = useContextMenuPosition( contextMenuPosition, getTriggerElement, getRootElement, getMenuElement, ); const handleClick = useCallback((e: React.MouseEvent) => { if (disabled || !onClick) { return; } onClick(e); if (IS_TOUCH_ENV && !ripple) { markIsTouched(); fastRaf(unmarkIsTouched); } }, [disabled, markIsTouched, onClick, ripple, unmarkIsTouched]); const handleSecondaryIconClick = (e: React.MouseEvent) => { if (disabled || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return; e.stopPropagation(); if (onSecondaryIconClick) { onSecondaryIconClick(e); } else { handleContextMenu(e); } }; const handleMouseDown = useCallback((e: React.MouseEvent) => { if (inactive || IS_TOUCH_ENV) { return; } if (contextActions && (e.button === 2 || !onClick)) { handleBeforeContextMenu(e); } if (e.button === 0) { if (!onClick) { handleContextMenu(e); } else { handleClick(e); } } }, [inactive, contextActions, onClick, handleBeforeContextMenu, handleContextMenu, handleClick]); const lang = useLang(); const fullClassName = buildClassName( 'ListItem', className, !isStatic && 'no-selection', ripple && 'has-ripple', narrow && 'narrow', disabled && 'disabled', inactive && 'inactive', contextMenuPosition && 'has-menu-open', focus && 'focus', destructive && 'destructive', multiline && 'multiline', isStatic && 'is-static', ); return (
{icon && ( )} {multiline && (
{children}
)} {!multiline && children} {!disabled && !inactive && ripple && ( )} {secondaryIcon && ( )}
{contextActions && contextMenuPosition !== undefined && ( {contextActions.map((action) => ( {action.title} ))} )}
); }; export default ListItem;