From 6b606d3e6ac4deace700baab47f1326e40552b97 Mon Sep 17 00:00:00 2001 From: shaoc23 Date: Tue, 9 Dec 2025 17:38:38 -0500 Subject: [PATCH] commit UI Mobile shubble UI --- client/package-lock.json | 11 - client/src/App.css | 92 +- client/src/App.jsx | 53 +- client/src/components/Button.jsx | 0 client/src/components/DropDown.css | 0 client/src/components/DropDown.jsx | 0 client/src/components/Dropdown/Dropdown.css | 5 + client/src/components/Dropdown/Dropdown.jsx | 34 + .../DropdownButton/DropdownButton.css | 40 + .../DropdownButton/DropdownButton.jsx | 20 + .../DropdownContent/DropdownContent.css | 39 + .../DropdownContent/DropdownContent.jsx | 12 + .../components/DropdownItem/DropdownItem.css | 11 + .../components/DropdownItem/DropdownItem.jsx | 13 + client/src/components/LoopToggle.jsx | 26 + client/src/components/Navbar.css | 0 client/src/components/Navbar.jsx | 0 client/src/components/Schedule.jsx | 15 +- client/src/pages/LiveLocation.jsx | 3 +- client/src/styles/About.css | 2 +- client/src/styles/LiveLocation.css | 52 ++ client/src/styles/LoopToggle.css | 52 ++ client/src/styles/MapKitMap.css | 5 +- client/src/styles/Schedule.css | 22 + package-lock.json | 843 ++++++++++-------- 25 files changed, 940 insertions(+), 410 deletions(-) delete mode 100644 client/package-lock.json create mode 100644 client/src/components/Button.jsx create mode 100644 client/src/components/DropDown.css create mode 100644 client/src/components/DropDown.jsx create mode 100644 client/src/components/Dropdown/Dropdown.css create mode 100644 client/src/components/Dropdown/Dropdown.jsx create mode 100644 client/src/components/DropdownButton/DropdownButton.css create mode 100644 client/src/components/DropdownButton/DropdownButton.jsx create mode 100644 client/src/components/DropdownContent/DropdownContent.css create mode 100644 client/src/components/DropdownContent/DropdownContent.jsx create mode 100644 client/src/components/DropdownItem/DropdownItem.css create mode 100644 client/src/components/DropdownItem/DropdownItem.jsx create mode 100644 client/src/components/LoopToggle.jsx create mode 100644 client/src/components/Navbar.css create mode 100644 client/src/components/Navbar.jsx create mode 100644 client/src/styles/LoopToggle.css diff --git a/client/package-lock.json b/client/package-lock.json deleted file mode 100644 index 7131c12b8..000000000 --- a/client/package-lock.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "client", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "client" - } - } -} diff --git a/client/src/App.css b/client/src/App.css index df999b0d4..d49a40d0d 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -48,7 +48,7 @@ li { } header { - position: sticky; + position: flex; top: 0; width: 100%; box-sizing: border-box; @@ -61,12 +61,64 @@ header { z-index: 1000; display: flex; flex-direction: row; - justify-content: space-between; gap: 10px; } +header .title-container { + display: flex; + align-items: center; + gap: 10px; + margin-left: auto; +} + +header .title-logo { + height: 40px; + width: auto; +} + +/* Dropdown container - hidden on desktop, shown on mobile */ +.dropmenu-container { + display: none; +} + @media (max-width: 1024px) { /* mobile mode*/ + @import url('https://fonts.googleapis.com/css2?family=KoHo:wght@400;500;600;700;800&display=swap'); + + :root { + --header-color: #578FCA; + } + + header { + position: fixed; + border-bottom-left-radius: 50px; + max-height: 60px; + justify-content: flex-start; + align-items: center; + z-index:1000; + } + + header .title { + font-family: 'KoHo', sans-serif; + font-weight: 700; + /* font-size: 0.8em; */ + } + + + header .title-containers { + margin-left: auto; + } + + .dropdown-item a { + color: white; + text-decoration: none; + font-weight: 500; + display: block; + width: 100%; + padding: 0.5rem 1rem; + transition: color 0.2s ease, background-color 0.2s ease; + } + .big { display: none; } @@ -76,7 +128,33 @@ header { } footer { - border-radius: 20px 20px 0 0; + display: none; + } + + .big footer { + display: none; + } + + + /* Show dropdown container on mobile */ + .dropmenu-container { + display: block; + position: absolute; /* Change from 'fixed' to 'absolute' */ + top: 10px; + left: 10px; + z-index: 999; + } + + + .dropdown-content { + position: fixed; + top: 60px; + left: 10px; + z-index: 500; + } + + .feedback-container{ + display:none; } } @@ -86,6 +164,10 @@ header { @media (min-width: 1024px) { /* desktop mode*/ + .dropmenu-container { + display: none; + } + .schedule { display: block; } @@ -93,6 +175,7 @@ header { footer { border-radius: 0; } + } li a { @@ -153,7 +236,6 @@ footer { margin: 0; } - .banner a { color: #ffffff; text-decoration: underline; @@ -165,4 +247,4 @@ footer { .banner * { margin: 5px; -} +} \ No newline at end of file diff --git a/client/src/App.jsx b/client/src/App.jsx index 6fdca57d0..ddcbde79d 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -14,16 +14,61 @@ import MapKitMap from './components/MapKitMap'; import routeData from './data/routes.json'; import { useState } from "react"; import WarningBanner from './components/WarningBanner'; +import Dropdown from './components/Dropdown/Dropdown'; +import DropdownItem from './components/DropdownItem/DropdownItem'; + function App() { const [selectedRoute, setSelectedRoute] = useState(null); const [selectedStop, setSelectedStop] = useState('all'); const staging = import.meta.env.VITE_DEPLOY_MODE !== 'production'; const GIT_REV = import.meta.env.GIT_REV || 'unknown'; + const [isMenuOpen, setIsMenuOpen] = useState(false); + const items = [ + { label: 'About', path: '/about' }, + { label: 'Live Location', path: '/' }, + { label: 'Full Schedule', path: '/schedule' }, +]; + + + + // const toggleMenu = () => { + // setIsMenuOpen(!isMenuOpen); + // }; + return (
- SHUBBLE + +
+ + + + } + content={ + <> + {items.map((item) => ( + + {item.label} + + ))} + + } + /> +
+ +
+ SHUBBLE +
+ +
- {staging && } + + + + + {/* {staging && } */}
} /> diff --git a/client/src/components/Button.jsx b/client/src/components/Button.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/components/DropDown.css b/client/src/components/DropDown.css new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/components/DropDown.jsx b/client/src/components/DropDown.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/components/Dropdown/Dropdown.css b/client/src/components/Dropdown/Dropdown.css new file mode 100644 index 000000000..d34a4a4e8 --- /dev/null +++ b/client/src/components/Dropdown/Dropdown.css @@ -0,0 +1,5 @@ +.dropdown { + position: relative; + display: inline-block; + z-index: 1001; /* Add this */ +} \ No newline at end of file diff --git a/client/src/components/Dropdown/Dropdown.jsx b/client/src/components/Dropdown/Dropdown.jsx new file mode 100644 index 000000000..078937b86 --- /dev/null +++ b/client/src/components/Dropdown/Dropdown.jsx @@ -0,0 +1,34 @@ +import React, { useState } from "react"; +import DropdownButton from "../DropdownButton/DropdownButton"; +import DropdownContent from "../DropdownContent/DropdownContent"; +import "./Dropdown.css"; + +const Dropdown = ({ buttonText, content }) => { + const [open, setOpen] = useState(false); + const toggleDropdown = () => setOpen(!open); + const closeDropdown = () => setOpen(false); + + return ( +
+ + {buttonText} + + + + {/* ✅ Wrap the content with a click handler */} +
{ + // only close when clicking a link, not when toggling dropdown + if (e.target.tagName === "A") { + closeDropdown(); + } + }} + > + {content} +
+
+
+ ); +}; + +export default Dropdown; diff --git a/client/src/components/DropdownButton/DropdownButton.css b/client/src/components/DropdownButton/DropdownButton.css new file mode 100644 index 000000000..af0a57a93 --- /dev/null +++ b/client/src/components/DropdownButton/DropdownButton.css @@ -0,0 +1,40 @@ +/* .dropdown-btn{ + display:flex; + align-items:center; + width:fit-content; + padding:1rem; + background-color:white; + border-radius: 0.5rem; + cursor:pointer; +} + +.toggle-icon { + display:flex; + align-items:center; + justify-content:center; + margin-left:1rem; +} + +.button-open { + border: #578FCA 2px solid; + +} */ + +.dropdownbtn { + display: flex; + align-items: center; + width: fit-content; + margin-top: -20px; + margin-left: 20px; + padding: 0.15rem; + background-color: #578FCA; + border: 2px solid transparent; /* Always has same thickness */ + border-radius: 0.5rem; + cursor: pointer; + transition: border-color 0.2s ease; + box-sizing: border-box; /* Ensures borders don’t change size */ +} + +.button-open { + border-color: #578FCA; /* Only the color changes */ +} diff --git a/client/src/components/DropdownButton/DropdownButton.jsx b/client/src/components/DropdownButton/DropdownButton.jsx new file mode 100644 index 000000000..5595a151c --- /dev/null +++ b/client/src/components/DropdownButton/DropdownButton.jsx @@ -0,0 +1,20 @@ +import "./DropdownButton.css" +// import {FaChevronDown} from "react-icons/fa" + +const DropdownButton = ({children, open, toggle}) => { + return ( +
+ + {children} + + {/* */} + +
+ ) + +}; + +export default DropdownButton; \ No newline at end of file diff --git a/client/src/components/DropdownContent/DropdownContent.css b/client/src/components/DropdownContent/DropdownContent.css new file mode 100644 index 000000000..c9e6f911a --- /dev/null +++ b/client/src/components/DropdownContent/DropdownContent.css @@ -0,0 +1,39 @@ +.dropdown-content { +position: fixed; + top: 55px; + left: 10px; + z-index: 500; + width: 220px; + display: flex; + flex-direction: column; + align-items: center; + padding-top: 1rem; + margin-top: 0.5rem; + background-color: #124170; + border-radius: 0.5rem; + border: 1px solid #578FCA; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + max-height: 40vh; + overflow-y: scroll; + scrollbar-width: none; + -ms-overflow-style: none; + + /* Hidden by default */ + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s; + pointer-events: none; +} + +.dropdown-content::-webkit-scrollbar { + width: 0; + height: 0; +} + +.content-open { + opacity: 1; + visibility: visible; + transform: translateY(0); + pointer-events: auto; +} \ No newline at end of file diff --git a/client/src/components/DropdownContent/DropdownContent.jsx b/client/src/components/DropdownContent/DropdownContent.jsx new file mode 100644 index 000000000..36a7b3e19 --- /dev/null +++ b/client/src/components/DropdownContent/DropdownContent.jsx @@ -0,0 +1,12 @@ +import React from "react"; +import "./DropdownContent.css"; + +const DropdownContent = ({ children, open }) => { + return ( +
+ {children} +
+ ); +}; + +export default DropdownContent; diff --git a/client/src/components/DropdownItem/DropdownItem.css b/client/src/components/DropdownItem/DropdownItem.css new file mode 100644 index 000000000..a8a1440fb --- /dev/null +++ b/client/src/components/DropdownItem/DropdownItem.css @@ -0,0 +1,11 @@ +.dropdown-item { + /* padding: 0.5rem; */ + /* margin:0.1rem; */ + padding-top:28px; + width:100%; + border-radius:0.5rem; + cursor:pointer; + +} +/* .dropdown-item:hover { + background-color: rgba(87, 143, 202, 0.3); /* Semi-transparent blue */ diff --git a/client/src/components/DropdownItem/DropdownItem.jsx b/client/src/components/DropdownItem/DropdownItem.jsx new file mode 100644 index 000000000..b9f3eb7b0 --- /dev/null +++ b/client/src/components/DropdownItem/DropdownItem.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import "./DropdownItem.css" + +const DropdownItem = ({ children, onClick }) => { + return ( +
+ {children} +
+ ); +}; + +export default DropdownItem; diff --git a/client/src/components/LoopToggle.jsx b/client/src/components/LoopToggle.jsx new file mode 100644 index 000000000..95bec5513 --- /dev/null +++ b/client/src/components/LoopToggle.jsx @@ -0,0 +1,26 @@ +import { useState, useEffect, useLayoutEffect } from 'react'; +import '../styles/LoopToggle.css'; +import scheduleData from '../data/schedule.json'; +import routeData from '../data/routes.json'; +import { aggregatedSchedule } from '../data/parseSchedule'; + import "../styles/LoopToggle.css"; +export default function LoopToggle() { + + const [active, setActive] = useState("north"); + return( +
+ + +
+ ); +} \ No newline at end of file diff --git a/client/src/components/Navbar.css b/client/src/components/Navbar.css new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/components/Schedule.jsx b/client/src/components/Schedule.jsx index 30b29951b..c3329b3ca 100644 --- a/client/src/components/Schedule.jsx +++ b/client/src/components/Schedule.jsx @@ -3,6 +3,8 @@ import '../styles/Schedule.css'; import scheduleData from '../data/schedule.json'; import routeData from '../data/routes.json'; import { aggregatedSchedule } from '../data/parseSchedule'; +import LoopToggle from './LoopToggle'; + export default function Schedule({ selectedRoute, setSelectedRoute, selectedStop, setSelectedStop }) { // Validate props once at the top @@ -87,7 +89,7 @@ export default function Schedule({ selectedRoute, setSelectedRoute, selectedStop }); if (currentTimeRow) { - currentTimeRow.scrollIntoView({ behavior: "auto" }); + // currentTimeRow.scrollIntoView({ behavior: "auto" }); } }, [selectedRoute, selectedDay, selectedStop, schedule]); @@ -96,8 +98,9 @@ export default function Schedule({ selectedRoute, setSelectedRoute, selectedStop return (
-

Schedule

-
+

Today's schedule

+ + {/*
-
-
+
*/} + {/*
-
+
*/}