# Gauge

A dial for a value within a known range.

Source: [gauge.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/gauge.tsx)

```tsx
import { Gauge, GaugeLabel, GaugeValue } from "@/components/ui/gauge";

export default function GaugeBasic() {
  return (
    <Gauge value={72}>
      <GaugeValue />
      <GaugeLabel>Memory</GaugeLabel>
    </Gauge>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/gauge
```

## Usage

```tsx
import { Gauge, GaugeLabel, GaugeValue } from "@/components/ui/gauge";

export function MemoryGauge() {
  return (
    <Gauge value={72}>
      <GaugeValue />
      <GaugeLabel>Memory</GaugeLabel>
    </Gauge>
  );
}
```

A gauge is a [Meter](https://ui.jarv.is/components/meter.md) drawn as an arc, for a measurement within a range that
reads at a glance, such as on a dashboard. Its children sit in the middle of the arc.

## Composition

- `Gauge`
  - `GaugeValue`
  - `GaugeLabel`

## Examples

### Arcs

`startAngle` and `endAngle` set where the arc begins and ends, in degrees clockwise from the top.
The gauge is only as big as its arc, so a semicircle takes half the height of a circle, and its
children sit toward the open side, at the bottom of a semicircle over the top.

```tsx
import { Gauge, GaugeLabel, GaugeValue } from "@/components/ui/gauge";

export default function GaugeArcs() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-8">
      <Gauge value={64}>
        <GaugeValue />
        <GaugeLabel>Storage</GaugeLabel>
      </Gauge>
      <Gauge value={48} startAngle={-135} endAngle={135}>
        <GaugeValue />
        <GaugeLabel>Memory</GaugeLabel>
      </Gauge>
      <Gauge value={81} startAngle={-90} endAngle={90}>
        <GaugeValue />
        <GaugeLabel>Bandwidth</GaugeLabel>
      </Gauge>
    </div>
  );
}
```

### Formatted value

`GaugeValue` shows a percentage by default. Pass `format` to format the value itself with
`Intl.NumberFormat`, or a function as the children of `GaugeValue` to format it yourself. Set
`max` when the range isn't 0 to 100.

```tsx
import { Gauge, GaugeLabel, GaugeValue } from "@/components/ui/gauge";

export default function GaugeFormattedValue() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-8">
      <Gauge value={7.2} max={10} format={{ style: "unit", unit: "gigabyte" }}>
        <GaugeValue />
        <GaugeLabel>of 10 GB</GaugeLabel>
      </Gauge>
      <Gauge
        value={184}
        max={250}
        format={{ style: "unit", unit: "millisecond" }}
        startAngle={-135}
        endAngle={135}
      >
        <GaugeValue>
          {(_, value) => (
            <>
              {value}
              <span className="ms-0.5 text-sm font-normal text-faint">ms</span>
            </>
          )}
        </GaugeValue>
        <GaugeLabel>p95 latency</GaugeLabel>
      </Gauge>
    </div>
  );
}
```

### Colors

Recolor the indicator by targeting its `data-slot` from the root.

```tsx
import { Gauge, GaugeLabel, GaugeValue } from "@/components/ui/gauge";

export default function GaugeColors() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-8">
      <Gauge value={78} className="**:data-[slot=gauge-indicator]:text-warning">
        <GaugeValue />
        <GaugeLabel>Build minutes</GaugeLabel>
      </Gauge>
      <Gauge value={94} className="**:data-[slot=gauge-indicator]:text-destructive">
        <GaugeValue />
        <GaugeLabel>Bandwidth</GaugeLabel>
      </Gauge>
      <Gauge value={52} className="**:data-[slot=gauge-indicator]:text-brand">
        <GaugeValue />
        <GaugeLabel>Requests</GaugeLabel>
      </Gauge>
    </div>
  );
}
```

### Thresholds

Choose the color from the value to warn as it climbs. The arc eases to each new value, unless the
reader prefers reduced motion.

```tsx
import * as React from "react";

import { Gauge, GaugeLabel, GaugeValue } from "@/components/ui/gauge";
import { Slider } from "@/components/ui/slider";
import { cn } from "@/lib/utils";

export default function GaugeThresholds() {
  const [value, setValue] = React.useState(64);

  return (
    <div className="flex w-full max-w-xs flex-col items-center gap-6">
      <Gauge
        value={value}
        size={160}
        thickness={10}
        startAngle={-120}
        endAngle={120}
        className={cn(
          value >= 90
            ? "**:data-[slot=gauge-indicator]:text-destructive"
            : value >= 75 && "**:data-[slot=gauge-indicator]:text-warning",
        )}
      >
        <GaugeValue />
        <GaugeLabel>CPU</GaugeLabel>
      </Gauge>
      <Slider value={value} onValueChange={setValue} aria-label="CPU" />
    </div>
  );
}
```

### Sizes

`size` sets the diameter and `thickness` the width of the arc, both in pixels, and a `thickness` of
half the `size` or more fills the gauge in like a pie. Size the text to fit, and leave out the label
on a small gauge, naming it with `aria-label` instead.

```tsx
import { Gauge, GaugeLabel, GaugeValue } from "@/components/ui/gauge";

export default function GaugeSizes() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-8">
      <Gauge value={64} size={64} thickness={6} aria-label="Storage">
        <GaugeValue className="text-sm" />
      </Gauge>
      <Gauge value={64}>
        <GaugeValue />
        <GaugeLabel>Storage</GaugeLabel>
      </Gauge>
      <Gauge value={64} size={168} thickness={12}>
        <GaugeValue />
        <GaugeLabel>Storage</GaugeLabel>
      </Gauge>
    </div>
  );
}
```

### Compact

Without children, a small gauge sits in a line of text, as in a list of resources.

```tsx
import { Gauge } from "@/components/ui/gauge";

const databases = [
  { name: "postgres-primary", used: 82 },
  { name: "redis-cache", used: 37 },
  { name: "analytics-replica", used: 12 },
];

export default function GaugeCompact() {
  return (
    <ul className="flex w-full max-w-xs flex-col divide-y-[0.5px] divide-border-muted">
      {databases.map((database) => (
        <li key={database.name} className="flex items-center gap-2.5 py-2.5">
          <Gauge
            value={database.used}
            size={16}
            thickness={3}
            aria-label={`${database.name} disk usage`}
          />
          <span className="min-w-0 truncate text-sm text-foreground">{database.name}</span>
          <span className="ml-auto text-sm text-faint tabular-nums">{database.used}%</span>
        </li>
      ))}
    </ul>
  );
}
```

## Props

| Part | Extends |
| --- | --- |
| `Gauge` | [`Meter.Root`](https://base-ui.com/react/components/meter#root) |
| `GaugeValue` | [`Meter.Value`](https://base-ui.com/react/components/meter#value) |
| `GaugeLabel` | [`Meter.Label`](https://base-ui.com/react/components/meter#label) |

The tables leave out `className`, `style` and `render`. Parts built on Base UI take them as its [styling](https://base-ui.com/react/handbook/styling) and [composition](https://base-ui.com/react/handbook/composition) guides describe.

### Gauge

| Prop                | Type                                                  | Description                                                                                                                                 |
| ------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `aria-valuetext?`   | `string`                                              | A string value that provides a user-friendly name for `aria-valuenow`, the current value of the meter.                                      |
| `format?`           | `Intl.NumberFormatOptions`                            | Options to format the value.                                                                                                                |
| `getAriaValueText?` | `((formattedValue: string, value: number) => string)` | A function that returns a string value that provides a human-readable text alternative for `aria-valuenow`, the current value of the meter. |
| `locale?`           | `Intl.LocalesArgument`                                | The locale used by `Intl.NumberFormat` when formatting the value. Defaults to the user's runtime locale.                                    |
| `max?`              | `number`                                              | The maximum value Default: `100`                                                                                                            |
| `min?`              | `number`                                              | The minimum value Default: `0`                                                                                                              |
| `value`             | `number`                                              | The current value.                                                                                                                          |
| `size?`             | `number`                                              | The gauge's diameter, in pixels. Default: `120`                                                                                             |
| `thickness?`        | `number`                                              | The width of the arc, in pixels. Default: `8`                                                                                               |
| `startAngle?`       | `number`                                              | Where the arc starts, in degrees clockwise from the top. Default: `0`                                                                       |
| `endAngle?`         | `number`                                              | Where the arc ends, in degrees clockwise from the top. 360 past `startAngle` draws a full circle. Default: `360`                            |

### GaugeValue

| Prop        | Type                                                                   | Description |
| ----------- | ---------------------------------------------------------------------- | ----------- |
| `children?` | `((formattedValue: string, value: number) => React.ReactNode) \| null` |             |
