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 da10bf951..d7399b53f 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; 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/Navigation.tsx b/client/src/components/Navigation.tsx index f610adcb4..b273842dd 100644 --- a/client/src/components/Navigation.tsx +++ b/client/src/components/Navigation.tsx @@ -2,12 +2,61 @@ import { Link, Outlet } from "react-router"; import Feedback from "./Feedback"; import WarningBanner from "./WarningBanner"; import config from "../ts/config"; +import Dropdown from './components/Dropdown/Dropdown'; +import DropdownItem from './components/DropdownItem/DropdownItem'; -export default function Navigation({ GIT_REV }: { GIT_REV: string }) { + +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 && } */}
{config.isStaging && } diff --git a/client/src/components/Schedule.tsx b/client/src/components/Schedule.tsx index 43621a3e6..6747d8031 100644 --- a/client/src/components/Schedule.tsx +++ b/client/src/components/Schedule.tsx @@ -92,7 +92,7 @@ export default function Schedule({ selectedRoute, setSelectedRoute }: SchedulePr }); if (currentTimeRow) { - currentTimeRow.scrollIntoView({ behavior: "auto" }); + // currentTimeRow.scrollIntoView({ behavior: "auto" }); } }, [selectedRoute, selectedDay, schedule]); @@ -101,9 +101,10 @@ export default function Schedule({ selectedRoute, setSelectedRoute }: SchedulePr return (
-

Schedule

-
- +

Today's schedule

+ + {/*
+ -
-
- +
*/} + {/*
+ +
*/} +
+ +
diff --git a/client/src/pages/LiveLocation.tsx b/client/src/pages/LiveLocation.tsx index c1444d741..53c7d3690 100644 --- a/client/src/pages/LiveLocation.tsx +++ b/client/src/pages/LiveLocation.tsx @@ -11,6 +11,7 @@ import type { VehicleInformationMap } from '../ts/types/vehicleLocation'; import type { ShuttleRouteData } from '../ts/types/route'; import aggregatedSchedule from '../data/aggregated_schedule.json'; + export default function LiveLocation() { const [location, setLocation] = useState(null); @@ -63,7 +64,7 @@ export default function LiveLocation() { selectedRoute={selectedRoute} setSelectedRoute={setSelectedRoute} /> -
+