Components

Ticket Number Ticker

Bump runKey (the Run button here) and the digits run up on an odometer - tabular-nums keeps every column 1ch wide so nothing shifts. The pill grows with the value up to a max-width, then middle-truncates to start…end - the git-SHA idiom - with the full value one hover or copy away. Spaces become -; drop the digits entirely and it becomes a plain name (no reels). Tick GitHub PR status for a live PR badge driven by the status prop - click it to cycle the lifecycle. Note: the demo below is scaled 1.5× for presentation - the installed component ships at a compact dashboard size that sits naturally in list rows and toolbars.

#
Ticket #232323

Kind: numeric · fits in full

Install

npx moumenlab add ticket-number-ticker

For AI

Open .md

Usage

"use client";

import { useState } from "react";
import TicketNumber from "./ticket-number-ticker";

export default function TicketNumberExample() {
  // Bump runKey whenever a "new ticket" lands and the digits run up like an
  // odometer.
  const [runKey, setRunKey] = useState(0);

  return (
    <div className="flex flex-col items-center gap-4">
      {/* Default: hugs the value, grows to the cap, then middle-truncates. */}
      <TicketNumber value="#100284" runKey={runKey} />

      {/* GitHub PR status badge, driven by your data ("open" | "draft" | "merged" | "closed"). */}
      <TicketNumber value="#1042" status="merged" />

      {/* width="fixed": every pill shares one footprint - steady columns in lists. */}
      <TicketNumber value="#7" width="fixed" status="open" />

      {/* Text ids work too: spaces become dashes, long ones truncate to start…end
          with the full value on hover. copyable={false} drops the copy button;
          maxWidth moves the truncation cap. */}
      <TicketNumber value="#billing webhook retry timeout 2026" maxWidth="16rem" copyable={false} />

      <button
        type="button"
        onClick={() => setRunKey((key) => key + 1)}
        className="rounded-full bg-primary px-4 py-2 text-xs font-medium text-primary-foreground transition-[background-color,scale] hover:bg-primary/85 active:scale-[0.96]"
      >
        Run the numbers
      </button>
    </div>
  );
}

Story

  1. A Jira itch, not a spec

    This one came from using Jira every day. Grabbing a ticket number was weirdly hard: click the id and the ticket opens; reach for the copy affordance and you get the whole URL. All I ever wanted was the number, and there was no clean way to take just that.

    The itch itself: hovering BP-2591 turns it into a link, the button beside it offers Copy link, the next one changes the work type, and the number ends up selected by hand.
  2. An experiment on the side

    Right after building task-management, the itch turned into an experiment: why isn't the ticket id a component of its own? A pill that hugs the number, with copy as a first-class, one-click action: the clipboard gets the number, not a link, and nothing opens by accident.

  3. The merge status, in shortcut

    The second itch: knowing what happened to the ticket's merge without opening anything. So the pill grew a badge speaking a language every developer already reads: GitHub's own four PR states in GitHub's own colours. Open, draft, merged, closed. The ticket's fate, at a glance.

  4. Then real ids arrived

    #42 was easy; twelve-digit ids and webhook-slug names blew past the pill. The obvious fix, CSS end-ellipsis, keeps the prefix every ticket shares and throws away the digits that tell them apart. Commit SHAs and wallet addresses solved this long ago: middle-truncate to start…end, keep both identifying ends, and let hover or copy recover the middle.

  5. Measured, not guessed

    How much survives can't be guessed in characters; it's measured in pixels. A hidden clone with the exact same typography is binary-searched to the longest #start…end that fits the cap's budget: the cap minus the chrome, so adding the status badge re-measures the truncation instead of clipping the last characters. All before paint, so the wrong cut never flashes.

  6. The odometer run-up

    A new ticket should feel like an event, not a swap. Each digit became a reel: a 1em window over a 0-9 strip, snapped to zero and released a full turn before landing, each column settling 55ms after the one before it. tabular-nums keeps every column exactly 1ch, so nothing shifts while it rolls, or truncates.

References