diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-24 17:53:32 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-24 17:53:32 +0100 |
| commit | 9ed46bea58dbb81ceada2a957fd1db653fb21e52 (patch) | |
| tree | f1cb09ad15571abbfae1c59105952330ec3a0502 /src/frontend/app/components | |
| parent | 4a866f5352a51916ddb9849b2d68213856196c9c (diff) | |
Implement new UI styles
Diffstat (limited to 'src/frontend/app/components')
11 files changed, 52 insertions, 794 deletions
diff --git a/src/frontend/app/components/PullToRefresh.css b/src/frontend/app/components/PullToRefresh.css index 3e8f802..910d201 100644 --- a/src/frontend/app/components/PullToRefresh.css +++ b/src/frontend/app/components/PullToRefresh.css @@ -20,21 +20,21 @@ width: 40px; height: 40px; border-radius: 50%; - background: var(--surface-color, #f8f9fa); - border: 2px solid var(--border-color, #e9ecef); + background: var(--message-background-color); + border: 2px solid var(--border-color); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: all 0.2s ease; } .refresh-icon-container.active { - background: var(--primary-color, #007bff); - border-color: var(--primary-color, #007bff); + background: var(--primary-color); + border-color: var(--primary-color); } .refresh-icon { width: 20px; height: 20px; - color: var(--text-secondary, #6c757d); + color: var(--text-secondary); transition: color 0.2s ease; } diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css deleted file mode 100644 index d9ed38f..0000000 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css +++ /dev/null @@ -1,212 +0,0 @@ -@import "../../tailwind.css"; - -.consolidated-circulation-card { - all: unset; - flex: 0 0 auto; - display: flex; - flex-direction: column; - gap: 0.5rem; - background-color: var(--message-background-color); - border-radius: 12px; - border: 1px solid var(--border-color); - padding: 0.65rem 0.85rem; - transition: all 0.2s ease; -} - -.consolidated-circulation-card.has-gps { - cursor: pointer; -} - -.consolidated-circulation-card.no-gps { - cursor: not-allowed; - opacity: 0.7; -} - -.consolidated-circulation-card.has-gps:hover { - box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); - border-color: var(--button-background-color); - background-color: color-mix( - in oklab, - var(--button-background-color) 5%, - var(--message-background-color) - ); -} - -.consolidated-circulation-card.has-gps:active { - transform: scale(0.98); -} - -.consolidated-circulation-card:disabled { - pointer-events: none; -} - -.consolidated-circulation-card .card-row { - display: flex; - align-items: center; - gap: 0.65rem; -} - -.consolidated-circulation-card .card-row.main { - min-height: 48px; -} - -.consolidated-circulation-card .line-info { - flex-shrink: 0; -} - -.consolidated-circulation-card .route-info { - flex: 1; - min-width: 0; -} - -.consolidated-circulation-card .route-info strong { - color: var(--text-color); - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 2; - line-clamp: 2; - -webkit-box-orient: vertical; - line-height: 1.25; -} - -.consolidated-circulation-card .eta-badge { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.3rem 0.45rem; - border-radius: 12px; -} - -.consolidated-circulation-card .eta-text { - display: flex; - flex-direction: column; - align-items: center; - line-height: 1; -} - -.consolidated-circulation-card .eta-value { - font-size: 1.15rem; - font-weight: 700; -} - -.consolidated-circulation-card .eta-unit { - font-size: 0.65rem; - text-transform: uppercase; - letter-spacing: 0.08em; -} - -.consolidated-circulation-card .eta-badge.time-running { - background: rgba(34, 197, 94, 0.12); - color: #1a9e56; -} - -.consolidated-circulation-card .eta-badge.time-delayed { - background: rgba(255, 106, 0, 0.12); - color: #d06100; -} - -.consolidated-circulation-card .eta-badge.time-scheduled { - background: rgba(11, 61, 145, 0.12); - color: #0b3d91; -} - -[data-theme="dark"] .consolidated-circulation-card .eta-badge.time-scheduled { - color: #8fb4ff; -} - -.consolidated-circulation-card .card-row.meta { - justify-content: flex-start; - flex-wrap: wrap; - gap: 0.4rem; -} - -.meta-chip { - font-size: 0.75rem; - padding: 0.2rem 0.55rem; - border-radius: 999px; - background: rgba(0, 0, 0, 0.03); - - @apply flex items-center justify-center gap-1 shrink-0 bg-gray-200/30 dark:bg-gray-600/30; -} - -.meta-chip.delay-ok { - @apply bg-green-600/80 dark:bg-green-600/30 border-green-500 dark:border-green-700 text-white dark:text-green-200; -} - -.meta-chip.delay-warn { - @apply bg-amber-600/80 dark:bg-yellow-600/30 border-yellow-500 dark:border-yellow-700 text-white dark:text-yellow-200; -} - -.meta-chip.delay-critical { - @apply bg-red-400/80 dark:bg-red-600/30 border-red-500 dark:border-red-700 text-white; -} - -.meta-chip.delay-early { - @apply bg-blue-400/80 dark:bg-blue-600/30 border-blue-500 dark:border-blue-700 text-white dark:text-blue-200; -} - -/* GPS Indicator */ -.gps-indicator { - display: flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - flex-shrink: 0; - position: relative; -} - -.gps-pulse { - position: absolute; - width: 8px; - height: 8px; - background: #22c55e; - border-radius: 50%; - box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2); - animation: gpsPulse 2s ease-in-out infinite; -} - -.gps-pulse.previous-trip { - background: #ff9800; - box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2); - animation: gpsPulseOrange 2s ease-in-out infinite; -} - -@keyframes gpsPulse { - 0% { - box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2); - } - 50% { - box-shadow: 0 0 0 6px rgba(34, 197, 94, 0.1); - } - 100% { - box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2); - } -} - -@keyframes gpsPulseOrange { - 0% { - box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2); - } - 50% { - box-shadow: 0 0 0 6px rgba(255, 152, 0, 0.1); - } - 100% { - box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2); - } -} - -@media (max-width: 480px) { - .consolidated-circulation-card { - padding: 0.65rem 0.75rem; - } - - .consolidated-circulation-card .card-row { - gap: 0.5rem; - } - - .consolidated-circulation-card .eta-badge { - padding: 0.25rem 0.4rem; - } -} diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx deleted file mode 100644 index 679345f..0000000 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx +++ /dev/null @@ -1,467 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import Marquee from "react-fast-marquee"; -import { useTranslation } from "react-i18next"; -import LineIcon from "~components/LineIcon"; -import { type ConsolidatedCirculation } from "~routes/stops-$id"; - -import { AlertTriangle, LocateIcon } from "lucide-react"; -import "./ConsolidatedCirculationCard.css"; - -interface ConsolidatedCirculationCardProps { - estimate: ConsolidatedCirculation; - onMapClick?: () => void; - readonly?: boolean; - reduced?: boolean; - driver?: string; -} - -// Utility function to parse service ID and get the turn number -const parseServiceId = (serviceId: string): string => { - const parts = serviceId.split("_"); - if (parts.length === 0) return ""; - - const lastPart = parts[parts.length - 1]; - if (lastPart.length < 6) return ""; - - const last6 = lastPart.slice(-6); - const lineCode = last6.slice(0, 3); - const turnCode = last6.slice(-3); - - // Remove leading zeros from turn - const turnNumber = parseInt(turnCode, 10).toString(); - - // Parse line number with special cases - const lineNumber = parseInt(lineCode, 10); - let displayLine: string; - - switch (lineNumber) { - case 1: - displayLine = "C1"; - break; - case 3: - displayLine = "C3"; - break; - case 30: - displayLine = "N1"; - break; - case 33: - displayLine = "N4"; - break; - case 8: - displayLine = "A"; - break; - case 101: - displayLine = "H"; - break; - case 150: - displayLine = "REF"; - break; - case 500: - displayLine = "TUR"; - break; - case 201: - displayLine = "U1"; - break; - case 202: - displayLine = "U2"; - break; - default: - displayLine = `L${lineNumber}`; - } - - return `${displayLine}-${turnNumber}`; -}; - -const AutoMarquee = ({ text }: { text: string }) => { - const containerRef = useRef<HTMLDivElement>(null); - const [shouldScroll, setShouldScroll] = useState(false); - - useEffect(() => { - const el = containerRef.current; - if (!el) return; - - const checkScroll = () => { - // 9px per char for text-sm font-mono is a safe upper bound estimate - // (14px * 0.6 = 8.4px) - const charWidth = 9; - const availableWidth = el.offsetWidth; - const textWidth = text.length * charWidth; - - setShouldScroll(textWidth > availableWidth); - }; - - checkScroll(); - - const observer = new ResizeObserver(checkScroll); - observer.observe(el); - - return () => observer.disconnect(); - }, [text]); - - if (shouldScroll) { - return ( - <div ref={containerRef} className="w-full overflow-hidden"> - <Marquee speed={60} gradient={false}> - <div className="mr-64 text-sm font-mono">{text}</div> - </Marquee> - </div> - ); - } - - return ( - <div - ref={containerRef} - className="w-full overflow-hidden text-sm font-mono truncate" - > - {text} - </div> - ); -}; - -export const ConsolidatedCirculationCard: React.FC< - ConsolidatedCirculationCardProps -> = ({ estimate, onMapClick, readonly, reduced, driver }) => { - const { t } = useTranslation(); - - const formatDistance = (meters: number) => { - if (meters > 1024) { - return `${(meters / 1000).toFixed(1)} km`; - } - return `${meters} ${t("estimates.meters", "m")}`; - }; - - const getTripIdDisplay = (tripId: string): string => { - const parts = tripId.split("_"); - return parts.length > 1 ? parts[1] : tripId; - }; - - const etaMinutes = - estimate.realTime?.minutes ?? estimate.schedule?.minutes ?? null; - - let etaValue: string; - let etaUnit: string; - - if (etaMinutes === null) { - etaValue = "--"; - etaUnit = t("estimates.minutes", "min"); - } else { - const isRenfe = driver === "renfe"; - const isLongWait = etaMinutes > 60; - - if (isRenfe || isLongWait) { - const now = new Date(); - const arrivalTime = new Date(now.getTime() + etaMinutes * 60 * 1000); - etaValue = arrivalTime.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); - etaUnit = ""; - } else { - etaValue = Math.max(0, Math.round(etaMinutes)).toString(); - etaUnit = t("estimates.minutes", "min"); - } - } - - const timeClass = useMemo(() => { - if (estimate.realTime && estimate.schedule?.running) { - return "time-running"; - } - if (estimate.realTime && !estimate.schedule) { - return "time-running"; - } - if (estimate.realTime && !estimate.schedule?.running) { - return "time-delayed"; - } - return "time-scheduled"; - }, [estimate.realTime, estimate.schedule]); - - const delayChip = useMemo(() => { - if (!estimate.schedule || !estimate.realTime) { - return null; - } - - const delta = Math.round( - estimate.realTime.minutes - estimate.schedule.minutes - ); - const absDelta = Math.abs(delta); - - // On time - if (delta === 0) { - return { - label: reduced ? "OK" : t("estimates.delay_on_time"), - tone: "delay-ok", - kind: "delay", - } as const; - } - - // Delayed - if (delta > 0) { - const tone = - delta <= 2 ? "delay-ok" : delta <= 10 ? "delay-warn" : "delay-critical"; - return { - label: reduced - ? `R${delta}` - : t("estimates.delay_positive", { - minutes: delta, - }), - tone, - kind: "delay", - } as const; - } - - // Early - const tone = absDelta <= 2 ? "delay-ok" : "delay-early"; - return { - label: reduced - ? `A${absDelta}` - : t("estimates.delay_negative", { - minutes: absDelta, - }), - tone, - kind: "delay", - } as const; - }, [estimate.schedule, estimate.realTime, t, reduced]); - - const metaChips = useMemo(() => { - const chips: Array<{ - label: string; - tone?: string; - kind?: "regular" | "gps" | "delay" | "warning"; - }> = []; - - if (delayChip) { - chips.push(delayChip); - } - - if (estimate.schedule && driver !== "renfe") { - chips.push({ - label: `${parseServiceId(estimate.schedule.serviceId)} · ${getTripIdDisplay( - estimate.schedule.tripId - )}`, - kind: "regular", - }); - } - - if (estimate.realTime && estimate.realTime.distance >= 0) { - chips.push({ - label: formatDistance(estimate.realTime.distance), - kind: "regular", - }); - } - - if (!reduced) { - if (estimate.currentPosition) { - if (estimate.isPreviousTrip) { - chips.push({ label: t("estimates.previous_trip"), kind: "gps" }); - } else { - chips.push({ label: t("estimates.bus_gps_position"), kind: "gps" }); - } - } - - if (driver !== "renfe") { - if (timeClass === "time-delayed") { - chips.push({ - label: reduced ? "!" : t("estimates.low_accuracy"), - tone: "warning", - kind: "warning", - }); - } - - if (timeClass === "time-scheduled") { - chips.push({ - label: reduced ? "⧗" : t("estimates.no_realtime"), - tone: "warning", - kind: "warning", - }); - } - } - } - - return chips; - }, [delayChip, estimate.schedule, estimate.realTime, timeClass, t, reduced]); - - // Check if bus has GPS position (live tracking) - const hasGpsPosition = !!estimate.currentPosition; - const isRenfe = driver === "renfe"; - const isClickable = hasGpsPosition; - const looksDisabled = !isClickable && !isRenfe; - - const Tag = readonly ? "div" : "button"; - const interactiveProps = readonly - ? {} - : { - onClick: isClickable ? onMapClick : undefined, - type: "button" as const, - disabled: !isClickable, - }; - - if (reduced) { - return ( - <Tag - className={` - flex-none flex items-center gap-2.5 min-h-12 - bg-(--message-background-color) border border-(--border-color) - rounded-xl px-3 py-2.5 transition-all - ${ - readonly - ? looksDisabled - ? "opacity-70 cursor-not-allowed" - : "" - : isClickable - ? "cursor-pointer hover:shadow-[0_4px_14px_rgba(0,0,0,0.08)] hover:border-(--button-background-color) hover:bg-[color-mix(in_oklab,var(--button-background-color)_5%,var(--message-background-color))] active:scale-[0.98]" - : looksDisabled - ? "opacity-70 cursor-not-allowed" - : "" - } - `.trim()} - {...interactiveProps} - > - <div className="shrink-0 min-w-[7ch]"> - <LineIcon line={estimate.line} mode="pill" /> - </div> - <div className="flex-1 min-w-0 flex flex-col gap-1"> - <strong className="text-base text-(--text-color) overflow-hidden text-ellipsis line-clamp-2 leading-tight"> - {driver === "renfe" && estimate.schedule?.tripId && ( - <span className="font-mono text-slate-500 mr-1.5 text-sm"> - {estimate.schedule.tripId} - </span> - )} - {driver === "renfe" ? estimate.route.toUpperCase() : estimate.route} - </strong> - {metaChips.length > 0 && ( - <div className="flex items-center gap-1.5 flex-wrap"> - {metaChips.map((chip, idx) => { - let chipColourClasses = ""; - switch (chip.tone) { - case "delay-ok": - chipColourClasses = - "bg-green-600/20 dark:bg-green-600/30 text-green-700 dark:text-green-300"; - break; - case "delay-warn": - chipColourClasses = - "bg-amber-600/20 dark:bg-yellow-600/30 text-amber-700 dark:text-yellow-300"; - break; - case "delay-critical": - chipColourClasses = - "bg-red-400/20 dark:bg-red-600/30 text-red-600 dark:text-red-300"; - break; - case "delay-early": - chipColourClasses = - "bg-blue-400/20 dark:bg-blue-600/30 text-blue-700 dark:text-blue-300"; - break; - case "warning": - chipColourClasses = - "bg-orange-400/20 dark:bg-orange-600/30 text-orange-700 dark:text-orange-300"; - break; - default: - chipColourClasses = - "bg-black/[0.06] dark:bg-white/[0.12] text-[var(--text-color)]"; - } - - return ( - <span - key={`${chip.label}-${idx}`} - className={`text-xs px-2 py-0.5 rounded-full flex items-center justify-center gap-1 shrink-0 ${chipColourClasses}`} - > - {chip.kind === "gps" && ( - <LocateIcon className="w-3 h-3 inline-block" /> - )} - {chip.kind === "warning" && ( - <AlertTriangle className="w-3 h-3 inline-block" /> - )} - {chip.label} - </span> - ); - })} - </div> - )} - </div> - <div - className={` - inline-flex items-center justify-center px-2 py-1.5 rounded-xl shrink-0 - ${ - timeClass === "time-running" - ? "bg-green-600/20 dark:bg-green-600/25 text-[#1a9e56] dark:text-[#22c55e]" - : timeClass === "time-delayed" - ? "bg-orange-600/20 dark:bg-orange-600/25 text-[#d06100] dark:text-[#fb923c]" - : "bg-blue-900/20 dark:bg-blue-600/25 text-[#0b3d91] dark:text-[#93c5fd]" - } - `.trim()} - > - <div className="flex flex-col items-center leading-none"> - <span className="text-lg font-bold leading-none">{etaValue}</span> - <span className="text-[0.65rem] uppercase tracking-wider mt-0.5 opacity-90"> - {etaUnit} - </span> - </div> - </div> - </Tag> - ); - } - - return ( - <Tag - className={`consolidated-circulation-card ${ - readonly - ? looksDisabled - ? "no-gps" - : "" - : isClickable - ? "has-gps" - : looksDisabled - ? "no-gps" - : "" - }`} - {...interactiveProps} - > - <> - <div className="card-row main"> - <div className="line-info"> - <LineIcon line={estimate.line} mode="pill" /> - </div> - <div className="route-info"> - <strong> - {driver === "renfe" && estimate.schedule?.tripId && ( - <span className="font-mono text-slate-500 mr-2 text-[0.9em]"> - {estimate.schedule.tripId} - </span> - )} - {driver === "renfe" - ? estimate.route.toUpperCase() - : estimate.route} - </strong> - {estimate.nextStreets && estimate.nextStreets.length > 0 && ( - <AutoMarquee text={estimate.nextStreets.join(" — ")} /> - )} - </div> - <div className={`eta-badge ${timeClass}`}> - <div className="eta-text"> - <span className="eta-value">{etaValue}</span> - <span className="eta-unit">{etaUnit}</span> - </div> - </div> - </div> - - {metaChips.length > 0 && ( - <div className="card-row meta"> - {metaChips.map((chip, idx) => ( - <span - key={`${chip.label}-${idx}`} - className={`meta-chip ${chip.tone ?? ""}`.trim()} - > - {chip.kind === "gps" && ( - <LocateIcon className="w-3 h-3 inline-block" /> - )} - {chip.kind === "warning" && ( - <AlertTriangle className="w-3 h-3 inline-block" /> - )} - {chip.label} - </span> - ))} - </div> - )} - </> - </Tag> - ); -}; diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css deleted file mode 100644 index 044b4a3..0000000 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css +++ /dev/null @@ -1,13 +0,0 @@ -.consolidated-circulation-caption { - font-size: 0.9rem; - color: var(--subtitle-color); - text-align: center; - padding: 0.5rem; -} - -.consolidated-circulation-no-data { - text-align: center; - padding: 2rem 1rem; - color: var(--subtitle-color); - font-size: 0.95rem; -} diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx deleted file mode 100644 index eea4582..0000000 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { type ConsolidatedCirculation } from "~routes/stops-$id"; -import { ConsolidatedCirculationCard } from "./ConsolidatedCirculationCard"; - -import { useCallback } from "react"; -import "./ConsolidatedCirculationList.css"; - -interface ConsolidatedCirculationListProps { - data: ConsolidatedCirculation[]; - onCirculationClick?: ( - estimate: ConsolidatedCirculation, - index: number - ) => void; - reduced?: boolean; - driver?: string; -} - -export const ConsolidatedCirculationList: React.FC< - ConsolidatedCirculationListProps -> = ({ data, onCirculationClick, reduced, driver }) => { - const { t } = useTranslation(); - - const sortedData = [...data].sort( - (a, b) => - (a.realTime?.minutes ?? a.schedule?.minutes ?? 999) - - (b.realTime?.minutes ?? b.schedule?.minutes ?? 999) - ); - - const generateKey = useCallback((estimate: ConsolidatedCirculation) => { - if (estimate.realTime && estimate.schedule) { - return `rt-${estimate.schedule.tripId}`; - } - - return `sch-${estimate.schedule ? estimate.schedule.tripId : estimate.line + "-" + estimate.route}`; - }, []); - - return ( - <> - {sortedData.length === 0 ? ( - <div className="consolidated-circulation-no-data"> - {t("estimates.none", "No hay estimaciones disponibles")} - </div> - ) : ( - <div className="flex flex-col gap-3"> - {sortedData.map((estimate, idx) => ( - <ConsolidatedCirculationCard - reduced={reduced} - driver={driver} - key={generateKey(estimate)} - estimate={estimate} - onMapClick={() => onCirculationClick?.(estimate, idx)} - /> - ))} - </div> - )} - </> - ); -}; diff --git a/src/frontend/app/components/ThemeColorManager.tsx b/src/frontend/app/components/ThemeColorManager.tsx index eba0471..30a5f7a 100644 --- a/src/frontend/app/components/ThemeColorManager.tsx +++ b/src/frontend/app/components/ThemeColorManager.tsx @@ -5,7 +5,7 @@ export const ThemeColorManager = () => { const { resolvedTheme } = useSettings(); useEffect(() => { - const color = resolvedTheme === "dark" ? "#121212" : "#ffffff"; + const color = resolvedTheme === "dark" ? "#1A1B26" : "#F7F7FF"; let meta = document.querySelector('meta[name="theme-color"]'); if (!meta) { diff --git a/src/frontend/app/components/arrivals/ArrivalCard.css b/src/frontend/app/components/arrivals/ArrivalCard.css index 5835352..0e5af25 100644 --- a/src/frontend/app/components/arrivals/ArrivalCard.css +++ b/src/frontend/app/components/arrivals/ArrivalCard.css @@ -13,5 +13,5 @@ } .time-scheduled { - @apply bg-blue-900/20 dark:bg-blue-600/25 text-[#0b3d91] dark:text-[#93c5fd]; + @apply bg-primary/20 dark:bg-primary/25 text-primary dark:text-primary; } diff --git a/src/frontend/app/components/layout/NavBar.module.css b/src/frontend/app/components/layout/NavBar.module.css index 19d0a93..32827c6 100644 --- a/src/frontend/app/components/layout/NavBar.module.css +++ b/src/frontend/app/components/layout/NavBar.module.css @@ -28,6 +28,16 @@ color: var(--text-color); padding: 0.25rem 0; border-radius: 0.5rem; + gap: 4px; +} + +.iconWrapper { + display: flex; + align-items: center; + justify-content: center; + padding: 4px 20px; + border-radius: 20px; + transition: background-color 0.2s ease; } .link svg { @@ -38,11 +48,27 @@ } .link span { - font-size: 13px; + font-size: 11px; line-height: 1; - font-family: system-ui; + font-family: var(--font-ui); } .active { color: var(--button-background-color); } + +.active .iconWrapper { + background-color: color-mix( + in oklab, + var(--button-background-color) 15%, + transparent + ); +} + +[data-theme="dark"] .active .iconWrapper { + background-color: color-mix( + in oklab, + var(--button-background-color) 20%, + transparent + ); +} diff --git a/src/frontend/app/components/layout/NavBar.tsx b/src/frontend/app/components/layout/NavBar.tsx index 9c42987..58228c7 100644 --- a/src/frontend/app/components/layout/NavBar.tsx +++ b/src/frontend/app/components/layout/NavBar.tsx @@ -112,7 +112,9 @@ export default function NavBar({ orientation = "horizontal" }: NavBarProps) { title={item.name} aria-label={item.name} > - <Icon size={24} /> + <div className={styles.iconWrapper}> + <Icon size={24} /> + </div> <span>{item.name}</span> </Link> ); diff --git a/src/frontend/app/components/map/StopSummarySheet.css b/src/frontend/app/components/map/StopSummarySheet.css index 5869d41..e39ac07 100644 --- a/src/frontend/app/components/map/StopSummarySheet.css +++ b/src/frontend/app/components/map/StopSummarySheet.css @@ -1,3 +1,5 @@ +@import "../../tailwind.css"; + /* Stop Sheet Styles */ .react-modal-sheet-container { background-color: var(--background-color) !important; @@ -29,6 +31,7 @@ } .stop-sheet-title { + font-family: var(--font-display); font-size: 1.5rem; font-weight: 600; color: var(--text-color); @@ -36,6 +39,7 @@ } .stop-sheet-id { + font-family: var(--font-display); font-size: 1rem; color: var(--subtitle-color); } @@ -85,6 +89,7 @@ } .stop-sheet-subtitle { + font-family: var(--font-display); font-size: 1.1rem; font-weight: 500; color: var(--text-color); @@ -92,6 +97,7 @@ } .stop-sheet-no-estimates { + font-family: var(--font-display); text-align: center; padding: 32px 16px; color: var(--subtitle-color); @@ -183,6 +189,7 @@ } .stop-sheet-timestamp { + font-family: var(--font-ui); font-size: 0.8rem; color: var(--subtitle-color); text-align: center; @@ -194,35 +201,19 @@ } .stop-sheet-reload { - display: inline-flex; - align-items: center; - gap: 0.4rem; - background: transparent; - border: 1px solid var(--border-color); - color: var(--text-color); - padding: 0.5rem 0.75rem; - border-radius: 6px; - font-size: 0.85rem; - cursor: pointer; - transition: all 0.2s ease; - flex: 1; - justify-content: center; + @apply inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border bg-transparent text-text text-sm font-sans cursor-pointer transition-all flex-1; } .stop-sheet-reload:hover:not(:disabled) { - background: var(--message-background-color); - border-color: var(--button-background-color); + @apply bg-surface border-primary; } .stop-sheet-reload:disabled { - opacity: 0.6; - cursor: not-allowed; + @apply opacity-60 cursor-not-allowed; } .reload-icon { - width: 14px; - height: 14px; - transition: transform 0.5s ease; + @apply w-4 h-4 transition-transform duration-500; } .reload-icon.spinning { @@ -239,22 +230,11 @@ } .stop-sheet-view-all { - display: block; - padding: 0.5rem 0.75rem; - background-color: var(--button-background-color); - color: white; - text-decoration: none; - text-align: center; - border-radius: 6px; - font-weight: 500; - font-size: 0.85rem; - transition: background-color 0.2s ease; - flex: 2; + @apply block px-4 py-2.5 bg-primary text-white no-underline text-center rounded-xl font-semibold text-sm font-sans transition-colors flex-[1.5]; } .stop-sheet-view-all:hover { - background-color: var(--button-hover-background-color); - text-decoration: none; + @apply bg-primary/90 no-underline; } /* Error display adjustments for sheet */ diff --git a/src/frontend/app/components/map/StopSummarySheet.tsx b/src/frontend/app/components/map/StopSummarySheet.tsx index 56e80a4..7024f41 100644 --- a/src/frontend/app/components/map/StopSummarySheet.tsx +++ b/src/frontend/app/components/map/StopSummarySheet.tsx @@ -26,7 +26,7 @@ export interface StopSheetProps { }; } -export const StopSheet: React.FC<StopSheetProps> = ({ +export const StopSummarySheet: React.FC<StopSheetProps> = ({ isOpen, onClose, stop, |
