NumberedStat

NumberedStat is the smallest stat on a page: a quiet label, the value under it, and an optional chip for the change since the last period. It can also be an entry point: give it onClick and the block becomes clickable, with a hover label and icon on the chip's line.

import NumberedStat from "@haloid/azucar/ui/numbered-stat";

The stat row

The reconciliation overview opens with four of them in a plain flex gap-12 row. The source is apps/north/src/app/(app)/reconciliation/overview/page.tsx. The component does not size or space itself. The row owns the gap.

<div className="flex gap-12">
<NumberedStat label="Customers" value="31" delta={-2} deltaPostfix="%" />
<NumberedStat label="Spaces" value="108" delta={-5} deltaPostfix="%" />
<NumberedStat label="Records" value="367" delta={-11} deltaPostfix="%" />
<NumberedStat label="Data Issues" value="17" delta={-46} deltaPostfix="%" lowerIsBetter />
</div>

deltaPostfix is appended as given, so delta={-11} with deltaPostfix="%" renders -11%. The component does no other formatting. Format value and delta before you pass them.

Polarity

By default the chip reads a rise as good and a fall as bad. A positive delta is green. A negative delta is red. That reading is wrong for counts of problems: a falling number of data issues is good. Set lowerIsBetter and the chip flips. A negative delta turns green, a positive one turns red.

The chip pairs step 1 of a status ramp as the surface with step 4 as the text: bg-green-1 text-green-4 or bg-red-1 text-red-4. That pairing is the house rule for status color. Color points to this chip as the reference for it.

Flip lowerIsBetter in the panel and watch the same fall change meaning.

delta
lowerIsBetter
<NumberedStat label="Data issues" value="17" deltaPostfix="%" delta={-46} />

Zero and missing deltas

A delta of exactly zero is neither good nor bad, so neither ramp applies. The chip goes neutral, bg-bg-2 with text-fg-3, and shows a placeholder glyph instead of the number: —% with a percent postfix.

An omitted delta is different. With no delta, the component renders no chip. Use zero for a value you measured that did not move. Omit the prop when no comparison exists yet.

<div className="flex gap-12">
<NumberedStat label="Facilities" value="6" />
<NumberedStat label="Spaces" value="108" delta={0} deltaPostfix="%" />
<NumberedStat label="Records" value="367" delta={-11} deltaPostfix="%" />
</div>

As an entry point

onClick makes the whole block clickable, by mouse or by Enter and Space, and adds the pointer cursor. hoverLabel and hoverIcon appear on the chip's line while the pointer is over the stat, after the chip when there is one. Both are unstyled scaffolding until their design pass lands: the label renders as plain text and the icon as given. Hover the demo to see them, and click it: the value counts the clicks.

function Demo() {
const [clicks, setClicks] = useState(0);

return (
  <NumberedStat
    label="Clicks"
    value={clicks}
    delta={4}
    deltaPostfix="%"
    onClick={() => setClicks((count) => count + 1)}
    hoverLabel="See"
    hoverIcon={<SFArrowForward size={12} />}
  />
);
}

render(<Demo />);

API

interface Props {
label: string;
value: string | number;
delta?: number;
deltaPostfix?: string; // appended verbatim, e.g. "%"
lowerIsBetter?: boolean;
onClick?: () => void; // present: the block is clickable by mouse, Enter, or Space, with cursor-pointer
hoverLabel?: string; // shown on the chip's line while hovered
hoverIcon?: React.ReactNode; // shown after the hover label
}

hoverLabel and hoverIcon are unstyled scaffolding until their design pass lands. They render as plain text and the icon as given.

Not supported: a plus sign on positive deltas (delta={5} renders 5%, not +5%), a className prop, and the orange and blue ramps. The chip knows only good, bad, and flat. The label renders as an h4, so screen readers hear every stat as a heading.