aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/components/arrivals
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/app/components/arrivals')
-rw-r--r--src/frontend/app/components/arrivals/ArrivalCard.css17
-rw-r--r--src/frontend/app/components/arrivals/ArrivalCard.tsx74
-rw-r--r--src/frontend/app/components/arrivals/ArrivalList.tsx25
3 files changed, 116 insertions, 0 deletions
diff --git a/src/frontend/app/components/arrivals/ArrivalCard.css b/src/frontend/app/components/arrivals/ArrivalCard.css
new file mode 100644
index 0000000..5835352
--- /dev/null
+++ b/src/frontend/app/components/arrivals/ArrivalCard.css
@@ -0,0 +1,17 @@
+@import "../../tailwind.css";
+
+.time-running {
+ @apply bg-green-600/20 dark:bg-green-600/25 text-[#1a9e56] dark:text-[#22c55e];
+}
+
+.time-delayed {
+ @apply bg-orange-600/20 dark:bg-orange-600/25 text-[#d06100] dark:text-[#fb923c];
+}
+
+.time-past {
+ @apply bg-gray-600/20 dark:bg-gray-600/25 text-gray-600 dark:text-gray-400;
+}
+
+.time-scheduled {
+ @apply bg-blue-900/20 dark:bg-blue-600/25 text-[#0b3d91] dark:text-[#93c5fd];
+}
diff --git a/src/frontend/app/components/arrivals/ArrivalCard.tsx b/src/frontend/app/components/arrivals/ArrivalCard.tsx
new file mode 100644
index 0000000..de4fcc7
--- /dev/null
+++ b/src/frontend/app/components/arrivals/ArrivalCard.tsx
@@ -0,0 +1,74 @@
+import React, { useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import LineIcon from "~/components/LineIcon";
+import { type Arrival } from "../../api/schema";
+import "./ArrivalCard.css";
+
+interface ArrivalCardProps {
+ arrival: Arrival;
+ reduced?: boolean;
+}
+
+export const ArrivalCard: React.FC<ArrivalCardProps> = ({
+ arrival,
+ reduced,
+}) => {
+ const { t } = useTranslation();
+ const { route, headsign, estimate } = arrival;
+
+ const etaValue = estimate.minutes.toString();
+ const etaUnit = t("estimates.minutes", "min");
+
+ const timeClass = useMemo(() => {
+ switch (estimate.precision) {
+ case "confident":
+ return "time-running";
+ case "unsure":
+ return "time-delayed";
+ case "past":
+ return "time-past";
+ default:
+ return "time-scheduled";
+ }
+ }, [estimate.precision]);
+
+ return (
+ <div
+ 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
+ ${reduced ? "reduced" : ""}
+ `.trim()}
+ >
+ <div className="shrink-0 min-w-[7ch]">
+ <LineIcon
+ line={route.shortName}
+ colour={route.colour}
+ textColour={route.textColour}
+ mode="pill"
+ />
+ </div>
+ <div className="flex-1 min-w-0 flex flex-col gap-1">
+ <strong
+ className={`text-base overflow-hidden text-ellipsis line-clamp-2 leading-tight ${estimate.precision == "past" ? "line-through" : ""}`}
+ >
+ {headsign.destination}
+ </strong>
+ </div>
+ <div
+ className={`
+ inline-flex items-center justify-center px-2 py-1.5 rounded-xl shrink-0
+ ${timeClass}
+ `.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>
+ </div>
+ );
+};
diff --git a/src/frontend/app/components/arrivals/ArrivalList.tsx b/src/frontend/app/components/arrivals/ArrivalList.tsx
new file mode 100644
index 0000000..a1210d5
--- /dev/null
+++ b/src/frontend/app/components/arrivals/ArrivalList.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import { type Arrival } from "../../api/schema";
+import { ArrivalCard } from "./ArrivalCard";
+
+interface ArrivalListProps {
+ arrivals: Arrival[];
+ reduced?: boolean;
+}
+
+export const ArrivalList: React.FC<ArrivalListProps> = ({
+ arrivals,
+ reduced,
+}) => {
+ return (
+ <div className="flex flex-col gap-3">
+ {arrivals.map((arrival, index) => (
+ <ArrivalCard
+ key={`${arrival.route.shortName}-${index}`}
+ arrival={arrival}
+ reduced={reduced}
+ />
+ ))}
+ </div>
+ );
+};