COMPONENT / status-bar
Status Bar_
Thin --ground-void system strip with fake system strings, an optional 4-bar signal meter and a live UTC clock. Requires @cybr/tokens.
Install
1. Map the
@cybrnamespace, once per project. Add this inside yourcomponents.json, or into its existing"registries"object. Nocomponents.jsonyet? Runnpx shadcn@latest initfirst to create one."registries": { "@cybr": "https://cybr-ui.com/r/{name}.json" }2. Add components:
$ npx shadcn@latest add @cybr/status-bar
If shadcn reports Unknown registry "@cybr", step 1 is missing from that project's components.json.
Preview
SYS:OKREV 0.1NODE / EU-NE--:--:-- UTC
registry/ui/status-bar.tsx
"use client";
import { useSyncExternalStore, type ComponentProps, type ReactNode } from "react";
import { cn } from "@/lib/utils";
const BARS = [4, 7, 10, 13];
function subscribe(onChange: () => void) {
const timer = setInterval(onChange, 1000);
return () => clearInterval(timer);
}
function utcTime() {
const now = new Date();
const pad = (n: number) => String(n).padStart(2, "0");
return `${pad(now.getUTCHours())}:${pad(now.getUTCMinutes())}:${pad(now.getUTCSeconds())}`;
}
// Placeholder on the server and during hydration, so server and client markup
// match; the real time takes over right after.
function serverTime() {
return "--:--:--";
}
function UtcClock() {
const time = useSyncExternalStore(subscribe, utcTime, serverTime);
return <span className="text-ink tabular-nums">{time} UTC</span>;
}
// Thin system strip for the top of a page: fake system strings on the left, a
// signal meter and a live UTC clock on the right. A plain <div>, so it can sit
// above a <nav> without adding a landmark.
export function StatusBar({
items,
signal,
clock = true,
className,
...props
}: Omit<ComponentProps<"div">, "children"> & {
items: ReactNode[];
signal?: 0 | 1 | 2 | 3 | 4;
clock?: boolean;
}) {
return (
<div
className={cn(
"flex h-7 items-center justify-between gap-6 border-b-(length:--border-hair) border-line bg-ground-void px-6 font-mono text-caption tracking-label whitespace-nowrap text-label sm:px-10",
className,
)}
{...props}
>
<span className="flex min-w-0 gap-5.5 overflow-hidden">
{items.map((entry, i) => (
<span key={i}>{entry}</span>
))}
</span>
<span className="flex shrink-0 items-center gap-5.5">
{signal !== undefined && (
<span role="img" aria-label={`Signal strength ${signal} of 4`} className="flex items-end gap-0.5">
{BARS.map((height, i) => (
<span
key={height}
className={cn("w-0.75", i < signal ? "bg-signal" : "bg-line")}
style={{ height }}
/>
))}
</span>
)}
{clock && <UtcClock />}
</span>
</div>
);
}
Repo source. The shadcn CLI rewrites import aliases to match your project.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | ReactNode[] | — | System strings on the left, such as SYS:OK or REV 0.1. They clip on narrow screens; the right side never does. |
| signal | 0 | 1 | 2 | 3 | 4 | — | Shows a 4-bar meter with this many bars lit in --signal. Leave it out to hide the meter. |
| clock | boolean | true | Shows a live HH:MM:SS UTC clock. |
| className | string | — | Extra classes, merged after the defaults with cn(). |
| ...props | Omit<ComponentProps<"div">, "children"> | — | Every other native <div> attribute and ref is forwarded. |
Usage
Needs the stylesheet from @cybr/tokens. After the install, import it in app/globals.css:
@import "tailwindcss";
@import "../styles/cybr-tokens.css";
import { StatusBar } from "@/components/ui/status-bar";
export function Header() {
return <StatusBar items={["SYS:OK", "REV 0.1"]} signal={3} />;
}
States
- Default: 28px strip on
--ground-voidwith--labelstrings and a--linerule underneath. - Signal: lit bars are
--signal, unlit bars are--line, rising 4, 7, 10 and 13px. - Clock: shows
--:--:-- UTCon the server and during hydration, then the real UTC time, updated every second. - Narrow: the left strings clip; the meter and clock keep their space.
Accessibility
- A plain
<div>, not a landmark. Place it above your<nav>or inside a<header>. - The meter is
role="img"with the label "Signal strength N of 4". The bars themselves carry no text. - The clock is plain text, not a live region, so screen readers do not announce every second.
--labelon--ground-voidis 9.19:1 and--inkon--ground-voidis 18.09:1. If you override colours withclassName, keep text at 4.5:1 or above.- Nothing animates; the clock text simply changes.