diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 7a4ce58..f29f529 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -16,6 +16,7 @@
"axios": "^1.9.0",
"bootstrap": "^5.3.3",
"chart.js": "^4.4.8",
+ "framer-motion": "^12.14.0",
"leaflet": "^1.9.4",
"leaflet.heat": "^0.2.0",
"react": "^19.0.0",
@@ -3706,6 +3707,33 @@
"node": ">=0.4.x"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.14.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.14.0.tgz",
+ "integrity": "sha512-P/CLiA+YLc0GL1nB2bgtRBFAbRh0aawU5KPLqnXi6QEZQE2BEwqgIH8qUIT7cMN+4a4nj0iZ2uWTKOrzcYsyGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.14.0",
+ "motion-utils": "^12.12.1",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -4802,6 +4830,21 @@
"node": "*"
}
},
+ "node_modules/motion-dom": {
+ "version": "12.14.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.14.0.tgz",
+ "integrity": "sha512-t5v1QNXrOsLmIzTdmB9OlZ1cA8zkHlpPhn85123/B+8Xe6tiENWKELAZm0yvAoFjcbFmq4u80uUXortZTTvDbg==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.12.1"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.12.1",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz",
+ "integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 061b6c6..9aa9a03 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -18,6 +18,7 @@
"axios": "^1.9.0",
"bootstrap": "^5.3.3",
"chart.js": "^4.4.8",
+ "framer-motion": "^12.14.0",
"leaflet": "^1.9.4",
"leaflet.heat": "^0.2.0",
"react": "^19.0.0",
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 4dca96a..8b0f4f7 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -5,19 +5,19 @@ import 'bootstrap/dist/js/bootstrap.bundle.min.js'
import Dashboard from '@/pages/Dashboard.jsx'
import Groups from '@/pages/Groups.jsx'
-import ThemeButton from '@/components/layout/ThemeButton.jsx'
import Header from '@/components/layout/Header.jsx'
import GroupView from '@/pages/GroupView.jsx'
import { Routes, Route } from 'react-router-dom'
-import ContentWrapper from './components/layout/ContentWrapper'
-import Docs from './pages/Docs'
+import ContentWrapper from '@/components/layout/ContentWrapper'
+import Docs from '@/pages/Docs'
+import FloatingMenu from '@/components/layout/FloatingMenu'
const App = () => {
return (
<>
-
+
diff --git a/frontend/src/components/layout/DocsButton.jsx b/frontend/src/components/layout/DocsButton.jsx
new file mode 100644
index 0000000..d342789
--- /dev/null
+++ b/frontend/src/components/layout/DocsButton.jsx
@@ -0,0 +1,12 @@
+import "@/css/DocsButton.css";
+import { Link } from "react-router-dom";
+
+const DocsButton = () => {
+ return (
+
+
+
+ );
+}
+
+export default DocsButton;
\ No newline at end of file
diff --git a/frontend/src/components/layout/FloatingMenu.jsx b/frontend/src/components/layout/FloatingMenu.jsx
new file mode 100644
index 0000000..6712c34
--- /dev/null
+++ b/frontend/src/components/layout/FloatingMenu.jsx
@@ -0,0 +1,61 @@
+import { useState } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import DocsButton from "./DocsButton";
+import ThemeButton from "./ThemeButton";
+import "@/css/FloatingMenu.css";
+import { faEllipsisVertical } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+const FloatingMenu = () => {
+ const [open, setOpen] = useState(false);
+
+ const buttonVariants = {
+ hidden: { opacity: 0, y: 10 },
+ visible: (i) => ({
+ opacity: 1,
+ y: 0,
+ transition: { delay: i * 0.05, type: "spring", stiffness: 300 }
+ }),
+ exit: { opacity: 0, y: 10, transition: { duration: 0.1 } }
+ };
+
+ const buttons = [
+ { component: , key: "docs", onClick: () => setOpen(false) },
+ { component: , key: "theme", onClick: () => setOpen(false) }
+ ];
+
+ return (
+
+
+ {open && (
+
+ {buttons.map((btn, i) => (
+
+ {btn.component}
+
+ ))}
+
+ )}
+
+
+
+
+ );
+};
+
+export default FloatingMenu;
\ No newline at end of file
diff --git a/frontend/src/css/DocsButton.css b/frontend/src/css/DocsButton.css
new file mode 100644
index 0000000..b7a32a2
--- /dev/null
+++ b/frontend/src/css/DocsButton.css
@@ -0,0 +1,19 @@
+.docs-button {
+ z-index: 1000;
+ border: none;
+ border-radius: 50%;
+ width: 50px;
+ height: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--primary-color);
+ color: white;
+ cursor: pointer;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ transition: background-color 0.3s, transform 0.3s;
+}
+
+.docs-button:hover {
+ background-color: var(--secondary-color);
+}
diff --git a/frontend/src/css/FloatingMenu.css b/frontend/src/css/FloatingMenu.css
new file mode 100644
index 0000000..c7570b8
--- /dev/null
+++ b/frontend/src/css/FloatingMenu.css
@@ -0,0 +1,55 @@
+.floating-menu {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 10px;
+}
+
+.menu-buttons {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 10px;
+}
+
+.menu-toggle {
+ border: none;
+ border-radius: 50%;
+ width: 50px;
+ height: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--primary-color);
+ color: white;
+ cursor: pointer;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ transition: background-color 0.3s, transform 0.3s;
+}
+
+.menu-toggle:hover {
+ background-color: var(--secondary-color);
+}
+
+.menu-buttons button {
+ border: none;
+ border-radius: 50%;
+ width: 50px;
+ height: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--primary-color);
+ color: white;
+ cursor: pointer;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ transition: background-color 0.3s, transform 0.3s;
+}
+
+.menu-buttons button:hover {
+ background-color: var(--secondary-color);
+}
diff --git a/frontend/src/css/ThemeButton.css b/frontend/src/css/ThemeButton.css
index e0f0f0d..805a93f 100644
--- a/frontend/src/css/ThemeButton.css
+++ b/frontend/src/css/ThemeButton.css
@@ -1,7 +1,4 @@
.theme-toggle {
- position: fixed;
- bottom: 20px;
- right: 20px;
z-index: 1000;
border: none;
border-radius: 50%;
@@ -19,4 +16,4 @@
.theme-toggle:hover {
background-color: var(--secondary-color);
-}
\ No newline at end of file
+}