# Meter

> Una medida dentro de un rango: disco usado, cupo consumido, ocupación. No es un progreso. Con `StackedMeter`, la barra de almacenamiento de iCloud.

```tsx
import { Meter, StackedMeter } from "sebs7n-ui/meter"
```

## Ejemplos

### Cuánto del plan se usó

Una medida, no una tarea: el número puede subir o bajar solo —al borrar un archivo el disco se libera— y nunca "termina". Por eso es `role="meter"` y no `role="progressbar"`.

```tsx
import { Button } from "sebs7n-ui/button"
import { Meter } from "sebs7n-ui/meter"
import { useState } from "react"

function EspacioDelPlan() {
  const [usado, setUsado] = useState(6.4)

  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <Meter
        format={{ style: "unit", unit: "gigabyte", maximumFractionDigits: 1 }}
        label="Espacio usado"
        locale="es-AR"
        max={10}
        showValue
        value={usado}
      />
      <div className="flex gap-2">
        <Button onClick={() => setUsado((v) => Math.min(10, v + 1.3))} size="sm" variant="secondary">
          Subir un adjunto
        </Button>
        <Button disabled={usado === 0} onClick={() => setUsado(0)} size="sm" variant="ghost">
          Vaciar la papelera
        </Button>
      </div>
    </div>
  )
}
```

### Con su propio rango y su propio formato

`min` y `max` son el rango real del dato y `format` es el de `Intl.NumberFormat`: lo que se ve y lo que lee el lector salen del mismo texto, así que no se pueden desincronizar. Sin `format`, lo que se anuncia es la proporción ("64%").

```tsx
import { Meter } from "sebs7n-ui/meter"

function CupoFacturado() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-6">
      <Meter
        format={{ style: "currency", currency: "ARS", maximumFractionDigits: 0 }}
        label="Consumo del mes"
        locale="es-AR"
        max={500_000}
        showValue
        value={321_400}
      />
      <Meter label="Legajos cargados" max={40} showValue value={26} />
      {/* `lg`: la barra de almacenamiento de Settings de iCloud, 16 de alto y radio 6. */}
      <Meter label="Almacenamiento" max={50} showValue size="lg" value={21.5} />
    </div>
  )
}
```

### Tamaños y uso en una lista

`sm` (4px) cuando la barra acompaña una fila y el texto de al lado ya dice el número; `md` (6px) suelta. Sin etiqueta visible va `aria-label`: una barra sin nombre no dice qué está midiendo.

```tsx
import { Meter } from "sebs7n-ui/meter"

function LicenciasPorEquipo() {
  const equipos = [
    { nombre: "Ventas", licencias: 48, usadas: 41 },
    { nombre: "Soporte", licencias: 30, usadas: 12 },
    { nombre: "Administración", licencias: 22, usadas: 22 },
  ]

  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      {equipos.map((equipo) => (
        <div className="flex flex-col gap-1.5" key={equipo.nombre}>
          <div className="flex items-baseline justify-between">
            <span className="text-callout text-label">{equipo.nombre}</span>
            <span className="text-footnote text-label-secondary">
              {equipo.usadas} de {equipo.licencias} licencias
            </span>
          </div>
          <Meter
            aria-label={`Licencias usadas de ${equipo.nombre}`}
            max={equipo.licencias}
            size="sm"
            value={equipo.usadas}
          />
        </div>
      ))}
    </div>
  )
}
```

### Apilado: la barra de almacenamiento

`StackedMeter` apila varias medidas del mismo total, como la barra de Almacenamiento de iCloud: cada segmento es un `meter` con su nombre («Facturas, 13,5 GB»), el resto queda gris, y arriba van el chip del total y «Libre · Usado».

```tsx
import { StackedMeter } from "sebs7n-ui/meter"

function Apilado() {
  return (
    <StackedMeter
      aria-label="Espacio de la cuenta"
      className="w-full max-w-xl"
      format={{ style: "unit", unit: "gigabyte", maximumFractionDigits: 1 }}
      legend
      locale="es-AR"
      max={50}
      segments={[
        { label: "Facturas", value: 13.5, color: "amber" },
        { label: "Documentos", value: 6.1, color: "purple" },
        { label: "Imágenes", value: 4, color: "teal" },
        { label: "Adjuntos", value: 1.2, color: "blue" },
      ]}
      total="50 GB"
    />
  )
}
```

## Props

### Meter

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `value` * | `number` | — | **Heredada de Base UI.** El valor actual, siempre un número: un `Meter` no tiene estado indeterminado. |
| `label` | `NonNullable<ReactNode>` | — | El nombre visible de la medida. Es la forma preferida de nombrarla; sin él, el tipo exige `aria-label` o `aria-labelledby`. |
| `showValue` | `boolean` | `false` | Muestra el valor formateado a la derecha. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | `sm` 4px · `md` 6px de alto de la pista, los mismos que `Progress` · `lg` 16px con radio 6, la barra de almacenamiento de iCloud. |
| `trackClassName` | `string` | — | Clases de la pista (el riel gris), por si hay que cambiarle el ancho o el radio. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |
| `format` | `Intl.NumberFormatOptions` | — | **Heredada de Base UI.** Opciones de `Intl.NumberFormat`. Cambian lo que se ve y lo que se lee, nunca el valor. |
| `locale` | `Intl.LocalesArgument` | — | **Heredada de Base UI.** El locale de `Intl.NumberFormat`. Por defecto, el del navegador. |

`*` obligatoria.

### StackedMeter

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `max` * | `number` | — | El total: el tamaño del plan. |
| `segments` * | `StackedMeterSegment[]` | — | Los segmentos, en orden: `{ label, value, color }` (`color` de la paleta de `Badge`). Lo que sobra hasta `max` queda gris. |
| `format` | `Intl.NumberFormatOptions` | — | Opciones de `Intl.NumberFormat` para los valores (`{ style: "unit", unit: "gigabyte" }`). |
| `labels` | `Partial<Labels["meter"]>` | — | Los textos de la cabecera («Libre», «Usado»). Le gana al `LabelsProvider`. |
| `legend` | `boolean` | `false` | El desglose debajo de la barra: punto, nombre y valor. |
| `locale` | `Intl.LocalesArgument` | — | El locale de `Intl.NumberFormat`. |
| `total` | `React.ReactNode` | — | El chip del total, a la izquierda de la cabecera: blanco, radio 10, 28/700. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

`*` obligatoria.

## Teclado

| Tecla | Qué hace |
|---|---|
| — | No es interactivo. |

## Accesibilidad

- Emite `role="meter"`, no `role="progressbar"`: el lector anuncia una medida y no una tarea en curso. Es la diferencia que hace que valga la pena tener los dos componentes.
- `aria-valuetext` lleva el valor ya formateado —«62%», «$ 321.400»—, que es más útil que el número crudo de `aria-valuenow`.
- Lo que se ve con `showValue` y lo que se lee salen del mismo texto: no se pueden desincronizar.
- El nombre es **obligatorio y lo exige el tipo**: `label` (visible, la preferida), `aria-label` o `aria-labelledby`. Mismo motivo que en `Progress`.
- Un valor fuera de rango se recorta contra `min` y `max` en vez de desbordar la pista.
- `StackedMeter` es un `role="group"` (con nombre obligatorio) con **un `role="meter"` por segmento**, nombrado por su `label` y con el valor formateado en `aria-valuetext`: el lector dice «Facturas, 13,5 GB» segmento por segmento, no un porcentaje suelto.
- En `StackedMeter` el color no es el único dato: cada segmento tiene nombre, y `legend` (o una `List` con `dot`) lo escribe.

## Reglas de uso

- **Si el número puede bajar solo, es un `Meter`.** Un disco que se libera al borrar un archivo, un cupo que se renueva, una ocupación que sube y baja. Si en cambio arrancó, va para un lado solo y al llegar al final la pantalla cambia de estado, es un `Progress`.
- **Ese es el error que garantiza que alguien use el equivocado**: las dos barras se ven igual, pero un lector de pantalla anuncia cosas distintas, y «subiendo el archivo» no es lo mismo que «6,4 GB de 10».
- `min` y `max` son el rango real del dato: la barra se llena sobre ese rango, no sobre 100.
- `format` y `locale` son los de `Intl.NumberFormat`. Sin `format`, lo que se anuncia es la proporción.
- Poné `label` o `aria-label`: una barra sin nombre no dice qué está midiendo.
- `size="sm"` cuando acompaña una fila de una lista y el texto de al lado ya dice el número; `md` suelta.
- **Varias medidas del mismo total → `StackedMeter`**, la barra de almacenamiento de iCloud: el chip del total (`total`), «Libre · Usado» arriba y el desglose con `legend` o, mejor, una `List` con `ListSection` y `dot` debajo.
- Hasta cinco o seis segmentos: más que eso no se distinguen en 16 px. Agrupá el resto en «Otros».

## Relacionados

[progress](/docs/components/progress.md) · [stat](/docs/components/stat.md) · [slider](/docs/components/slider.md)
