Usage summaryDesign system
Product

Usage summary

Usage dialog with remaining credits, daily and monthly totals, and usage by API.

npx context-ds add usage-summary
In the product

Usage

Use it for

Opening usage over the overview from the credits card.

Avoid

  • Navigating away for a quick look

Dependencies: stat-card, dialog

Example

examples/usage-summary.tsx
"use client";import { useState } from "react";import { CardLink } from "@/components/product/glyph-card";import { CreditsRemaining, CreditsUsed, UsageBarList, UsageSummaryDialog } from "@/components/product/usage-summary";import { Button } from "@/components/ui/button";const TOP_APIS = [  { key: "scrape", label: "Scrape", value: 30410 },  { key: "retrieve", label: "Brand retrieval", value: 15820 },  { key: "search", label: "Search", value: 8960 },];function Summary() {  return (    <>      <div className="flex flex-wrap items-end justify-between gap-x-6 gap-y-3">        <CreditsRemaining remaining={41590} total={100000} />        <CreditsUsed today={2318} month={58410} />      </div>      <UsageBarList title="Top APIs this month" items={TOP_APIS} className="mt-3" />    </>  );}// The dialog opens over the overview from the Credits remaining card. Its pieces are shown below the button as they// sit inside it: what's left over the long dot meter, today's and this month's use, and the ranked list.export function Product() {  const [open, setOpen] = useState(false);  return (    <div className="flex w-full max-w-xl flex-col gap-6">      <Button variant="outline" size="sm" className="self-start" onClick={() => setOpen(true)}>        Open the usage summary      </Button>      <div className="flex flex-col gap-3">        <Summary />      </div>      <UsageSummaryDialog open={open} onOpenChange={setOpen} description="On the Pro plan, resetting Nov 1." footer={<CardLink href="/preview/usage-card/Product">See full usage</CardLink>}>        <Summary />      </UsageSummaryDialog>    </div>  );}

Source

components/product/usage-summary.tsx
"use client";import * as React from "react";import { StatFigure, StatMeter } from "@/components/product/stat-card";import { Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";import { cn } from "@/lib/utils";// The usage page at a glance, over the overview rather than away from it (opened from the Credits remaining card):// what's left over a long dot meter with today's and this month's use beside it, three centered stat cards (calls,// credits, success rate), the day-by-day chart in a glyph card, and the APIs that spent the most this month.const compact = (n: number) => n.toLocaleString("en-US", { notation: "compact", maximumFractionDigits: 1 });/** Credits remaining: the count over its quota, then a row of dots (40 here, 20 on the cards), brand blue for what's left. */function CreditsRemaining({ remaining, total, dots = 40, className }: { remaining: number; total?: number; dots?: number; className?: string }) {  return (    <div data-slot="credits-remaining" className={cn("max-w-md min-w-56 flex-1", className)}>      <p className="text-caption text-fg-muted">Credits remaining</p>      <StatFigure suffix={total !== undefined ? `/${compact(total)}` : undefined} className="mt-2">        {remaining.toLocaleString("en-US")}      </StatFigure>      {total !== undefined && <StatMeter value={remaining} max={total} dots={dots} className="mt-3" />}    </div>  );}/** "1,204 used today · 41,880 this month", the numbers in full text color. */function CreditsUsed({ today, month }: { today: number; month: number }) {  return (    <p className="text-caption text-fg-muted">      <span className="text-fg tabular-nums">{today.toLocaleString("en-US")}</span> used today · <span className="text-fg tabular-nums">{month.toLocaleString("en-US")}</span> this month    </p>  );}/** A ranked list: each name, a thin bar against the top one, and its count. */function UsageBarList({ title, items, className }: { title?: React.ReactNode; items: { key: string; label: React.ReactNode; value: number }[]; className?: string }) {  const top = items[0]?.value ?? 0;  return (    <div data-slot="usage-bar-list" className={className}>      {title && <p className="mb-2 text-caption text-fg-muted">{title}</p>}      <ul className="flex flex-col gap-2">        {items.map((item) => (          <li key={item.key} className="grid grid-cols-[minmax(0,9rem)_1fr_3rem] items-center gap-3 text-body-sm">            <span className="truncate">{item.label}</span>            <span className="h-1 rounded-pill bg-blue-10">              <span className="block h-full rounded-pill bg-brand" style={{ width: `${top > 0 ? (item.value / top) * 100 : 0}%` }} />            </span>            <span className="text-right text-fg-muted tabular-nums">{item.value.toLocaleString("en-US")}</span>          </li>        ))}      </ul>    </div>  );}/** The window: "Usage" over the plan line, the pieces scrolling inside on a short screen, the link out in the footer. */function UsageSummaryDialog({  open,  onOpenChange,  title = "Usage",  description,  footer,  children,}: {  open: boolean;  onOpenChange: (open: boolean) => void;  title?: React.ReactNode;  description?: React.ReactNode;  /** "See full usage": your router's link. */  footer?: React.ReactNode;  children: React.ReactNode;}) {  return (    <Dialog open={open} onOpenChange={onOpenChange}>      <DialogContent size="lg">        <DialogHeader>          <DialogTitle>{title}</DialogTitle>          {description && <DialogDescription>{description}</DialogDescription>}        </DialogHeader>        <DialogBody className="@container flex flex-col gap-3">{children}</DialogBody>        {footer && <DialogFooter className="justify-start">{footer}</DialogFooter>}      </DialogContent>    </Dialog>  );}export { CreditsRemaining, CreditsUsed, UsageBarList, UsageSummaryDialog };

Reference: context-dashboard c0ddf7a src/components/overview/usage-summary-dialog.tsx.