This commit is contained in:
2025-10-21 19:09:33 +05:00
2 changed files with 69 additions and 19 deletions
+66 -17
View File
@@ -2,13 +2,15 @@ import { useEffect, useRef, useState } from "react";
import type { ReactNode } from "react";
import clsx from "clsx";
interface Position {
top?: number;
left?: number;
right?: number;
bottom?: number;
export interface Position {
top?: number | string;
left?: number | string;
right?: number | string;
bottom?: number | string;
}
export type Corner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
interface DraggableContainerProps {
/** Содержимое контейнера */
children: ReactNode;
@@ -18,10 +20,12 @@ interface DraggableContainerProps {
autoAlign?: boolean;
/** Ограничить перетаскивание границами окна (по умолчанию false) */
constrainToBounds?: boolean;
/** Начальная позиция контейнера */
/** Начальный угол экрана (имеет приоритет над initialPosition) */
initialCorner?: Corner;
/** Начальная позиция контейнера (используется если не указан initialCorner) */
initialPosition?: Position;
/** Отступ от краев экрана при снэпинге (по умолчанию 20px) */
padding?: number;
/** Отступ от краев экрана при снэпинге и начальной позиции (по умолчанию "20px"). Можно указать в px, %, vw, vh */
padding?: number | string;
/** Дополнительные CSS классы */
className?: string;
/** Колбэк при изменении позиции */
@@ -43,8 +47,20 @@ interface DraggableContainerProps {
* </DraggableContainer>
*
* @example
* // С указанием начального угла и отступами в процентах
* <DraggableContainer initialCorner="bottom-right" padding="2%">
* <YourContent />
* </DraggableContainer>
*
* @example
* // С отступами в vw
* <DraggableContainer initialCorner="top-left" padding="5vw">
* <YourContent />
* </DraggableContainer>
*
* @example
* // С ограничением перетаскивания границами окна
* <DraggableContainer constrainToBounds={true}>
* <DraggableContainer constrainToBounds={true} initialCorner="top-left">
* <YourContent />
* </DraggableContainer>
*
@@ -59,8 +75,9 @@ export default function DraggableContainer({
enableSnapping = false,
autoAlign = false,
constrainToBounds = false,
initialPosition = { top: 20, right: 20 },
padding = 20,
initialCorner,
initialPosition,
padding = "20px",
className = "",
onPositionChange,
}: DraggableContainerProps) {
@@ -72,7 +89,33 @@ export default function DraggableContainer({
initialPosition: { top: 0, left: 0 },
});
const [position, setPosition] = useState<Position>(initialPosition);
// Функция для преобразования угла в позицию
const getPositionFromCorner = (corner: Corner): Position => {
switch (corner) {
case "top-left":
return { top: padding, left: padding };
case "top-right":
return { top: padding, right: padding };
case "bottom-left":
return { bottom: padding, left: padding };
case "bottom-right":
return { bottom: padding, right: padding };
}
};
// Определяем начальную позицию
const getInitialPosition = (): Position => {
if (initialCorner) {
return getPositionFromCorner(initialCorner);
}
if (initialPosition) {
return initialPosition;
}
// По умолчанию - верхний правый угол
return { top: padding, right: padding };
};
const [position, setPosition] = useState<Position>(getInitialPosition());
const [isDragging, setIsDragging] = useState(false);
const [dragStartAlignment, setDragStartAlignment] = useState<string>("");
@@ -256,7 +299,9 @@ export default function DraggableContainer({
if (isDragging) {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("touchmove", handleTouchMove, { passive: false });
document.addEventListener("touchmove", handleTouchMove, {
passive: false,
});
document.addEventListener("touchend", handleTouchEnd);
document.addEventListener("touchcancel", handleTouchEnd);
@@ -277,10 +322,14 @@ export default function DraggableContainer({
zIndex: 1000,
};
if (position.top !== undefined) style.top = `${position.top}px`;
if (position.left !== undefined) style.left = `${position.left}px`;
if (position.right !== undefined) style.right = `${position.right}px`;
if (position.bottom !== undefined) style.bottom = `${position.bottom}px`;
const formatValue = (value: number | string): string => {
return typeof value === "number" ? `${value}px` : value;
};
if (position.top !== undefined) style.top = formatValue(position.top);
if (position.left !== undefined) style.left = formatValue(position.left);
if (position.right !== undefined) style.right = formatValue(position.right);
if (position.bottom !== undefined) style.bottom = formatValue(position.bottom);
return style;
};
+3 -2
View File
@@ -35,8 +35,9 @@ function SessionUsersPanel2() {
<DraggableContainer
enableSnapping={true}
autoAlign={true}
initialPosition={{ bottom: 16, right: 16 }}
padding={20}
// initialPosition={{ top: 20, left: 20 }}
initialCorner="bottom-right"
padding="1.111vw"
className="flex gap-4"
>
{users.map((user) => (