aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/components
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2026-03-08 23:01:01 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2026-03-08 23:01:01 +0100
commit2063f8101b1c887e079e11c96755a2441aa1b57b (patch)
tree60b64c3567fa6d543c88bd0f827675df3b44ea90 /src/frontend/app/components
parentc3db1a9a85745597c1bec334443d630f009e30c8 (diff)
Rename LineIcon -> RouteIcon, fix some size issues
Diffstat (limited to 'src/frontend/app/components')
-rw-r--r--src/frontend/app/components/LineIcon.css146
-rw-r--r--src/frontend/app/components/RouteIcon.css45
-rw-r--r--src/frontend/app/components/RouteIcon.tsx (renamed from src/frontend/app/components/LineIcon.tsx)27
-rw-r--r--src/frontend/app/components/StopGalleryItem.tsx4
-rw-r--r--src/frontend/app/components/StopItem.tsx4
-rw-r--r--src/frontend/app/components/arrivals/ArrivalCard.css23
-rw-r--r--src/frontend/app/components/arrivals/ArrivalCard.tsx129
-rw-r--r--src/frontend/app/components/arrivals/ReducedArrivalCard.tsx4
-rw-r--r--src/frontend/app/components/map/StopSummarySheet.tsx4
9 files changed, 150 insertions, 236 deletions
diff --git a/src/frontend/app/components/LineIcon.css b/src/frontend/app/components/LineIcon.css
deleted file mode 100644
index a8a413c..0000000
--- a/src/frontend/app/components/LineIcon.css
+++ /dev/null
@@ -1,146 +0,0 @@
-/* Vigo line colors */
-:root {
- --line-c1: hsl(14, 86%, 50%);
- --line-c1-text: hsl(0, 0%, 100%);
- --line-c3d: hsl(48, 100%, 50%);
- --line-c3i: hsl(48, 100%, 50%);
- --line-l4a: hsl(120, 100%, 30%);
- --line-l4a-text: hsl(0, 0%, 100%);
- --line-l4c: hsl(120, 100%, 30%);
- --line-l4c-text: hsl(0, 0%, 100%);
- --line-l5a: hsl(204, 100%, 54%);
- --line-l5a-text: hsl(0, 0%, 100%);
- --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%);
- --line-l10-text: hsl(0, 0%, 100%);
- --line-l11: hsl(0, 100%, 44%);
- --line-l11-text: hsl(0, 0%, 100%);
- --line-l12a: hsl(210, 40%, 56%);
- --line-l12b: hsl(209, 39%, 58%);
- --line-l13: hsl(196, 100%, 47%);
- --line-l14: hsl(120, 10%, 44%);
- --line-l14-text: hsl(0, 0%, 100%);
- --line-l15a: hsl(313, 38%, 75%);
- --line-l15b: hsl(313, 38%, 75%);
- --line-l15c: #d8a8a8;
- --line-l16: hsl(120, 10%, 44%);
- --line-l16-text: hsl(0, 0%, 100%);
- --line-l17: hsl(69, 91%, 54%);
- --line-l18a: hsl(320, 61%, 57%);
- --line-l18a-text: hsl(0, 0%, 100%);
- --line-l18b: hsl(320, 61%, 57%);
- --line-l18b-text: hsl(0, 0%, 100%);
- --line-l18h: hsl(320, 61%, 57%);
- --line-l18h-text: hsl(0, 0%, 100%);
- --line-l23: hsl(220, 100%, 41%);
- --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%);
- --line-l29: hsl(36, 92%, 66%);
- --line-l31: hsl(60, 100%, 50%);
- --line-a: hsl(300, 70%, 35%);
- --line-a-text: hsl(0, 0%, 100%);
- --line-a1: hsl(300, 70%, 35%);
- --line-a1-text: hsl(0, 0%, 100%);
- --line-h: hsl(210, 100%, 33%);
- --line-h-text: hsl(0, 0%, 100%);
- --line-h1: hsl(210, 100%, 33%);
- --line-h1-text: hsl(0, 0%, 100%);
- --line-h2: hsl(210, 100%, 33%);
- --line-h2-text: hsl(0, 0%, 100%);
- --line-h3: hsl(210, 100%, 33%);
- --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%);
- --line-psa4: hsl(120, 100%, 30%);
- --line-ptl: hsl(120, 60%, 70%);
- --line-tur: hsl(300, 33%, 30%);
- --line-tur-text: hsl(0, 0%, 100%);
- --line-u1: hsl(30, 80%, 20%);
- --line-u1-text: hsl(0, 0%, 100%);
- --line-u2: hsl(30, 80%, 20%);
- --line-u2-text: hsl(0, 0%, 100%);
- --line-vts: hsl(300, 33%, 30%);
- --line-vts-text: hsl(0, 0%, 100%);
-
- /* Special christmas line - Touristic bus */
- --line-nad: hsl(0, 100%, 40%);
- --line-nad-text: hsl(0, 0%, 100%);
-
- --line-mar: hsl(208, 68%, 66%);
- --line-mar-text: hsl(0, 0%, 100%);
- --line-rio: hsl(208, 68%, 66%);
- --line-rio-text: hsl(0, 0%, 100%);
- --line-gol: hsl(208, 68%, 66%);
- --line-gol-text: hsl(0, 0%, 100%);
-
- --line-md: hsl(316, 99%, 27%);
- --line-md-text: hsl(0, 0%, 100%);
- --line-ave: hsl(316, 99%, 27%);
- --line-ave-text: hsl(0, 0%, 100%);
- --line-alvia: hsl(316, 99%, 27%);
- --line-alvia-text: hsl(0, 0%, 100%);
- --line-trencelta: hsl(135, 58%, 25%);
- --line-trencelta-text: hsl(0, 0%, 100%);
- --line-regional: hsl(316, 99%, 27%);
- --line-regional-text: hsl(0, 0%, 100%);
-}
-
-.line-icon-default {
- display: inline-block;
- padding: 0.25rem 0.5rem;
- margin-right: 0.5rem;
- font-size: 0.9rem;
- font-weight: 600;
- text-transform: uppercase;
- border-radius: 0.25rem 0.25rem 0 0;
- color: var(--text-color);
- background-color: var(--background-color);
-
- border-bottom: 3px solid;
- border-color: var(--line-colour);
-}
-
-.line-icon-pill {
- display: inline-block;
- padding: 0.25rem 0.5rem;
- margin-right: 0.5rem;
- font-size: 0.9rem;
- font-weight: 600;
- border-radius: 0.25rem;
-
- background-color: var(--line-colour);
- color: var(--line-text-colour);
-}
-
-.line-icon-rounded {
- display: flex;
- align-items: center;
- justify-content: center;
- min-width: 4.25ch;
- height: 4.25ch;
- box-sizing: border-box;
-
- background-color: var(--line-colour);
- color: var(--line-text-colour);
- padding: 0 0.8ch;
- text-align: center;
- border-radius: 2.125ch;
-
- font: 600 13px / 1 monospace;
- letter-spacing: 0.05em;
- white-space: nowrap;
-}
diff --git a/src/frontend/app/components/RouteIcon.css b/src/frontend/app/components/RouteIcon.css
new file mode 100644
index 0000000..f74b01f
--- /dev/null
+++ b/src/frontend/app/components/RouteIcon.css
@@ -0,0 +1,45 @@
+.line-icon-default {
+ display: inline-block;
+ padding: 0.25rem 0.5rem;
+ margin-right: 0.5rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ border-radius: 0.25rem 0.25rem 0 0;
+ color: var(--text-color);
+ background-color: var(--background-color);
+
+ border-bottom: 3px solid;
+ border-color: var(--line-colour);
+}
+
+.line-icon-pill {
+ display: inline-block;
+ padding: 0.25rem 0.5rem;
+ margin-right: 0.5rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+ border-radius: 0.25rem;
+
+ background-color: var(--line-colour);
+ color: var(--line-text-colour);
+}
+
+.line-icon-rounded {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 4.25ch;
+ height: 4.25ch;
+ box-sizing: border-box;
+
+ background-color: var(--line-colour);
+ color: var(--line-text-colour);
+ padding: 0 0.8ch;
+ text-align: center;
+ border-radius: 2.125ch;
+
+ font: 600 13px / 1 monospace;
+ letter-spacing: 0.05em;
+ white-space: nowrap;
+}
diff --git a/src/frontend/app/components/LineIcon.tsx b/src/frontend/app/components/RouteIcon.tsx
index 5d85c60..7d984b9 100644
--- a/src/frontend/app/components/LineIcon.tsx
+++ b/src/frontend/app/components/RouteIcon.tsx
@@ -1,14 +1,15 @@
import React, { useMemo } from "react";
-import "./LineIcon.css";
+import { formatHex } from "~/utils/colours";
+import "./RouteIcon.css";
-interface LineIconProps {
+interface RouteIconProps {
line: string;
mode?: "rounded" | "pill" | "default";
- colour?: string;
- textColour?: string;
+ colour: string;
+ textColour: string;
}
-const LineIcon: React.FC<LineIconProps> = ({
+const RouteIcon: React.FC<RouteIconProps> = ({
line,
mode = "default",
colour,
@@ -23,17 +24,11 @@ const LineIcon: React.FC<LineIconProps> = ({
}, [actualLine]);
const actualLineColour = useMemo(() => {
- const actualColour = colour?.startsWith("#") ? colour : `#${colour}`;
- return colour ? actualColour : `var(--line-${formattedLine.toLowerCase()})`;
- }, [formattedLine]);
+ return formatHex(colour, true);
+ }, [colour]);
const actualTextColour = useMemo(() => {
- const actualTextColour = textColour?.startsWith("#")
- ? textColour
- : `#${textColour}`;
- return textColour
- ? actualTextColour
- : `var(--line-${formattedLine.toLowerCase()}-text, #000000)`;
- }, [formattedLine]);
+ return formatHex(textColour, true);
+ }, [textColour]);
return (
<span
@@ -50,4 +45,4 @@ const LineIcon: React.FC<LineIconProps> = ({
);
};
-export default LineIcon;
+export default RouteIcon;
diff --git a/src/frontend/app/components/StopGalleryItem.tsx b/src/frontend/app/components/StopGalleryItem.tsx
index de369d8..d407816 100644
--- a/src/frontend/app/components/StopGalleryItem.tsx
+++ b/src/frontend/app/components/StopGalleryItem.tsx
@@ -1,7 +1,7 @@
import React from "react";
import { Link } from "react-router";
import StopDataProvider, { type Stop } from "../data/StopDataProvider";
-import LineIcon from "./LineIcon";
+import RouteIcon from "./RouteIcon";
interface StopGalleryItemProps {
stop: Stop;
@@ -42,7 +42,7 @@ const StopGalleryItem: React.FC<StopGalleryItemProps> = ({ stop }) => {
</div>
<div className="flex flex-wrap gap-1 items-center">
{stop.lines?.slice(0, 5).map((lineObj) => (
- <LineIcon
+ <RouteIcon
key={lineObj.line}
line={lineObj.line}
colour={lineObj.colour}
diff --git a/src/frontend/app/components/StopItem.tsx b/src/frontend/app/components/StopItem.tsx
index 391e605..35ccf6d 100644
--- a/src/frontend/app/components/StopItem.tsx
+++ b/src/frontend/app/components/StopItem.tsx
@@ -1,7 +1,7 @@
import React from "react";
import { Link } from "react-router";
import StopDataProvider, { type Stop } from "../data/StopDataProvider";
-import LineIcon from "./LineIcon";
+import RouteIcon from "./RouteIcon";
interface StopItemProps {
stop: Stop;
@@ -25,7 +25,7 @@ const StopItem: React.FC<StopItemProps> = ({ stop }) => {
</div>
<div className="flex flex-wrap gap-1 mt-1">
{stop.lines?.map((lineObj) => (
- <LineIcon
+ <RouteIcon
key={lineObj.line}
line={lineObj.line}
colour={lineObj.colour}
diff --git a/src/frontend/app/components/arrivals/ArrivalCard.css b/src/frontend/app/components/arrivals/ArrivalCard.css
index 0e5af25..ee5f8e6 100644
--- a/src/frontend/app/components/arrivals/ArrivalCard.css
+++ b/src/frontend/app/components/arrivals/ArrivalCard.css
@@ -1,5 +1,28 @@
@import "../../tailwind.css";
+.arrival-card {
+ display: grid;
+ grid-template-areas:
+ "icon route minutes"
+ "meta meta meta";
+
+ grid-template-columns: auto 1fr auto;
+}
+
+arrival-card--icon {
+ grid-area: icon;
+}
+.arrival-card--route {
+ grid-area: route;
+ min-width: 0;
+}
+.arrival-card--minutes {
+ grid-area: minutes;
+}
+.arrival-card--meta {
+ grid-area: meta;
+}
+
.time-running {
@apply bg-green-600/20 dark:bg-green-600/25 text-[#1a9e56] dark:text-[#22c55e];
}
diff --git a/src/frontend/app/components/arrivals/ArrivalCard.tsx b/src/frontend/app/components/arrivals/ArrivalCard.tsx
index f1fc1a5..b99d3aa 100644
--- a/src/frontend/app/components/arrivals/ArrivalCard.tsx
+++ b/src/frontend/app/components/arrivals/ArrivalCard.tsx
@@ -2,7 +2,7 @@ import { AlertTriangle, BusFront, LocateIcon } from "lucide-react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import Marquee from "react-fast-marquee";
import { useTranslation } from "react-i18next";
-import LineIcon from "~/components/LineIcon";
+import RouteIcon from "~/components/RouteIcon";
import { type Arrival } from "../../api/schema";
import "./ArrivalCard.css";
@@ -188,21 +188,22 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
<Tag
type={isClickable ? "button" : undefined}
onClick={isClickable ? onClick : undefined}
- className={`w-full text-left flex items-start gap-3 rounded-xl px-3 py-3 transition-all bg-slate-50 dark:bg-slate-800 border border-gray-200 dark:border-gray-700 shadow-sm ${
+ className={`arrival-card w-full text-left gap-x-4 gap-y-3 rounded-xl p-2 transition-all bg-slate-50 dark:bg-slate-800 border border-gray-200 dark:border-gray-700 shadow-sm ${
isClickable
? "hover:border-blue-400 dark:hover:border-blue-500 active:scale-[0.98] cursor-pointer"
: ""
}`}
>
- <div className="shrink-0 min-w-[7ch] mt-0.5">
- <LineIcon
+ <div className="arrival-card--icon shrink-0 min-w-[3ch] mt-0.5">
+ <RouteIcon
line={route.shortName}
colour={route.colour}
textColour={route.textColour}
mode="pill"
/>
</div>
- <div className="flex-1 min-w-0 flex flex-col gap-1">
+
+ <div className="arrival-card--route shrink-0 flex flex-col gap-1">
<div className="flex justify-between items-start gap-2">
<div className="flex-1 min-w-0">
<span
@@ -211,74 +212,70 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
{headsign.destination}
</span>
{headsign.marquee && (
- <div className="mt-0.5">
+ <div className="mt-0.5 w-auto">
<AutoMarquee text={headsign.marquee} />
</div>
)}
</div>
- <div
- className={`
- inline-flex items-center justify-center px-2 py-1 rounded-lg shrink-0 min-w-12
- ${timeClass}
- `.trim()}
- >
- <div className="flex flex-col items-center leading-none">
- <span className="text-lg font-bold">{etaValue}</span>
- <span className="text-[0.55rem] font-bold uppercase tracking-tighter opacity-80">
- {etaUnit}
- </span>
- </div>
- </div>
</div>
+ </div>
+ <div
+ className={`
+ arrival-card--minutes inline-flex items-center justify-center px-2 py-1 rounded-lg min-w-11 text-center ${timeClass}
+ `.trim()}
+ >
+ <span className="text-lg font-bold leading-tight">
+ {etaValue}&apos;
+ </span>
+ </div>
- <div className="flex items-center gap-2 flex-wrap">
- {metaChips.map((chip, idx) => {
- let chipColourClasses = "";
- switch (chip.tone) {
- case "delay-ok":
- chipColourClasses =
- "bg-green-600/10 dark:bg-green-600/20 text-green-700 dark:text-green-300";
- break;
- case "delay-warn":
- chipColourClasses =
- "bg-amber-600/10 dark:bg-yellow-600/20 text-amber-700 dark:text-yellow-300";
- break;
- case "delay-critical":
- chipColourClasses =
- "bg-red-400/10 dark:bg-red-600/20 text-red-600 dark:text-red-300";
- break;
- case "delay-early":
- chipColourClasses =
- "bg-blue-400/10 dark:bg-blue-600/20 text-blue-700 dark:text-blue-300";
- break;
- case "warning":
- chipColourClasses =
- "bg-orange-400/10 dark:bg-orange-600/20 text-orange-700 dark:text-orange-300";
- break;
- default:
- chipColourClasses =
- "bg-black/[0.04] dark:bg-white/[0.08] text-slate-500 dark:text-slate-400";
- }
+ <div className="arrival-card--meta flex w-auto items-center gap-2 flex-wrap">
+ {metaChips.map((chip, idx) => {
+ let chipColourClasses = "";
+ switch (chip.tone) {
+ case "delay-ok":
+ chipColourClasses =
+ "bg-green-600/10 dark:bg-green-600/20 text-green-700 dark:text-green-300";
+ break;
+ case "delay-warn":
+ chipColourClasses =
+ "bg-amber-600/10 dark:bg-yellow-600/20 text-amber-700 dark:text-yellow-300";
+ break;
+ case "delay-critical":
+ chipColourClasses =
+ "bg-red-400/10 dark:bg-red-600/20 text-red-600 dark:text-red-300";
+ break;
+ case "delay-early":
+ chipColourClasses =
+ "bg-blue-400/10 dark:bg-blue-600/20 text-blue-700 dark:text-blue-300";
+ break;
+ case "warning":
+ chipColourClasses =
+ "bg-orange-400/10 dark:bg-orange-600/20 text-orange-700 dark:text-orange-300";
+ break;
+ default:
+ chipColourClasses =
+ "bg-black/[0.04] dark:bg-white/[0.08] text-slate-500 dark:text-slate-400";
+ }
- return (
- <span
- key={`${chip.label}-${idx}`}
- className={`text-xs px-2.5 py-0.5 rounded-full flex items-center justify-center gap-1 shrink-0 font-medium tracking-wide ${chipColourClasses}`}
- >
- {chip.kind === "gps" && (
- <LocateIcon className="w-3 h-3 inline-block" />
- )}
- {chip.kind === "warning" && (
- <AlertTriangle className="w-3 h-3 inline-block" />
- )}
- {chip.kind === "vehicle" && (
- <BusFront className="w-3 h-3 inline-block" />
- )}
- {chip.label}
- </span>
- );
- })}
- </div>
+ return (
+ <span
+ key={`${chip.label}-${idx}`}
+ className={`text-xs px-2.5 py-0.5 rounded-full flex items-center justify-center gap-1 shrink-0 font-medium tracking-wide ${chipColourClasses}`}
+ >
+ {chip.kind === "gps" && (
+ <LocateIcon className="w-3 h-3 inline-block" />
+ )}
+ {chip.kind === "warning" && (
+ <AlertTriangle className="w-3 h-3 inline-block" />
+ )}
+ {chip.kind === "vehicle" && (
+ <BusFront className="w-3 h-3 inline-block" />
+ )}
+ {chip.label}
+ </span>
+ );
+ })}
</div>
</Tag>
);
diff --git a/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx b/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx
index 44c8eda..4ba08b1 100644
--- a/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx
+++ b/src/frontend/app/components/arrivals/ReducedArrivalCard.tsx
@@ -1,7 +1,7 @@
import { AlertTriangle, BusFront, LocateIcon } from "lucide-react";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
-import LineIcon from "~/components/LineIcon";
+import RouteIcon from "~/components/RouteIcon";
import { type Arrival } from "../../api/schema";
import "./ArrivalCard.css";
@@ -145,7 +145,7 @@ export const ReducedArrivalCard: React.FC<ArrivalCardProps> = ({
}`}
>
<div className="shrink-0 min-w-[7ch] mt-0.5">
- <LineIcon
+ <RouteIcon
line={route.shortName}
colour={route.colour}
textColour={route.textColour}
diff --git a/src/frontend/app/components/map/StopSummarySheet.tsx b/src/frontend/app/components/map/StopSummarySheet.tsx
index 7b4dd7b..be2df67 100644
--- a/src/frontend/app/components/map/StopSummarySheet.tsx
+++ b/src/frontend/app/components/map/StopSummarySheet.tsx
@@ -6,7 +6,7 @@ import { Link } from "react-router";
import { ArrivalList } from "~/components/arrivals/ArrivalList";
import { useStopArrivals } from "../../hooks/useArrivals";
import { ErrorDisplay } from "../ErrorDisplay";
-import LineIcon from "../LineIcon";
+import RouteIcon from "../RouteIcon";
import "./StopSummarySheet.css";
import { StopSummarySheetSkeleton } from "./StopSummarySheetSkeleton";
@@ -53,7 +53,7 @@ export const StopSummarySheet: React.FC<StopSheetProps> = ({
<div className={`flex flex-wrap flex-row gap-2`}>
{data?.routes.map((lineObj) => (
- <LineIcon
+ <RouteIcon
key={lineObj.shortName}
line={lineObj.shortName}
mode="pill"