+
+
+
+
setModal(null)}
+ />
+ {modal}
+
+
+
+
+ )
+ );
}
export default ModalContainer;
diff --git a/client/src/components/ModalHeader.tsx b/client/src/components/ModalHeader.tsx
new file mode 100644
index 0000000..6047dee
--- /dev/null
+++ b/client/src/components/ModalHeader.tsx
@@ -0,0 +1,28 @@
+import Button from "./ui/Button";
+import useModalStore from "../store/modalStore";
+import XMarkIcon from "./icons/XMarkIcon";
+
+interface ModalHeaderProps {
+ title?: string;
+ leftButton?: React.ReactNode;
+}
+
+function ModalHeader({ title, leftButton }: ModalHeaderProps) {
+ const { setModal } = useModalStore();
+
+ return (
+
+
{leftButton}
+ {title && (
+
{title}
+ )}
+
+
+ );
+}
+
+export default ModalHeader;
diff --git a/client/src/components/ModalWrapper.tsx b/client/src/components/ModalWrapper.tsx
new file mode 100644
index 0000000..7e625ea
--- /dev/null
+++ b/client/src/components/ModalWrapper.tsx
@@ -0,0 +1,25 @@
+import ModalHeader from "./ModalHeader";
+import clsx from "clsx";
+
+interface ModalWrapperProps {
+ children: React.ReactNode;
+ title?: string;
+ leftButton?: React.ReactNode;
+ className?: string;
+}
+
+function ModalWrapper({
+ children,
+ title,
+ leftButton,
+ className,
+}: ModalWrapperProps) {
+ return (
+
+ );
+}
+
+export default ModalWrapper;
diff --git a/client/src/components/PopupContainer.tsx b/client/src/components/PopupContainer.tsx
new file mode 100644
index 0000000..0bceddb
--- /dev/null
+++ b/client/src/components/PopupContainer.tsx
@@ -0,0 +1,13 @@
+import usePopupStore from "../store/popupStore";
+
+function PopupContainer() {
+ const { popup, position } = usePopupStore();
+
+ return (
+
+ {popup}
+
+ );
+}
+
+export default PopupContainer;
diff --git a/client/src/components/PopupHeader.tsx b/client/src/components/PopupHeader.tsx
new file mode 100644
index 0000000..b2ee21a
--- /dev/null
+++ b/client/src/components/PopupHeader.tsx
@@ -0,0 +1,33 @@
+import usePopupStore from "../store/popupStore";
+import XMarkIcon from "./icons/XMarkIcon";
+import Button from "./ui/Button";
+
+interface PopupHeaderProps {
+ title?: string;
+ leftButton?: React.ReactNode;
+ headerRef: React.RefObject
;
+}
+
+function PopupHeader({ title, leftButton, headerRef }: PopupHeaderProps) {
+ const { setPopup } = usePopupStore();
+
+ return (
+
+
{leftButton}
+ {title && (
+
{title}
+ )}
+
+
+
+ );
+}
+
+export default PopupHeader;
diff --git a/client/src/components/PopupWrapper.tsx b/client/src/components/PopupWrapper.tsx
new file mode 100644
index 0000000..e2e633a
--- /dev/null
+++ b/client/src/components/PopupWrapper.tsx
@@ -0,0 +1,92 @@
+/* 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";
+
+interface PopupWrapperProps {
+ children: React.ReactNode;
+ className?: string;
+ title?: string;
+ leftButton?: React.ReactNode;
+ draggable?: boolean;
+}
+
+function PopupWrapper({
+ children,
+ className,
+ title,
+ leftButton,
+ draggable,
+}: PopupWrapperProps) {
+ const { position, setPosition } = usePopupStore();
+ const [mouseDown, setMouseDown] = useState(false);
+ const [mouseDownPosition, setMouseDownPosition] = useState(position);
+
+ const wrapperRef = useRef(null);
+ const headerRef = useRef(null);
+
+ useEffect(() => {
+ addEventListener("mouseup", () => setMouseDown(false));
+ return () => removeEventListener("mouseup", () => setMouseDown(false));
+ }, []);
+
+ function handleMouseMove(e: MouseEvent) {
+ if (draggable && mouseDown && wrapperRef.current) {
+ e.preventDefault();
+ setPosition({
+ x: Math.min(
+ Math.max(0, position.x + e.clientX - mouseDownPosition.x),
+ window.innerWidth - wrapperRef.current.clientWidth
+ ),
+ y: Math.min(
+ Math.max(0, position.y + e.clientY - mouseDownPosition.y),
+ window.innerHeight - wrapperRef.current.clientHeight
+ ),
+ });
+ setMouseDownPosition({ x: e.clientX, y: e.clientY });
+ }
+ }
+
+ useEffect(() => {
+ addEventListener("mousemove", handleMouseMove);
+ return () => removeEventListener("mousemove", handleMouseMove);
+ }, [handleMouseMove]);
+
+ useEffect(() => {
+ if (headerRef.current) {
+ headerRef.current.addEventListener("mousedown", (e) => {
+ setMouseDown(true);
+ setMouseDownPosition({ x: e.clientX, y: e.clientY });
+ });
+ }
+ return () => {
+ if (headerRef.current) {
+ headerRef.current.removeEventListener("mousedown", (e) => {
+ setMouseDown(true);
+ setMouseDownPosition({ x: e.clientX, y: e.clientY });
+ });
+ }
+ };
+ }, []);
+
+ return (
+
+ );
+}
+
+export default PopupWrapper;
diff --git a/client/src/components/modals/ShareModal.tsx b/client/src/components/modals/ShareModal.tsx
index fcd4b14..4473987 100644
--- a/client/src/components/modals/ShareModal.tsx
+++ b/client/src/components/modals/ShareModal.tsx
@@ -1,24 +1,21 @@
import Button from "../ui/Button";
import LinkShare from "../ui/LinkShare";
import ShareFilledIcon from "../icons/ShareFilledIcon";
+import ModalWrapper from "../ModalWrapper";
export default function ShareModal() {
return (
- <>
+
-
Скопировать ссылку
+
Скопировать ссылку
-