From a477dda9dc4291ab25fffe2525acf44177154c86 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Sun, 30 Nov 2025 23:27:33 +0100 Subject: Remake home and settings pages using Tailwind styles --- src/frontend/app/components/LineIcon.css | 3 + src/frontend/app/components/StopGallery.tsx | 36 +-- src/frontend/app/components/StopGalleryItem.tsx | 38 ++- src/frontend/app/components/StopItem.tsx | 17 +- src/frontend/app/root.css | 1 - src/frontend/app/routes/home.css | 320 ------------------------ src/frontend/app/routes/home.tsx | 67 +++-- src/frontend/app/routes/settings.css | 283 --------------------- src/frontend/app/routes/settings.tsx | 145 ++++++----- src/frontend/app/tailwind-full.css | 3 + 10 files changed, 190 insertions(+), 723 deletions(-) delete mode 100644 src/frontend/app/routes/home.css delete mode 100644 src/frontend/app/routes/settings.css create mode 100644 src/frontend/app/tailwind-full.css diff --git a/src/frontend/app/components/LineIcon.css b/src/frontend/app/components/LineIcon.css index c6dffd8..6363c85 100644 --- a/src/frontend/app/components/LineIcon.css +++ b/src/frontend/app/components/LineIcon.css @@ -13,6 +13,7 @@ --line-l5b: hsl(204, 100%, 54%); --line-l5b-text: hsl(0, 0%, 100%); --line-l6: hsl(330, 60%, 50%); + --line-l6-text: hsl(0, 0%, 100%); --line-l7: hsl(120, 60%, 70%); --line-l9b: hsl(36, 83%, 75%); --line-l10: hsl(30, 80%, 20%); @@ -40,6 +41,7 @@ --line-l23-text: hsl(0, 0%, 100%); --line-l24: hsl(0, 0%, 75%); --line-l25: hsl(34, 95%, 35%); + --line-l25-text: hsl(0, 0%, 100%); --line-l27: hsl(30, 60%, 30%); --line-l27-text: hsl(0, 0%, 100%); --line-l28: hsl(230, 98%, 84%); @@ -59,6 +61,7 @@ --line-h3-text: hsl(0, 0%, 100%); --line-lzd: hsl(220, 60%, 50%); --line-n1: hsl(0, 51%, 53%); + --line-n1-text: hsl(0, 0%, 100%); --line-n4: hsl(300, 33%, 30%); --line-n4-text: hsl(0, 0%, 100%); --line-psa1: hsl(120, 100%, 30%); diff --git a/src/frontend/app/components/StopGallery.tsx b/src/frontend/app/components/StopGallery.tsx index 500ea20..c1d9780 100644 --- a/src/frontend/app/components/StopGallery.tsx +++ b/src/frontend/app/components/StopGallery.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; import { type Stop } from "../data/StopDataProvider"; -import "./StopGallery.css"; import StopGalleryItem from "./StopGalleryItem"; interface StopGalleryProps { @@ -36,10 +35,12 @@ const StopGallery: React.FC = ({ if (stops.length === 0 && emptyMessage) { return ( -
-

{title}

-
-

{emptyMessage}

+
+

{title}

+
+

+ {emptyMessage} +

); @@ -50,24 +51,31 @@ const StopGallery: React.FC = ({ } return ( -
-
-

{title}

- {stops.length} -
+
+

{title}

-
-
+
+
{stops.map((stop) => ( ))}
-
+
{stops.map((_, index) => ( ))}
diff --git a/src/frontend/app/components/StopGalleryItem.tsx b/src/frontend/app/components/StopGalleryItem.tsx index 72a13e5..6c80362 100644 --- a/src/frontend/app/components/StopGalleryItem.tsx +++ b/src/frontend/app/components/StopGalleryItem.tsx @@ -9,21 +9,43 @@ interface StopGalleryItemProps { const StopGalleryItem: React.FC = ({ stop }) => { return ( -
- -
- {stop.favourite && } - ({stop.stopId}) +
+ +
+ {stop.favourite && } + + ({stop.stopId}) +
-
+
{StopDataProvider.getDisplayName(stop)}
-
+
{stop.lines?.slice(0, 5).map((line) => ( ))} {stop.lines && stop.lines.length > 5 && ( - +{stop.lines.length - 5} + + +{stop.lines.length - 5} + )}
diff --git a/src/frontend/app/components/StopItem.tsx b/src/frontend/app/components/StopItem.tsx index 7875b59..9679b05 100644 --- a/src/frontend/app/components/StopItem.tsx +++ b/src/frontend/app/components/StopItem.tsx @@ -9,18 +9,21 @@ interface StopItemProps { const StopItem: React.FC = ({ stop }) => { return ( -
  • - -
    - - {stop.favourite && } +
  • + +
    + + {stop.favourite && } {StopDataProvider.getDisplayName(stop)} - + ({stop.stopId})
    -
    +
    {stop.lines?.map((line) => ( ))} diff --git a/src/frontend/app/root.css b/src/frontend/app/root.css index 8c3ef47..5365d53 100644 --- a/src/frontend/app/root.css +++ b/src/frontend/app/root.css @@ -45,7 +45,6 @@ --error-message-color: #7f8c8d; color-scheme: light; - font-family: "Roboto Variable", Roboto, Arial, sans-serif; } [data-theme="dark"] { diff --git a/src/frontend/app/routes/home.css b/src/frontend/app/routes/home.css deleted file mode 100644 index b935518..0000000 --- a/src/frontend/app/routes/home.css +++ /dev/null @@ -1,320 +0,0 @@ -/* Common page styles */ -.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; -} - -.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: 0; -} - -.form-group { - margin: 0; - display: flex; - flex-direction: column; -} - -.form-label { - font-size: 0.85rem; - margin-bottom: 0.5rem; - font-weight: 500; -} - -.form-input { - padding: 0.75rem; - font-size: 1rem; - border: 1px solid var(--border-color); - border-radius: 8px; -} - -/* List styles */ -.list-container { - 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: 0.75rem; - border-bottom: 1px solid var(--border-color); -} - -.list-item-link { - display: block; - color: var(--text-color); - text-decoration: none; - font-size: 1rem; /* Reduced font size */ -} - -.list-item-link:hover { - color: var(--button-background-color); -} - -.list-item-link:hover .line-icon { - color: var(--text-color); -} - -.distance-info { - font-size: 0.9rem; - color: var(--subtitle-color); -} - -/* Message styles */ -.message { - padding: 1rem; - background-color: var(--message-background-color); - border-radius: 8px; - margin-bottom: 1rem; -} - -/* About page specific styles */ -.about-page { - text-align: center; - padding: 1rem; -} - -.about-version { - color: var(--subtitle-color); - font-size: 0.9rem; - margin-top: 2rem; -} - -.about-description { - margin-top: 1rem; - line-height: 1.6; -} - -/* Map page specific styles */ -.map-container { - height: calc(100dvh - 140px); - margin: -16px; - margin-bottom: 1rem; - position: relative; -} - -/* Fullscreen map styles */ -.fullscreen-container { - position: absolute; - top: 0; - left: 0; - width: 100vw; - height: 100dvh; - padding: 0; - margin: 0; - max-width: none; - overflow: hidden; -} - -.fullscreen-map { - width: 100%; - height: 100%; -} - -.fullscreen-loading { - display: flex; - justify-content: center; - align-items: center; - height: 100dvh; - width: 100vw; - font-size: 1.8rem; - font-weight: 600; - color: var(--text-color); -} - -/* Map marker and popup styles */ -.stop-marker { - box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); - transition: all 0.2s ease-in-out; -} - -.stop-marker:hover { - transform: scale(1.2); -} - -.maplibregl-popup { - max-width: 250px; -} - -.maplibregl-popup-content { - padding: 12px; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); -} - -.popup-line-icons { - display: flex; - flex-wrap: wrap; - margin: 6px 0; - gap: 5px; -} - -.popup-line { - display: inline-block; - background-color: var(--button-background-color); - color: white; - padding: 2px 6px; - margin-right: 4px; - border-radius: 4px; - font-size: 0.8rem; - font-weight: 500; -} - -.popup-link { - display: block; - margin-top: 8px; - color: var(--button-background-color); - text-decoration: none; - font-weight: 500; -} - -.popup-link:hover { - text-decoration: underline; -} - -/* Estimates page specific styles */ -.estimates-header { - display: flex; - align-items: center; - margin-bottom: 1rem; -} - -.estimates-stop-id { - font-size: 1rem; - color: var(--subtitle-color); - margin-left: 0.5rem; -} - -.estimates-arrival { - color: #28a745; - font-weight: 500; -} - -.estimates-delayed { - color: #dc3545; -} - -.button-group { - display: flex; - gap: 1rem; - margin-bottom: 1.5rem; - flex-wrap: wrap; -} - -.button { - 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; - text-align: center; - text-decoration: none; - display: inline-block; -} - -.button:hover { - background-color: var(--button-hover-background-color); -} - -.button:disabled { - background-color: var(--button-disabled-background-color); - cursor: not-allowed; -} - -.star-icon { - margin-right: 0.5rem; - color: #ccc; - fill: none; -} - -.star-icon.active { - color: var(--star-color); /* Yellow color for active star */ - fill: var(--star-color); -} - -/* Tablet and larger breakpoint */ -@media (min-width: 768px) { - .search-form { - display: flex; - align-items: flex-end; - gap: 1rem; - } - - .form-group { - flex: 1; - margin-bottom: 0; - } - - .form-button { - width: auto; - margin-top: 0; - } - - .list { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 1rem; - } - - .list-item { - border: 1px solid var(--border-color); - border-radius: 8px; - margin-bottom: 0; - } -} - -/* Desktop breakpoint */ -@media (min-width: 1024px) { - .list { - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - } -} diff --git a/src/frontend/app/routes/home.tsx b/src/frontend/app/routes/home.tsx index 7d8338f..cb640c3 100644 --- a/src/frontend/app/routes/home.tsx +++ b/src/frontend/app/routes/home.tsx @@ -2,22 +2,18 @@ import Fuse from "fuse.js"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { usePageTitle } from "~/contexts/PageTitleContext"; -import { useApp } from "../AppContext"; import StopGallery from "../components/StopGallery"; import StopItem from "../components/StopItem"; import StopItemSkeleton from "../components/StopItemSkeleton"; import StopDataProvider, { type Stop } from "../data/StopDataProvider"; -import "./home.css"; +import "../tailwind-full.css"; export default function StopList() { const { t } = useTranslation(); usePageTitle(t("navbar.stops", "Paradas")); - const { region } = useApp(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [searchResults, setSearchResults] = useState(null); - const [favouriteIds, setFavouriteIds] = useState([]); - const [recentIds, setRecentIds] = useState([]); const [favouriteStops, setFavouriteStops] = useState([]); const [recentStops, setRecentStops] = useState([]); const [userLocation, setUserLocation] = useState<{ @@ -158,12 +154,6 @@ export default function StopList() { .map(({ stop }) => stop); }, [data, userLocation]); - // Load favourite and recent IDs immediately from localStorage - useEffect(() => { - setFavouriteIds(StopDataProvider.getFavouriteIds()); - setRecentIds(StopDataProvider.getRecent()); - }, [region]); - // Load stops from network const loadStops = useCallback(async () => { try { @@ -196,7 +186,7 @@ export default function StopList() { } finally { setLoading(false); } - }, [region]); + }, []); useEffect(() => { loadStops(); @@ -246,23 +236,35 @@ export default function StopList() { }; return ( -
    -
    -

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

    +
    + {/* Search Section */} +
    +

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

    + {/* Search Results */} {searchResults && searchResults.length > 0 && ( -
    -

    +
    +

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

    -
      +
        {searchResults.map((stop: Stop) => ( ))} @@ -270,6 +272,7 @@ export default function StopList() {
    )} + {/* Favourites Gallery */} {!loading && ( a.stopId - b.stopId)} @@ -278,7 +281,8 @@ export default function StopList() { /> )} - {!loading && ( + {/* Recent Stops Gallery - only show if no favourites */} + {!loading && favouriteStops.length === 0 && ( */} -
    -

    - {userLocation - ? t("stoplist.nearby_stops", "Nearby stops") - : t("stoplist.all_stops", "Paradas")} -

    + {/* All Stops / Nearby Stops */} +
    +
    + {userLocation && ( + + + + + )} +

    + {userLocation + ? t("stoplist.nearby_stops", "Nearby stops") + : t("stoplist.all_stops", "Paradas")} +

    +
    -
      +
        {loading && ( <> {Array.from({ length: 6 }, (_, index) => ( diff --git a/src/frontend/app/routes/settings.css b/src/frontend/app/routes/settings.css deleted file mode 100644 index 02708a7..0000000 --- a/src/frontend/app/routes/settings.css +++ /dev/null @@ -1,283 +0,0 @@ -/* About page specific styles */ -.about-page { - text-align: center; - padding: 1rem; -} - -.about-version { - color: var(--subtitle-color); - font-size: 0.9rem; - margin-top: 2rem; -} - -.about-description { - margin-top: 1rem; - line-height: 1.6; -} - -.settings-section { - margin-bottom: 2em; - padding: 1rem; - border: 1px solid var(--border-color); - border-radius: 8px; - background-color: var(--message-background-color); - text-align: left; -} - -.settings-section h2 { - margin-bottom: 1em; -} - -.settings-content { - display: flex; - flex-direction: column; - align-items: flex-start; - margin-bottom: 1em; -} - -.settings-content-inline { - display: flex; - flex-direction: column; - align-items: stretch; - margin-bottom: 1em; -} - -.settings-section .form-button { - margin-bottom: 1em; - padding: 0.75rem 1.5rem; - font-size: 1.1rem; -} - -.settings-section .form-select-inline { - margin-left: 0.5em; - padding: 0.5rem; - font-size: 1rem; - border-radius: 8px; -} - -.settings-section .form-label-inline { - font-weight: 500; -} - -.settings-section .form-label { - display: block; - margin-bottom: 0.5em; - font-weight: 500; -} - -.settings-section .form-description { - margin-top: 0.5em; - font-size: 0.9rem; - color: var(--subtitle-color); -} - -.settings-section .form-details { - margin-top: 0.5em; - font-size: 0.9rem; - color: var(--subtitle-color); - border: 1px solid var(--border-color); - border-radius: 8px; - padding: 0.5rem; -} - -.settings-section .form-details summary { - cursor: pointer; - font-weight: 500; -} - -.settings-section .form-details p { - margin-top: 0.5em; -} - -.settings-section p { - margin-top: 0.5em; -} - -/* Update controls styles */ -.update-controls { - display: flex; - gap: 1rem; - margin-bottom: 1rem; - flex-wrap: wrap; -} - -.update-button, -.clear-cache-button { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1rem; - border: none; - border-radius: 8px; - font-size: 0.9rem; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - text-decoration: none; -} - -.update-button { - background-color: var(--button-background-color); - color: white; -} - -.update-button:hover:not(:disabled) { - background-color: var(--button-hover-background-color); -} - -.update-button:disabled { - background-color: var(--button-disabled-background-color); - cursor: not-allowed; -} - -.clear-cache-button { - background-color: #6c757d; - color: white; -} - -.clear-cache-button:hover { - background-color: #5a6268; -} - -.reset-pwa-button { - background-color: #dc3545; - color: white; - font-weight: bold; -} - -.reset-pwa-button:hover { - background-color: #c82333; -} - -.update-message { - padding: 0.75rem; - border-radius: 6px; - font-size: 0.9rem; - margin-bottom: 1rem; -} - -.update-message.success { - background-color: #d4edda; - color: #155724; - border: 1px solid #c3e6cb; -} - -.update-message.error { - background-color: #f8d7da; - color: #721c24; - border: 1px solid #f5c6cb; -} - -.update-help-text { - font-size: 0.85rem; - color: var(--subtitle-color); - line-height: 1.4; - margin: 0; -} - -.spinning { - animation: spin 1s linear infinite; -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -/* Modal styles */ -.modal-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; - padding: 1rem; -} - -.modal-content { - background-color: var(--message-background-color); - padding: 2rem; - border-radius: 12px; - max-width: 500px; - width: 100%; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); -} - -.modal-content h2 { - margin-top: 0; - margin-bottom: 1rem; - color: var(--text-color); -} - -.modal-content p { - margin-bottom: 1.5rem; - line-height: 1.6; - color: var(--text-color); -} - -.modal-buttons { - display: flex; - gap: 1rem; - justify-content: flex-end; -} - -.modal-button { - padding: 0.75rem 1.5rem; - border: none; - border-radius: 8px; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; -} - -.modal-button-cancel { - background-color: #6c757d; - color: white; -} - -.modal-button-cancel:hover { - background-color: #5a6268; -} - -.modal-button-confirm { - background-color: var(--button-background-color); - color: white; -} - -.modal-button-confirm:hover { - background-color: var(--button-hover-background-color); -} - -@media (max-width: 768px) { - .update-controls { - flex-direction: column; - } - - .update-button, - .clear-cache-button { - justify-content: center; - } - - .modal-content { - padding: 1.5rem; - } - - .modal-buttons { - flex-direction: column; - gap: 0.5rem; - } - - .modal-button { - width: 100%; - } -} diff --git a/src/frontend/app/routes/settings.tsx b/src/frontend/app/routes/settings.tsx index faad5a6..9b4625f 100644 --- a/src/frontend/app/routes/settings.tsx +++ b/src/frontend/app/routes/settings.tsx @@ -1,8 +1,8 @@ import { Computer, Moon, Sun } from "lucide-react"; import { useTranslation } from "react-i18next"; import { usePageTitle } from "~/contexts/PageTitleContext"; -import { type Theme, useApp } from "../AppContext"; -import "./settings.css"; +import { useApp, type Theme } from "../AppContext"; +import '../tailwind-full.css'; export default function Settings() { const { t, i18n } = useTranslation(); @@ -14,72 +14,91 @@ export default function Settings() { setMapPositionMode } = useApp(); - return ( -
        -
        -

        {t("about.settings")}

        - -
        - + const THEMES = [ + { value: "light" as Theme, label: t("about.theme_light", "Claro"), icon: Sun }, + { value: "dark" as Theme, label: t("about.theme_dark", "Oscuro"), icon: Moon }, + { value: "system" as Theme, label: t("about.theme_system", "Sistema"), icon: Computer }, + ]; -
        - - - -
        - - + ))}
        +
        -
        - - -
        -
        - - -
        + {/* Map Position Mode */} +
        + + +
        + {/* Language Selection */} +
        + +
        ); diff --git a/src/frontend/app/tailwind-full.css b/src/frontend/app/tailwind-full.css new file mode 100644 index 0000000..1767d61 --- /dev/null +++ b/src/frontend/app/tailwind-full.css @@ -0,0 +1,3 @@ +@import "tailwindcss"; + +@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); -- cgit v1.3