From 51f2013bee2d362f44f5e09b7947d37b7209c731 Mon Sep 17 00:00:00 2001 From: inmake Date: Wed, 22 Oct 2025 15:12:00 +0500 Subject: [PATCH] Enhance DraggableContainer to support dragHandleRef for improved drag functionality. Update Popup components (PopupHeader, PopupWrapper, ChatPopup, ParticipantsPopup, QRCodePopup, SharePopup) to utilize dragHandleRef, allowing dragging only from specified elements. Improve cursor styles during dragging for better user experience. --- client/src/components/DraggableContainer.tsx | 50 +++++++++++++++++-- client/src/components/PopupHeader.tsx | 8 ++- client/src/components/PopupWrapper.tsx | 4 +- client/src/components/popups/ChatPopup.tsx | 4 +- .../components/popups/ParticipantsPopup.tsx | 4 +- client/src/components/popups/QRCodePopup.tsx | 4 ++ client/src/components/popups/SharePopup.tsx | 4 ++ 7 files changed, 70 insertions(+), 8 deletions(-) diff --git a/client/src/components/DraggableContainer.tsx b/client/src/components/DraggableContainer.tsx index d76d295..23b2b34 100644 --- a/client/src/components/DraggableContainer.tsx +++ b/client/src/components/DraggableContainer.tsx @@ -19,6 +19,8 @@ interface DraggableContainerProps { enabled?: boolean; /** Включить возможность перетаскивания (по умолчанию true) */ draggable?: boolean; + /** Ref элемента-хэндла для перетаскивания. Если указан, перетаскивание будет работать только при клике на этот элемент */ + dragHandleRef?: React.RefObject; /** Включить снэпинг к ближайшей четверти экрана при отпускании (по умолчанию true) */ enableSnapping?: boolean; /** Автоматическое flex-выравнивание в зависимости от прижатого угла (по умолчанию false) */ @@ -114,11 +116,22 @@ interface DraggableContainerProps { * * * + * + * @example + * // С указанием элемента-хэндла для перетаскивания (например, только за шапку) + * const headerRef = useRef(null); + * + *
+ *
Header (drag me!)
+ *
Content
+ *
+ *
*/ export default function DraggableContainer({ children, enabled = true, draggable = true, + dragHandleRef, enableSnapping = false, autoAlign = false, constrainToBounds = false, @@ -204,6 +217,17 @@ export default function DraggableContainer({ const [isDragging, setIsDragging] = useState(false); const [dragStartAlignment, setDragStartAlignment] = useState(""); + // Проверяет, что событие началось на элементе-хэндле или его потомках + const isEventOnDragHandle = (target: EventTarget | null): boolean => { + if (!dragHandleRef?.current || !target) return true; // Если хэндл не указан, разрешаем перетаскивание откуда угодно + + const element = target as Node; + return ( + dragHandleRef.current === element || + dragHandleRef.current.contains(element) + ); + }; + const getAlignmentClassesFromPosition = (pos: Position): string => { if (!autoAlign) return ""; @@ -281,11 +305,13 @@ export default function DraggableContainer({ const handleMouseDown = (e: React.MouseEvent) => { if (!draggable) return; + if (!isEventOnDragHandle(e.target)) return; startDrag(e.clientX, e.clientY); }; const handleTouchStart = (e: React.TouchEvent) => { if (!draggable) return; + if (!isEventOnDragHandle(e.target)) return; if (e.touches.length > 0) { startDrag(e.touches[0].clientX, e.touches[0].clientY); } @@ -411,6 +437,22 @@ export default function DraggableContainer({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [isDragging]); + // Устанавливаем cursor стили на элемент-хэндл + useEffect(() => { + if (!draggable || !dragHandleRef?.current) return; + + const handleElement = dragHandleRef.current; + handleElement.style.cursor = isDragging ? "grabbing" : "grab"; + handleElement.style.userSelect = "none"; + handleElement.style.touchAction = "none"; + + return () => { + handleElement.style.cursor = ""; + handleElement.style.userSelect = ""; + handleElement.style.touchAction = ""; + }; + }, [draggable, dragHandleRef, isDragging]); + const getContainerStyle = (): React.CSSProperties => { const style: React.CSSProperties = { position: "fixed", @@ -452,10 +494,12 @@ export default function DraggableContainer({
; } -function PopupHeader({ title, leftButton }: PopupHeaderProps) { +function PopupHeader({ title, leftButton, ref }: PopupHeaderProps) { const { setPopup } = usePopupStore(); return ( -
+
{leftButton}
{title && (

{title}

diff --git a/client/src/components/PopupWrapper.tsx b/client/src/components/PopupWrapper.tsx index 5db65a0..d6a9f57 100644 --- a/client/src/components/PopupWrapper.tsx +++ b/client/src/components/PopupWrapper.tsx @@ -7,6 +7,7 @@ interface PopupWrapperProps { className?: string; title?: string; leftButton?: React.ReactNode; + headerRef?: React.RefObject; } function PopupWrapper({ @@ -14,6 +15,7 @@ function PopupWrapper({ className, title, leftButton, + headerRef, }: PopupWrapperProps) { return (
- +
{children}
); diff --git a/client/src/components/popups/ChatPopup.tsx b/client/src/components/popups/ChatPopup.tsx index 49da40d..de918dd 100644 --- a/client/src/components/popups/ChatPopup.tsx +++ b/client/src/components/popups/ChatPopup.tsx @@ -7,6 +7,7 @@ import PopupWrapper from "../PopupWrapper"; import DraggableContainer from "../DraggableContainer"; export default function ChatPopup() { + const headerRef = useRef(null); const [messages, setMessages] = useState([ { senderId: "1", @@ -38,8 +39,9 @@ export default function ChatPopup() { centerVertical constrainToBounds initialPosition={{ right: "5vw" }} + dragHandleRef={headerRef} > - +
diff --git a/client/src/components/popups/ParticipantsPopup.tsx b/client/src/components/popups/ParticipantsPopup.tsx index 3dedab6..ceabff3 100644 --- a/client/src/components/popups/ParticipantsPopup.tsx +++ b/client/src/components/popups/ParticipantsPopup.tsx @@ -14,6 +14,7 @@ import DraggableContainer from "../DraggableContainer"; export default function ParticipantsPopup() { const participants = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const headerRef = useRef(null); return ( - +
{participants.map((participant, index) => ( diff --git a/client/src/components/popups/QRCodePopup.tsx b/client/src/components/popups/QRCodePopup.tsx index 8b9b5ca..0f41cbd 100644 --- a/client/src/components/popups/QRCodePopup.tsx +++ b/client/src/components/popups/QRCodePopup.tsx @@ -5,6 +5,7 @@ import usePopupStore from "../../store/popupStore"; import SharePopup from "./SharePopup"; import QRCode from "react-qr-code"; import DraggableContainer from "../DraggableContainer"; +import { useRef } from "react"; interface QRCodePopupProps { link: string; @@ -12,6 +13,7 @@ interface QRCodePopupProps { function QRCodePopup({ link }: QRCodePopupProps) { const { setPopup } = usePopupStore(); + const headerRef = useRef(null); return ( (null); function handleShare() { if (navigator.share) { @@ -28,9 +30,11 @@ function SharePopup({ link }: { link: string }) { centerVertical constrainToBounds initialPosition={{ right: "5vw" }} + dragHandleRef={headerRef} >