Game UI
Ring meter
A gauge filling round an icon.
stablev0.2.0 Chromium 103 ui-scale safe
Install
Copies the file into
components/ds/, with its registry dependencies and npm packages. See Installation for the one-time setup.bunx shadcn@3 add @fastrp/ring-meterExamples
Default
$15,150
$700
API
<RingMeter />A ring that fills clockwise around an icon — GTA VI's money and ability gauges. Drawn in an SVG viewBox, so it is as large as its box: size it with a class (`size-12`).
| Prop | Type | Default | Notes |
|---|---|---|---|
| value* | number | — | |
| children | ReactNode | — | What sits in the ring: an icon, a short figure. |
| label | string | — | |
| max | number | 100 | |
| tone | "primary" | "danger" | "success" | "warning" | "pink" | "teal" | "purple" | "cyan" | primary |
Dependencies
npm
- None
Registry
- @fastrp/utils
Source
components/ds/ring-meter.tsxShowHide
import type * as React from 'react';
import { cn } from '@/lib/utils';
// @fast-ds [email protected]
/** Stroke and text in the tone, so the glow's `currentColor` matches the ring. */
const STROKE = {
primary: 'stroke-primary text-primary',
success: 'stroke-success text-success',
danger: 'stroke-danger text-danger',
warning: 'stroke-warning text-warning',
pink: 'stroke-pink text-pink',
teal: 'stroke-teal text-teal',
purple: 'stroke-purple text-purple',
cyan: 'stroke-cyan text-cyan',
} as const;
const RADIUS = 15.5;
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
export type RingMeterProps = Omit<React.ComponentProps<'div'>, 'children'> & {
value: number;
max?: number;
tone?: keyof typeof STROKE;
/** What sits in the ring: an icon, a short figure. */
children?: React.ReactNode;
label?: string;
};
/**
* A ring that fills clockwise around an icon — GTA VI's money and ability gauges. Drawn in an SVG
* viewBox, so it is as large as its box: size it with a class (`size-12`).
*/
function RingMeter({
value,
max = 100,
tone = 'primary',
label,
children,
className,
...props
}: RingMeterProps) {
const share = max > 0 ? Math.min(1, Math.max(0, value / max)) : 0;
return (
// biome-ignore lint/a11y/useSemanticElements: a native <meter> cannot hold the label and the drawn fill
<div
data-slot="ring-meter"
role="meter"
aria-valuenow={value}
aria-valuemin={0}
aria-valuemax={max}
aria-label={label}
className={cn('relative inline-flex size-12 shrink-0 items-center justify-center', className)}
{...props}
>
<svg viewBox="0 0 36 36" aria-hidden className="absolute inset-0 size-full -rotate-90">
<circle
cx="18"
cy="18"
r={RADIUS}
className="fill-ink/60 stroke-foreground/15"
strokeWidth="2.5"
/>
<circle
cx="18"
cy="18"
r={RADIUS}
fill="none"
strokeWidth="2.5"
strokeLinecap="round"
strokeDasharray={CIRCUMFERENCE}
strokeDashoffset={CIRCUMFERENCE * (1 - share)}
className={cn(
'transition-[stroke-dashoffset] duration-slow ease-brand [filter:drop-shadow(0_0_0.2rem_currentColor)]',
STROKE[tone],
)}
/>
</svg>
<span className="relative flex items-center justify-center font-display text-sm text-foreground [&_svg]:size-[45%]">
{children}
</span>
</div>
);
}
export { RingMeter };