import { useCallback, useEffect, useRef, useState, type CSSProperties } from 'react'; type Props = { children: React.ReactNode; }; const MIN = 160; const MAX = 1200; export function ResizableStage({ children }: Props) { const [size, setSize] = useState({ width: 520, height: 480 }); const dragRef = useRef<{ edge: 'e' | 's' | 'se'; startX: number; startY: number; startW: number; startH: number; } | null>(null); const onPointerMove = useCallback((e: PointerEvent) => { const drag = dragRef.current; if (!drag) return; const dx = e.clientX - drag.startX; const dy = e.clientY - drag.startY; setSize((prev) => { let width = prev.width; let height = prev.height; if (drag.edge === 'e' || drag.edge === 'se') { width = clamp(drag.startW + dx, MIN, MAX); } if (drag.edge === 's' || drag.edge === 'se') { height = clamp(drag.startH + dy, MIN, MAX); } return { width, height }; }); }, []); const onPointerUp = useCallback(() => { dragRef.current = null; window.removeEventListener('pointermove', onPointerMove); window.removeEventListener('pointerup', onPointerUp); }, [onPointerMove]); const startDrag = (edge: 'e' | 's' | 'se') => (e: React.PointerEvent) => { e.preventDefault(); dragRef.current = { edge, startX: e.clientX, startY: e.clientY, startW: size.width, startH: size.height, }; window.addEventListener('pointermove', onPointerMove); window.addEventListener('pointerup', onPointerUp); }; useEffect(() => () => onPointerUp(), [onPointerUp]); const stageStyle: CSSProperties = { width: size.width, height: size.height, }; return (
{size.width} × {size.height}
{children}
); } function clamp(n: number, min: number, max: number) { return Math.min(max, Math.max(min, n)); }