Progreso

Usa una barra determinada siempre que sepas cuánto queda: es la única que le dice algo al lector. Omite value y la barra se anima en su lugar, lo cual dice «sigo trabajando» y nada más.

Determinada

stack({ gap: 'lg' },
  progress({ value: 25 }),
  progress({ value: 60 }),
  progress({ value: 100 }),
)

Indeterminada

progress()

Una barra sin label se marca con aria-hidden: un rol progressbar sin nombre accesible no le dice nada a un lector de pantalla, así que una barra sin etiqueta se trata como decoración. Etiqueta todo aquello que el lector deba seguir.

Etiquetas

Una etiqueta nombra lo que está pasando; showValue añade el porcentaje a la derecha.

Renderizando páginas72%
Optimizando imágenes50%
Esperando al despliegue
stack({ gap: 'lg' },
  progress({ value: 72, label: 'Renderizando páginas', showValue: true }),
  progress({ value: 30, max: 60, label: 'Optimizando imágenes', showValue: true }),
  progress({ label: 'Esperando al despliegue' }),
)

Colores y altura

Correcto80%
Degradado45%
Fallando20%
stack({ gap: 'lg' },
  progress({ value: 80, color: 'success', label: 'Correcto', showValue: true }),
  progress({ value: 45, color: 'warning', label: 'Degradado', showValue: true }),
  progress({ value: 20, color: 'danger', label: 'Fallando', showValue: true }),
  progress({ value: 60, color: 'neutral', height: 'xs' }),
  progress({ value: 60, color: 'primary', height: '1rem' }),
)

Una escala distinta de 100

max te deja pasar los números tal cual — páginas construidas sobre páginas totales — en vez de calcular antes un porcentaje.

118 de 169 páginas70%
progress({ value: 118, max: 169, label: '118 de 169 páginas', showValue: true })

Moverla desde el navegador

Una barra es HTML renderizado en el servidor: el porcentaje es una propiedad personalizada en el relleno y un número en aria-valuenow, y nada en la página cambia ninguno de los dos por su cuenta. Dale a la barra un id y setProgress los mueve a la vez — el relleno, el valor anunciado y el porcentaje junto a la etiqueta.

import { setProgress } from 'sitelo/ui/client'

const request = new XMLHttpRequest()

request.upload.addEventListener('progress', (event) => {
  setProgress('upload', event.loaded, { max: event.total })
})

El máximo se recuerda, así que las llamadas siguientes son solo un valor. O alcanza el módulo igual que lo hacen los componentes, y sáltate el bundle por completo:

button({ onclick: "import('/su/progress.js').then(m=>m.set('upload',100))" }, 'Terminar')

Pasar null — o cualquier cosa que no sea un número finito — devuelve la barra a la animación indeterminada, así que el trabajo que deja de dar números no necesita un caso aparte. getProgress() vuelve a leer el valor actual, en la escala propia de la barra.

Pruébalo

Esta página carga el runtime, así que los botones de abajo mueven la barra de verdad.

Subiendo0%
stack({ gap: 'md' },
  progress({ id: 'demo-progress', value: 0, label: 'Subiendo', showValue: true }),
  stack({ direction: 'row', gap: 'sm', wrap: true },
    button({ size: 'sm', variant: 'outline', onclick: "import('/su/progress.js').then(m=>m.set('demo-progress',0))" }, 'Reiniciar'),
    button({ size: 'sm', variant: 'outline', onclick: "import('/su/progress.js').then(m=>m.set('demo-progress',35))" }, '35%'),
    button({ size: 'sm', variant: 'outline', onclick: "import('/su/progress.js').then(m=>m.set('demo-progress',80))" }, '80%'),
    button({ size: 'sm', variant: 'outline', onclick: "import('/su/progress.js').then(m=>m.set('demo-progress',100))" }, 'Listo'),
    button({ size: 'sm', variant: 'ghost', onclick: "import('/su/progress.js').then(m=>m.set('demo-progress',null))" }, 'Desconocido'),
  ),
)

Una barra sin etiqueta también se mueve, pero sigue siendo aria-hidden — se renderizó sin nombre a propósito, y anunciarle un valor ahora metería en el árbol de accesibilidad un progressbar sin nombre.

Spinner

No hay un componente spinner — el spinner es un icono, y spin es lo que lo hace girar. Como cualquier icono se dimensiona en em, así que encaja con el texto que tenga al lado sin que haya que decirle un tamaño.

stack({ direction: 'row', gap: 'lg', align: 'center' },
  icon('spinner', { spin: true, size: 'sm' }),
  icon('spinner', { spin: true }),
  icon('spinner', { spin: true, size: 'lg' }),
)

El spinner en contexto

Dale a un spinner suelto un label para que se anuncie. Uno dentro de un botón no lo necesita: el botón ya dice qué está haciendo.

Obteniendo la última compilación…

stack({ gap: 'md' },
  stack({ direction: 'row', gap: 'sm', align: 'center' },
    icon('spinner', { spin: true, label: 'Cargando' }),
    text({ variant: 'small', tone: 'muted' }, 'Obteniendo la última compilación…'),
  ),
  stack({ direction: 'row', gap: 'sm' },
    button({ loading: true }, 'Desplegando'),
    button({ variant: 'outline', loading: true }, 'Comprobando enlaces'),
  ),
)

Props

progress() — también exportado como progressBar:

PropTipoPor defectoDescripción
valuenumber—Cuánto se lleva hecho. Omítelo para la animación indeterminada.
maxnumber100Qué valor cuenta como completo.
color'primary' | 'neutral' | 'success' | 'warning' | 'danger''primary'Color del relleno.
labelChild—Texto sobre la barra; también su nombre accesible.
showValuebooleanfalseMostrar el porcentaje junto a la etiqueta.
heightSpace'0.5rem'Grosor de la barra.

setProgress() de sitelo/ui/client:

PropTipoPor defectoDescripción
targetElement | string—La barra, o el id de una. Si ningún elemento tiene ese id, se prueba como selector.
valuenumber | null—Adónde moverla. null la devuelve a la animación indeterminada.
options.maxnumber100Qué cuenta como completo. Se recuerda para las llamadas siguientes.

El spinner no tiene props propias — es icon('spinner', { spin: true }), y acepta lo que acepte icon().