Readout list
A list that lines up the columns of its readout list items.
On this page
Passage to Sóller
Yanmar 4JH110
Code
import { ReadoutDataQuality, ReadoutList, ReadoutListItem, ReadoutValueType } from "@bosunkit/instruments";
import { Label } from "@bosunkit/components/label";
import { Switch } from "@bosunkit/components/switch";
import { Compass, Fuel, Gauge, Thermometer, Waves, Wind } from "lucide-react";
import { useEffect, useId, useState } from "react";
import type { CSSProperties, Dispatch, ReactElement, SetStateAction } from "react";
export const description: string =
"Two of Althea's lists on a one-second feed: the depth grows from one to three digits off the shelf and every " +
"column keeps its place, degree rows give the others a spacer, rows without an icon keep the icon's column, a " +
"text value shortens to fit its card, a heading inside the list renders as given, and a switch outlines the " +
"columns the list reserves.";
const PANEL_STYLE: CSSProperties = {
display: "grid",
gap: 6,
padding: 12,
borderRadius: 10,
background: "var(--container-section-color)",
minWidth: 0,
};
const HEADING_STYLE: CSSProperties = {
margin: "0 0 4px",
fontSize: 12,
fontWeight: 600,
color: "var(--element-neutral-color)",
};
export default function Showcase(): ReactElement {
const [step, setStep]: [number, Dispatch<SetStateAction<number>>] = useState<number>(0);
const switchId: string = useId();
const [showColumns, setShowColumns]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
useEffect((): (() => void) => {
const timer: number = window.setInterval((): void => {
setStep((previous: number): number => previous + 1);
}, 1000);
return (): void => {
window.clearInterval(timer);
};
}, []);
const phase: number = step % 30;
// Off the Mallorca shelf the bottom drops from 8 m to over 180 m in half a minute of the feed.
const depth: number = phase < 25 ? 8.4 * Math.pow(1.13, phase) : 8.4;
const heading: number = 64 + 2 * Math.sin(step / 3);
const courseOverGround: number = heading + 4;
const speed: number = 6.1 + 0.3 * Math.sin(step / 2);
const rpm: number = 2280 + 40 * Math.sin(step / 4);
const coolant: number = 83 + 2 * Math.sin(step / 5);
const fuelRate: number = 3.2 + 0.2 * Math.sin(step / 3);
return (
<div style={{ display: "grid", gap: 12, width: "100%", maxWidth: 620 }}>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(270px, 1fr))", gap: 12 }}>
<div style={PANEL_STYLE}>
<ReadoutList showDebugOverlay={showColumns}>
<p style={HEADING_STYLE}>Passage to Sóller</p>
<ReadoutListItem
label="HDG"
hasLeadingIcon
leadingIcon={<Compass size={16} aria-hidden />}
value={heading}
hasDegree
maxDigits={3}
src="Fluxgate"
hasSetpoint
setpoint={64}
/>
<ReadoutListItem label="COG" value={courseOverGround} hasDegree maxDigits={3} src="GPS" />
<ReadoutListItem label="SOG" value={speed} fractionDigits={1} unit="kn" src="GPS" hasAdvice advice={6.5} />
<ReadoutListItem
label="Depth"
hasLeadingIcon
leadingIcon={<Waves size={16} aria-hidden />}
value={depth}
fractionDigits={1}
maxDigits={3}
unit="m"
src="Echosounder"
/>
<ReadoutListItem
label="TWS"
hasLeadingIcon
leadingIcon={<Wind size={16} aria-hidden />}
value={17.6}
fractionDigits={1}
unit="kn"
src="Masthead"
dataQuality={ReadoutDataQuality.lowIntegrity}
/>
<ReadoutListItem label="Waypoint" value="Cap de Formentor" valueType={ReadoutValueType.text} src="Route" />
</ReadoutList>
</div>
<div style={PANEL_STYLE}>
<ReadoutList showDebugOverlay={showColumns}>
<p style={HEADING_STYLE}>Yanmar 4JH110</p>
<ReadoutListItem
label="Engine speed"
hasLeadingIcon
leadingIcon={<Gauge size={16} aria-hidden />}
value={rpm}
maxDigits={4}
unit="rpm"
src="J1939"
/>
<ReadoutListItem
label="Coolant"
hasLeadingIcon
leadingIcon={<Thermometer size={16} aria-hidden />}
value={coolant}
unit="°C"
src="J1939"
/>
<ReadoutListItem
label="Fuel rate"
hasLeadingIcon
leadingIcon={<Fuel size={16} aria-hidden />}
value={fuelRate}
fractionDigits={1}
unit="L/h"
src="J1939"
/>
<ReadoutListItem
label="Oil pressure"
value={null}
unit="bar"
src="Sender"
dataQuality={ReadoutDataQuality.invalid}
/>
<ReadoutListItem label="Generator" value={0} off offText="Off" unit="kW" src="Panel" />
<ReadoutListItem label="Next service in 38 engine hours" hasValue={false} />
</ReadoutList>
</div>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Switch id={switchId} checked={showColumns} onCheckedChange={setShowColumns} />
<Label htmlFor={switchId} style={{ marginBottom: 0, cursor: "pointer" }}>
Outline the reserved columns
</Label>
</div>
</div>
);
}
A readout list groups readout list items and lines up their columns. It reads each row and passes the same reserved widths to all of them: the longest unit, the widest value, setpoint and advice, and the longest source. No row is clipped by a wider one, and the columns line up from row to row. Alignment is always on.
Rows must be direct children of the list. Other children are rendered as given.
Examples
Depth and heading
Code
import { ReadoutList, ReadoutListItem } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string = "Two rows in one list, which reserves shared widths so their values line up.";
export default function DepthAndHeading(): ReactElement {
return (
<ReadoutList>
<ReadoutListItem label="Depth" value={123.4} fractionDigits={1} unit="m" />
<ReadoutListItem label="HDG" value={182} hasDegree />
</ReadoutList>
);
}
Shared columns and grouping
Put ReadoutListItem elements directly inside the list so their reservations participate in alignment. A wrapper around an item is not an aligned row. Group readings with compatible context and use a heading when the source, reference or equipment changes.
Test a transition across a digit boundary, missing values, the longest unit and source, and narrow layouts. Reserve the expected range to reduce column movement; alignment alone does not guarantee that a dense list fits a phone. Split the group or choose a stacked layout when labels and units cannot remain readable.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| showDebugOverlay | boolean | undefined | false | Development aid: outline each row's readout building blocks (red), degree columns (blue) and degree spacer (green) so the reserved column widths are visible. Propagated to every row. Off by default. |
| children | ReactNode | undefined | – | The ReadoutListItem rows (the upstream default slot). Direct
ReadoutListItem children are auto-aligned; any other children render
untouched. The upstream element also found rows nested inside wrapper
elements via DOM traversal — the React translation aligns direct children
only, so keep rows immediate children of the list. |
| className | string | undefined | – | Class name added to the root element. |
Related
- Readout list item: the rows a list aligns.
- Readout: a single value with more room.
- Instruments: the other instruments.