All files / src/components/layout StatusBar.tsx

0% Statements 0/87
100% Branches 1/1
100% Functions 1/1
0% Lines 0/87

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124                                                                                                                                                                                                                                                       
import type { JSX } from "react";
import { Circle, Menu, PanelLeft } from "lucide-react";
import type { ConnectionStatus, Environment, Session } from "../../hooks/types.js";
import { ICON_LG, ICON_XS } from "../../utils/iconSize.js";
import { HOME_URL, useAppNavigate } from "../../utils/navigation.js";
import { assetUrl } from "../../utils/assetUrl.js";
import { Tooltip } from "../display/Tooltip.js";
import styles from "./StatusBar.module.scss";
 
/** Human-readable label for each connection state. */
const CONNECTION_LABEL: Record<ConnectionStatus, string> = {
  connected: "Connected",
  connecting: "Connecting...",
  disconnected: "Disconnected",
};
 
/** CSS class for the connection dot in each state. */
const CONNECTION_DOT_CLASS: Record<ConnectionStatus, string> = {
  connected: styles.connected,
  connecting: styles.connecting,
  disconnected: styles.disconnected,
};
 
/** Props for the StatusBar component. */
interface StatusBarProps {
  /** Current connection state of the event stream. */
  connectionStatus: ConnectionStatus;
  /** List of all environments. */
  environments: Environment[];
  /** List of all sessions. */
  sessions: Session[];
  /** Callback to toggle the mobile sidebar drawer. */
  onToggleSidebar?: () => void;
  /** Whether the sidebar drawer is currently open (for aria-expanded). */
  sidebarOpen?: boolean;
  /** Callback to toggle the mobile context-nav drawer. */
  onToggleContextNav?: () => void;
  /** Whether the context-nav drawer is currently open (for aria-expanded). */
  contextNavOpen?: boolean;
}
 
/** Top status bar showing connection state, environment counts, and active session count. */
export function StatusBar({
  connectionStatus,
  environments,
  sessions,
  onToggleSidebar,
  sidebarOpen,
  onToggleContextNav,
  contextNavOpen,
}: StatusBarProps): JSX.Element {
  const navigate = useAppNavigate();
  const totalEnvs = environments.length;
  const connectedEnvs = environments.filter((e) => e.status === "connected").length;
  const activeCount = sessions.filter((s) => ["running", "idle"].includes(s.status)).length;
  const label = CONNECTION_LABEL[connectionStatus];
 
  return (
    <div className={styles.container}>
      {(onToggleContextNav ?? onToggleSidebar) && (
        <div className={styles.drawerControls}>
          {onToggleContextNav && (
            <button
              type="button"
              className={styles.hamburger}
              onClick={onToggleContextNav}
              aria-label="Toggle contexts"
              aria-expanded={contextNavOpen}
              data-testid="statusbar-context-toggle"
            >
              <PanelLeft size={ICON_LG} aria-hidden="true" />
            </button>
          )}
          {onToggleSidebar && (
            <button
              type="button"
              className={styles.hamburger}
              onClick={onToggleSidebar}
              aria-label="Toggle sidebar"
              aria-expanded={sidebarOpen}
            >
              <Menu size={ICON_LG} aria-hidden="true" />
            </button>
          )}
        </div>
      )}
      <Tooltip text="Home" placement="bottom">
        <button
          type="button"
          className={styles.brand}
          onClick={() => navigate(HOME_URL)}
          data-testid="statusbar-brand"
        >
          <img
            src={assetUrl("icon-192x192.png")}
            alt=""
            className={styles.brandLogo}
            aria-hidden="true"
            data-testid="statusbar-logo"
          />
          Grackle
        </button>
      </Tooltip>
      <div className={styles.info}>
        <span aria-label={label}>
          <span
            className={`${styles.connectionDot} ${CONNECTION_DOT_CLASS[connectionStatus]}`}
            aria-hidden="true"
          >
            <Circle size={ICON_XS} fill="currentColor" />
          </span>{" "}
          <span className={styles.connectionLabel} aria-hidden="true">
            {label}
          </span>
        </span>
        <span>
          {connectedEnvs}/{totalEnvs} env{totalEnvs !== 1 ? "s" : ""}
        </span>
        <span>{activeCount} active</span>
      </div>
    </div>
  );
}