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.
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"`.
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%").
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.
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».
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
Generadas del TypeScript del paquete. Las propias del componente, más las heredadas del primitivo que tienen algo que explicar —marcadas «heredada de Base UI»—. El resto está en la línea «hereda de».
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. |
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. |
Teclado
- —
- No es interactivo.
Accesibilidad
- Emite
role="meter", norole="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-valuetextlleva el valor ya formateado —«62%», «$ 321.400»—, que es más útil que el número crudo dearia-valuenow.- Lo que se ve con
showValuey 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-labeloaria-labelledby. Mismo motivo que enProgress. - Un valor fuera de rango se recorta contra
minymaxen vez de desbordar la pista. StackedMeteres unrole="group"(con nombre obligatorio) con un `role="meter"` por segmento, nombrado por sulabely con el valor formateado enaria-valuetext: el lector dice «Facturas, 13,5 GB» segmento por segmento, no un porcentaje suelto.- En
StackedMeterel color no es el único dato: cada segmento tiene nombre, ylegend(o unaListcondot) 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».
minymaxson el rango real del dato: la barra se llena sobre ese rango, no sobre 100.formatylocaleson los deIntl.NumberFormat. Sinformat, lo que se anuncia es la proporción.- Poné
labeloaria-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;mdsuelta.- Varias medidas del mismo total → `StackedMeter`, la barra de almacenamiento de iCloud: el chip del total (
total), «Libre · Usado» arriba y el desglose conlegendo, mejor, unaListconListSectionydotdebajo. - Hasta cinco o seis segmentos: más que eso no se distinguen en 16 px. Agrupá el resto en «Otros».