Alert list row
One alert in an alert list, with its priority's icon and word, its state, its time and its acknowledge button.
On this page
Code
import { AlertListRow, type AlertListRowState } from "@bosunkit/components/alert-list-row";
import type { Priority } from "@bosunkit/components/priority";
import { useState, type CSSProperties, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"The alert list at anchor in Cala Portals at 02:20 UTC, by priority and then by time: the anchor alarm waits for " +
"acknowledgement and shows pending until the acknowledgement is confirmed, a close-quarters alarm is acknowledged, " +
"a low battery warning is silenced, a bilge warning has cleared without acknowledgement, and a wind sensor " +
"caution is active. Press a row to open its alert.";
interface Alert {
readonly id: string;
readonly priority: Exclude<Priority, "none">;
readonly title: string;
readonly description: string;
readonly source: string;
readonly time: string;
readonly state: AlertListRowState;
}
const RANK: Readonly<Record<Exclude<Priority, "none">, number>> = { emergency: 0, alarm: 1, warning: 2, caution: 3 };
const ALERTS: readonly Alert[] = [
{
id: "anchor",
priority: "alarm",
title: "Anchor dragging",
description: "58 m from the drop point, outside the 45 m swing circle. Start the engine and re-anchor.",
source: "Anchor watch",
time: "02:16",
state: "unacknowledged",
},
{
id: "cpa",
priority: "alarm",
title: "Close quarters, AR MEN",
description: "The fishing vessel passes 0.2 NM off in 6 minutes on her present course.",
source: "AIS",
time: "02:09",
state: "acknowledged",
},
{
id: "battery",
priority: "warning",
title: "House bank at 11.9 V",
description: "The anchor light and the fridge are drawing 6 A. Start the engine or switch the fridge off.",
source: "Battery monitor",
time: "02:12",
state: "silenced",
},
{
id: "bilge",
priority: "warning",
title: "Bilge pump ran 6 times in an hour",
description: "The bilge is dry now. Check the stern gland when you go below.",
source: "Bilge",
time: "01:30",
state: "cleared",
},
{
id: "wind",
priority: "caution",
title: "Masthead wind sensor silent",
description: "No reading for 4 minutes. Wind speed and direction show a dash until it reports again.",
source: "Instruments",
time: "02:02",
state: "unacknowledged",
},
];
/** How long the example takes to confirm an acknowledgement, as bosund's next board update would. */
const CONFIRM_MILLISECONDS: number = 1200;
const LIST: CSSProperties = {
width: "100%",
maxWidth: 680,
margin: 0,
padding: 0,
border: "1px solid var(--border)",
borderRadius: "var(--radius-md)",
overflow: "hidden",
};
function ordered(alerts: readonly Alert[]): Alert[] {
return [...alerts].sort((left: Alert, right: Alert): number => {
const byPriority: number = RANK[left.priority] - RANK[right.priority];
return byPriority !== 0 ? byPriority : right.time.localeCompare(left.time);
});
}
export default function Showcase(): ReactElement {
const [alerts, setAlerts]: [readonly Alert[], Dispatch<SetStateAction<readonly Alert[]>>] =
useState<readonly Alert[]>(ALERTS);
const [opened, setOpened]: [string, Dispatch<SetStateAction<string>>] = useState<string>("");
function acknowledge(id: string): () => Promise<void> {
return (): Promise<void> =>
new Promise<void>((resolve: () => void): void => {
window.setTimeout((): void => {
// A cleared alert leaves the list once it is acknowledged (MSC.302); any other reads acknowledged.
setAlerts((current: readonly Alert[]): readonly Alert[] =>
current.flatMap((alert: Alert): Alert[] => {
if (alert.id !== id) {
return [alert];
}
return alert.state === "cleared" ? [] : [{ ...alert, state: "acknowledged" }];
}),
);
resolve();
}, CONFIRM_MILLISECONDS);
});
}
return (
<div style={{ width: "100%", display: "flex", flexDirection: "column", gap: "var(--space-sm)" }}>
<ul style={LIST}>
{ordered(alerts).map((alert: Alert): ReactElement => (
<AlertListRow
key={alert.id}
priority={alert.priority}
title={alert.title}
description={alert.description}
source={alert.source}
time={alert.time}
timeReference="UTC"
state={alert.state}
acknowledgeAction={acknowledge(alert.id)}
onPress={(): void => {
setOpened(alert.title);
}}
/>
))}
</ul>
<span aria-live="polite" style={{ color: "var(--text-dim)" }}>
{opened === "" ? "Press a row to open its alert." : `Opened: ${opened}`}
</span>
</div>
);
}
An alert list row shows one alert as OpenBridge's alert list draws it: the alert icon for its priority, the title and a line saying what is wrong and what to do, the priority, the system that raised it and the state in words, and the time with its reference. Rows go in a list, grouped by priority and then by time (MSC.302 §9.16).
The icon carries the priority's IEC 62923 shape (▲ alarm, ● warning, ■ caution) and its state. An unacknowledged alarm or emergency flashes on the shared blink clock, in step with every other alert on screen, and keeps flashing under reduced motion; a warning is steady, as a caution is; nothing else in the row moves. The priority's color runs down the row's leading edge, and its word sits beside the source, so the row never says its priority by color alone.
States
| State | When | Icon | Acknowledge button |
|---|---|---|---|
unacknowledged | Raised, and nobody has acknowledged it. | Flashes | Offered |
silenced | Its sound is off for now, and it needs acknowledging. | Flashes, silenced | Offered |
acknowledged | Acknowledged while its cause is present. | Steady | None |
cleared | Its cause has cleared before anyone acknowledged it. | Flashes, rectified | Offered |
A caution needs no acknowledging: it reads Active while it lasts and Cleared after, and offers no button. An acknowledged alert whose cause clears leaves the list.
Examples
Priorities
- Person overboardJoost's lifejacket beacon triggered 0.1 NM astern.02:14 UTC
- Depth below 3.0 m2.6 m under the keel and shoaling.02:11 UTC
- Engine coolant 92 °C and risingReduce to 1800 rpm and check the raw-water strainer.02:09 UTC
- Fuel tank at 18 percentAbout 40 L left, enough for 7 hours at cruising revs.01:48 UTC
Code
import { AlertListRow } from "@bosunkit/components/alert-list-row";
import type { ReactElement } from "react";
export const description: string =
"The four priorities, unacknowledged: an emergency alarm and an alarm flash with the alarm icon, a warning flashes " +
"more slowly with its own, and a caution is steady. Each says its priority in words.";
export default function Priorities(): ReactElement {
return (
<ul style={{ width: "100%", maxWidth: 640, margin: 0, padding: 0 }}>
<AlertListRow
priority="emergency"
title="Person overboard"
description="Joost's lifejacket beacon triggered 0.1 NM astern."
source="AIS beacon"
time="02:14"
timeReference="UTC"
state="unacknowledged"
/>
<AlertListRow
priority="alarm"
title="Depth below 3.0 m"
description="2.6 m under the keel and shoaling."
source="Depth"
time="02:11"
timeReference="UTC"
state="unacknowledged"
/>
<AlertListRow
priority="warning"
title="Engine coolant 92 °C and rising"
description="Reduce to 1800 rpm and check the raw-water strainer."
source="Engine"
time="02:09"
timeReference="UTC"
state="unacknowledged"
/>
<AlertListRow
priority="caution"
title="Fuel tank at 18 percent"
description="About 40 L left, enough for 7 hours at cruising revs."
source="Tanks"
time="01:48"
timeReference="UTC"
state="unacknowledged"
/>
</ul>
);
}
States
- Anchor dragging02:16 UTC
- Anchor dragging02:16 UTC
- Anchor dragging02:16 UTC
- Anchor dragging02:16 UTC
Code
import { AlertListRow, type AlertListRowState } from "@bosunkit/components/alert-list-row";
import type { ReactElement } from "react";
export const description: string =
"One alarm in each state: unacknowledged and silenced flash, acknowledged is steady, and cleared keeps flashing " +
"until someone acknowledges it.";
const STATES: readonly AlertListRowState[] = ["unacknowledged", "silenced", "acknowledged", "cleared"];
export default function States(): ReactElement {
return (
<ul style={{ width: "100%", maxWidth: 640, margin: 0, padding: 0 }}>
{STATES.map((state: AlertListRowState): ReactElement => (
<AlertListRow
key={state}
priority="alarm"
title="Anchor dragging"
source="Anchor watch"
time="02:16"
timeReference="UTC"
state={state}
/>
))}
</ul>
);
}
Acknowledging
- House bank at 11.9 VStart the engine or switch the fridge off.02:12 UTC
Code
import { AlertListRow, type AlertListRowState } from "@bosunkit/components/alert-list-row";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"Acknowledging one alert: the button reads Acknowledging… until the action settles, and the row reads " +
"Acknowledged only when its state says so, here 1.5 s later as bosund's next board update would.";
export default function Acknowledge(): ReactElement {
const [state, setState]: [AlertListRowState, Dispatch<SetStateAction<AlertListRowState>>] =
useState<AlertListRowState>("unacknowledged");
function acknowledge(): Promise<void> {
return new Promise<void>((resolve: () => void): void => {
window.setTimeout((): void => {
setState("acknowledged");
resolve();
}, 1500);
});
}
return (
<ul style={{ width: "100%", maxWidth: 640, margin: 0, padding: 0 }}>
<AlertListRow
priority="warning"
title="House bank at 11.9 V"
description="Start the engine or switch the fridge off."
source="Battery monitor"
time="02:12"
timeReference="UTC"
state={state}
acknowledgeAction={acknowledge}
/>
</ul>
);
}
A failed acknowledgement
- Bilge water highThe float switch in the main bilge is up.02:18 UTC
Code
import { AlertListRow } from "@bosunkit/components/alert-list-row";
import type { ReactElement } from "react";
export const description: string =
"An acknowledgement that fails: the alarm stays unacknowledged and flashing, the row says so, and the button " +
"offers it again.";
function unreachable(): Promise<void> {
return new Promise<void>((_resolve: () => void, reject: (reason: Error) => void): void => {
window.setTimeout((): void => {
reject(new Error("bosund did not answer"));
}, 800);
});
}
export default function Failed(): ReactElement {
return (
<ul style={{ width: "100%", maxWidth: 640, margin: 0, padding: 0 }}>
<AlertListRow
priority="alarm"
title="Bilge water high"
description="The float switch in the main bilge is up."
source="Bilge"
time="02:18"
timeReference="UTC"
state="unacknowledged"
acknowledgeAction={unreachable}
/>
</ul>
);
}
Opening an alert
Code
import { AlertListRow } from "@bosunkit/components/alert-list-row";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"With `onPress` the alert is a button named by its title, which Enter and Space press, and the acknowledge button " +
"is the next tab stop.";
export default function Press(): ReactElement {
const [presses, setPresses]: [number, Dispatch<SetStateAction<number>>] = useState<number>(0);
return (
<div style={{ width: "100%", display: "flex", flexDirection: "column", gap: "var(--space-sm)" }}>
<ul style={{ width: "100%", maxWidth: 640, margin: 0, padding: 0 }}>
<AlertListRow
priority="caution"
title="Masthead wind sensor silent"
description="No reading for 4 minutes."
source="Instruments"
time="02:02"
timeReference="UTC"
state="unacknowledged"
onPress={(): void => {
setPresses(presses + 1);
}}
/>
</ul>
<span aria-live="polite" style={{ color: "var(--text-dim)" }}>
{presses === 0 ? "Not opened yet." : `Opened ${String(presses)} times.`}
</span>
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| priorityrequired | Exclude<Priority, "none"> | – | The alert's priority: its IEC 62923 shape, its color, its word and whether it flashes and needs acknowledging. |
| titlerequired | string | – | What the alert is about, translated by the caller. |
| description | ReactNode | undefined | – | A line under the title: what is wrong and what to do. |
| source | string | undefined | – | The system or sensor that raised the alert, translated by the caller (Anchor watch, Engine). |
| timerequired | string | – | When the alert was raised, as the caller writes it (02:16). |
| timeReferencerequired | string | – | The reference time is in, printed after it (UTC). |
| staterequired | AlertListRowState | – | Where the alert is in its lifecycle. |
| acknowledgeAction | (() => Promise<void>) | undefined | – | Acknowledges this alert. The row offers its acknowledge button while the alert needs acknowledging and this is
set, shows it pending until state says acknowledged (or, after the action resolves, for at most
ACKNOWLEDGE_CONFIRM_MS), shows the alert acknowledged only once state says so, and says why when a press does
not acknowledge it. |
| onPress | (() => void) | undefined | – | Called when the crew press the row, to open the alert's detail; the row is then a button. |
| className | string | undefined | – | Classes added to the row's own. |
| testId | string | undefined | – | data-testid on the row; its acknowledge button is ${testId}--ack. |
| ref | Ref<HTMLLIElement> | undefined | – | The row's <li> element. |
- Anchor dragging02:16 UTC
- House bank at 11.9 V02:18 UTC
- House bank at 11.9 V02:18 UTC
- Anchor dragging02:16 UTC
Related
- Alert icon: the icon each row draws, in every state.
- Alert card: one alert with its advisory and delivery, for the alert's own view.
- Alarms and severity: the priorities, their shapes and the flash rates.