Прогресс

Берите определённую полосу всякий раз, когда знаете, сколько осталось: только она хоть что-то сообщает читателю. Опустите value — и полоса вместо этого анимируется, что говорит «всё ещё работаю» и больше ничего.

Определённый прогресс

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

Неопределённый прогресс

progress()

Полоса без label помечается aria-hidden: роль progressbar без доступного имени ничего не сообщает скринридеру, поэтому неподписанная полоса считается декорацией. Подписывайте всё, за чем читателю положено следить.

Подписи

Подпись называет происходящее; showValue добавляет справа проценты.

Рендерим страницы72%
Оптимизируем изображения50%
Ждём развёртывания
stack({ gap: 'lg' },
  progress({ value: 72, label: 'Рендерим страницы', showValue: true }),
  progress({ value: 30, max: 60, label: 'Оптимизируем изображения', showValue: true }),
  progress({ label: 'Ждём развёртывания' }),
)

Цвета и высота

Успешно80%
С оговорками45%
Падает20%
stack({ gap: 'lg' },
  progress({ value: 80, color: 'success', label: 'Успешно', showValue: true }),
  progress({ value: 45, color: 'warning', label: 'С оговорками', showValue: true }),
  progress({ value: 20, color: 'danger', label: 'Падает', showValue: true }),
  progress({ value: 60, color: 'neutral', height: 'xs' }),
  progress({ value: 60, color: 'primary', height: '1rem' }),
)

Шкала не из 100

max позволяет передавать сырые числа — собранных страниц из общего числа — вместо того чтобы сначала считать проценты.

118 из 169 страниц70%
progress({ value: 118, max: 169, label: '118 из 169 страниц', showValue: true })

Двигать её из браузера

Полоса — это HTML, отрисованный на сервере: процент лежит в пользовательском свойстве на заливке, а число — в aria-valuenow, и ничто на странице не меняет ни то, ни другое само по себе. Дайте полосе id, и setProgress сдвинет всё сразу — заливку, объявляемое значение и процент рядом с подписью.

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

const request = new XMLHttpRequest()

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

Максимум запоминается, так что дальше достаточно передавать значение. Или дотянитесь до модуля так же, как это делают компоненты, и обойдитесь без бандла вовсе:

button({ onclick: "import('/su/progress.js').then(m=>m.set('upload',100))" }, 'Завершить')

Передача null — или чего угодно, что не является конечным числом — возвращает полосу к неопределённой анимации, поэтому работу, переставшую сообщать числа, не нужно выделять в отдельный случай. getProgress() читает текущее значение обратно, в собственной шкале полосы.

Попробуйте

Эта страница загружает рантайм, так что кнопки ниже действительно двигают полосу.

Загрузка0%
stack({ gap: 'md' },
  progress({ id: 'demo-progress', value: 0, label: 'Загрузка', 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))" }, 'Сброс'),
    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))" }, 'Готово'),
    button({ size: 'sm', variant: 'ghost', onclick: "import('/su/progress.js').then(m=>m.set('demo-progress',null))" }, 'Неизвестно'),
  ),
)

Полоса без подписи тоже сдвинется, но останется aria-hidden — её намеренно отрисовали без имени, и объявить ей значение сейчас значило бы поместить в дерево доступности progressbar без имени.

Спиннер

Отдельного компонента-спиннера нет — спиннер это иконка, а вращает её spin. Как и любая иконка, он задаётся в em, поэтому подходит к любому соседнему тексту, и размер ему называть не нужно.

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

Спиннер в контексте

Дайте одиночному спиннеру label, чтобы его объявляли. Спиннеру внутри кнопки это не нужно: кнопка и так говорит, что делает.

Получаем последнюю сборку…

stack({ gap: 'md' },
  stack({ direction: 'row', gap: 'sm', align: 'center' },
    icon('spinner', { spin: true, label: 'Загрузка' }),
    text({ variant: 'small', tone: 'muted' }, 'Получаем последнюю сборку…'),
  ),
  stack({ direction: 'row', gap: 'sm' },
    button({ loading: true }, 'Разворачиваем'),
    button({ variant: 'outline', loading: true }, 'Проверяем ссылки'),
  ),
)

Пропсы

progress() — экспортируется также как progressBar:

ПропТипПо умолчаниюОписание
valuenumber—Насколько продвинулись. Опустите для неопределённой анимации.
maxnumber100Какое значение считается завершением.
color'primary' | 'neutral' | 'success' | 'warning' | 'danger''primary'Цвет заливки.
labelChild—Текст над полосой; он же её доступное имя.
showValuebooleanfalseПоказывать проценты рядом с подписью.
heightSpace'0.5rem'Толщина полосы.

setProgress() из sitelo/ui/client:

ПропТипПо умолчаниюОписание
targetElement | string—Полоса или её id. Если элемента с таким id нет, строка пробуется как селектор.
valuenumber | null—Куда её сдвинуть. null возвращает её к неопределённой анимации.
options.maxnumber100Что считается завершённым. Запоминается для последующих вызовов.

Своих пропсов у спиннера нет — это icon('spinner', { spin: true }), и он принимает то же, что и icon().