import React, { ReactNode, useCallback, useRef, useState } from 'react'; import FocusTrap from 'focus-trap-react'; import { Box, Overlay, OverlayBackdrop, config, color, toRem } from 'folds'; import { stopPropagation } from '../../utils/keyboard'; type ActionSheetProps = { open: boolean; onClose: () => void; 'aria-label'?: string; children: ReactNode; }; /** * [Gitea #166] A bottom sheet for touch screens: slides up from the bottom, * swipe-down or backdrop tap dismisses. Content is whatever the caller * renders (quick reactions + a folds Menu, for message actions). */ export function ActionSheet({ open, onClose, children, 'aria-label': ariaLabel, }: ActionSheetProps) { const [dragY, setDragY] = useState(0); const startY = useRef(null); // The long-press that opened us ends with a touchend/click that lands // "outside" the sheet — ignore outside clicks for the first moments. const openedAt = useRef(Date.now()); const onTouchStart = useCallback((evt: React.TouchEvent) => { startY.current = evt.touches[0]?.clientY ?? null; }, []); const onTouchMove = useCallback((evt: React.TouchEvent) => { if (startY.current === null) return; const dy = (evt.touches[0]?.clientY ?? startY.current) - startY.current; setDragY(Math.max(0, dy)); }, []); const onTouchEnd = useCallback(() => { const dismiss = dragY > 80; startY.current = null; setDragY(0); if (dismiss) onClose(); }, [dragY, onClose]); if (!open) return null; return ( }> Date.now() - openedAt.current > 600, escapeDeactivates: stopPropagation, }} >
{children} ); }