Readout list item
A dense row for a list or a table, with a label, a value and the parts around it.
On this page
Autopilot: the crew is turning the knob toward 064° for the Sóller lighthouse; the setpoint shows its focus state.
Code
import {
ObcTextboxFontWeight,
ReadoutDataQuality,
ReadoutListItem,
ReadoutSize,
ReadoutValueType,
} from "@bosunkit/instruments";
import { Anchor, Compass, Gauge, MapPin, Navigation, Target, Waves, Wind } from "lucide-react";
import { useEffect, useState } from "react";
import type { CSSProperties, Dispatch, ReactElement, SetStateAction } from "react";
export const description: string =
"Althea's passage rows on a one-second feed, aligned by hand with space reservers: a clickable autopilot row whose setpoint the crew is dialing, advised speed, hinted zeros, degrees beside a degree spacer, low-integrity wind, an engine that is off, text rows and a label-only anchor row; tap a row to see what it is for.";
/** Shared widths so the standalone rows line up without a list. */
const VALUE_RESERVER: string = "000.0";
const UNIT_RESERVER: string = "nmi";
const SOURCE_RESERVER: string = "Echosounder";
const ROWS_STYLE: CSSProperties = {
display: "grid",
gap: 2,
padding: 12,
borderRadius: 10,
background: "var(--container-section-color)",
width: "100%",
maxWidth: 560,
};
const NOTE_STYLE: CSSProperties = { margin: "8px 4px 0", fontSize: 12, color: "var(--element-neutral-color)" };
type RowId = "autopilot" | "sog" | "depth" | "wind" | "engine";
const ROW_NOTES: Record<RowId, string> = {
autopilot:
"Autopilot: the crew is turning the knob toward 064° for the Sóller lighthouse; the setpoint shows its focus state.",
sog: "Speed over ground against the 6.5 kn advised for an 18:00 arrival in Port de Sóller.",
depth: "Depth from the echosounder; the forward sonar is the second source.",
wind: "True wind from the masthead unit, marked low integrity until it is recalibrated.",
engine: "The engine is stopped under sail; its readings return when it starts.",
};
export default function Showcase(): ReactElement {
const [step, setStep]: [number, Dispatch<SetStateAction<number>>] = useState<number>(0);
const [selected, setSelected]: [RowId, Dispatch<SetStateAction<RowId>>] = useState<RowId>("autopilot");
useEffect((): (() => void) => {
const timer: number = window.setInterval((): void => {
setStep((previous: number): number => previous + 1);
}, 1000);
return (): void => {
window.clearInterval(timer);
};
}, []);
const phase: number = step % 20;
const dialing: boolean = phase < 8;
const headingSetpoint: number = dialing ? 54 + phase * 1.25 : 64;
const heading: number = Math.min(headingSetpoint, 52 + phase * 0.9);
const speed: number = 6.1 + 0.3 * Math.sin(step / 3);
const distanceToGo: number = 14.2 - phase * 0.03;
const depth: number = 42 + 6 * Math.sin(step / 5);
const trueWind: number = 17 + 2 * Math.sin(step / 2);
function select(id: RowId): () => void {
return (): void => {
setSelected(id);
};
}
return (
<div style={ROWS_STYLE}>
<ReadoutListItem
label="Autopilot"
hasLeadingIcon
leadingIcon={<Navigation size={16} aria-hidden />}
value={heading}
hasDegree
maxDigits={3}
hasSetpoint
setpoint={headingSetpoint}
setpointIcon={<Compass size={14} aria-hidden />}
setpointOptions={{ touching: dialing, hintedZeros: true, spaceReserver: VALUE_RESERVER }}
valueOptions={{ weight: ObcTextboxFontWeight.bold, hintedZeros: true, spaceReserver: VALUE_RESERVER }}
unitOptions={{ spaceReserver: UNIT_RESERVER }}
srcOptions={{ spaceReserver: SOURCE_RESERVER }}
src="Fluxgate"
size={ReadoutSize.medium}
clickable
onClick={select("autopilot")}
/>
<ReadoutListItem
label="SOG"
hasLeadingIcon
leadingIcon={<Gauge size={16} aria-hidden />}
value={speed}
fractionDigits={1}
maxDigits={3}
unit="kn"
src="GPS"
hasAdvice
advice={6.5}
adviceIcon={<Target size={14} aria-hidden />}
adviceOptions={{ spaceReserver: VALUE_RESERVER }}
valueOptions={{ spaceReserver: VALUE_RESERVER }}
unitOptions={{ spaceReserver: UNIT_RESERVER }}
srcOptions={{ spaceReserver: SOURCE_RESERVER }}
hasDegreeSpacer
clickable
onClick={select("sog")}
/>
<ReadoutListItem
label="Distance to go"
hasLeadingIcon
leadingIcon={<MapPin size={16} aria-hidden />}
value={distanceToGo}
fractionDigits={1}
maxDigits={3}
unit="nmi"
src="Route"
valueOptions={{ weight: ObcTextboxFontWeight.semibold, spaceReserver: VALUE_RESERVER }}
unitOptions={{ spaceReserver: UNIT_RESERVER }}
srcOptions={{ spaceReserver: SOURCE_RESERVER }}
hasDegreeSpacer
/>
<ReadoutListItem
label="Depth"
hasLeadingIcon
leadingIcon={<Waves size={16} aria-hidden />}
value={depth}
fractionDigits={1}
maxDigits={3}
unit="m"
src="Echosounder"
valueOptions={{ spaceReserver: VALUE_RESERVER, hintedZeros: true }}
unitOptions={{ spaceReserver: UNIT_RESERVER }}
srcOptions={{ spaceReserver: SOURCE_RESERVER }}
hasDegreeSpacer
clickable
onClick={select("depth")}
/>
<ReadoutListItem
label="TWS"
hasLeadingIcon
leadingIcon={<Wind size={16} aria-hidden />}
value={trueWind}
fractionDigits={1}
maxDigits={3}
unit="kn"
src="Masthead"
dataQuality={ReadoutDataQuality.lowIntegrity}
valueOptions={{ spaceReserver: VALUE_RESERVER }}
unitOptions={{ spaceReserver: UNIT_RESERVER }}
srcOptions={{ spaceReserver: SOURCE_RESERVER, dataQuality: ReadoutDataQuality.lowIntegrity }}
hasDegreeSpacer
clickable
onClick={select("wind")}
/>
<ReadoutListItem
label="Engine"
value={0}
off
offText="Stopped"
unit="rpm"
src="J1939"
valueOptions={{ spaceReserver: VALUE_RESERVER }}
unitOptions={{ spaceReserver: UNIT_RESERVER }}
srcOptions={{ spaceReserver: SOURCE_RESERVER }}
clickable
onClick={select("engine")}
/>
<ReadoutListItem
label="Next waypoint"
value="Cap de Formentor"
valueType={ReadoutValueType.text}
src="Route"
srcOptions={{ spaceReserver: SOURCE_RESERVER }}
/>
<ReadoutListItem
label="Sea temp"
value={null}
unit="°C"
src="Transducer"
dataQuality={ReadoutDataQuality.invalid}
valueOptions={{ spaceReserver: VALUE_RESERVER }}
unitOptions={{ spaceReserver: UNIT_RESERVER }}
srcOptions={{ spaceReserver: SOURCE_RESERVER }}
/>
<ReadoutListItem
label="Anchor alarm off until Sóller"
hasLeadingIcon
leadingIcon={<Anchor size={16} aria-hidden />}
hasValue={false}
/>
<p style={NOTE_STYLE}>{ROW_NOTES[selected]}</p>
</div>
);
}
A readout list item is one row of a list or a table. It shows a label at the left and a value at the right, with an
optional unit, source, setpoint and advice value. stacking places the unit and the source: after the value, or beside
the label. fractionDigits sets the decimals and maxDigits reserves the integer width. clickable makes the row a
button that calls onClick.
Put rows in a readout list to line up their columns. Use the readout for a single value with more room.
Examples
With unit and source
Code
import { ReadoutListItem } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string = "A row with a label, a value to one decimal, a unit and the source of the reading.";
export default function WithUnitAndSource(): ReactElement {
return (
<ReadoutListItem label="Depth" value={23.456} fractionDigits={1} unit="m" src="Sonar" />
);
}
With setpoint
Code
import { ReadoutListItem } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string = "A row with a value of 20 and a setpoint of 30 beside it.";
export default function WithSetpoint(): ReactElement {
return (
<ReadoutListItem value={20} hasSetpoint setpoint={30} />
);
}
Rows and actions
Keep value, unit and source in stable columns through ReadoutList. Reserve widths for the largest expected value and test the longest translated label. Use stacking to fit the relationship, not to conceal missing units or distinguish two otherwise ambiguous readings.
Make a row clickable only when it opens details or performs a clear action. Keep its label meaningful as a button name and avoid embedding competing controls inside a clickable row. Read-only report rows should remain read-only, with source and quality information available in the surrounding text.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | undefined | – | Name of the quantity, shown at the left of the row, for example "Depth". |
| unit | string | undefined | – | Unit of the value, for example "m". Its place follows stacking. Nothing is shown when it is empty. |
| src | string | undefined | – | Name of the source of the reading, for example "Sonar". Its place follows stacking. A blank string shows no
source. |
| hasValue | boolean | undefined | true | Layout switch: false renders a deliberately value-less (label-only)
row that hugs its remaining parts. For a temporarily missing value keep
hasValue and set value to null instead — the dash keeps the value
block at full size, so the row does not shift when data arrives. |
| value | number | string | null | undefined | null | The value; null renders a dash. A number by default, or text when
{@link ReadoutListItemProps.valueType} is text. |
| valueType | ReadoutValueType | undefined | ReadoutValueType.number | How {@link ReadoutListItemProps.value} is interpreted. number (default)
formats it via fractionDigits; text renders it verbatim and ignores
the numeric format options. |
| off | boolean | undefined | false | Render the value as offText (e.g. equipment powered down). Affects the value only. |
| offText | string | undefined | "OFF" | Text shown in place of the value when off is true. Available when off==true. |
| hasSetpoint | boolean | undefined | false | Shows the setpoint block beside the value. |
| setpoint | number | undefined | – | Available when hasSetpoint==true. |
| hasAdvice | boolean | undefined | false | Shows the advice block beside the value. |
| advice | number | undefined | – | Available when hasAdvice==true. |
| size | ReadoutListItemSize | undefined | – | Density of the row: small (default), medium or large. Larger sizes use larger digits. |
| priority | ReadoutListItemPriority | undefined | – | Color emphasis of the value: regular (default) or enhanced, the in-command accent color. |
| stacking | ReadoutListItemStacking | undefined | – | Where the unit and source sit. trailing-unit (default) puts the unit after the value and the source after a
divider. leading-unit puts the unit beside the label. leading-src puts the source beside the label. |
| clickable | boolean | ReadoutListItemClickable | undefined | false | Renders the row as a button. true gives a squared surface; an object picks the corner style. |
| hasLeadingIcon | boolean | undefined | false | Shows leadingIcon before the label. |
| hasDegree | boolean | undefined | false | Appends a degree sign (°) after each number, for headings and angles. |
| hasDegreeSpacer | boolean | undefined | false | On a row without a degree sign, reserves the width of one, so its digits line up with the rows that have it. |
| hasLeadingIconSpacer | boolean | undefined | false | On a row without a leading icon, reserves the icon's width (house deviation), so its label starts where the labels
of the rows with icons start. ReadoutList sets it when any of its rows has one. |
| fractionDigits | number | undefined | 0 | Also formats the numeric setpoint / advice blocks, which stay numeric even when the value is text. Available when valueType==number || hasSetpoint || hasAdvice. |
| maxDigits | number | undefined | 0 | Same availability as {@link ReadoutListItemProps.fractionDigits}. |
| dataQuality | ReadoutListItemDataQuality | undefined | – | Marks the value as low-integrity or invalid with an outline. |
| alert | boolean | AlertFrameConfig | undefined | false | Row-level alert frame. true normalizes to a default frame; an
{@link AlertFrameConfig} object configures it; false renders none. |
| valueOptions | ReadoutValueOptions | undefined | – | Options for the value block: hinted leading zeros, font weight, icon, alert frame, data quality and reserved width. |
| setpointOptions | ReadoutSetpointOptions | undefined | – | Options for the setpoint block, including how it behaves as the value reaches it. Applies when hasSetpoint is
set. |
| adviceOptions | ReadoutAdviceOptions | undefined | – | Options for the advice block: hinted leading zeros, alert frame, data quality and reserved width. Applies when
hasAdvice is set. |
| unitOptions | ReadoutReserverOptions | undefined | – | Options for the unit: the longest unit to reserve width for, so the unit columns of several rows line up. |
| srcOptions | ReadoutSrcOptions | undefined | – | Options for the source: alert frame, data quality and the longest source to reserve width for. |
| showDebugOverlay | boolean | undefined | false | Development aid: outline the readout building blocks (red), the degree columns (blue) and the degree spacer (green) so reserver widths / alignment are visible. Off by default. |
| listColumns | ReadoutListColumns | undefined | – | Every unit and every source of the rows in this row's list (house deviation). ReadoutList sets it, so the
unit and source columns take the width of the widest entry as drawn, not of the one with the most characters
("Echosounder" is wider than "Bilge switch"). When set, it reserves those columns in place of the
spaceReserver of unitOptions and srcOptions. |
| leadingIcon | ReactNode | undefined | – | Icon before the label (the upstream leading-icon slot). Available when hasLeadingIcon==true. |
| valueIcon | ReactNode | undefined | – | Icon before the value (the upstream value-icon slot). Available when valueOptions.hasIcon==true. |
| setpointIcon | ReactNode | undefined | – | Overrides the default setpoint icon (the upstream setpoint-icon slot). |
| adviceIcon | ReactNode | undefined | – | Overrides the default advice icon (the upstream advice-icon slot). |
| onClick | MouseEventHandler<HTMLButtonElement> | undefined | – | Click handler for the clickable row (the upstream native button click event). |
| className | string | undefined | – | Class name added to the root element. |
Related
- Readout list: the list that lines up the rows.
- Readout: a single value with more room.
- Instruments: the other instruments.