Enhance DraggableContainer with new props for enabling/disabling functionality and dragging. Update Popup components to improve structure and responsiveness, removing unnecessary draggable props. Integrate console logging for debugging in PopupContainer and ControlsPopover.

This commit is contained in:
2025-10-21 20:28:30 +05:00
parent 8c61f10fdb
commit 3f463b8ff3
7 changed files with 80 additions and 30 deletions
+47 -5
View File
@@ -15,6 +15,10 @@ export type Corner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
interface DraggableContainerProps {
/** Содержимое контейнера */
children: ReactNode;
/** Включить весь функционал компонента (по умолчанию true). Если false, компонент просто рендерит children без стилей и позиционирования */
enabled?: boolean;
/** Включить возможность перетаскивания (по умолчанию true) */
draggable?: boolean;
/** Включить снэпинг к ближайшей четверти экрана при отпускании (по умолчанию true) */
enableSnapping?: boolean;
/** Автоматическое flex-выравнивание в зависимости от прижатого угла (по умолчанию false) */
@@ -70,6 +74,24 @@ interface DraggableContainerProps {
* </DraggableContainer>
*
* @example
* // Центрирование по вертикали с указанием позиции справа
* <DraggableContainer centerVertical={true} initialPosition={{ right: "4.444vw" }}>
* <YourContent />
* </DraggableContainer>
*
* @example
* // Отключение перетаскивания (статичный контейнер)
* <DraggableContainer draggable={false} initialPosition={{ top: "20px", right: "20px" }}>
* <YourContent />
* </DraggableContainer>
*
* @example
* // Полное отключение функционала (компонент не применяет никаких стилей)
* <DraggableContainer enabled={false}>
* <YourContent />
* </DraggableContainer>
*
* @example
* // С указанием начального угла и отступами в процентах
* <DraggableContainer initialCorner="bottom-right" padding="2%">
* <YourContent />
@@ -95,6 +117,8 @@ interface DraggableContainerProps {
*/
export default function DraggableContainer({
children,
enabled = true,
draggable = true,
enableSnapping = false,
autoAlign = false,
constrainToBounds = false,
@@ -102,7 +126,7 @@ export default function DraggableContainer({
centerHorizontal = false,
initialCorner,
initialPosition,
padding = "16px",
padding = "1.111vw",
className = "",
onPositionChange,
}: DraggableContainerProps) {
@@ -135,16 +159,26 @@ export default function DraggableContainer({
const position: Position = {};
const transforms: string[] = [];
// Вертикальное позиционирование
if (centerVertical) {
position.top = "50%";
transforms.push("translateY(-50%)");
} else if (initialPosition?.top !== undefined) {
position.top = initialPosition.top;
} else if (initialPosition?.bottom !== undefined) {
position.bottom = initialPosition.bottom;
} else {
position.top = padding;
}
// Горизонтальное позиционирование
if (centerHorizontal) {
position.left = "50%";
transforms.push("translateX(-50%)");
} else if (initialPosition?.left !== undefined) {
position.left = initialPosition.left;
} else if (initialPosition?.right !== undefined) {
position.right = initialPosition.right;
} else {
position.left = padding;
}
@@ -246,10 +280,12 @@ export default function DraggableContainer({
};
const handleMouseDown = (e: React.MouseEvent) => {
if (!draggable) return;
startDrag(e.clientX, e.clientY);
};
const handleTouchStart = (e: React.TouchEvent) => {
if (!draggable) return;
if (e.touches.length > 0) {
startDrag(e.touches[0].clientX, e.touches[0].clientY);
}
@@ -407,19 +443,25 @@ export default function DraggableContainer({
return getAlignmentClassesFromPosition(position);
};
// Если компонент отключен, просто рендерим children без стилей и логики
if (!enabled) {
return <>{children}</>;
}
return (
<div
ref={containerRef}
className={clsx(
"pointer-events-auto select-none touch-none",
"pointer-events-auto select-none",
draggable && "touch-none",
!isDragging && "transition-all duration-500 ease-out",
isDragging ? "cursor-grabbing" : "cursor-grab",
draggable && (isDragging ? "cursor-grabbing" : "cursor-grab"),
getAlignmentClasses(),
className
)}
style={getContainerStyle()}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
onMouseDown={draggable ? handleMouseDown : undefined}
onTouchStart={draggable ? handleTouchStart : undefined}
>
{children}
</div>
+7 -1
View File
@@ -1,19 +1,25 @@
import { AnimatePresence, motion } from "motion/react";
import usePopupStore from "../store/popupStore";
import { useEffect } from "react";
function PopupContainer() {
const { popup } = usePopupStore();
const isMobile = innerWidth < 640;
useEffect(() => {
console.log(popup);
}, [popup]);
return (
<AnimatePresence>
{popup && (
<motion.div
className="absolute max-sm:fixed"
className="fixed bottom-0"
initial={{ opacity: 0, y: isMobile ? "100%" : undefined }}
animate={{ opacity: 1, y: isMobile ? "0%" : undefined }}
exit={{ opacity: 0, y: isMobile ? "100%" : undefined }}
transition={{ bounce: 0, ease: "easeInOut" }}
>
{popup}
</motion.div>
+3 -10
View File
@@ -1,4 +1,3 @@
import clsx from "clsx";
import usePopupStore from "../store/popupStore";
import XMarkIcon from "./icons/XMarkIcon";
import Button from "./ui/Button";
@@ -6,22 +5,16 @@ import Button from "./ui/Button";
interface PopupHeaderProps {
title?: string;
leftButton?: React.ReactNode;
draggable?: boolean;
}
function PopupHeader({ title, leftButton, draggable }: PopupHeaderProps) {
function PopupHeader({ title, leftButton }: PopupHeaderProps) {
const { setPopup } = usePopupStore();
return (
<div
className={clsx(
"2xl:p-[1.111vw] p-4 flex justify-between items-center select-none relative",
draggable && "cursor-grab active:cursor-grabbing"
)}
>
<div 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="title-s flex-1 font-medium text-center">{title}</p>
<p className="flex-1 font-medium text-center title-s">{title}</p>
)}
<Button variant="secondary" size="small" onClick={() => setPopup(null)}>
<div className="2xl:size-[1.111vw] size-4">
+2 -8
View File
@@ -7,7 +7,6 @@ interface PopupWrapperProps {
className?: string;
title?: string;
leftButton?: React.ReactNode;
draggable?: boolean;
}
function PopupWrapper({
@@ -15,7 +14,6 @@ function PopupWrapper({
className,
title,
leftButton,
draggable,
}: PopupWrapperProps) {
return (
<div
@@ -25,15 +23,11 @@ function PopupWrapper({
)}
>
{/* Полоска-ручка для свайпа на мобильных */}
<div className="hidden max-sm:flex justify-center pt-1 pb-1 absolute -top-3 left-1/2 -translate-x-1/2">
<div className="hidden absolute -top-3 left-1/2 justify-center pt-1 pb-1 -translate-x-1/2 max-sm:flex">
<div className="w-8 h-1 bg-[#141414] rounded-full opacity-50" />
</div>
<PopupHeader
title={title}
leftButton={leftButton}
draggable={draggable}
/>
<PopupHeader title={title} leftButton={leftButton} />
<div className="2xl:p-[1.389vw] p-5">{children}</div>
</div>
);
+7 -2
View File
@@ -33,8 +33,13 @@ export default function ChatPopup() {
}
return (
<DraggableContainer centerVertical>
<PopupWrapper title="Чат" draggable className="sm:overflow-hidden">
<DraggableContainer
enabled={window.innerWidth >= 640}
centerVertical
constrainToBounds
initialPosition={{ right: "5vw" }}
>
<PopupWrapper title="Чат" className="sm:overflow-hidden">
<div className="flex flex-col 2xl:h-[27.778vw] relative 2xl:-m-[1.389vw] -m-5">
<MessageFeed messages={messages} />
<MessageInput onMessageSend={onMessageSend} />
+13 -4
View File
@@ -38,17 +38,26 @@ function ControlsPopover() {
};
}, []);
const { setPopup } = usePopupStore();
const { popup, setPopup } = usePopupStore();
const { setModal } = useModalStore();
function handleClickOpenChatPopup() {
console.log("handleClickOpenChatPopup");
setPopup(<ChatPopup />);
}
useEffect(() => {
console.log(popup);
}, [popup]);
return (
<div className="2xl:hidden order-3 relative">
<div className="relative order-3 2xl:hidden">
<FloatingActionButton
ref={buttonRef}
className={clsx(isOpened && "!bg-[#7B60F3]")}
onClick={() => setIsOpened(!isOpened)}
>
<div className="size-4 text-white">
<div className="text-white size-4">
<MoreIcon />
</div>
</FloatingActionButton>
@@ -60,7 +69,7 @@ function ControlsPopover() {
<Button
variant="tertiary"
className="w-full !justify-start"
onClick={() => setPopup(<ChatPopup />)}
onClick={handleClickOpenChatPopup}
>
<div className="size-4">
<ChatFilledIcon />
+1
View File
@@ -76,6 +76,7 @@ function NewSessionPage() {
const session = sessionData?.session;
function handleChatOpen() {
console.log("handleChatOpen");
setPopup(<ChatPopup />);
}