import { useEffect, useRef, useState, type KeyboardEvent, type PointerEvent, type ReactNode } from "react"; import { cn } from "@/shared/ui/cn"; import { getMobileWorkbenchSheetHeight, type MobileWorkbenchSheetSnap } from "./mobile-workbench-sheet-layout"; export type { MobileWorkbenchSheetSnap } from "./mobile-workbench-sheet-layout"; type MobileWorkbenchSheetProps = { label: string; snap: MobileWorkbenchSheetSnap; onSnapChange: (snap: MobileWorkbenchSheetSnap) => void; onClose: () => void; children: ReactNode; className?: string; }; const SNAP_ORDER: MobileWorkbenchSheetSnap[] = ["half", "full"]; export function MobileWorkbenchSheet({ label, snap, onSnapChange, onClose, children, className }: MobileWorkbenchSheetProps) { const pointerStartRef = useRef<{ y: number; height: number } | null>(null); const draggedRef = useRef(false); const [dragHeight, setDragHeight] = useState(null); const [viewportHeight, setViewportHeight] = useState( typeof window === "undefined" ? 844 : window.innerHeight ); const restingHeight = getMobileWorkbenchSheetHeight(snap, viewportHeight); useEffect(() => { const updateViewportHeight = () => setViewportHeight(window.innerHeight); window.addEventListener("resize", updateViewportHeight); window.visualViewport?.addEventListener("resize", updateViewportHeight); return () => { window.removeEventListener("resize", updateViewportHeight); window.visualViewport?.removeEventListener("resize", updateViewportHeight); }; }, []); useEffect(() => { const handleEscape = (event: globalThis.KeyboardEvent) => { if (event.key === "Escape") { onClose(); } }; window.addEventListener("keydown", handleEscape); return () => window.removeEventListener("keydown", handleEscape); }, [onClose]); function handlePointerDown(event: PointerEvent) { pointerStartRef.current = { y: event.clientY, height: restingHeight }; draggedRef.current = false; event.currentTarget.setPointerCapture(event.pointerId); } function handlePointerMove(event: PointerEvent) { const start = pointerStartRef.current; if (!start) { return; } const delta = start.y - event.clientY; if (Math.abs(delta) > 4) { draggedRef.current = true; } const minHeight = getMobileWorkbenchSheetHeight("half", window.innerHeight); const maxHeight = getMobileWorkbenchSheetHeight("full", window.innerHeight); setDragHeight(Math.min(maxHeight, Math.max(minHeight, start.height + delta))); } function handlePointerUp(event: PointerEvent) { if (!pointerStartRef.current) { return; } const finalHeight = dragHeight ?? pointerStartRef.current.height; pointerStartRef.current = null; setDragHeight(null); event.currentTarget.releasePointerCapture(event.pointerId); if (!draggedRef.current) { return; } onSnapChange(getClosestSnap(finalHeight, window.innerHeight)); } function handleClick() { if (draggedRef.current) { draggedRef.current = false; return; } const currentIndex = SNAP_ORDER.indexOf(snap); onSnapChange(SNAP_ORDER[(currentIndex + 1) % SNAP_ORDER.length] ?? "half"); } function handleKeyDown(event: KeyboardEvent) { if (event.key === "ArrowUp") { event.preventDefault(); onSnapChange(getAdjacentSnap(snap, 1)); return; } if (event.key === "ArrowDown") { event.preventDefault(); onSnapChange(getAdjacentSnap(snap, -1)); return; } if (event.key === "Home") { event.preventDefault(); onSnapChange("half"); return; } if (event.key === "End") { event.preventDefault(); onSnapChange("full"); } } return (
{children}
); } function getAdjacentSnap( current: MobileWorkbenchSheetSnap, direction: -1 | 1 ): MobileWorkbenchSheetSnap { const currentIndex = SNAP_ORDER.indexOf(current); const nextIndex = Math.min(SNAP_ORDER.length - 1, Math.max(0, currentIndex + direction)); return SNAP_ORDER[nextIndex] ?? current; } function getClosestSnap(height: number, viewportHeight: number): MobileWorkbenchSheetSnap { return SNAP_ORDER.reduce((closest, candidate) => { const candidateDistance = Math.abs( getMobileWorkbenchSheetHeight(candidate, viewportHeight) - height ); const closestDistance = Math.abs( getMobileWorkbenchSheetHeight(closest, viewportHeight) - height ); return candidateDistance < closestDistance ? candidate : closest; }, "half"); } function getSnapLabel(snap: MobileWorkbenchSheetSnap) { if (snap === "half") { return "半屏档"; } return "全屏档"; }