In-Degree Badge
A pill-sized circular badge that displays a graph vertex's in-degree — the number of incoming edges still waiting to be processed. Drives a Kahn-style topological sweep declaratively: pass a status of "idle" while the vertex is waiting, flip to "ready" when the count reaches zero, and to "done" once it has been dequeued. A separate previous prop fires a one-shot delta beat whenever the count changes between renders so the eye lands on the vertex whose in-degree just dropped.
Self-contained <svg> element — the caller positions it however the parent layout demands. No layout dependencies, no project-specific props, no track-hex plumbing.
Installation
npx shadcn@latest add https://craftbits.dev/r/in-degree-badge.jsonUsage
import { InDegreeBadge } from "@craft-bits/viz/in-degree-badge";
<InDegreeBadge count={3} status="idle" />Mark a vertex as ready to dequeue:
<InDegreeBadge count={0} status="ready" />Fade the badge once the vertex has been processed:
<InDegreeBadge count={0} status="done" />Drive the per-vertex delta beat from a parent that already tracks history:
<InDegreeBadge count={current} previous={prior} status={status} />Stack a short label above the badge:
<InDegreeBadge count={1} label="v3" size={28} />Understanding the component
- Self-contained SVG. The badge renders as a viewBox-sized
<svg>element, so it scales cleanly inside<foreignObject>or stacks inline next to text. The caller positions it; the component never setstop/left/translate. - Three-state lifecycle.
idlereads as "still waiting" — neutral chip palette, no pulse.readyswaps to the success palette and turns on a breathing ring so the eye lands on the vertex about to be dequeued.doneshifts to a muted fg-subtle tone for vertices that have already been processed. - Delta beat. When
previousdiffers fromcount(or the internally-tracked last-rendered value differs from the newcount), the badge fires a one-shot scale pop — capped at 1.04 to honour thesubtle-deformation-scaleceiling. The beat draws attention to the specific vertex whose in-degree just changed without overplaying. - Pulse ring. Only renders while
status === "ready"and motion is allowed. The 1s breathing ring expands just outside the badge in the success tone. Reduced-motion users see no ring at all. - Value transitions. Swapping
countslides the new digit up from below and fades the previous digit out — built onAnimatePresencewithSPRINGS.snapso the counter feels responsive on rapid algorithm ticks. - Reduced motion. Enter scale, delta beat, value slide, and pulse ring all collapse to instant under
prefers-reduced-motion: reduce. The text content still updates; only the motion drops.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | required | Incoming-edge count. Negative values render as 0. |
previous | number | — | Prior count. When set and different from count, fires a delta beat. |
status | "idle" | "ready" | "done" | "idle" | Lifecycle state — drives palette and the ready-state pulse ring. |
size | number | 22 | Diameter (px). Floored at 12. |
label | ReactNode | — | Optional short label rendered above the badge. |
ariaLabel | string | — | Override the announced string. Defaults to the canonical "In-degree: N (status)" form. |
transition | Transition | SPRINGS.snap | Enter / value-swap / delta-beat transition. Reduced-motion users snap regardless. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The outer
<svg>isrole="img"with a<title>derived fromariaLabelor the canonical "In-degree: N (status)" string. Screen readers hear both the count and the lifecycle state without parsing the SVG geometry. - Each badge exposes
data-status(idle/ready/done) so consumer apps can hook custom styles or assistive tooling. - Status is never the only signal — the count digit renders on every state, and the ready transition shifts both the fill and the stroke so colourblind users still see the change.
- Motion respects
prefers-reduced-motion: reduce— the enter scale, delta beat, value-swap slide, and pulse ring all collapse to instant. The text content updates regardless.
Credits
- Extracted from:
AlgoFlashcards(src/lessons/primitives/graph/InDegreeBadge.tsx). The source was a<motion.g>fragment that expectedx/ycoordinates plus a rawhexcolour pulled from the lesson's track, with a single booleanpulsingflag. The craft-bits version is a self-contained<svg>— the caller positions it however the parent layout wants — and the raw hex is replaced by a three-state lifecycle enum so the same badge covers idle / ready / done semantics without per-lesson colour plumbing. A newpreviousprop replaces the source's implicit "did the value just change" detection with an explicit, testable delta-beat trigger.