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.
This commit is contained in:
@@ -19,6 +19,8 @@ interface DraggableContainerProps {
|
||||
enabled?: boolean;
|
||||
/** Включить возможность перетаскивания (по умолчанию true) */
|
||||
draggable?: boolean;
|
||||
/** Ref элемента-хэндла для перетаскивания. Если указан, перетаскивание будет работать только при клике на этот элемент */
|
||||
dragHandleRef?: React.RefObject<HTMLElement | null>;
|
||||
/** Включить снэпинг к ближайшей четверти экрана при отпускании (по умолчанию true) */
|
||||
enableSnapping?: boolean;
|
||||
/** Автоматическое flex-выравнивание в зависимости от прижатого угла (по умолчанию false) */
|
||||
@@ -114,11 +116,22 @@ interface DraggableContainerProps {
|
||||
* <DraggableContainer enableSnapping={true} className="flex flex-col gap-4">
|
||||
* <YourContent />
|
||||
* </DraggableContainer>
|
||||
*
|
||||
* @example
|
||||
* // С указанием элемента-хэндла для перетаскивания (например, только за шапку)
|
||||
* const headerRef = useRef<HTMLDivElement>(null);
|
||||
* <DraggableContainer dragHandleRef={headerRef}>
|
||||
* <div>
|
||||
* <div ref={headerRef}>Header (drag me!)</div>
|
||||
* <div>Content</div>
|
||||
* </div>
|
||||
* </DraggableContainer>
|
||||
*/
|
||||
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<string>("");
|
||||
|
||||
// Проверяет, что событие началось на элементе-хэндле или его потомках
|
||||
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({
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={clsx(
|
||||
"pointer-events-auto select-none",
|
||||
draggable && "touch-none",
|
||||
"pointer-events-auto",
|
||||
// draggable && "touch-none",
|
||||
!isDragging && "transition-all duration-500 ease-out",
|
||||
draggable && (isDragging ? "cursor-grabbing" : "cursor-grab"),
|
||||
draggable &&
|
||||
!dragHandleRef &&
|
||||
(isDragging ? "cursor-grabbing" : "cursor-grab"),
|
||||
getAlignmentClasses(),
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -5,13 +5,17 @@ import Button from "./ui/Button";
|
||||
interface PopupHeaderProps {
|
||||
title?: string;
|
||||
leftButton?: React.ReactNode;
|
||||
ref?: React.RefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
function PopupHeader({ title, leftButton }: PopupHeaderProps) {
|
||||
function PopupHeader({ title, leftButton, ref }: PopupHeaderProps) {
|
||||
const { setPopup } = usePopupStore();
|
||||
|
||||
return (
|
||||
<div className="2xl:p-[1.111vw] p-4 flex justify-between items-center select-none relative">
|
||||
<div
|
||||
ref={ref}
|
||||
className="2xl:p-[1.111vw] p-4 flex justify-between items-center select-none relative"
|
||||
>
|
||||
<div className="2xl:size-[2.222vw] size-8">{leftButton}</div>
|
||||
{title && (
|
||||
<p className="flex-1 font-medium text-center title-s">{title}</p>
|
||||
|
||||
@@ -7,6 +7,7 @@ interface PopupWrapperProps {
|
||||
className?: string;
|
||||
title?: string;
|
||||
leftButton?: React.ReactNode;
|
||||
headerRef?: React.RefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
function PopupWrapper({
|
||||
@@ -14,6 +15,7 @@ function PopupWrapper({
|
||||
className,
|
||||
title,
|
||||
leftButton,
|
||||
headerRef,
|
||||
}: PopupWrapperProps) {
|
||||
return (
|
||||
<div
|
||||
@@ -27,7 +29,7 @@ function PopupWrapper({
|
||||
<div className="w-8 h-1 bg-[#141414] rounded-full opacity-50" />
|
||||
</div>
|
||||
|
||||
<PopupHeader title={title} leftButton={leftButton} />
|
||||
<PopupHeader title={title} leftButton={leftButton} ref={headerRef} />
|
||||
<div className="2xl:p-[1.389vw] p-5">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import PopupWrapper from "../PopupWrapper";
|
||||
import DraggableContainer from "../DraggableContainer";
|
||||
|
||||
export default function ChatPopup() {
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const [messages, setMessages] = useState<MessageItemProps[]>([
|
||||
{
|
||||
senderId: "1",
|
||||
@@ -38,8 +39,9 @@ export default function ChatPopup() {
|
||||
centerVertical
|
||||
constrainToBounds
|
||||
initialPosition={{ right: "5vw" }}
|
||||
dragHandleRef={headerRef}
|
||||
>
|
||||
<PopupWrapper title="Чат" className="sm:overflow-hidden">
|
||||
<PopupWrapper title="Чат" className="sm:overflow-hidden" headerRef={headerRef}>
|
||||
<div className="flex flex-col 2xl:h-[19.444vw] max-sm:h-[87.5dvh] 2xl:-m-[1.389vw] -m-5">
|
||||
<MessageFeed messages={messages} />
|
||||
<MessageInput onMessageSend={onMessageSend} />
|
||||
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<DraggableContainer
|
||||
@@ -21,8 +22,9 @@ export default function ParticipantsPopup() {
|
||||
centerVertical
|
||||
constrainToBounds
|
||||
initialPosition={{ right: "5vw" }}
|
||||
dragHandleRef={headerRef}
|
||||
>
|
||||
<PopupWrapper title="Участники">
|
||||
<PopupWrapper title="Участники" headerRef={headerRef}>
|
||||
<div className="flex flex-col 2xl:gap-[1.667vw] gap-6 2xl:-mt-[1.389vw] -mt-5">
|
||||
<div className="flex flex-col gap-4 2xl:gap-[1.111vw] 2xl:max-h-[calc(11.944vw+1.389vw)] max-h-[73.75dvh] overflow-y-auto 2xl:pt-[1.389vw] pt-5">
|
||||
{participants.map((participant, index) => (
|
||||
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<DraggableContainer
|
||||
@@ -19,9 +21,11 @@ function QRCodePopup({ link }: QRCodePopupProps) {
|
||||
centerVertical
|
||||
constrainToBounds
|
||||
initialPosition={{ right: "5vw" }}
|
||||
dragHandleRef={headerRef}
|
||||
>
|
||||
<PopupWrapper
|
||||
className="w-[21.667vw]"
|
||||
headerRef={headerRef}
|
||||
leftButton={
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
||||
@@ -6,9 +6,11 @@ import LinkShare from "../ui/LinkShare";
|
||||
import usePopupStore from "../../store/popupStore";
|
||||
import QRCodePopup from "./QRCodePopup";
|
||||
import DraggableContainer from "../DraggableContainer";
|
||||
import { useRef } from "react";
|
||||
|
||||
function SharePopup({ link }: { link: string }) {
|
||||
const { setPopup } = usePopupStore();
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function handleShare() {
|
||||
if (navigator.share) {
|
||||
@@ -28,9 +30,11 @@ function SharePopup({ link }: { link: string }) {
|
||||
centerVertical
|
||||
constrainToBounds
|
||||
initialPosition={{ right: "5vw" }}
|
||||
dragHandleRef={headerRef}
|
||||
>
|
||||
<PopupWrapper
|
||||
title="Пригласить"
|
||||
headerRef={headerRef}
|
||||
leftButton={
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
||||
Reference in New Issue
Block a user