Add FloatingMenu component and integrate DocsButton; update styles for ThemeButton and FloatingMenu
This commit is contained in:
43
frontend/package-lock.json
generated
43
frontend/package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"chart.js": "^4.4.8",
|
"chart.js": "^4.4.8",
|
||||||
|
"framer-motion": "^12.14.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"leaflet.heat": "^0.2.0",
|
"leaflet.heat": "^0.2.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -3706,6 +3707,33 @@
|
|||||||
"node": ">=0.4.x"
|
"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": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -4802,6 +4830,21 @@
|
|||||||
"node": "*"
|
"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": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"chart.js": "^4.4.8",
|
"chart.js": "^4.4.8",
|
||||||
|
"framer-motion": "^12.14.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"leaflet.heat": "^0.2.0",
|
"leaflet.heat": "^0.2.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ import 'bootstrap/dist/js/bootstrap.bundle.min.js'
|
|||||||
|
|
||||||
import Dashboard from '@/pages/Dashboard.jsx'
|
import Dashboard from '@/pages/Dashboard.jsx'
|
||||||
import Groups from '@/pages/Groups.jsx'
|
import Groups from '@/pages/Groups.jsx'
|
||||||
import ThemeButton from '@/components/layout/ThemeButton.jsx'
|
|
||||||
import Header from '@/components/layout/Header.jsx'
|
import Header from '@/components/layout/Header.jsx'
|
||||||
import GroupView from '@/pages/GroupView.jsx'
|
import GroupView from '@/pages/GroupView.jsx'
|
||||||
|
|
||||||
import { Routes, Route } from 'react-router-dom'
|
import { Routes, Route } from 'react-router-dom'
|
||||||
import ContentWrapper from './components/layout/ContentWrapper'
|
import ContentWrapper from '@/components/layout/ContentWrapper'
|
||||||
import Docs from './pages/Docs'
|
import Docs from '@/pages/Docs'
|
||||||
|
import FloatingMenu from '@/components/layout/FloatingMenu'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ThemeButton />
|
<FloatingMenu />
|
||||||
<Header subtitle='Midiendo la calidad del aire y las calles en Sevilla 🌿🚛' />
|
<Header subtitle='Midiendo la calidad del aire y las calles en Sevilla 🌿🚛' />
|
||||||
<ContentWrapper>
|
<ContentWrapper>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|||||||
12
frontend/src/components/layout/DocsButton.jsx
Normal file
12
frontend/src/components/layout/DocsButton.jsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import "@/css/DocsButton.css";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const DocsButton = () => {
|
||||||
|
return (
|
||||||
|
<Link to="/docs">
|
||||||
|
<button className="docs-button">📃</button>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DocsButton;
|
||||||
61
frontend/src/components/layout/FloatingMenu.jsx
Normal file
61
frontend/src/components/layout/FloatingMenu.jsx
Normal file
@@ -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: <DocsButton />, key: "docs", onClick: () => setOpen(false) },
|
||||||
|
{ component: <ThemeButton />, key: "theme", onClick: () => setOpen(false) }
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="floating-menu">
|
||||||
|
<AnimatePresence>
|
||||||
|
{open && (
|
||||||
|
<motion.div
|
||||||
|
className="menu-buttons"
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
exit="hidden"
|
||||||
|
>
|
||||||
|
{buttons.map((btn, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={btn.key}
|
||||||
|
custom={i}
|
||||||
|
variants={buttonVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
exit="exit"
|
||||||
|
onClick={btn.onClick}
|
||||||
|
>
|
||||||
|
{btn.component}
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<button className="menu-toggle" onClick={() => setOpen(prev => !prev)}>
|
||||||
|
<FontAwesomeIcon icon={faEllipsisVertical} className="fa-lg" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FloatingMenu;
|
||||||
19
frontend/src/css/DocsButton.css
Normal file
19
frontend/src/css/DocsButton.css
Normal file
@@ -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);
|
||||||
|
}
|
||||||
55
frontend/src/css/FloatingMenu.css
Normal file
55
frontend/src/css/FloatingMenu.css
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
.theme-toggle {
|
.theme-toggle {
|
||||||
position: fixed;
|
|
||||||
bottom: 20px;
|
|
||||||
right: 20px;
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@@ -19,4 +16,4 @@
|
|||||||
|
|
||||||
.theme-toggle:hover {
|
.theme-toggle:hover {
|
||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user