Skip to content
design
Components

Dial frame

A square face for one dial with its label, value and source under it, so a row of dials shares one face size.

On this page
Heading196°Compass
ESWN
True wind18 knMasthead
24681000
Speed over ground6.4 knGPS
Rate of turn4°/minCompass
035-35
Rudder3° SAutopilot
Five dials from the helm, each in a frame of one face size with its label, value and source under it.
Code
import { DialFrame } from "@bosunkit/components/dial-frame";
import {
  Compass,
  CompassDirection,
  Priority,
  RateOfTurn,
  Rudder,
  SpeedGauge,
  Wind,
  WindVariant,
} from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string =
  "Five dials from the helm on passage to Audierne, each in a frame of one face size with its label, value and " +
  "source under it, so the row lines up across the faces whatever the captions hold.";

export default function Showcase(): ReactElement {
  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fit, minmax(9em, 1fr))",
        gap: "var(--space-md)",
        width: "100%",
      }}
    >
      <DialFrame label="Heading" value="196°" source="Compass">
        <Compass
          heading={196}
          courseOverGround={200}
          headingSetpoint={196}
          direction={CompassDirection.NorthUp}
          priority={Priority.enhanced}
        />
      </DialFrame>
      <DialFrame label="True wind" value="18 kn" source="Masthead">
        <Wind
          variant={WindVariant.medium}
          priority={Priority.enhanced}
          currentWindSpeedKnots={18}
          currentWindFromDirection={245}
          vesselHeadingDeg={196}
        />
      </DialFrame>
      <DialFrame label="Speed over ground" value="6.4 kn" source="GPS">
        <SpeedGauge speed={6.4} maxSpeed={10} tickmarkInterval={2} showLabels />
      </DialFrame>
      <DialFrame label="Rate of turn" value="4°/min" source="Compass">
        <RateOfTurn rateOfTurnDegreesPerMinute={4} />
      </DialFrame>
      <DialFrame label="Rudder" value="3° S" source="Autopilot">
        <Rudder angle={3} maxAngle={35} showLabels />
      </DialFrame>
    </div>
  );
}

A dial frame holds one instrument in a square face and captions it: the label says what the dial shows, the value beside it repeats the reading in figures, and the source under them says where the data comes from. The instruments fill their parent, so the frame is what gives each dial its size. The face sits at the top of the frame, so dials side by side in an even grid share one face size and one top line, however long their captions are. The frame is a <figure> named by its label.

01One face size per rowEvery dial in a row is drawn at the same size, so a glance across them lands at the same height and the needles compare at a glance.
02Named under the faceThe label and the source sit under the dial, where they never cover the scale or the needle.
03Figures beside the needleThe value in figures sits next to the label, for the reading a needle gives only roughly.

Examples

Face size

By default the face takes the frame's full width, so a grid of equal columns gives every dial one size. faceSize fixes the face instead, in em, for dials in a row that is not a grid.

Heading196°Compass
Rudder3° SAutopilot
Two dials with a fixed 8em face, side by side in a wide row: the faces stay 8em whatever the row's width.
Code
import { DialFrame } from "@bosunkit/components/dial-frame";
import { Compass, CompassDirection, Priority, Rudder } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string =
  "Two dials with a fixed 8em face, side by side in a wide row: the faces stay 8em whatever the row's width.";

export default function FaceSize(): ReactElement {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: "var(--space-md)", width: "100%" }}>
      <DialFrame label="Heading" value="196°" source="Compass" faceSize="8em">
        <Compass heading={196} direction={CompassDirection.NorthUp} priority={Priority.enhanced} />
      </DialFrame>
      <DialFrame label="Rudder" value="3° S" source="Autopilot" faceSize="8em">
        <Rudder angle={3} maxAngle={35} />
      </DialFrame>
    </div>
  );
}

Caption

The label is required; the source and the value are optional. The frame is a size container, and a frame narrower than 8em puts the value under the label.

2468101214161820222426283032343638
Depth
2468101214161820222426283032343638
DepthSounder
2468101214161820222426283032343638
Depth12.4 mSounder
The caption's three parts: the depth dial with its label alone, with its source, and with its value; in a narrow frame the value moves under the label.
Code
import { DialFrame } from "@bosunkit/components/dial-frame";
import { DepthActual } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string =
  "The caption's three parts: the depth dial with its label alone, with its source, and with its value; in a narrow " +
  "frame the value moves under the label.";

export default function Caption(): ReactElement {
  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "repeat(3, minmax(6em, 10em))",
        gap: "var(--space-md)",
        width: "100%",
      }}
    >
      <DialFrame label="Depth">
        <DepthActual depth={12.4} draft={2.2} maxDepth={40} />
      </DialFrame>
      <DialFrame label="Depth" source="Sounder">
        <DepthActual depth={12.4} draft={2.2} maxDepth={40} />
      </DialFrame>
      <DialFrame label="Depth" source="Sounder" value="12.4 m">
        <DepthActual depth={12.4} draft={2.2} maxDepth={40} />
      </DialFrame>
    </div>
  );
}

Props

PropTypeDefaultDescription
labelrequiredstring–What the dial shows ("Heading", "True wind"), as the caller displays it; it names the figure.
sourcestring | undefined–Where the dial's data comes from ("Compass", "GPS"), as the caller displays it, under the label.
valueReactNode | undefined–The dial's value in words or figures ("196°", "6.4 kn"), as the caller writes it, beside the label.
childrenrequiredReactNode–The instrument the frame holds; it fills the square face.
faceSizestring | undefined–The side of the square face, as a CSS length in em ("9em"). By default the face takes the frame's full width, so frames in an even grid share one face size.
classNamestring | undefined–Classes added to the frame's own.
testIdstring | undefined–data-testid on the frame; its parts are ${testId}--face and ${testId}--caption.
refRef<HTMLElement> | undefined–The frame's <figure> element.
Do
Heading196°Compass
Rudder3° SAutopilot
Frame every dial in a row, so the faces share one size and one top line.
Don't
Don't size each dial by its own box. Faces of different sizes make the row hard to read across.
  • Instruments: the dials a frame holds, with their props.
  • Reading: a value with its source and integrity, for a figure without a dial.
  • Conning: a row of six framed dials at helm size.