Skip to content
design
Components

Startup modal

bosund's startup as a modal, with the boot steps, a progress bar and a status line.

On this page
  1. Choose a start; each answer bosund gives appears here.
Three starts of bosund, clean, with voice failing and with navigation failing, each opened from its button and logging every status answer beside it.
Code
import { Button } from "@bosunkit/components/button";
import { StartupModal, type StartupPhase, type StartupStatus } from "@bosunkit/components/startup-modal";
import { MicOff, Navigation, Power, type LucideIcon } from "lucide-react";
import {
  useEffect,
  useRef,
  useState,
  type Dispatch,
  type ReactElement,
  type RefObject,
  type SetStateAction,
} from "react";

export const description: string =
  "Three starts of bosund to choose from, a clean one, one where voice fails and one where navigation fails, " +
  "each opening the modal and logging every /startup/status answer beside the buttons.";

/** One answer of `/startup/status`, and how long bosund takes to reach it after the previous answer. */
interface BootStep {
  readonly status: StartupStatus;
  readonly afterMilliseconds: number;
}

interface Scenario {
  readonly id: string;
  readonly icon: LucideIcon;
  readonly label: string;
  readonly script: readonly BootStep[];
}

function running(phase: StartupPhase, appAvailable: boolean, afterMilliseconds: number): BootStep {
  return { status: { state: "starting", phase, app_available: appAvailable }, afterMilliseconds };
}

// The modal opens on "Looking for Bosun" (no answer yet); each script is every answer after that.
const SCENARIOS: readonly Scenario[] = [
  {
    id: "clean",
    icon: Power,
    label: "Start Bosun",
    script: [
      running("preflight", false, 1200),
      running("registry", false, 900),
      running("core", false, 1100),
      // The app is available behind the modal from here, so it offers to continue in the background.
      running("deferred", true, 1400),
      { status: { state: "ready", phase: "ready", app_available: true }, afterMilliseconds: 4200 },
    ],
  },
  {
    id: "voice",
    icon: MicOff,
    label: "Start with voice failing",
    script: [
      running("preflight", false, 900),
      running("registry", false, 800),
      running("core", false, 1000),
      running("deferred", true, 1400),
      {
        status: { state: "degraded", phase: "ready", app_available: true, failure_codes: ["deferred_startup_failed"] },
        afterMilliseconds: 2600,
      },
    ],
  },
  {
    id: "core",
    icon: Navigation,
    label: "Start with navigation failing",
    script: [
      running("preflight", false, 900),
      running("registry", false, 800),
      running("core", false, 1000),
      {
        status: { state: "failed", phase: "core", app_available: false, failure_codes: ["core_startup_failed"] },
        afterMilliseconds: 2200,
      },
    ],
  },
];

function describeStatus(status: StartupStatus): string {
  const codes: string =
    status.failure_codes === undefined || status.failure_codes.length === 0
      ? ""
      : ` · ${status.failure_codes.join(", ")}`;
  return `${status.state} · ${status.phase}${status.app_available ? " · app available" : ""}${codes}`;
}

export default function Showcase(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  const [status, setStatus]: [StartupStatus | null, Dispatch<SetStateAction<StartupStatus | null>>] =
    useState<StartupStatus | null>(null);
  const [answers, setAnswers]: [readonly string[], Dispatch<SetStateAction<readonly string[]>>] = useState<
    readonly string[]
  >([]);
  const timers: RefObject<number[]> = useRef<number[]>([]);

  function stop(): void {
    for (const timer of timers.current) {
      window.clearTimeout(timer);
    }
    timers.current = [];
  }

  // A start still running when the example unmounts must not set state afterwards.
  useEffect((): (() => void) => {
    return (): void => {
      for (const timer of timers.current) {
        window.clearTimeout(timer);
      }
    };
  }, []);

  function start(scenario: Scenario): void {
    stop();
    setStatus(null);
    setAnswers(["no answer yet · looking for Bosun"]);
    setOpen(true);
    let elapsed: number = 0;
    for (const step of scenario.script) {
      elapsed += step.afterMilliseconds;
      timers.current.push(
        window.setTimeout((): void => {
          setStatus(step.status);
          setAnswers((current: readonly string[]): readonly string[] => [...current, describeStatus(step.status)]);
          if (step.status.state === "ready") {
            setOpen(false);
          }
        }, elapsed),
      );
    }
  }

  function dismiss(): void {
    stop();
    setOpen(false);
  }

  return (
    <div style={{ display: "flex", gap: "var(--space-xl)", flexWrap: "wrap", width: "100%" }}>
      <div style={{ display: "flex", flexDirection: "column", gap: "var(--space-sm)" }}>
        {SCENARIOS.map((scenario: Scenario): ReactElement => {
          const Icon: LucideIcon = scenario.icon;
          return (
            <Button
              key={scenario.id}
              variant={scenario.id === "clean" ? "primary" : "default"}
              onClick={(): void => {
                start(scenario);
              }}
            >
              <Icon size={14} />
              {scenario.label}
            </Button>
          );
        })}
      </div>
      <ol
        aria-label="Answers from /startup/status"
        style={{
          flex: 1,
          minWidth: 260,
          margin: 0,
          padding: "var(--space-sm) var(--space-md)",
          listStyle: "none",
          border: "1px solid var(--border)",
          borderRadius: "var(--radius-md)",
          fontFamily: "var(--font-mono)",
          fontSize: 11,
          color: "var(--text-dim)",
          display: "flex",
          flexDirection: "column",
          gap: "var(--space-xs)",
        }}
      >
        {answers.length === 0 ? (
          <li>Choose a start; each answer bosund gives appears here.</li>
        ) : (
          answers.map((answer: string, index: number): ReactElement => (
            <li key={index}>
              {String(index).padStart(2, "0")} {answer}
            </li>
          ))
        )}
      </ol>
      <StartupModal status={status} open={open} onDismiss={dismiss} />
    </div>
  );
}

The startup modal shows the crew how far bosund has come in starting: a greeting, the four boot steps with the current one marked, a progress bar and a status line. It renders whatever /startup/status last answered, and null while bosund cannot be reached. The steps follow the daemon's phases: vessel checks (preflight), core systems (registry), navigation and instruments (core) and voice and watchkeeping (deferred, which loads the models and the dialogue). The app becomes available behind the modal when core ends, so during the last step the modal offers to continue in the background. When the daemon finishes with an issue reported, the state is degraded and the modal offers to continue with limited features. When a step fails, the step and the bar are marked and the status line says so; the recovery screen stays available. In the app the modal closes when the state is ready.

Each example is a script of /startup/status answers on timers, and each opens the modal from a button because the modal covers the page. Closing the modal stops the script, and the button starts it again from the beginning.

Examples

Live start

A full start. The modal opens on "Looking for Bosun", then each phase runs in turn, and the modal closes when the daemon is ready.

A simulated start: bosund moves through its four phases on timers, the checklist fills and the bar advances, and the modal closes itself when the daemon is ready.
Code
import { Button } from "@bosunkit/components/button";
import { StartupModal, type StartupPhase, type StartupStatus } from "@bosunkit/components/startup-modal";
import { useEffect, useRef, useState, type Dispatch, type ReactElement, type RefObject, type SetStateAction } from "react";

export const description: string =
  "A simulated start: bosund moves through its four phases on timers, the checklist fills and the bar advances, and the modal closes itself when the daemon is ready.";

export const readsClock: boolean = true;

/** One answer of `/startup/status`, and how long bosund takes to reach it after the previous answer. */
interface BootStep {
  readonly status: StartupStatus;
  readonly afterMilliseconds: number;
}

function running(phase: StartupPhase, appAvailable: boolean): StartupStatus {
  return { state: "starting", phase, app_available: appAvailable };
}

// The modal opens on "Looking for Bosun" (no answer yet); the script is every answer after that.
const SCRIPT: readonly BootStep[] = [
  { status: running("preflight", false), afterMilliseconds: 1200 },
  { status: running("registry", false), afterMilliseconds: 900 },
  { status: running("core", false), afterMilliseconds: 1100 },
  { status: running("deferred", true), afterMilliseconds: 1800 },
  { status: { state: "ready", phase: "ready", app_available: true }, afterMilliseconds: 2400 },
];

export default function LiveBoot(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  const [status, setStatus]: [StartupStatus | null, Dispatch<SetStateAction<StartupStatus | null>>] = useState<
    StartupStatus | null
  >(null);
  const timers: RefObject<number[]> = useRef<number[]>([]);

  function stop(): void {
    for (const timer of timers.current) {
      window.clearTimeout(timer);
    }
    timers.current = [];
  }

  // A boot still running when the example unmounts must not set state afterwards.
  useEffect((): (() => void) => {
    return (): void => {
      for (const timer of timers.current) {
        window.clearTimeout(timer);
      }
    };
  }, []);

  function start(): void {
    stop();
    setStatus(null);
    setOpen(true);
    let elapsed: number = 0;
    for (const step of SCRIPT) {
      elapsed += step.afterMilliseconds;
      timers.current.push(
        window.setTimeout((): void => {
          setStatus(step.status);
          if (step.status.state === "ready") {
            setOpen(false);
          }
        }, elapsed),
      );
    }
  }

  function dismiss(): void {
    stop();
    setOpen(false);
  }

  return (
    <>
      <Button onClick={start}>Start Bosun</Button>
      <StartupModal status={status} open={open} onDismiss={dismiss} />
    </>
  );
}

Slow last step

The app is already available, so the modal offers to continue in the background while voice and watchkeeping load. Close it to carry on, or wait for the start to finish.

A start held at the last step: the app is already available behind the modal, so it offers to continue in the background while voice and watchkeeping finish loading.
Code
import { Button } from "@bosunkit/components/button";
import { StartupModal, type StartupPhase, type StartupStatus } from "@bosunkit/components/startup-modal";
import { useEffect, useRef, useState, type Dispatch, type ReactElement, type RefObject, type SetStateAction } from "react";

export const description: string =
  "A start held at the last step: the app is already available behind the modal, so it offers to continue in the background while voice and watchkeeping finish loading.";

export const readsClock: boolean = true;

/** One answer of `/startup/status`, and how long bosund takes to reach it after the previous answer. */
interface BootStep {
  readonly status: StartupStatus;
  readonly afterMilliseconds: number;
}

function running(phase: StartupPhase, appAvailable: boolean): StartupStatus {
  return { state: "starting", phase, app_available: appAvailable };
}

// The modal opens on "Looking for Bosun" (no answer yet); the script is every answer after that.
const SCRIPT: readonly BootStep[] = [
  { status: running("preflight", false), afterMilliseconds: 900 },
  { status: running("registry", false), afterMilliseconds: 800 },
  { status: running("core", false), afterMilliseconds: 1000 },
  { status: running("deferred", true), afterMilliseconds: 1400 },
  { status: { state: "ready", phase: "ready", app_available: true }, afterMilliseconds: 15000 },
];

export default function SlowVoiceModels(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  const [status, setStatus]: [StartupStatus | null, Dispatch<SetStateAction<StartupStatus | null>>] = useState<
    StartupStatus | null
  >(null);
  const timers: RefObject<number[]> = useRef<number[]>([]);

  function stop(): void {
    for (const timer of timers.current) {
      window.clearTimeout(timer);
    }
    timers.current = [];
  }

  // A boot still running when the example unmounts must not set state afterwards.
  useEffect((): (() => void) => {
    return (): void => {
      for (const timer of timers.current) {
        window.clearTimeout(timer);
      }
    };
  }, []);

  function start(): void {
    stop();
    setStatus(null);
    setOpen(true);
    let elapsed: number = 0;
    for (const step of SCRIPT) {
      elapsed += step.afterMilliseconds;
      timers.current.push(
        window.setTimeout((): void => {
          setStatus(step.status);
          if (step.status.state === "ready") {
            setOpen(false);
          }
        }, elapsed),
      );
    }
  }

  function dismiss(): void {
    stop();
    setOpen(false);
  }

  return (
    <>
      <Button onClick={start}>Start Bosun</Button>
      <StartupModal status={status} open={open} onDismiss={dismiss} />
    </>
  );
}

Degraded: voice and watchkeeping

The last phase failed (deferred_startup_failed). The vessel can proceed, so the modal waits for the crew to continue with limited features.

A start where the last phase fails: the app is up and the vessel can proceed, but the modal waits for the crew to continue with limited features.
Code
import { Button } from "@bosunkit/components/button";
import { StartupModal, type StartupPhase, type StartupStatus } from "@bosunkit/components/startup-modal";
import { useEffect, useRef, useState, type Dispatch, type ReactElement, type RefObject, type SetStateAction } from "react";

export const description: string =
  "A start where the last phase fails: the app is up and the vessel can proceed, but the modal waits for the crew to continue with limited features.";

export const readsClock: boolean = true;

/** One answer of `/startup/status`, and how long bosund takes to reach it after the previous answer. */
interface BootStep {
  readonly status: StartupStatus;
  readonly afterMilliseconds: number;
}

function running(phase: StartupPhase, appAvailable: boolean): StartupStatus {
  return { state: "starting", phase, app_available: appAvailable };
}

// The modal opens on "Looking for Bosun" (no answer yet); the script is every answer after that.
const SCRIPT: readonly BootStep[] = [
  { status: running("preflight", false), afterMilliseconds: 900 },
  { status: running("registry", false), afterMilliseconds: 800 },
  { status: running("core", false), afterMilliseconds: 1000 },
  { status: running("deferred", true), afterMilliseconds: 1400 },
  {
    status: {
      state: "degraded",
      phase: "ready",
      app_available: true,
      failure_codes: ["deferred_startup_failed"],
    },
    afterMilliseconds: 2600,
  },
];

export default function DegradedVoice(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  const [status, setStatus]: [StartupStatus | null, Dispatch<SetStateAction<StartupStatus | null>>] = useState<
    StartupStatus | null
  >(null);
  const timers: RefObject<number[]> = useRef<number[]>([]);

  function stop(): void {
    for (const timer of timers.current) {
      window.clearTimeout(timer);
    }
    timers.current = [];
  }

  // A boot still running when the example unmounts must not set state afterwards.
  useEffect((): (() => void) => {
    return (): void => {
      for (const timer of timers.current) {
        window.clearTimeout(timer);
      }
    };
  }, []);

  function start(): void {
    stop();
    setStatus(null);
    setOpen(true);
    let elapsed: number = 0;
    for (const step of SCRIPT) {
      elapsed += step.afterMilliseconds;
      timers.current.push(
        window.setTimeout((): void => {
          setStatus(step.status);
        }, elapsed),
      );
    }
  }

  function dismiss(): void {
    stop();
    setOpen(false);
  }

  return (
    <>
      <Button onClick={start}>Start Bosun</Button>
      <StartupModal status={status} open={open} onDismiss={dismiss} />
    </>
  );
}

Degraded: terminal and phone apps

The listener for the terminal and phone apps could not bind (grpc_unavailable). The web app works, and the modal reports it only after the last phase.

A start where the listener for the terminal and phone apps could not bind: bosund finishes every phase, then reports the issue and offers to continue with limited features.
Code
import { Button } from "@bosunkit/components/button";
import { StartupModal, type StartupPhase, type StartupStatus } from "@bosunkit/components/startup-modal";
import { useEffect, useRef, useState, type Dispatch, type ReactElement, type RefObject, type SetStateAction } from "react";

export const description: string =
  "A start where the listener for the terminal and phone apps could not bind: bosund finishes every phase, then reports the issue and offers to continue with limited features.";

export const readsClock: boolean = true;

/** One answer of `/startup/status`, and how long bosund takes to reach it after the previous answer. */
interface BootStep {
  readonly status: StartupStatus;
  readonly afterMilliseconds: number;
}

function running(phase: StartupPhase, appAvailable: boolean): StartupStatus {
  return { state: "starting", phase, app_available: appAvailable };
}

// The modal opens on "Looking for Bosun" (no answer yet); the script is every answer after that.
const SCRIPT: readonly BootStep[] = [
  { status: running("preflight", false), afterMilliseconds: 900 },
  { status: running("registry", false), afterMilliseconds: 800 },
  { status: running("core", false), afterMilliseconds: 1000 },
  { status: running("deferred", true), afterMilliseconds: 1400 },
  {
    status: {
      state: "degraded",
      phase: "ready",
      app_available: true,
      failure_codes: ["grpc_unavailable"],
    },
    afterMilliseconds: 1800,
  },
];

export default function DegradedGrpc(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  const [status, setStatus]: [StartupStatus | null, Dispatch<SetStateAction<StartupStatus | null>>] = useState<
    StartupStatus | null
  >(null);
  const timers: RefObject<number[]> = useRef<number[]>([]);

  function stop(): void {
    for (const timer of timers.current) {
      window.clearTimeout(timer);
    }
    timers.current = [];
  }

  // A boot still running when the example unmounts must not set state afterwards.
  useEffect((): (() => void) => {
    return (): void => {
      for (const timer of timers.current) {
        window.clearTimeout(timer);
      }
    };
  }, []);

  function start(): void {
    stop();
    setStatus(null);
    setOpen(true);
    let elapsed: number = 0;
    for (const step of SCRIPT) {
      elapsed += step.afterMilliseconds;
      timers.current.push(
        window.setTimeout((): void => {
          setStatus(step.status);
        }, elapsed),
      );
    }
  }

  function dismiss(): void {
    stop();
    setOpen(false);
  }

  return (
    <>
      <Button onClick={start}>Start Bosun</Button>
      <StartupModal status={status} open={open} onDismiss={dismiss} />
    </>
  );
}

Failed at the core step

Navigation and instruments did not start (core_startup_failed). The step shows a mark, the bar changes color and no button is offered, because the app never became available.

A start that fails at the third step: the step shows a mark, the bar turns to the alarm color and the app never becomes available.
Code
import { Button } from "@bosunkit/components/button";
import { StartupModal, type StartupPhase, type StartupStatus } from "@bosunkit/components/startup-modal";
import { useEffect, useRef, useState, type Dispatch, type ReactElement, type RefObject, type SetStateAction } from "react";

export const description: string =
  "A start that fails at the third step: the step shows a mark, the bar turns to the alarm color and the app never becomes available.";

export const readsClock: boolean = true;

/** One answer of `/startup/status`, and how long bosund takes to reach it after the previous answer. */
interface BootStep {
  readonly status: StartupStatus;
  readonly afterMilliseconds: number;
}

function running(phase: StartupPhase, appAvailable: boolean): StartupStatus {
  return { state: "starting", phase, app_available: appAvailable };
}

// The modal opens on "Looking for Bosun" (no answer yet); the script is every answer after that.
const SCRIPT: readonly BootStep[] = [
  { status: running("preflight", false), afterMilliseconds: 900 },
  { status: running("registry", false), afterMilliseconds: 800 },
  { status: running("core", false), afterMilliseconds: 1000 },
  {
    status: { state: "failed", phase: "core", app_available: false, failure_codes: ["core_startup_failed"] },
    afterMilliseconds: 2400,
  },
];

export default function CoreFailed(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  const [status, setStatus]: [StartupStatus | null, Dispatch<SetStateAction<StartupStatus | null>>] = useState<
    StartupStatus | null
  >(null);
  const timers: RefObject<number[]> = useRef<number[]>([]);

  function stop(): void {
    for (const timer of timers.current) {
      window.clearTimeout(timer);
    }
    timers.current = [];
  }

  // A boot still running when the example unmounts must not set state afterwards.
  useEffect((): (() => void) => {
    return (): void => {
      for (const timer of timers.current) {
        window.clearTimeout(timer);
      }
    };
  }, []);

  function start(): void {
    stop();
    setStatus(null);
    setOpen(true);
    let elapsed: number = 0;
    for (const step of SCRIPT) {
      elapsed += step.afterMilliseconds;
      timers.current.push(
        window.setTimeout((): void => {
          setStatus(step.status);
        }, elapsed),
      );
    }
  }

  function dismiss(): void {
    stop();
    setOpen(false);
  }

  return (
    <>
      <Button onClick={start}>Start Bosun</Button>
      <StartupModal status={status} open={open} onDismiss={dismiss} />
    </>
  );
}

Failed at the vessel checks

The checks failed before any system came online (startup_checks_failed). The other three steps stay pending.

A start that fails at the first step, before any system is brought online: the other three steps stay pending.
Code
import { Button } from "@bosunkit/components/button";
import { StartupModal, type StartupPhase, type StartupStatus } from "@bosunkit/components/startup-modal";
import { useEffect, useRef, useState, type Dispatch, type ReactElement, type RefObject, type SetStateAction } from "react";

export const description: string =
  "A start that fails at the first step, before any system is brought online: the other three steps stay pending.";

export const readsClock: boolean = true;

/** One answer of `/startup/status`, and how long bosund takes to reach it after the previous answer. */
interface BootStep {
  readonly status: StartupStatus;
  readonly afterMilliseconds: number;
}

function running(phase: StartupPhase, appAvailable: boolean): StartupStatus {
  return { state: "starting", phase, app_available: appAvailable };
}

// The modal opens on "Looking for Bosun" (no answer yet); the script is every answer after that.
const SCRIPT: readonly BootStep[] = [
  { status: running("preflight", false), afterMilliseconds: 1000 },
  {
    status: { state: "failed", phase: "preflight", app_available: false, failure_codes: ["startup_checks_failed"] },
    afterMilliseconds: 1600,
  },
];

export default function ChecksFailed(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  const [status, setStatus]: [StartupStatus | null, Dispatch<SetStateAction<StartupStatus | null>>] = useState<
    StartupStatus | null
  >(null);
  const timers: RefObject<number[]> = useRef<number[]>([]);

  function stop(): void {
    for (const timer of timers.current) {
      window.clearTimeout(timer);
    }
    timers.current = [];
  }

  // A boot still running when the example unmounts must not set state afterwards.
  useEffect((): (() => void) => {
    return (): void => {
      for (const timer of timers.current) {
        window.clearTimeout(timer);
      }
    };
  }, []);

  function start(): void {
    stop();
    setStatus(null);
    setOpen(true);
    let elapsed: number = 0;
    for (const step of SCRIPT) {
      elapsed += step.afterMilliseconds;
      timers.current.push(
        window.setTimeout((): void => {
          setStatus(step.status);
        }, elapsed),
      );
    }
  }

  function dismiss(): void {
    stop();
    setOpen(false);
  }

  return (
    <>
      <Button onClick={start}>Start Bosun</Button>
      <StartupModal status={status} open={open} onDismiss={dismiss} />
    </>
  );
}

Looking for Bosun

Before bosund has answered, the modal shows no steps and an almost empty bar.

The modal before bosund has answered: no steps, an almost empty bar and a status line saying Bosun is being looked for.
Code
import { Button } from "@bosunkit/components/button";
import { StartupModal, type StartupStatus } from "@bosunkit/components/startup-modal";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "The modal before bosund has answered: no steps, an almost empty bar and a status line saying Bosun is being looked for.";

export const readsClock: boolean = true;

const STATUS: StartupStatus | null = null;

export default function Reconnecting(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  return (
    <>
      <Button
        onClick={(): void => {
          setOpen(true);
        }}
      >
        Show the startup modal
      </Button>
      <StartupModal
        status={STATUS}
        open={open}
        onDismiss={(): void => {
          setOpen(false);
        }}
      />
    </>
  );
}

Without motion

The startup modal moves as a task dialog does: it scales in over a fading overlay and leaves faster than it came, and only fades under reduced motion. motion={false} opens and closes it at once.

With `motion={false}` the startup modal opens and closes at once; by default it scales in as every modal does.
Code
import { Button } from "@bosunkit/components/button";
import { StartupModal, type StartupStatus } from "@bosunkit/components/startup-modal";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "With `motion={false}` the startup modal opens and closes at once; by default it scales in as every modal does.";

const STATUS: StartupStatus = { state: "starting", phase: "core", app_available: false };

export default function WithoutMotion(): ReactElement {
  const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false);
  return (
    <>
      <Button
        onClick={(): void => {
          setOpen(true);
        }}
      >
        Show it without motion
      </Button>
      <StartupModal
        status={STATUS}
        open={open}
        motion={false}
        onDismiss={(): void => {
          setOpen(false);
        }}
      />
    </>
  );
}

Props

PropTypeDefaultDescription
statusrequiredStartupStatus | null–bosund's startup status; null while bosund cannot be reached.
openrequiredboolean–Whether the modal is showing.
onDismissrequired() => void–Called when the crew close the modal or choose to continue.
motionboolean | undefinedtrueWhether the dialog moves as a TaskDialog does: on by default; false opens and closes it at once.