reactjsantd

Handle "long press" in order to display dropdown in Ant Design


This component shows a dropdown menu on desktop devices by triggering the context menu event. Is it possible to use this dropdown component on touch devices, but instead of the context menu event, display the dropdown with long press? There are no context menu events on touch devices.

I want to display a dropdown with a long press technique.

import React from 'react';
import type { MenuProps } from 'antd';
import { Dropdown, theme } from 'antd';

const items: MenuProps['items'] = [
  {
    label: '1st menu item',
    key: '1',
  },
  {
    label: '2nd menu item',
    key: '2',
  },
  {
    label: '3rd menu item',
    key: '3',
  },
];

const App: React.FC = () => {
  const {
    token: { colorBgLayout, colorTextTertiary },
  } = theme.useToken();

  return (
    <Dropdown menu={{ items }} trigger={['contextMenu']}>
      <div
        style={{
          color: colorTextTertiary,
          background: colorBgLayout,
          height: 200,
          textAlign: 'center',
          lineHeight: '200px',
        }}
      >
        Right Click on here
      </div>
    </Dropdown>
  );
};

export default App;

I tried to wrap the content with extra div and apply touch events on it. Is it a correct approach or maybe there is some built-in solution?

      <div
        onTouchStart={handleTouchStart}
        onTouchEnd={handleTouchEnd}
        onContextMenu={e => e.preventDefault()}
      >
        Right Click on here or Long Press on Touch Devices
      </div>

Solution

  • Dropdown pass 4 props to it's children when trigger is set to contextMenu

    1. className
    2. disabled
    3. onClick (Not available when disabled is true)
    4. onContextMenu (Not available when disabled is true)

    I try pointerDown and pointerUp event instead of onTouchEvent (antd throw error. It may be some field/value missing from onTouchEvent). You can use pointer down and up events to detect long press and call onContextMenu function to open context menu.

    Here's the complete code:

    import type { MenuProps } from 'antd';
    import { Dropdown, theme } from 'antd';
    import type React from 'react';
    import { type ForwardedRef, forwardRef, useRef } from 'react';
    
    const items: MenuProps['items'] = [
        {
            label: '1st menu item',
            key: '1'
        },
        {
            label: '2nd menu item',
            key: '2'
        },
        {
            label: '3rd menu item',
            key: '3'
        }
    ];
    
    const App: React.FC = () => {
        return (
            <Dropdown menu={{ items }} trigger={['contextMenu']}>
                <Content />
            </Dropdown>
        );
    };
    
    export default App;
    
    interface ContentProps {
        onClick?: () => void;
        onContextMenu?: (e: React.MouseEvent) => void;
        className?: string;
        // if disabled is true, onContextMenu and onClick events are not available
        disabled?: boolean;
    }
    
    const Content = forwardRef((props: ContentProps, ref: ForwardedRef<HTMLDivElement>) => {
        const { onContextMenu, className, disabled } = props;
    
        const {
            token: { colorBgLayout, colorTextTertiary }
        } = theme.useToken();
        const timer = useRef<ReturnType<typeof setTimeout>>(null);
    
        const clearTimer = () => {
            if (timer.current) {
                clearTimeout(timer.current);
                timer.current = null;
            }
        };
    
        return (
            <div
                ref={ref}
                style={{
                    color: colorTextTertiary,
                    background: colorBgLayout,
                    height: 200,
                    textAlign: 'center',
                    lineHeight: '200px'
                }}
                className={className}
                onContextMenu={onContextMenu}
                onPointerDown={(e) => {
                    if (disabled || e.pointerType !== 'touch') {
                        return;
                    }
    
                    // Clear timer if it is already running
                    clearTimer();
    
                    timer.current = setTimeout(() => {
                        onContextMenu?.(e);
                        clearTimer();
                    }, 2000);
                }}
                onPointerUp={clearTimer}
            >
                Right Click on here or Long Press on Touch Devices
            </div>
        );
    });