Componentfeedback1.11 KB gzipZero dependencies
Meter
Turnstile capacity meter with notched frame, optimal indicator and tick marks.
Preview
Live Preview
TURNSTILE CAPACITY68%
0255075100
ZONE RATINGOPTIMAL
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add meterOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { Meter } from "@/components/ui/meter";
<Meter value={68} min={0} max={100} label="TURNSTILE CAPACITY" />Usage guidance
Use Meter when a ticket stub surface needs Turnstile capacity meter with notched frame, optimal indicator and tick marks. Keep props minimal and prefer composition with sibling stubs.
Avoid Meter for long form flows or dense data grids where a dedicated layout block fits better.
Source code
meter.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface MeterProps extends React.HTMLAttributes<HTMLDivElement> {
value: number; // 0 to 100
min?: number;
max?: number;
label?: string;
unit?: string;
low?: number;
high?: number;
optimum?: number;
notched?: boolean;
showTicks?: boolean;
className?: string;
}
export function Meter({
value,
min = 0,
max = 100,
label = "TURNSTILE OCCUPANCY",
unit = "%",
low = 30,
high = 80,
optimum = 50,
notched = true,
showTicks = true,
className,
...props
}: MeterProps) {
const clampedValue = Math.max(min, Math.min(max, value));
const percent = ((clampedValue - min) / (max - min)) * 100;
// Determine status tone
let tone = "bg-primary";
let statusText = "OPTIMAL";
if (clampedValue > high) {
tone = "bg-destructive";
statusText = "HIGH CAPACITY";
} else if (clampedValue < low) {
tone = "bg-accent";
statusText = "LOW LOAD";
}
return (
<div
className={cn(
"relative flex flex-col rounded-lg border-2 border-foreground bg-card p-4 font-mono select-none shadow-sm",
className
)}
{...props}
>
{notched && (
<>
<div
aria-hidden="true"
className="absolute -left-1.5 top-1/2 -translate-y-1/2 size-3 rounded-full border border-foreground bg-background"
/>
<div
aria-hidden="true"
className="absolute -right-1.5 top-1/2 -translate-y-1/2 size-3 rounded-full border border-foreground bg-background"
/>
</>
)}
{/* Header */}
<div className="flex items-center justify-between border-b border-dashed border-border pb-2 text-[10px] tracking-widest text-muted-foreground">
<span>{label}</span>
<span className="font-bold text-foreground">
{clampedValue}
{unit}
</span>
</div>
{/* Meter Track */}
<div className="relative mt-3 h-6 w-full rounded border-2 border-foreground bg-secondary/60 p-0.5 overflow-hidden shadow-inner">
<div
style={{ width: `${percent}%` }}
className={cn(
"h-full rounded-xs transition-all duration-300 ease-out",
tone
)}
/>
</div>
{/* Tick Marks */}
{showTicks && (
<div className="mt-1 flex justify-between px-0.5 text-[9px] text-muted-foreground">
<span>{min}</span>
<span>{Math.round((max - min) * 0.25 + min)}</span>
<span>{Math.round((max - min) * 0.5 + min)}</span>
<span>{Math.round((max - min) * 0.75 + min)}</span>
<span>{max}</span>
</div>
)}
{/* Status Eyebrow */}
<div className="mt-2 flex items-center justify-between border-t border-dashed border-border pt-2 text-[9px] text-muted-foreground uppercase">
<span>ZONE RATING</span>
<span className="font-bold text-accent">{statusText}</span>
</div>
</div>
);
}
Props
| Prop | Type | Description |
|---|---|---|
| value | number | Current numeric value (0 to 100) |
| min | number | Minimum value (default 0) |
| max | number | Maximum value (default 100) |
| label | string | Label shown on top |
| unit | string | Unit symbol (default "%") |