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.

In-degree: 0 (ready)0In-degree: 1 (idle)1In-degree: 2 (idle)2In-degree: 3 (idle)3In-degree: 2 (idle)2In-degree: 4 (idle)4
Customize
Shape
26 px
State
0
ready
Playback
700 ms

Installation

npx shadcn@latest add https://craftbits.dev/r/in-degree-badge.json

Usage

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

  1. 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 sets top / left / translate.
  2. Three-state lifecycle. idle reads as "still waiting" — neutral chip palette, no pulse. ready swaps to the success palette and turns on a breathing ring so the eye lands on the vertex about to be dequeued. done shifts to a muted fg-subtle tone for vertices that have already been processed.
  3. Delta beat. When previous differs from count (or the internally-tracked last-rendered value differs from the new count), the badge fires a one-shot scale pop — capped at 1.04 to honour the subtle-deformation-scale ceiling. The beat draws attention to the specific vertex whose in-degree just changed without overplaying.
  4. 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.
  5. Value transitions. Swapping count slides the new digit up from below and fades the previous digit out — built on AnimatePresence with SPRINGS.snap so the counter feels responsive on rapid algorithm ticks.
  6. 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

PropTypeDefaultDescription
countnumberrequiredIncoming-edge count. Negative values render as 0.
previousnumberPrior 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.
sizenumber22Diameter (px). Floored at 12.
labelReactNodeOptional short label rendered above the badge.
ariaLabelstringOverride the announced string. Defaults to the canonical "In-degree: N (status)" form.
transitionTransitionSPRINGS.snapEnter / value-swap / delta-beat transition. Reduced-motion users snap regardless.
classNamestringMerged onto the root via cn().

Accessibility

  • The outer <svg> is role="img" with a <title> derived from ariaLabel or 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 expected x / y coordinates plus a raw hex colour pulled from the lesson's track, with a single boolean pulsing flag. 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 new previous prop replaces the source's implicit "did the value just change" detection with an explicit, testable delta-beat trigger.