Beste UI Pieces
componentai10source: ok · refreshed
Usage Ring
Monthly quota meter with a tone-colored SVG donut arc, percentage in the middle, used/total request count, and reset countdown.
npx shadcn add "https://ui.beste.co/piece/r/ai10.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightclsxtailwind-mergelucide-react
Files1 · source cached 10/8/2026, 6:17:17 AM
registry-pieces/ai10/ai10.tsx
"use client";
import { cn } from "@/lib/utils";
type Surface = "card" | "glass";
type Tone =
| "primary"
| "foreground"
| "violet"
| "emerald"
| "sky"
| "amber"
| "rose";
interface Ai10Props {
label?: string;
used?: number;
quota?: number;
resetsIn?: string;
tone?: Tone;
surface?: Surface;
bordered?: boolean;
inverted?: boolean;
className?: string;
}
const arcClasses: Record<Tone, string> = {
primary: "text-primary",
foreground: "text-foreground",
violet: "text-violet-500",
emerald: "text-emerald-500",
sky: "text-sky-500",
amber: "text-amber-500",
rose: "text-rose-500",
};
const RADIUS = 18;
const CIRC = 2 * Math.PI * RADIUS;
/* The card sets the colour and everything inside it is drawn in `current`, so
inverting is two classes rather than a condition on every element.
`glass` is a deliberate exception to the solid-surface rule: these pieces sit
over section background images, and a frosted panel is the point of it. */
const surfaceClasses: Record<Surface, { plain: string; inverted: string }> = {
card: {
plain: "bg-card text-card-foreground",
inverted: "bg-foreground text-background",
},
glass: {
plain: "bg-card/60 text-card-foreground backdrop-blur-md",
inverted: "bg-foreground/60 text-background backdrop-blur-md",
},
};
export const ai10Demo: Ai10Props = {
surface: "card",
bordered: false,
inverted: false,
label: "Monthly requests",
used: 4200,
quota: 10000,
tone: "primary",
};
export function Ai10({
label = "Requests",
used = 0,
quota = 1000,
resetsIn,
tone = "primary",
surface = "card",
bordered = false,
inverted = false,
className,
}: Ai10Props) {
const pct = Math.max(0, Math.min(100, (used / Math.max(1, quota)) * 100));
const offset = CIRC * (1 - pct / 100);
const surfaceTone = surfaceClasses[surface][inverted ? "inverted" : "plain"];
return (
<div
className={cn(
"relative flex size-full items-center justify-center p-4",
className
)}
>
<div
className={cn(
"flex w-full max-w-72 items-center gap-3 rounded-md p-3 shadow-sm",
surfaceTone,
bordered && "border border-current/15"
)}
>
<div
className={cn(
"relative flex size-11 shrink-0 items-center justify-center",
arcClasses[tone]
)}
>
<svg
className="absolute inset-0 size-11 -rotate-90"
viewBox="0 0 44 44"
aria-hidden="true"
>
<circle
cx="22"
cy="22"
r={RADIUS}
fill="none"
strokeWidth="4"
className="stroke-muted"
/>
<circle
cx="22"
cy="22"
r={RADIUS}
fill="none"
strokeWidth="4"
strokeLinecap="round"
stroke="currentColor"
strokeDasharray={CIRC}
strokeDashoffset={offset}
/>
</svg>
<span className="relative text-xs font-semibold tabular-nums">
{Math.round(pct)}%
</span>
</div>
<div className="flex min-w-0 flex-1 flex-col">
<span className="truncate text-xs font-semibold text-current/60">
{label}
</span>
<span className="text-sm tabular-nums">
{used.toLocaleString()}{" "}
<span className="text-current/60">
/ {quota.toLocaleString()}
</span>
</span>
{resetsIn && (
<span className="truncate text-xs text-current/60">
{resetsIn}
</span>
)}
</div>
</div>
</div>
);
}