diff --git a/client/bun.lock b/client/bun.lock
index 964685f..9ad6317 100644
--- a/client/bun.lock
+++ b/client/bun.lock
@@ -9,6 +9,7 @@
"@uidotdev/usehooks": "^2.4.1",
"clsx": "^2.1.1",
"ky": "^1.11.0",
+ "motion": "^12.23.24",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-qr-code": "^2.0.18",
@@ -377,6 +378,8 @@
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
+ "framer-motion": ["framer-motion@12.23.24", "", { "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w=="],
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -453,6 +456,12 @@
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+ "motion": ["motion@12.23.24", "", { "dependencies": { "framer-motion": "^12.23.24", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw=="],
+
+ "motion-dom": ["motion-dom@12.23.23", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA=="],
+
+ "motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
@@ -591,6 +600,8 @@
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
diff --git a/client/package.json b/client/package.json
index 77f0268..b2409ef 100644
--- a/client/package.json
+++ b/client/package.json
@@ -15,6 +15,7 @@
"@uidotdev/usehooks": "^2.4.1",
"clsx": "^2.1.1",
"ky": "^1.11.0",
+ "motion": "^12.23.24",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-qr-code": "^2.0.18",
diff --git a/client/src/components/ModalContainer.tsx b/client/src/components/ModalContainer.tsx
index 17c39e8..1d52d3e 100644
--- a/client/src/components/ModalContainer.tsx
+++ b/client/src/components/ModalContainer.tsx
@@ -2,6 +2,7 @@
import { useEffect, useRef } from "react";
import useModalStore from "../store/modalStore";
import clsx from "clsx";
+import { AnimatePresence, motion } from "motion/react";
function ModalContainer() {
const { modal, setModal, position } = useModalStore();
@@ -35,28 +36,35 @@ function ModalContainer() {
}, []);
return (
- modal && (
-
-
+ {modal && (
+
-
-
-
setModal(null)}
- />
- {modal}
+
+
+
+
setModal(null)}
+ />
+ {modal}
+
-
-
- )
+
+ )}
+
);
}
diff --git a/client/src/components/PopupContainer.tsx b/client/src/components/PopupContainer.tsx
index 0bceddb..dd42e28 100644
--- a/client/src/components/PopupContainer.tsx
+++ b/client/src/components/PopupContainer.tsx
@@ -1,12 +1,23 @@
+import { AnimatePresence, motion } from "motion/react";
import usePopupStore from "../store/popupStore";
function PopupContainer() {
const { popup, position } = usePopupStore();
return (
-
- {popup}
-
+
+ {popup && (
+
+ {popup}
+
+ )}
+
);
}
diff --git a/client/src/components/PopupWrapper.tsx b/client/src/components/PopupWrapper.tsx
index b722750..a7641d3 100644
--- a/client/src/components/PopupWrapper.tsx
+++ b/client/src/components/PopupWrapper.tsx
@@ -29,30 +29,40 @@ function PopupWrapper({
useEffect(() => {
addEventListener("mouseup", () => setMouseDown(false));
- return () => removeEventListener("mouseup", () => setMouseDown(false));
+ addEventListener("touchend", () => setMouseDown(false));
+ return () => {
+ removeEventListener("mouseup", () => setMouseDown(false));
+ removeEventListener("touchend", () => setMouseDown(false));
+ };
}, []);
- function handleMouseMove(e: MouseEvent) {
+ 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 + e.clientX - mouseDownPosition.x),
+ Math.max(0, position.x + x - mouseDownPosition.x),
window.innerWidth - wrapperRef.current.clientWidth
),
y: Math.min(
- Math.max(0, position.y + e.clientY - mouseDownPosition.y),
+ Math.max(0, position.y + y - mouseDownPosition.y),
window.innerHeight - wrapperRef.current.clientHeight
),
});
- setMouseDownPosition({ x: e.clientX, y: e.clientY });
+ setMouseDownPosition({ x, y });
}
}
useEffect(() => {
- addEventListener("mousemove", handleMouseMove);
- return () => removeEventListener("mousemove", handleMouseMove);
- }, [handleMouseMove]);
+ addEventListener("mousemove", handleMove);
+ addEventListener("touchmove", handleMove);
+ return () => {
+ removeEventListener("mousemove", handleMove);
+ removeEventListener("touchmove", handleMove);
+ };
+ }, [handleMove]);
useEffect(() => {
if (headerRef.current) {
@@ -60,6 +70,13 @@ function PopupWrapper({
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) {
@@ -67,6 +84,13 @@ function PopupWrapper({
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,
+ });
+ });
}
};
}, []);
@@ -75,7 +99,7 @@ function PopupWrapper({
diff --git a/client/src/components/modals/SettingsModal.tsx b/client/src/components/modals/SettingsModal.tsx
index 367d625..457af3b 100644
--- a/client/src/components/modals/SettingsModal.tsx
+++ b/client/src/components/modals/SettingsModal.tsx
@@ -168,8 +168,7 @@ function SettingsModal() {
if (device.kind === "videoinput") {
const deviceInfo: MediaDevice = {
deviceId: device.deviceId,
- label:
- device.label || `${device.kind} (${device.deviceId.slice(0, 8)})`,
+ label: device.label || `${device.kind} (${device.deviceId})`,
};
videoInputs.push(deviceInfo);
}
@@ -299,8 +298,8 @@ function SettingsModal() {
setModal(null)}
/>
);
};
@@ -312,13 +311,16 @@ function SettingsModal() {
selectedSpeaker={selectedSpeaker}
speakers={speakers}
onSelectSpeaker={setSelectedSpeaker}
- onClose={() => setModal(null)}
+ speakerVolume={speakerVolume}
/>
);
};
return (
-
+