49 lines
2.1 KiB
TypeScript
49 lines
2.1 KiB
TypeScript
import { formatClock, formatShortDate } from "@/lib/display";
|
||
import { pixelLabel } from "@/lib/styles";
|
||
import type { AlertSeverity, WeatherAlert } from "@/lib/weather/types";
|
||
import { Slot } from "./Slot";
|
||
|
||
const SEVERITY_TEXT: Record<AlertSeverity, string> = {
|
||
extreme: "text-heart",
|
||
severe: "text-heart",
|
||
moderate: "text-gold-bright",
|
||
minor: "text-ink",
|
||
unknown: "text-ink",
|
||
};
|
||
|
||
/** Active weather alerts. Each one opens to show the full text and instructions. */
|
||
export function AlertsBanner({ alerts, source, timeZone }: { alerts: WeatherAlert[]; source: string | null; timeZone: string | null }) {
|
||
if (alerts.length === 0) return null;
|
||
|
||
const when = (iso: string | null) => (iso ? `${formatShortDate(iso, timeZone)}, ${formatClock(iso, timeZone)}` : null);
|
||
|
||
return (
|
||
<Slot as="section" tone="danger" innerClassName="flex flex-col gap-2 p-4 sm:px-5">
|
||
<h2 className="font-display font-bold text-heart">
|
||
{alerts.length === 1 ? "1 active alert" : `${alerts.length} active alerts`}
|
||
</h2>
|
||
|
||
{alerts.map((alert, i) => (
|
||
<details key={`${alert.title}-${alert.starts ?? i}`} className="group border-t border-heart-empty pt-2">
|
||
<summary className={`cursor-pointer list-none font-medium ${SEVERITY_TEXT[alert.severity]}`}>
|
||
<span className="mr-2 inline-block transition-transform group-open:rotate-90" aria-hidden="true">›</span>
|
||
{alert.title}
|
||
</summary>
|
||
<div className="mt-2 flex flex-col gap-2 pl-4 text-sm">
|
||
<p className={pixelLabel}>
|
||
{[alert.source, when(alert.starts) && `from ${when(alert.starts)}`, when(alert.ends) && `until ${when(alert.ends)}`]
|
||
.filter(Boolean)
|
||
.join(" · ")}
|
||
</p>
|
||
<p className="whitespace-pre-line">{alert.description}</p>
|
||
{alert.instruction && <p className="whitespace-pre-line text-gold-bright">{alert.instruction}</p>}
|
||
</div>
|
||
</details>
|
||
))}
|
||
|
||
<p className={pixelLabel}>
|
||
{source ? `Alerts from ${source} · ` : ""}Follow official local emergency guidance for life-safety decisions.
|
||
</p>
|
||
</Slot>
|
||
);
|
||
}
|