diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/frontend/app/components/ServiceAlerts.css | 5 | ||||
| -rw-r--r-- | src/frontend/app/components/ServiceAlerts.tsx | 2 | ||||
| -rw-r--r-- | src/frontend/app/components/StopGallery.css | 51 | ||||
| -rw-r--r-- | src/frontend/app/components/StopGallery.tsx | 42 | ||||
| -rw-r--r-- | src/frontend/app/components/StopItem.tsx | 15 | ||||
| -rw-r--r-- | src/frontend/app/components/ThemeColorManager.tsx | 20 | ||||
| -rw-r--r-- | src/frontend/app/components/layout/AppShell.tsx | 2 | ||||
| -rw-r--r-- | src/frontend/app/components/layout/Header.css | 1 | ||||
| -rw-r--r-- | src/frontend/app/contexts/SettingsContext.tsx | 2 | ||||
| -rw-r--r-- | src/frontend/app/root.css | 23 | ||||
| -rw-r--r-- | src/frontend/app/root.tsx | 14 | ||||
| -rw-r--r-- | src/frontend/app/routes/home.css | 86 | ||||
| -rw-r--r-- | src/frontend/app/routes/home.tsx | 35 |
13 files changed, 183 insertions, 115 deletions
diff --git a/src/frontend/app/components/ServiceAlerts.css b/src/frontend/app/components/ServiceAlerts.css index 7c271f9..c0f67f5 100644 --- a/src/frontend/app/components/ServiceAlerts.css +++ b/src/frontend/app/components/ServiceAlerts.css @@ -1,6 +1,9 @@ /* Service Alerts Container */ .service-alerts-container { - margin-bottom: 1.5rem; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.75rem; } .service-alert { diff --git a/src/frontend/app/components/ServiceAlerts.tsx b/src/frontend/app/components/ServiceAlerts.tsx index eba8a92..a6a1ee8 100644 --- a/src/frontend/app/components/ServiceAlerts.tsx +++ b/src/frontend/app/components/ServiceAlerts.tsx @@ -6,7 +6,7 @@ const ServiceAlerts: React.FC = () => { const { t } = useTranslation(); return ( - <div className="service-alerts-container"> + <div className="service-alerts-container stoplist-section"> <h2 className="page-subtitle">{t("stoplist.service_alerts")}</h2> <div className="service-alert info"> <div className="alert-icon">ℹ️</div> diff --git a/src/frontend/app/components/StopGallery.css b/src/frontend/app/components/StopGallery.css index bc9b955..070a01f 100644 --- a/src/frontend/app/components/StopGallery.css +++ b/src/frontend/app/components/StopGallery.css @@ -1,13 +1,16 @@ /* Gallery Container */ .gallery-container { - margin-bottom: 1.5rem; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.75rem; } .gallery-header { display: flex; align-items: center; justify-content: space-between; - margin-bottom: 0.75rem; + margin-bottom: 0.5rem; } .gallery-counter { @@ -24,6 +27,15 @@ font-weight: 600; } +/* Empty State */ +.gallery-empty-state { + text-align: center; +} + +.gallery-empty-state .message { + font-size: 0.85rem; +} + /* Scroll Container */ .gallery-scroll-container { overflow-x: auto; @@ -32,7 +44,6 @@ scroll-snap-type: x mandatory; scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* IE and Edge */ - padding: 0 1rem; } .gallery-scroll-container::-webkit-scrollbar { @@ -48,14 +59,15 @@ /* Gallery Item */ .gallery-item { - flex: 0 0 100%; + flex: 0 0 90%; + max-width: 320px; scroll-snap-align: start; scroll-snap-stop: always; } .gallery-item-link { display: block; - padding: 1rem; + padding: 0.75rem; background-color: var( --card-background-color, var(--message-background-color) @@ -64,7 +76,7 @@ border-radius: 12px; text-decoration: none; color: var(--text-color); - min-height: 120px; + min-height: 100px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } @@ -72,24 +84,24 @@ display: flex; align-items: center; gap: 0.5rem; - margin-bottom: 0.5rem; + margin-bottom: 0.25rem; } .gallery-item-header .favourite-icon { color: var(--star-color); - font-size: 1rem; + font-size: 0.9rem; } .gallery-item-code { - font-size: 0.85rem; + font-size: 0.8rem; color: var(--subtitle-color); font-weight: 500; } .gallery-item-name { - font-size: 1rem; + font-size: 0.95rem; font-weight: 600; - margin-bottom: 0.75rem; + margin-bottom: 0.5rem; line-height: 1.3; display: -webkit-box; /* Standard property for compatibility */ @@ -97,7 +109,7 @@ -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; - min-height: 2.6em; + min-height: 2.5em; } .gallery-item-lines { @@ -117,23 +129,22 @@ } /* Gallery Indicators */ -.gallery-indicators { +.gallery-dots { display: flex; justify-content: center; - gap: 0.5rem; - margin-top: 1rem; - padding: 0.5rem 0; + gap: 0.35rem; + margin-top: 0.25rem; } -.gallery-indicator { - width: 8px; - height: 8px; +.dot { + width: 6px; + height: 6px; border-radius: 50%; background-color: var(--border-color); transition: background-color 0.2s ease-in-out; } -.gallery-indicator.active { +.dot.active { background-color: var(--button-background-color); } diff --git a/src/frontend/app/components/StopGallery.tsx b/src/frontend/app/components/StopGallery.tsx index 18d0725..500ea20 100644 --- a/src/frontend/app/components/StopGallery.tsx +++ b/src/frontend/app/components/StopGallery.tsx @@ -1,7 +1,7 @@ -import React, { useRef, useState, useEffect } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { type Stop } from "../data/StopDataProvider"; -import StopGalleryItem from "./StopGalleryItem"; import "./StopGallery.css"; +import StopGalleryItem from "./StopGalleryItem"; interface StopGalleryProps { stops: Stop[]; @@ -19,7 +19,9 @@ const StopGallery: React.FC<StopGalleryProps> = ({ useEffect(() => { const element = scrollRef.current; - if (!element) return; + if (!element || stops.length === 0) { + return; + } const handleScroll = () => { const scrollLeft = element.scrollLeft; @@ -34,9 +36,11 @@ const StopGallery: React.FC<StopGalleryProps> = ({ if (stops.length === 0 && emptyMessage) { return ( - <div className="gallery-container"> - <h2 className="page-subtitle">{title}</h2> - <p className="message">{emptyMessage}</p> + <div className="gallery-container stoplist-section"> + <h3 className="page-subtitle">{title}</h3> + <div className="gallery-empty-state"> + <p className="message">{emptyMessage}</p> + </div> </div> ); } @@ -46,29 +50,27 @@ const StopGallery: React.FC<StopGalleryProps> = ({ } return ( - <div className="gallery-container"> + <div className="gallery-container stoplist-section"> <div className="gallery-header"> - <h2 className="page-subtitle">{title}</h2> + <h3 className="page-subtitle">{title}</h3> + <span className="gallery-counter">{stops.length}</span> </div> - <div className="gallery-scroll-container" ref={scrollRef}> + <div ref={scrollRef} className="gallery-scroll-container"> <div className="gallery-track"> {stops.map((stop) => ( <StopGalleryItem key={stop.stopId} stop={stop} /> ))} </div> </div> - - {stops.length > 1 && ( - <div className="gallery-indicators"> - {stops.map((_, index) => ( - <div - key={index} - className={`gallery-indicator ${index === activeIndex ? "active" : ""}`} - /> - ))} - </div> - )} + <div className="gallery-dots"> + {stops.map((_, index) => ( + <span + key={index} + className={`dot ${index === activeIndex ? "active" : ""}`} + ></span> + ))} + </div> </div> ); }; diff --git a/src/frontend/app/components/StopItem.tsx b/src/frontend/app/components/StopItem.tsx index ae51df8..de51576 100644 --- a/src/frontend/app/components/StopItem.tsx +++ b/src/frontend/app/components/StopItem.tsx @@ -1,8 +1,8 @@ import React from "react"; import { Link } from "react-router"; +import { useApp } from "../AppContext"; import StopDataProvider, { type Stop } from "../data/StopDataProvider"; import LineIcon from "./LineIcon"; -import { useApp } from "../AppContext"; interface StopItemProps { stop: Stop; @@ -14,9 +14,16 @@ const StopItem: React.FC<StopItemProps> = ({ stop }) => { return ( <li className="list-item"> <Link className="list-item-link" to={`/estimates/${stop.stopId}`}> - {stop.favourite && <span className="favourite-icon">★</span>} ( - {stop.stopId}) {StopDataProvider.getDisplayName(region, stop)} - <div className="line-icons"> + <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline" }}> + <span style={{ fontWeight: 600 }}> + {stop.favourite && <span className="favourite-icon">★</span>} + {StopDataProvider.getDisplayName(region, stop)} + </span> + <span style={{ fontSize: "0.85em", color: "var(--subtitle-color)", marginLeft: "0.5rem" }}> + ({stop.stopId}) + </span> + </div> + <div className="line-icons" style={{ marginTop: "0.25rem" }}> {stop.lines?.map((line) => ( <LineIcon key={line} line={line} region={region} /> ))} diff --git a/src/frontend/app/components/ThemeColorManager.tsx b/src/frontend/app/components/ThemeColorManager.tsx new file mode 100644 index 0000000..c138dc9 --- /dev/null +++ b/src/frontend/app/components/ThemeColorManager.tsx @@ -0,0 +1,20 @@ +import { useEffect } from "react"; +import { useSettings } from "../contexts/SettingsContext"; + +export const ThemeColorManager = () => { + const { resolvedTheme } = useSettings(); + + useEffect(() => { + const color = resolvedTheme === "dark" ? "#121212" : "#ffffff"; + + let meta = document.querySelector('meta[name="theme-color"]'); + if (!meta) { + meta = document.createElement('meta'); + meta.setAttribute('name', 'theme-color'); + document.head.appendChild(meta); + } + meta.setAttribute('content', color); + }, [resolvedTheme]); + + return null; +}; diff --git a/src/frontend/app/components/layout/AppShell.tsx b/src/frontend/app/components/layout/AppShell.tsx index d0c0121..e0559ac 100644 --- a/src/frontend/app/components/layout/AppShell.tsx +++ b/src/frontend/app/components/layout/AppShell.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { Outlet } from "react-router"; import { PageTitleProvider, usePageTitleContext } from "~/contexts/PageTitleContext"; import NavBar from "../NavBar"; +import { ThemeColorManager } from "../ThemeColorManager"; import "./AppShell.css"; import { Drawer } from "./Drawer"; import { Header } from "./Header"; @@ -12,6 +13,7 @@ const AppShellContent: React.FC = () => { return ( <div className="app-shell"> + <ThemeColorManager /> <Header className="app-shell__header" title={title} diff --git a/src/frontend/app/components/layout/Header.css b/src/frontend/app/components/layout/Header.css index c95226f..4ff492e 100644 --- a/src/frontend/app/components/layout/Header.css +++ b/src/frontend/app/components/layout/Header.css @@ -4,7 +4,6 @@ justify-content: space-between; padding: 0.5rem 1rem; background-color: var(--background-color); - border-bottom: 1px solid var(--border-color); height: 60px; box-sizing: border-box; width: 100%; diff --git a/src/frontend/app/contexts/SettingsContext.tsx b/src/frontend/app/contexts/SettingsContext.tsx index ed20fcb..a273008 100644 --- a/src/frontend/app/contexts/SettingsContext.tsx +++ b/src/frontend/app/contexts/SettingsContext.tsx @@ -30,6 +30,7 @@ interface SettingsContextProps { region: RegionId; setRegion: (region: RegionId) => void; + resolvedTheme: "light" | "dark"; } const SettingsContext = createContext<SettingsContextProps | undefined>( @@ -182,6 +183,7 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => { setMapPositionMode, region, setRegion, + resolvedTheme, }} > {children} diff --git a/src/frontend/app/root.css b/src/frontend/app/root.css index 6c3dd99..f87fdc3 100644 --- a/src/frontend/app/root.css +++ b/src/frontend/app/root.css @@ -182,6 +182,29 @@ body { color: var(--text-color); } +.page-title { + font-size: 1.25rem; + font-weight: 600; + color: var(--text-color); + margin: 0; +} + +.page-subtitle { + font-size: 1rem; + font-weight: 500; + color: var(--subtitle-color); + margin: 0 0 0.5rem 0; +} + +.message { + background-color: var(--message-background-color); + padding: 1rem; + border-radius: 0.5rem; + margin: 0; + color: var(--text-color); + font-size: 0.9rem; +} + @media (min-width: 768px) { .page-container { width: 90%; diff --git a/src/frontend/app/root.tsx b/src/frontend/app/root.tsx index 8f0c916..7bf07a0 100644 --- a/src/frontend/app/root.tsx +++ b/src/frontend/app/root.tsx @@ -1,9 +1,9 @@ import { - isRouteErrorResponse, - Links, - Meta, - Scripts, - ScrollRestoration + isRouteErrorResponse, + Links, + Meta, + Scripts, + ScrollRestoration } from "react-router"; import "@fontsource-variable/roboto"; @@ -37,12 +37,10 @@ export function Layout({ children }: { children: React.ReactNode }) { <meta name="mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="default" /> - <link rel="icon" type="image/jpg" href="/logo-512.jpg" /> <link rel="icon" href="/favicon.ico" sizes="64x64" /> <link rel="apple-touch-icon" href="/logo-512.jpg" sizes="512x512" /> - <meta name="theme-color" content="#007bff" /> - + <meta name="theme-color" content="#ffffff" /> <link rel="canonical" href="https://busurbano.costas.dev/" /> <meta diff --git a/src/frontend/app/routes/home.css b/src/frontend/app/routes/home.css index 3d5ba3a..b935518 100644 --- a/src/frontend/app/routes/home.css +++ b/src/frontend/app/routes/home.css @@ -1,33 +1,59 @@ /* Common page styles */ -.page-title { - font-size: 1.4rem; - margin-bottom: 1rem; - font-weight: 600; +.stoplist-page { + display: flex; + flex-direction: column; + gap: 1.5rem; + padding: 1rem 0 2rem; +} + +.stoplist-section { + width: 100%; + padding: 0 1rem; + box-sizing: border-box; +} + +.search-container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.search-bar { + width: 100%; + padding: 0.75rem 1rem; + font-size: 1rem; + border: 1px solid var(--border-color); + border-radius: 0.75rem; + background-color: var(--card-background, var(--background-color)); color: var(--text-color); + transition: border-color 0.2s ease, box-shadow 0.2s ease; } -.page-subtitle { - font-size: 1.4rem; - margin-top: 1.5rem; - margin-bottom: 0.75rem; - font-weight: 500; +.search-bar::placeholder { color: var(--subtitle-color); + opacity: 0.8; +} + +.search-bar:focus { + outline: none; + border-color: var(--button-background-color); + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05); } /* Form styles */ .search-form { - margin-bottom: 1.5rem; + margin: 0; } .form-group { - margin-bottom: 1rem; + margin: 0; display: flex; flex-direction: column; } .form-label { - font-size: 0.9rem; - margin-bottom: 0.25rem; + font-size: 0.85rem; + margin-bottom: 0.5rem; font-weight: 500; } @@ -38,41 +64,25 @@ border-radius: 8px; } -.form-button { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 1rem; - - padding: 0.75rem 1rem; - background-color: var(--button-background-color); - color: white; - border: none; - border-radius: 8px; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - width: 100%; - margin-top: 0.5rem; -} - -.form-button:hover { - background-color: var(--button-hover-background-color); -} - /* List styles */ .list-container { - margin-bottom: 1.5rem; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.75rem; } .list { list-style: none; padding: 0; margin: 0; + display: flex; + flex-direction: column; + gap: 0.75rem; } .list-item { - padding: 1rem; + padding: 0.75rem; border-bottom: 1px solid var(--border-color); } @@ -80,7 +90,7 @@ display: block; color: var(--text-color); text-decoration: none; - font-size: 1.1rem; /* Increased font size for stop name */ + font-size: 1rem; /* Reduced font size */ } .list-item-link:hover { diff --git a/src/frontend/app/routes/home.tsx b/src/frontend/app/routes/home.tsx index 8a1e3b3..ca6a20b 100644 --- a/src/frontend/app/routes/home.tsx +++ b/src/frontend/app/routes/home.tsx @@ -2,10 +2,8 @@ import Fuse from "fuse.js"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { REGIONS } from "~/config/RegionConfig"; import { usePageTitle } from "~/contexts/PageTitleContext"; import { useApp } from "../AppContext"; -import ServiceAlerts from "../components/ServiceAlerts"; import StopGallery from "../components/StopGallery"; import StopItem from "../components/StopItem"; import StopItemSkeleton from "../components/StopItemSkeleton"; @@ -249,26 +247,19 @@ export default function StopList() { }; return ( - <div className="page-container stoplist-page"> - <h1 className="page-title">BusUrbano - {REGIONS[region].name}</h1> - - <form className="search-form"> - <div className="form-group"> - <label className="form-label" htmlFor="stopName"> - {t("stoplist.search_label", "Buscar paradas")} - </label> - <input - className="form-input" - type="text" - placeholder={randomPlaceholder} - id="stopName" - onChange={handleStopSearch} - /> - </div> - </form> + <div className="stoplist-page"> + <div className="stoplist-section search-container"> + <h3 className="page-subtitle">{t("stoplist.search_label", "Buscar paradas")}</h3> + <input + type="search" + placeholder={randomPlaceholder} + onChange={handleStopSearch} + className="search-bar" + /> + </div> {searchResults && searchResults.length > 0 && ( - <div className="list-container"> + <div className="stoplist-section list-container"> <h2 className="page-subtitle"> {t("stoplist.search_results", "Resultados de la búsqueda")} </h2> @@ -295,9 +286,9 @@ export default function StopList() { /> )} - <ServiceAlerts /> + {/*<ServiceAlerts />*/} - <div className="list-container"> + <div className="stoplist-section list-container"> <h2 className="page-subtitle"> {userLocation ? t("stoplist.nearby_stops", "Nearby stops") |
