From 747c579b15c54dc5dbc50482d3361761853e007a Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Wed, 19 Nov 2025 22:34:07 +0100 Subject: feat: Refactor layout and styles for StopList and related components; add ThemeColorManager for dynamic theme support --- src/frontend/app/components/ServiceAlerts.css | 5 +- src/frontend/app/components/ServiceAlerts.tsx | 2 +- src/frontend/app/components/StopGallery.css | 51 ++++++++------ src/frontend/app/components/StopGallery.tsx | 42 +++++------ src/frontend/app/components/StopItem.tsx | 15 ++-- src/frontend/app/components/ThemeColorManager.tsx | 20 ++++++ src/frontend/app/components/layout/AppShell.tsx | 2 + src/frontend/app/components/layout/Header.css | 1 - src/frontend/app/contexts/SettingsContext.tsx | 2 + src/frontend/app/root.css | 23 ++++++ src/frontend/app/root.tsx | 14 ++-- src/frontend/app/routes/home.css | 86 +++++++++++++---------- src/frontend/app/routes/home.tsx | 35 ++++----- 13 files changed, 183 insertions(+), 115 deletions(-) create mode 100644 src/frontend/app/components/ThemeColorManager.tsx (limited to 'src/frontend') 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 ( -
+

{t("stoplist.service_alerts")}

ℹ️
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 = ({ 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 = ({ if (stops.length === 0 && emptyMessage) { return ( -
-

{title}

-

{emptyMessage}

+
+

{title}

+
+

{emptyMessage}

+
); } @@ -46,29 +50,27 @@ const StopGallery: React.FC = ({ } return ( -
+
-

{title}

+

{title}

+ {stops.length}
-
+
{stops.map((stop) => ( ))}
- - {stops.length > 1 && ( -
- {stops.map((_, index) => ( -
- ))} -
- )} +
+ {stops.map((_, index) => ( + + ))} +
); }; 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 = ({ stop }) => { return (
  • - {stop.favourite && } ( - {stop.stopId}) {StopDataProvider.getDisplayName(region, stop)} -
    +
    + + {stop.favourite && } + {StopDataProvider.getDisplayName(region, stop)} + + + ({stop.stopId}) + +
    +
    {stop.lines?.map((line) => ( ))} 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 (
    +
    void; + resolvedTheme: "light" | "dark"; } const SettingsContext = createContext( @@ -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 }) { - - - + -

    BusUrbano - {REGIONS[region].name}

    - -
    -
    - - -
    -
    +
    +
    +

    {t("stoplist.search_label", "Buscar paradas")}

    + +
    {searchResults && searchResults.length > 0 && ( -
    +

    {t("stoplist.search_results", "Resultados de la búsqueda")}

    @@ -295,9 +286,9 @@ export default function StopList() { /> )} - + {/**/} -
    +

    {userLocation ? t("stoplist.nearby_stops", "Nearby stops") -- cgit v1.3