Live data connection notice
The red notice shown when the browser has lost its live connection to Bosun.
On this page
Code
import { Button } from "@bosunkit/components/button";
import { LiveDataConnectionNotice } from "@bosunkit/components/live-data-connection-notice";
import { Reading } from "@bosunkit/components/reading";
import { RefreshCw, Unplug } from "lucide-react";
import { useEffect, useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"A passage page whose readings update every second until the browser loses its stream from Bosun: the notice " +
"appears above them and every reading turns stale with its age, until the stream returns.";
interface Feed {
readonly connected: boolean;
/** Seconds in the current state. */
readonly seconds: number;
readonly sog: number;
readonly depth: number;
readonly aws: number;
}
const START: Feed = { connected: true, seconds: 0, sog: 6.4, depth: 23.6, aws: 16.2 };
/** How long the example's stream stays up, and how long it stays lost, before it changes by itself. */
const LIVE_FOR_SECONDS: number = 8;
const LOST_FOR_SECONDS: number = 14;
function tick(feed: Feed): Feed {
if (!feed.connected) {
return feed.seconds + 1 >= LOST_FOR_SECONDS
? { ...feed, connected: true, seconds: 0 }
: { ...feed, seconds: feed.seconds + 1 };
}
if (feed.seconds + 1 >= LIVE_FOR_SECONDS) {
return { ...feed, connected: false, seconds: 0 };
}
// A gentle swell on the readings, so a frozen value is easy to tell from a live one.
const phase: number = feed.seconds + 1;
return {
...feed,
seconds: phase,
sog: 6.4 + Math.sin(phase / 2) * 0.3,
depth: 23.6 + Math.sin(phase / 4) * 1.2,
aws: 16.2 + Math.cos(phase / 3) * 1.4,
};
}
export default function Showcase(): ReactElement {
const [feed, setFeed]: [Feed, Dispatch<SetStateAction<Feed>>] = useState<Feed>(START);
useEffect((): (() => void) => {
const timer: ReturnType<typeof setInterval> = setInterval((): void => {
setFeed(tick);
}, 1000);
return (): void => {
clearInterval(timer);
};
}, []);
const integrity: "fresh" | "stale" = feed.connected ? "fresh" : "stale";
const age: number | undefined = feed.connected ? undefined : feed.seconds + 1;
return (
<div style={{ display: "flex", flexDirection: "column", width: "100%" }}>
{!feed.connected && <LiveDataConnectionNotice data-testid="live-data-lost" />}
<div style={{ display: "flex", gap: "var(--space-xl)", flexWrap: "wrap", alignItems: "flex-start" }}>
<Reading
value={feed.sog}
unit="kn"
label="SOG"
source="GPS"
fractionDigits={1}
integrity={integrity}
ageSeconds={age}
/>
<Reading
value={feed.depth}
unit="m"
label="Depth"
source="DST810"
fractionDigits={1}
maxDigits={2}
integrity={integrity}
ageSeconds={age}
/>
<Reading
value={feed.aws}
unit="kn"
label="AWS"
source="WS320"
fractionDigits={1}
integrity={integrity}
ageSeconds={age}
/>
<Reading value={41} unit="NM" label="To A Coruña" source="Route" integrity={integrity} ageSeconds={age} />
</div>
<div style={{ marginTop: "var(--space-lg)" }}>
<Button
size="sm"
onClick={(): void => {
setFeed((current: Feed): Feed => ({ ...current, connected: !current.connected, seconds: 0 }));
}}
>
{feed.connected ? <Unplug size={14} /> : <RefreshCw size={14} />}
{feed.connected ? "Drop the stream" : "Restore the stream"}
</Button>
</div>
</div>
);
}
The live data connection notice tells the crew that the page they are reading has stopped updating. It
carries a red badge and one line of text, and it is announced as an alert so a screen reader speaks it when it
appears. It takes the props of a div.
Examples
Lost
Code
import { LiveDataConnectionNotice } from "@bosunkit/components/live-data-connection-notice";
import type { ReactElement } from "react";
export const description: string = "The notice shown when the browser has lost its live connection to Bosun.";
export default function Lost(): ReactElement {
return <LiveDataConnectionNotice />;
}
Motion
By default the notice rises in when the caller shows it inside a MotionPresence as the connection is lost, and
fades out when it returns; a notice there on the first render does not move. motion takes the kit's
motion prop: true does the same outside a presence, and false turns it off. Its words
are announced from the first frame, and under reduced motion it only fades.
Code
import { Button } from "@bosunkit/components/button";
import { MotionPresence } from "@bosunkit/components/motion";
import { LiveDataConnectionNotice } from "@bosunkit/components/live-data-connection-notice";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"The notice rises in when the browser loses its live connection to Bosun, announced from its first frame, and fades out when it comes back.";
export default function Motion(): ReactElement {
const [lost, setLost]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
return (
<div style={{ display: "flex", flexDirection: "column", gap: "var(--space-md)", width: "100%" }}>
<Button
size="sm"
onClick={(): void => {
setLost((current: boolean): boolean => !current);
}}
>
{lost ? "The connection returns" : "The connection drops"}
</Button>
<div style={{ minHeight: 56 }}>
<MotionPresence>{lost && <LiveDataConnectionNotice key="notice" />}</MotionPresence>
</div>
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| motion | MotionProp | undefined | – | How the notice moves. By default, inside a MotionPresence where the caller shows and hides it, it rises in when
the loss is noticed and fades out when it is over; a notice there on the first render does not move. true does
the same outside a presence too; a preset (fade, rise, scale) or { enter, exit } chooses; false turns
it off. Its words are announced from the first frame. |
| ref | Ref<HTMLDivElement> | undefined | – | The notice's element. |
Also takes every attribute of HTMLAttributes<HTMLDivElement>.
Related
- Data integrity: how a reading that stops arriving is shown.
- Badge: the red badge the notice carries.