Skip to content
design
Components

Section divider

A named rule between groups of alert cards, colored by urgency.

On this page
Anchor drag02:11
42 m from the drop position, outside the 35 m swing radius.
Close quarters: MARIA K02:12
Cargo vessel, closest approach 0.3 nm in 9 min. Call her on VHF 16 or alter course to starboard.
Wind above the anchoring limit01:40
24 kn from the southwest, gusting 31 kn.
Acknowledged
House bank at 48%00:52
Reaches 40% at about 05:30 at the present draw.
Acknowledged
Shallow water23:18
Depth fell to 3.1 m on the approach; above 5.0 m since 23:26.
Acknowledged
The three sections of a night's alert list, with a count in each label and acknowledged alarms moving down.
Code
import { AlertCard } from "@bosunkit/components/alert-card";
import { Button } from "@bosunkit/components/button";
import type { Priority } from "@bosunkit/components/priority";
import { SectionDivider, type SectionDividerVariant } from "@bosunkit/components/section-divider";
import { Check } from "lucide-react";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "An alert list in its three sections, each label carrying its count: acknowledging an immediate alarm moves it " +
  "under monitoring, and the cleared section recedes at the bottom.";

interface Alert {
  readonly id: string;
  readonly priority: Priority;
  readonly title: string;
  readonly time: string;
  readonly message: string;
  readonly section: SectionDividerVariant;
}

const ALERTS: readonly Alert[] = [
  {
    id: "drag",
    priority: "alarm",
    title: "Anchor drag",
    time: "02:11",
    message: "42 m from the drop position, outside the 35 m swing radius.",
    section: "immediate",
  },
  {
    id: "cpa",
    priority: "alarm",
    title: "Close quarters: MARIA K",
    time: "02:12",
    message: "Cargo vessel, closest approach 0.3 nm in 9 min. Call her on VHF 16 or alter course to starboard.",
    section: "immediate",
  },
  {
    id: "wind",
    priority: "warning",
    title: "Wind above the anchoring limit",
    time: "01:40",
    message: "24 kn from the southwest, gusting 31 kn.",
    section: "monitoring",
  },
  {
    id: "battery",
    priority: "caution",
    title: "House bank at 48%",
    time: "00:52",
    message: "Reaches 40% at about 05:30 at the present draw.",
    section: "monitoring",
  },
  {
    id: "shallow",
    priority: "caution",
    title: "Shallow water",
    time: "23:18",
    message: "Depth fell to 3.1 m on the approach; above 5.0 m since 23:26.",
    section: "cleared",
  },
];

const SECTIONS: readonly { readonly variant: SectionDividerVariant; readonly name: string }[] = [
  { variant: "immediate", name: "Immediate" },
  { variant: "monitoring", name: "Monitoring" },
  { variant: "cleared", name: "Cleared tonight" },
];

export default function Showcase(): ReactElement {
  const [alerts, setAlerts]: [readonly Alert[], Dispatch<SetStateAction<readonly Alert[]>>] =
    useState<readonly Alert[]>(ALERTS);

  function acknowledge(id: string): void {
    setAlerts((current: readonly Alert[]): readonly Alert[] =>
      current.map((alert: Alert): Alert => (alert.id === id ? { ...alert, section: "monitoring" } : alert)),
    );
  }

  return (
    <div style={{ width: "100%", maxWidth: 560, display: "flex", flexDirection: "column", gap: 10 }}>
      {SECTIONS.map((section: { readonly variant: SectionDividerVariant; readonly name: string }): ReactElement => {
        const inSection: readonly Alert[] = alerts.filter((alert: Alert): boolean => alert.section === section.variant);
        return (
          <div key={section.variant} style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <SectionDivider label={`${section.name} (${String(inSection.length)})`} variant={section.variant} />
            {section.variant === "immediate" && inSection.length === 0 && (
              <span style={{ fontSize: 12, color: "var(--text-dim)", textAlign: "center" }}>
                Nothing needs the crew now.
              </span>
            )}
            {inSection.map((alert: Alert): ReactElement => (
              <AlertCard
                key={alert.id}
                priority={alert.priority}
                title={alert.title}
                time={alert.time}
                message={alert.message}
                immediate={alert.section === "immediate"}
                acknowledged={alert.section !== "immediate"}
                cleared={alert.section === "cleared"}
                actions={
                  alert.section === "immediate" ? (
                    <Button
                      variant="primary"
                      size="sm"
                      onClick={(): void => {
                        acknowledge(alert.id);
                      }}
                    >
                      <Check size={12} aria-hidden="true" />
                      Acknowledge
                    </Button>
                  ) : undefined
                }
              />
            ))}
          </div>
        );
      })}
    </div>
  );
}

A section divider names a group of alert cards with a label between two rules. Its variant sets the urgency of the group. immediate draws the label and its rules in the alarm color, monitoring in the dim text color, and cleared in the dim color at half strength, so the groups recede as the urgency does. In an alert card grid the divider spans both columns. The divider is a separator.

Examples

Immediate

Anchor drag02:11
42 m from the drop position, outside the 35 m swing radius.
The alarm color, over the alarm that needs the crew now.
Code
import { AlertCard } from "@bosunkit/components/alert-card";
import { SectionDivider } from "@bosunkit/components/section-divider";
import type { ReactElement } from "react";

export const description: string = "The alarm color, over the alarm that needs the crew now.";

export default function Immediate(): ReactElement {
  return (
    <div style={{ width: "100%", display: "flex", flexDirection: "column", gap: 10 }}>
      <SectionDivider label="Immediate" variant="immediate" />
      <AlertCard
        priority="alarm"
        title="Anchor drag"
        time="02:11"
        message="42 m from the drop position, outside the 35 m swing radius."
        immediate
      />
    </div>
  );
}

Monitoring

Wind above the anchoring limit01:40
24 kn from the southwest, gusting 31 kn.
Acknowledged
House bank at 48%00:52
Reaches 40% at about 05:30 at the present draw.
Acknowledged
The dim color, over the acknowledged alerts the crew is watching.
Code
import { AlertCard } from "@bosunkit/components/alert-card";
import { SectionDivider } from "@bosunkit/components/section-divider";
import type { ReactElement } from "react";

export const description: string = "The dim color, over the acknowledged alerts the crew is watching.";

export default function Monitoring(): ReactElement {
  return (
    <div style={{ width: "100%", display: "flex", flexDirection: "column", gap: 10 }}>
      <SectionDivider label="Monitoring" variant="monitoring" />
      <AlertCard
        priority="warning"
        title="Wind above the anchoring limit"
        time="01:40"
        message="24 kn from the southwest, gusting 31 kn."
        acknowledged
      />
      <AlertCard
        priority="caution"
        title="House bank at 48%"
        time="00:52"
        message="Reaches 40% at about 05:30 at the present draw."
        acknowledged
      />
    </div>
  );
}

Cleared

Shallow water23:18
Depth fell to 3.1 m on the approach; above 5.0 m since 23:26.
Acknowledged
The dimmest rule, over the alerts that are over.
Code
import { AlertCard } from "@bosunkit/components/alert-card";
import { SectionDivider } from "@bosunkit/components/section-divider";
import type { ReactElement } from "react";

export const description: string = "The dimmest rule, over the alerts that are over.";

export default function Cleared(): ReactElement {
  return (
    <div style={{ width: "100%", display: "flex", flexDirection: "column", gap: 10 }}>
      <SectionDivider label="Cleared" variant="cleared" />
      <AlertCard
        priority="caution"
        title="Shallow water"
        time="23:18"
        message="Depth fell to 3.1 m on the approach; above 5.0 m since 23:26."
        acknowledged
        cleared
      />
    </div>
  );
}

Motion

In a moving alert list (alert card grid) a divider fades in and out with its section and slides when the cards above it change, unless the crew is using the list. Outside a list it moves only when asked, and motion={false} stops its motion anywhere. Under reduced motion it takes its new place at once.

House battery low
12.1 V, 48 % charge
Fuel below a quarter
46 L in the tank
In an alert list the Monitoring divider slides down to make room when an alarm arrives above it, and back up when the alarm clears.
Code
import { AlertCard } from "@bosunkit/components/alert-card";
import { Button } from "@bosunkit/components/button";
import { AlertCardGrid } from "@bosunkit/components/alert-card-grid";
import { SectionDivider } from "@bosunkit/components/section-divider";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "In an alert list the Monitoring divider slides down to make room when an alarm arrives above it, and back up " +
  "when the alarm clears.";

export default function Motion(): ReactElement {
  const [raised, setRaised]: [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 => {
          setRaised((current: boolean): boolean => !current);
        }}
      >
        {raised ? "The anchor holds again" : "The anchor drags"}
      </Button>
      <AlertCardGrid>
        {raised && (
          <AlertCard
            key="drag"
            className="alarm--full"
            priority="alarm"
            title="Anchor drag"
            message="38 m outside the swing circle"
          />
        )}
        <SectionDivider key="monitoring" label="Monitoring" variant="monitoring" />
        <AlertCard key="battery" priority="caution" title="House battery low" message="12.1 V, 48 % charge" />
        <AlertCard key="fuel" priority="caution" title="Fuel below a quarter" message="46 L in the tank" />
      </AlertCardGrid>
    </div>
  );
}

Props

PropTypeDefaultDescription
labelrequiredstring–The section's name.
variantrequiredSectionDividerVariant–The section's urgency, which sets its color.
classNamestring | undefined–Classes added to the divider's own.
motionMotionProp | undefined–How the divider moves. Inside a moving alert list (AlertCardGrid) it fades in and out with its section and slides when the cards above it change; elsewhere it moves only when asked. false turns it off.