Skip to content
design
Components

Due chip

A small chip marking a record whose date has passed or is coming, with the priority's shape as well as its color.

On this page

Service 3

Liferaft serviceViking 6-person RescYou, last serviced in Palma; the hydrostatic release is due with it.Overdue by 12 days
Raw-water impellerYanmar 4JH57, 1 412 engine hours; replace every 500 hours or each season.Due in 38 engine hours
Fire extinguishersThree 2 kg dry powder: saloon, galley and the cockpit locker.Due in 9 days

Documents 3

Insurance certificateThird-party cover for the Balearics, with the Spanish translation the port police ask for.Expired 3 days ago
Ship station licenseVHF with DSC, MMSI 224123456, call sign EA1234.Expires in 3 weeks
RegistrationSpanish flag, home port Valencia.Valid until March 2029
A vessel's service schedule and documents, with overdue records in the warning chip, records coming due in the caution chip, and one in date with none.
Code
import { DueChip } from "@bosunkit/components/due-chip";
import { SectionHeading } from "@bosunkit/components/section-heading";
import { FileText, FireExtinguisher, LifeBuoy, Radio, ShieldCheck, Wrench, type LucideIcon } from "lucide-react";
import type { ReactElement } from "react";

export const description: string =
  "SV Althea's documents and service schedule, with overdue items in the warning treatment, items coming due in " +
  "the caution one, and the records in date left plain.";

interface DueRecord {
  readonly id: string;
  readonly icon: LucideIcon;
  readonly name: string;
  readonly detail: string;
  /** The chip, or null for a record in date. */
  readonly due: { readonly priority: "warning" | "caution"; readonly label: string } | null;
  /** What a record in date shows. */
  readonly validUntil?: string;
}

const SECTIONS: readonly { readonly title: string; readonly records: readonly DueRecord[] }[] = [
  {
    title: "Service",
    records: [
      {
        id: "liferaft",
        icon: LifeBuoy,
        name: "Liferaft service",
        detail: "Viking 6-person RescYou, last serviced in Palma; the hydrostatic release is due with it.",
        due: { priority: "warning", label: "Overdue by 12 days" },
      },
      {
        id: "impeller",
        icon: Wrench,
        name: "Raw-water impeller",
        detail: "Yanmar 4JH57, 1 412 engine hours; replace every 500 hours or each season.",
        due: { priority: "caution", label: "Due in 38 engine hours" },
      },
      {
        id: "extinguishers",
        icon: FireExtinguisher,
        name: "Fire extinguishers",
        detail: "Three 2 kg dry powder: saloon, galley and the cockpit locker.",
        due: { priority: "caution", label: "Due in 9 days" },
      },
    ],
  },
  {
    title: "Documents",
    records: [
      {
        id: "insurance",
        icon: ShieldCheck,
        name: "Insurance certificate",
        detail: "Third-party cover for the Balearics, with the Spanish translation the port police ask for.",
        due: { priority: "warning", label: "Expired 3 days ago" },
      },
      {
        id: "vhf",
        icon: Radio,
        name: "Ship station license",
        detail: "VHF with DSC, MMSI 224123456, call sign EA1234.",
        due: { priority: "caution", label: "Expires in 3 weeks" },
      },
      {
        id: "registration",
        icon: FileText,
        name: "Registration",
        detail: "Spanish flag, home port Valencia.",
        due: null,
        validUntil: "Valid until March 2029",
      },
    ],
  },
];

export default function Showcase(): ReactElement {
  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
        gap: "var(--space-xl)",
        width: "100%",
      }}
    >
      {SECTIONS.map((section: { readonly title: string; readonly records: readonly DueRecord[] }): ReactElement => (
        <section key={section.title} style={{ display: "flex", flexDirection: "column", gap: "var(--space-md)" }}>
          <SectionHeading level={3} count={section.records.length}>
            {section.title}
          </SectionHeading>
          {section.records.map((record: DueRecord): ReactElement => {
            const Icon: LucideIcon = record.icon;
            return (
              <div key={record.id} style={{ display: "flex", gap: "var(--space-md)", alignItems: "flex-start" }}>
                <Icon size={18} aria-hidden="true" style={{ flexShrink: 0, marginTop: 2, color: "var(--text-dim)" }} />
                <div style={{ display: "flex", flexDirection: "column", gap: "var(--space-xs)", minWidth: 0 }}>
                  <span style={{ fontWeight: 600 }}>{record.name}</span>
                  <span style={{ fontSize: 12, color: "var(--text-dim)" }}>{record.detail}</span>
                  <span>
                    {record.due === null ? (
                      <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-dim)" }}>
                        {record.validUntil}
                      </span>
                    ) : (
                      <DueChip priority={record.due.priority} label={record.due.label} testId={`due-${record.id}`} />
                    )}
                  </span>
                </div>
              </div>
            );
          })}
        </section>
      ))}
    </div>
  );
}

A due chip marks a record whose date has come or is coming, such as a document running out or a service falling due. It draws the priority's shape beside its color and never flashes: an expiry is not an alarm, so it takes the warning and caution treatments only, or none for a date not yet near. The caller supplies the label already translated. A countdown drawn as a chip is a due chip, with its timer as the label.

Examples

Warning

Overdue
An obligation already past its date, in the warning color and shape.
Code
import { DueChip } from "@bosunkit/components/due-chip";
import type { ReactElement } from "react";

export const description: string = "An obligation already past its date, in the warning color and shape.";

export default function Warning(): ReactElement {
  return <DueChip priority="warning" label="Overdue" />;
}

Caution

Due in 3 days
An obligation coming due, in the caution color and shape.
Code
import { DueChip } from "@bosunkit/components/due-chip";
import type { ReactElement } from "react";

export const description: string = "An obligation coming due, in the caution color and shape.";

export default function Caution(): ReactElement {
  return <DueChip priority="caution" label="Due in 3 days" />;
}

No treatment yet

Due in 5 months
An obligation not yet near its date, in the chip's neutral colors with no shape.
Code
import { DueChip } from "@bosunkit/components/due-chip";
import type { ReactElement } from "react";

export const description: string = "An obligation not yet near its date, in the chip's neutral colors with no shape.";

export default function Neutral(): ReactElement {
  return <DueChip priority="none" label="Due in 5 months" />;
}

Motion

By default a due chip fades its new words in when the label changes, as when a service falls due; changeKey makes it fade when something else changes instead, as a countdown's treatment. motion takes the kit's motion prop: true also scales it in and out inside a MotionPresence, and false turns it off. It never flashes, whatever motion holds: an expiry is not an alarm. Under reduced motion it only fades.

Life raft serviceDue tomorrowWithout motionDue tomorrow
A life raft service chip that fades its new words in by default when the date passes and it turns from caution to warning, beside the same chip with motion={false}. It never flashes: an expiry is not an alarm.
Code
import { Button } from "@bosunkit/components/button";
import { DueChip } from "@bosunkit/components/due-chip";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "A life raft service chip that fades its new words in by default when the date passes and it turns from caution " +
  "to warning, beside the same chip with motion={false}. It never flashes: an expiry is not an alarm.";

export default function Motion(): ReactElement {
  const [overdue, setOverdue]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  return (
    <div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: "var(--space-sm)" }}>
      <Button
        size="sm"
        onClick={(): void => {
          setOverdue((current: boolean): boolean => !current);
        }}
      >
        {overdue ? "Back a day" : "A day later"}
      </Button>
      <span>Life raft service</span>
      <DueChip priority={overdue ? "warning" : "caution"} label={overdue ? "Overdue" : "Due tomorrow"} />
      <span>Without motion</span>
      <DueChip
        priority={overdue ? "warning" : "caution"}
        label={overdue ? "Overdue" : "Due tomorrow"}
        motion={false}
      />
    </div>
  );
}

Props

PropTypeDefaultDescription
priorityrequiredExtract<Priority, "warning" | "caution" | "none">–Treatment: warning for an obligation already past its date, caution for one coming due, none for one not yet near (the chip's neutral colors and no shape).
labelrequiredReactNode–Display text, already translated by the caller ("Overdue", "Due in 12 days"). It may carry markup, as a countdown's timer and <time> element.
changeKeyunknown–What the fade on change follows. Unless set, it is the label when the label is text or a number, and the priority when it is markup, which is a new object on every render. A countdown passes its treatment, so a tick doesn't fade.
testIdstring | undefined–Becomes data-testid on the root.
motionMotionProp | undefined–How the chip moves. By default it fades its new words in when the label changes, as a service falls due. true also scales it in as it mounts, wherever it is, and out when it leaves a MotionPresence; a preset (fade, rise, scale) or { enter, exit, change } chooses; false turns it off. It never flashes, as an expiry is not an alarm.
Do
Expired 2 days ago
Mark a date that has passed with a warning due chip, its shape beside its color.
Don't
Expired 2 days ago
Don't draw an expiry in the alarm color. A lapsed certificate is a due date, not an alarm, and an alarm color here teaches the crew to read past red.