# Progress

A progress bar for tasks with a known amount, or an indeterminate one when the amount is unknown.

Docs: https://corsairui.vercel.app/docs/progress

## Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/progress
```

Also adds: `utils`.
npm: `@radix-ui/react-progress`, `clsx`, `tailwind-merge`.

## API

Built on Radix Progress (https://www.radix-ui.com/primitives/docs/components/progress); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number \| null` | `null` | 0 to max, clamped; null means the amount is unknown. |
| `max` | `number` | `100` | The value that means done. |
| `getValueLabel` | `(value, max) => string` |  | What screen readers read out; a percentage by default. |

## Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `progress`, `progress-indicator`

## Accessibility

- A progressbar with aria-valuenow and aria-valuemax. Name it with aria-label or aria-labelledby.
- Indeterminate bars slide a segment across; with prefers-reduced-motion they hold still at half opacity.

## Examples

On the docs page: Determinate, Indeterminate. The usage examples in the source comments below are the reference.

## Source

What `shadcn add` writes into the project:

### progress.tsx

```tsx
"use client";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import type { ComponentProps } from "react";
import { cn } from "@/registry/default/lib/utils";
interface ProgressProps extends ComponentProps<typeof ProgressPrimitive.Root> {
  /** How far along the task is, from 0 to `max`. `null` (the default) means the amount is unknown. */
  value?: number | null;
  /** The value that means done. */
  max?: number;
}
/**
 * A bar that shows how far a task has got: uploads, installs, multi-step
 * forms. It is a `progressbar` with `aria-valuenow` / `aria-valuemax`, and
 * `getValueLabel` sets the text screen readers read out (a percentage by
 * default). Give it a name with `aria-label` or `aria-labelledby` pointing
 * at a visible label. Pass `value={null}` when the amount is unknown: the
 * bar becomes indeterminate (`data-state="indeterminate"`) and a short
 * segment slides across it; with reduced motion it shows a still,
 * half-opacity bar instead. Values outside 0 to `max` are clamped.
 *
 * @example
 * <Progress value={uploaded} max={total} aria-label="Uploading photos" />
 * <Progress value={null} aria-label="Loading results" />
 */
function Progress({ className, value = null, max = 100, ...props }: ProgressProps) {
  const limit = max > 0 ? max : 100;
  const clamped = value === null ? null : Math.min(limit, Math.max(0, value));
  const percent = clamped === null ? 0 : (clamped / limit) * 100;
  return (
    <ProgressPrimitive.Root
      data-slot="progress"
      value={clamped}
      max={limit}
      className={cn("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", className)}
      {...props}
    >
      <ProgressPrimitive.Indicator
        data-slot="progress-indicator"
        className={cn(
          "bg-primary h-full w-full flex-1 transition-transform motion-reduce:transition-none",
          // Unknown amount: a 40% segment slides across. With reduced motion the
          // whole bar stays put at half opacity instead.
          "motion-safe:data-[state=indeterminate]:animate-progress-indeterminate motion-safe:data-[state=indeterminate]:w-2/5",
          "motion-reduce:data-[state=indeterminate]:opacity-50"
        )}
        style={clamped === null ? undefined : { transform: `translateX(-${100 - percent}%)` }}
      />
    </ProgressPrimitive.Root>
  );
}
export { Progress, type ProgressProps };
```
