Refactor Popup components to remove position handling and drag functionality; integrate DraggableContainer for improved drag-and-drop experience. Update PopupHeader and PopupWrapper for cleaner structure and enhanced mobile responsiveness.
This commit is contained in:
@@ -2,20 +2,10 @@ import { AnimatePresence, motion } from "motion/react";
|
||||
import usePopupStore from "../store/popupStore";
|
||||
|
||||
function PopupContainer() {
|
||||
const { popup, position, setPopup } = usePopupStore();
|
||||
const { popup } = usePopupStore();
|
||||
|
||||
const isMobile = innerWidth < 640;
|
||||
|
||||
function handleDragEnd(
|
||||
_event: unknown,
|
||||
info: { offset: { y: number }; velocity: { y: number } }
|
||||
) {
|
||||
// Закрываем попап если свайпнули вниз больше чем на 100px или со скоростью > 500
|
||||
if (info.offset.y > 100 || info.velocity.y > 500) {
|
||||
setPopup(null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{popup && (
|
||||
@@ -24,16 +14,6 @@ function PopupContainer() {
|
||||
initial={{ opacity: 0, y: isMobile ? "100%" : undefined }}
|
||||
animate={{ opacity: 1, y: isMobile ? "0%" : undefined }}
|
||||
exit={{ opacity: 0, y: isMobile ? "100%" : undefined }}
|
||||
drag={isMobile ? "y" : false}
|
||||
dragConstraints={{ top: 0, bottom: 0 }}
|
||||
dragElastic={{ top: 0, bottom: 0.5 }}
|
||||
onDragEnd={isMobile ? handleDragEnd : undefined}
|
||||
transition={{ bounce: 0 }}
|
||||
style={
|
||||
!isMobile
|
||||
? { top: position.y, left: position.x }
|
||||
: { bottom: 0, left: 0, right: 0 }
|
||||
}
|
||||
>
|
||||
{popup}
|
||||
</motion.div>
|
||||
|
||||
@@ -6,21 +6,14 @@ import Button from "./ui/Button";
|
||||
interface PopupHeaderProps {
|
||||
title?: string;
|
||||
leftButton?: React.ReactNode;
|
||||
headerRef: React.RefObject<HTMLDivElement | null>;
|
||||
draggable?: boolean;
|
||||
}
|
||||
|
||||
function PopupHeader({
|
||||
title,
|
||||
leftButton,
|
||||
headerRef,
|
||||
draggable,
|
||||
}: PopupHeaderProps) {
|
||||
function PopupHeader({ title, leftButton, draggable }: PopupHeaderProps) {
|
||||
const { setPopup } = usePopupStore();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={headerRef}
|
||||
className={clsx(
|
||||
"2xl:p-[1.111vw] p-4 flex justify-between items-center select-none relative",
|
||||
draggable && "cursor-grab active:cursor-grabbing"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import clsx from "clsx";
|
||||
import PopupHeader from "./PopupHeader";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import usePopupStore from "../store/popupStore";
|
||||
import DraggableContainer from "./DraggableContainer";
|
||||
|
||||
interface PopupWrapperProps {
|
||||
children: React.ReactNode;
|
||||
@@ -20,84 +18,9 @@ function PopupWrapper({
|
||||
leftButton,
|
||||
draggable,
|
||||
}: PopupWrapperProps) {
|
||||
const { position, setPosition } = usePopupStore();
|
||||
const [mouseDown, setMouseDown] = useState(false);
|
||||
const [mouseDownPosition, setMouseDownPosition] = useState(position);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
addEventListener("mouseup", () => setMouseDown(false));
|
||||
addEventListener("touchend", () => setMouseDown(false));
|
||||
return () => {
|
||||
removeEventListener("mouseup", () => setMouseDown(false));
|
||||
removeEventListener("touchend", () => setMouseDown(false));
|
||||
};
|
||||
}, []);
|
||||
|
||||
function handleMove(e: MouseEvent | TouchEvent) {
|
||||
if (draggable && mouseDown && wrapperRef.current) {
|
||||
e.preventDefault();
|
||||
const x = "clientX" in e ? e.clientX : e.touches[0].clientX;
|
||||
const y = "clientY" in e ? e.clientY : e.touches[0].clientY;
|
||||
setPosition({
|
||||
x: Math.min(
|
||||
Math.max(0, position.x + x - mouseDownPosition.x),
|
||||
innerWidth - wrapperRef.current.clientWidth
|
||||
),
|
||||
y: Math.min(
|
||||
Math.max(0, position.y + y - mouseDownPosition.y),
|
||||
innerHeight - wrapperRef.current.clientHeight
|
||||
),
|
||||
});
|
||||
setMouseDownPosition({ x, y });
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
addEventListener("mousemove", handleMove);
|
||||
addEventListener("touchmove", handleMove);
|
||||
return () => {
|
||||
removeEventListener("mousemove", handleMove);
|
||||
removeEventListener("touchmove", handleMove);
|
||||
};
|
||||
}, [handleMove]);
|
||||
|
||||
useEffect(() => {
|
||||
if (headerRef.current) {
|
||||
headerRef.current.addEventListener("mousedown", (e) => {
|
||||
setMouseDown(true);
|
||||
setMouseDownPosition({ x: e.clientX, y: e.clientY });
|
||||
});
|
||||
headerRef.current.addEventListener("touchstart", (e) => {
|
||||
setMouseDown(true);
|
||||
setMouseDownPosition({
|
||||
x: e.touches[0].clientX,
|
||||
y: e.touches[0].clientY,
|
||||
});
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
if (headerRef.current) {
|
||||
headerRef.current.removeEventListener("mousedown", (e) => {
|
||||
setMouseDown(true);
|
||||
setMouseDownPosition({ x: e.clientX, y: e.clientY });
|
||||
});
|
||||
headerRef.current.removeEventListener("touchstart", (e) => {
|
||||
setMouseDown(true);
|
||||
setMouseDownPosition({
|
||||
x: e.touches[0].clientX,
|
||||
y: e.touches[0].clientY,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
<DraggableContainer
|
||||
constrainToBounds
|
||||
className={clsx(
|
||||
"2xl:rounded-[2.222vw] relative bg-white shadow-[0_4px_40px_0_rgba(15,16,17,0.1)] 2xl:w-[21.667vw] sm:rounded-[32px] max-sm:w-screen max-sm:rounded-t-[32px]",
|
||||
className
|
||||
@@ -109,13 +32,12 @@ function PopupWrapper({
|
||||
</div>
|
||||
|
||||
<PopupHeader
|
||||
headerRef={headerRef}
|
||||
title={title}
|
||||
leftButton={leftButton}
|
||||
draggable={draggable}
|
||||
/>
|
||||
<div className="2xl:p-[1.389vw] p-5">{children}</div>
|
||||
</div>
|
||||
</DraggableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ function SessionUsersPanel2() {
|
||||
<DraggableContainer
|
||||
enableSnapping={true}
|
||||
autoAlign={true}
|
||||
initialPosition={{ top: 20, left: 20 }}
|
||||
initialPosition={{ bottom: 16, right: 16 }}
|
||||
padding={20}
|
||||
className="flex gap-4"
|
||||
>
|
||||
|
||||
@@ -15,13 +15,9 @@ export default function ParticipantsPopup() {
|
||||
const participants = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
return (
|
||||
<PopupWrapper
|
||||
title="Участники"
|
||||
draggable
|
||||
className="h-max 2xl:w-[21.667vw]"
|
||||
>
|
||||
<div className="flex flex-col gap-[1.667vw] relative">
|
||||
<ul className="flex flex-col gap-0 2xl:gap-[1.111vw] 2xl:max-h-auto max-h-[calc(100dvh-50vw)] overflow-y-auto">
|
||||
<PopupWrapper title="Участники" draggable>
|
||||
<div className="flex flex-col 2xl:gap-[1.667vw] gap-6 relative 2xl:-mt-[1.389vw] -mt-5">
|
||||
<ul className="flex flex-col gap-0 2xl:gap-[1.111vw] 2xl:max-h-[calc(11.944vw+1.389vw)] overflow-y-auto 2xl:pt-[1.389vw] pt-5">
|
||||
{participants.map((participant, index) => (
|
||||
<Fragment key={index}>
|
||||
<ParticipantItem id={participant.toString()} />
|
||||
|
||||
@@ -8,7 +8,6 @@ import SettingsModal from "../components/modals/SettingsModal";
|
||||
import useModalStore from "../store/modalStore";
|
||||
import CogFilledIcon from "../components/icons/CogFilledIcon";
|
||||
// import SessionUsersPanel from "../components/SessionUsersPanel";
|
||||
import SharePopup from "../components/popups/SharePopup";
|
||||
// import { useEffect } from "react";
|
||||
// import useToastsStore from "../store/toastsStore";
|
||||
import ChatPopup from "../components/popups/ChatPopup";
|
||||
|
||||
@@ -25,7 +25,7 @@ import LoaderIcon from "../components/icons/LoaderIcon";
|
||||
import SessionUsersPanel from "../components/SessionUsersPanel2";
|
||||
|
||||
function NewSessionPage() {
|
||||
const { setPopup, setPosition } = usePopupStore();
|
||||
const { setPopup } = usePopupStore();
|
||||
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
@@ -75,6 +75,18 @@ function NewSessionPage() {
|
||||
|
||||
const session = sessionData?.session;
|
||||
|
||||
function handleChatOpen() {
|
||||
setPopup(<ChatPopup />);
|
||||
}
|
||||
|
||||
function handleParticipantsOpen() {
|
||||
setPopup(<ParticipantsPopup />);
|
||||
}
|
||||
|
||||
function handleShareOpen() {
|
||||
setPopup(<SharePopup link={`${window.location.origin}/sessions/${id}`} />);
|
||||
}
|
||||
|
||||
// Перенаправление на тестовую страницу при завершении сессии
|
||||
useEffect(() => {
|
||||
if (session?.status === "ended") {
|
||||
@@ -150,13 +162,7 @@ function NewSessionPage() {
|
||||
<ActionsSidebarWrapper>
|
||||
<FloatingActionButton
|
||||
className="max-2xl:hidden"
|
||||
onClick={() => {
|
||||
setPosition({
|
||||
x: ((1440 - 384) / 1440) * innerWidth,
|
||||
y: (200 / 1440) * innerWidth,
|
||||
});
|
||||
setPopup(<ChatPopup />);
|
||||
}}
|
||||
onClick={handleChatOpen}
|
||||
>
|
||||
<div className="size-[1.111vw] text-white">
|
||||
<ChatFilledIcon />
|
||||
@@ -164,13 +170,7 @@ function NewSessionPage() {
|
||||
</FloatingActionButton>
|
||||
<FloatingActionButton
|
||||
className="max-2xl:hidden"
|
||||
onClick={() => {
|
||||
setPosition({
|
||||
x: ((1440 - 384) / 1440) * innerWidth,
|
||||
y: (234 / 800) * innerHeight,
|
||||
});
|
||||
setPopup(<ParticipantsPopup />);
|
||||
}}
|
||||
onClick={handleParticipantsOpen}
|
||||
>
|
||||
<div className="size-[1.111vw] text-white">
|
||||
<UsersFilledIcon />
|
||||
@@ -178,11 +178,7 @@ function NewSessionPage() {
|
||||
</FloatingActionButton>
|
||||
<FloatingActionButton
|
||||
className="max-2xl:hidden"
|
||||
onClick={() =>
|
||||
setPopup(
|
||||
<SharePopup link={`${window.location.origin}/sessions/${id}`} />
|
||||
)
|
||||
}
|
||||
onClick={handleShareOpen}
|
||||
>
|
||||
<div className="size-[1.111vw] text-white">
|
||||
<ShareFilledIcon />
|
||||
|
||||
@@ -4,15 +4,15 @@ import type { ReactNode } from "react";
|
||||
interface PopupState {
|
||||
popup: ReactNode | null;
|
||||
setPopup: (popup: ReactNode | null) => void;
|
||||
position: { x: number; y: number };
|
||||
setPosition: (position: { x: number; y: number }) => void;
|
||||
// position: { x: number; y: number };
|
||||
// setPosition: (position: { x: number; y: number }) => void;
|
||||
}
|
||||
|
||||
const usePopupStore = create<PopupState>()((set) => ({
|
||||
popup: null,
|
||||
position: { x: 0, y: 0 },
|
||||
setPosition: (position) => set({ position }),
|
||||
setPopup: (popup) => set({ popup }),
|
||||
// position: { x: 0, y: 0 },
|
||||
// setPosition: (position) => set({ position }),
|
||||
}));
|
||||
|
||||
export default usePopupStore;
|
||||
|
||||
Reference in New Issue
Block a user