import type { ConnectionStatus } from "@charcuterie/logic" import type { ComponentPropsWithRef, ReactNode, } from "react" import { DOT_SIZE_CLASS } from "../controlStyles.ts" import { INTENT_CONTENT_CLASS, INTENT_SOLID_FILL_CLASS, } from "../intentStyles.ts" import { getConnectionIntent, getConnectionLabel, getIsConnectionBusy, } from "../statusIntent.ts" import { toClassName } from "../toClassName.ts" import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.tsx" export type LiveStatusIndicatorSize = keyof typeof DOT_SIZE_CLASS export type LiveStatusIndicatorProps = Omit< ComponentPropsWithRef<"span">, "children" > & { /** Extra context, announced after the label. "3 of 4 bays." */ detail?: string isLabelVisible?: boolean /** Overrides the machine's own wording. Rarely wanted. */ label?: string size?: LiveStatusIndicatorSize status: ConnectionStatus } /** * Four repos, four different implementations, and **it is the thing * users read most** — a wall-mounted panel showing stale data with a * green dot is worse than one showing nothing. * * The second reason it is P0: all four collapse `connecting` and * `reconnecting` into one state, which reads wrong on exactly the * link that needs it. A user who has seen data wants "lost it, * getting it back" (amber, moving), not the cold-start spinner they * already sat through (blue) and not a dead link (red). That * distinction is why `connectionTransitions` is one of the two * machines `@charcuterie/logic` ships rather than something each app * defines, and `getConnectionIntent` / `getConnectionLabel` are * exhaustive switches over it — add a fifth state and this component * stops compiling instead of rendering a grey dot. * * `role="status"` + `aria-live="polite"` on the wrapper, so a * transition is announced without interrupting whatever the user is * doing. `aria-live="assertive"` would be wrong here even for * `disconnected`: it cuts off the sentence in progress, and the dot * is not going anywhere. */ export const LiveStatusIndicator = ({ className, detail, isLabelVisible = true, label, size = "md", status, ...spanProps }: LiveStatusIndicatorProps): ReactNode => { const intent = getConnectionIntent(status) const statusLabel = label ?? getConnectionLabel(status) return ( ) }