mirror of
https://github.com/block/buzz.git
synced 2026-11-03 16:01:35 +00:00
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
46 lines
1.3 KiB
TypeScript
46 lines
1.3 KiB
TypeScript
import * as React from "react";
|
||
|
||
import { cn } from "@/shared/lib/cn";
|
||
|
||
/**
|
||
* shadcn-style progress bar (no Radix dependency). `value` is 0–100; a
|
||
* `null`/`undefined` value renders an indeterminate sweep, used for phases
|
||
* with no byte counts (e.g. video transcoding before the upload starts).
|
||
*/
|
||
const Progress = React.forwardRef<
|
||
HTMLDivElement,
|
||
React.HTMLAttributes<HTMLDivElement> & { value?: number | null }
|
||
>(({ className, value, ...props }, ref) => {
|
||
const clamped =
|
||
typeof value === "number" && Number.isFinite(value)
|
||
? Math.min(100, Math.max(0, value))
|
||
: null;
|
||
|
||
return (
|
||
<div
|
||
aria-valuemax={100}
|
||
aria-valuemin={0}
|
||
aria-valuenow={clamped ?? undefined}
|
||
className={cn(
|
||
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
||
className,
|
||
)}
|
||
ref={ref}
|
||
role="progressbar"
|
||
{...props}
|
||
>
|
||
{clamped === null ? (
|
||
<div className="h-full w-1/3 animate-[progress-indeterminate_1.2s_ease-in-out_infinite] rounded-full bg-primary" />
|
||
) : (
|
||
<div
|
||
className="h-full w-full flex-1 rounded-full bg-primary transition-transform duration-200"
|
||
style={{ transform: `translateX(-${100 - clamped}%)` }}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
});
|
||
Progress.displayName = "Progress";
|
||
|
||
export { Progress };
|