Przejdź do treści

Progress

Pokazuje, jak daleko zaszło zadanie — albo że trwa, gdy nie wiadomo, ile jeszcze potrwa.

Kod źródłowyBase UIRejestr
Przesyłanie zasobów
x
import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

export default function ProgressDemo() {
  return (
    <Progress value={64} className="w-full max-w-xs">
      <ProgressLabel>Przesyłanie zasobów</ProgressLabel>
      <ProgressValue />
    </Progress>
  )
}

Anatomia

Przesyłanie zasobów
x
  1. 1EtykietaMówi, co jest w toku. Nazywa też pasek dla czytników ekranu.
  2. 2TorCałe zadanie, w stonowanym odcieniu.
  3. 3WartośćProcent zapisany cyframi tabelarycznymi, dzięki czemu nie drga przy zmianie.
  4. 4WskaźnikUkończona część. Animuje przejścia między wartościami, a gdy wartość jest nieznana, przesuwa się wzdłuż toru.

Instalacja

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/progress.json

Użycie

import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress"
<Progress value={64}>
  <ProgressLabel>Uploading assets</ProgressLabel>
  <ProgressValue />
</Progress>

Progress sam renderuje tor i wskaźnik. Elementy potomne — zwykle etykieta i wartość — trafiają do wiersza nad paskiem.

Przykłady

Rozmiary

sm (4 px) mieści się w listach i komórkach tabel, default (6 px) pasuje do większości formularzy i kart, a lg (10 px) jest przeznaczony dla paska postępu, który jest głównym elementem widoku.

Mały
x
Domyślny
x
Duży
x
import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

export default function ProgressSizes() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-6">
      <Progress value={30} size="sm">
        <ProgressLabel>Mały</ProgressLabel>
        <ProgressValue />
      </Progress>
      <Progress value={55}>
        <ProgressLabel>Domyślny</ProgressLabel>
        <ProgressValue />
      </Progress>
      <Progress value={80} size="lg">
        <ProgressLabel>Duży</ProgressLabel>
        <ProgressValue />
      </Progress>
    </div>
  )
}

Stan nieokreślony

Przekaż value={null}, gdy wiesz, że praca trwa, ale nie wiesz, ile jej zostało. Fragment paska przesuwa się wzdłuż toru, dopóki nie przełączysz go na określoną wartość.

Przygotowujemy eksport…
x
import { Progress, ProgressLabel } from "@/components/ui/progress"

export default function ProgressIndeterminate() {
  return (
    <Progress value={null} className="w-full max-w-xs">
      <ProgressLabel>Przygotowujemy eksport…</ProgressLabel>
    </Progress>
  )
}

Aktualizacje na żywo

Wskaźnik animuje przejścia między wartościami z czasem trwania slow i krzywą standard, więc częste aktualizacje wyglądają jak płynny ruch, a nie skoki.

Budowanie…
x
"use client"

import * as React from "react"
import { RotateCcwIcon } from "lucide-react"

import { Button } from "@/components/ui/button"
import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

export default function ProgressLive() {
  const [value, setValue] = React.useState(0)
  const [run, setRun] = React.useState(0)

  React.useEffect(() => {
    // Each run counts up from zero; Restart resets the value and starts a new run.
    let progress = 0
    const timer = setInterval(() => {
      progress = Math.min(100, progress + Math.round(Math.random() * 12 + 4))
      setValue(progress)
      if (progress === 100) clearInterval(timer)
    }, 450)
    return () => clearInterval(timer)
  }, [run])

  return (
    <div className="flex w-full max-w-xs flex-col items-end gap-4">
      <Progress value={value} className="w-full">
        <ProgressLabel>
          {value === 100 ? "Budowanie zakończone" : "Budowanie…"}
        </ProgressLabel>
        <ProgressValue />
      </Progress>
      <Button
        variant="outline"
        size="xs"
        onClick={() => {
          setValue(0)
          setRun((n) => n + 1)
        }}
      >
        <RotateCcwIcon data-icon="inline-start" />
        Uruchom ponownie
      </Button>
    </div>
  )
}

Przesyłanie plików

W listach użyj rozmiaru sm, a etykietą niech będzie nazwa pliku. Gdy pozycja się zakończy, zastąp wartość potwierdzeniem, aby ukończone wiersze przestały przyciągać wzrok.

  • brand-guidelines.pdfGotowe4,2 MB
    x
  • hero-illustration.png2,8 MB
    x
  • product-walkthrough.mp4148 MB
    x
import {
  CircleCheckIcon,
  FileTextIcon,
  ImageIcon,
  FileVideoIcon,
} from "lucide-react"

import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

const files = [
  {
    name: "brand-guidelines.pdf",
    size: "4,2 MB",
    value: 100,
    icon: FileTextIcon,
  },
  { name: "hero-illustration.png", size: "2,8 MB", value: 72, icon: ImageIcon },
  {
    name: "product-walkthrough.mp4",
    size: "148 MB",
    value: 18,
    icon: FileVideoIcon,
  },
]

export default function ProgressFileUpload() {
  return (
    <ul className="flex w-full max-w-md flex-col divide-y rounded-xl border bg-card shadow-raised">
      {files.map((file) => {
        const Icon = file.icon
        const done = file.value === 100
        return (
          <li key={file.name} className="flex items-start gap-3 p-4">
            <span className="flex size-9 shrink-0 items-center justify-center rounded-lg border bg-surface text-muted-foreground">
              <Icon className="size-4" />
            </span>
            <Progress value={file.value} size="sm" className="min-w-0 flex-1">
              <ProgressLabel className="truncate">{file.name}</ProgressLabel>
              {done ? (
                <span className="ml-auto flex items-center gap-1 text-sm text-success-11">
                  <CircleCheckIcon className="size-4" />
                  Gotowe
                </span>
              ) : (
                <ProgressValue />
              )}
              <span className="order-last w-full text-xs text-muted-foreground">
                {file.size}
              </span>
            </Progress>
          </li>
        )
      })}
    </ul>
  )
}

Własna wartość

ProgressValue przyjmuje funkcję renderującą dla jednostek innych niż procent. Gdy zmieniasz widoczny format, przekaż technologiom wspomagającym te same słowa przez getAriaValueText.

Miejsce na dane
x
"use client"

import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@/components/ui/progress"

const used = 7.2
const total = 10
const format = (value: number) => value.toLocaleString("pl-PL")

export default function ProgressCustomValue() {
  return (
    <Progress
      value={used}
      max={total}
      getAriaValueText={() =>
        `Wykorzystano ${format(used)} z ${format(total)} gigabajtów`
      }
      className="w-full max-w-xs"
    >
      <ProgressLabel>Miejsce na dane</ProgressLabel>
      <ProgressValue>
        {() => `${format(used)} GB z ${format(total)} GB`}
      </ProgressValue>
    </Progress>
  )
}
<Progress
  value={7.2}
  max={10}
  getAriaValueText={() => "7.2 of 10 gigabytes used"}
>
  <ProgressLabel>Storage</ProgressLabel>
  <ProgressValue>{() => "7.2 GB of 10 GB"}</ProgressValue>
</Progress>

Wytyczne

Kiedy używać

  • Do zadań, które trwają dłużej niż około sekundy i raportują mierzalny postęp: przesyłania, eksportu, importu, budowania.
  • Do wielkości mierzonych względem limitu — miejsca na dane, licencji, przydziałów — gdy pasek jest wskaźnikiem poziomu, a nie zegarem.

Kiedy nie używać

  • Do krótkich lub niemierzalnych oczekiwań wewnątrz kontrolki — użyj Spinner albo stanu loading komponentu Button.
  • Do treści, która wciąż się ładuje — użyj Skeleton, który zapowiada układ.
  • Do kroków w procesie. Pasek postępu mierzy wykonaną pracę, a nie nawigację.

Skeleton, Spinner czy Progress?

OczekiwanieRozwiązanieDlaczego
Poniżej ~1 sekundyBrak wskaźnikaWskaźniki, które migną tylko na moment, sprawiają wrażenie wolniejszych niż ich brak.
Treść jest w drodzeSkeletonPokazuje kształt tego, co nadchodzi, więc strona nie przeskakuje.
Akcja jest w tokuSpinner lub Button loadingPotwierdza, że system przyjął polecenie.
Mierzalna praca, ponad ~1 sekundęProgressMówi, jak długo trzeba będzie czekać, aby można było zdecydować, czy zostać.

Bądź uczciwy

Nigdy nie pozwól, by określony pasek utknął na 99% albo się cofnął. Jeśli nie potrafisz wiarygodnie oszacować postępu, użyj stanu nieokreślonego i opisz w etykiecie, co się dzieje.

Importowanie 1280 kontaktów
x
DobrzeEtykieta mówi, co się dzieje, a wartość — jak daleko to zaszło.
x
ŹlePasek bez etykiety zmusza do zgadywania, co się ładuje.

Dostępność

  • Renderuje role="progressbar" z aria-valuemin, aria-valuemax i aria-valuenow. Stan nieokreślony pomija aria-valuenow — czytniki ekranu odczytują wtedy pasek jako zajęty.
  • ProgressLabel jest powiązany z paskiem przez aria-labelledby. Gdy nie możesz pokazać etykiety, przekaż zamiast niej aria-label do Progress — pasek postępu zawsze musi mieć nazwę.
  • aria-valuetext jest generowany ze sformatowanej wartości. Nadpisz go przez getAriaValueText za każdym razem, gdy widoczna wartość nie jest procentem.
  • Paski postępu nie są regionami na żywo (live regions): czytniki ekranu odczytują wartość, gdy użytkownik do niej przejdzie, a nie przy każdej zmianie. Kamienie milowe — Przesyłanie zakończone — ogłaszaj przez Toast lub komunikat z role="status".
  • Przy prefers-reduced-motion zmiany wartości i przesuwanie się paska w stanie nieokreślonym nie są animowane.

Dokumentacja API

Progress

Komponent główny. Renderuje <div> z twoimi elementami potomnymi, po których następują ProgressTrack i ProgressIndicator. Przyjmuje wszystkie propsy Progress.Root z Base UI.

PropTypDomyślnie
valuewymagany

Bieżąca wartość. null renderuje stan nieokreślony.

number | nullBrak wartości domyślnej
min

Wartość minimalna.

number0
max

Wartość maksymalna.

number100
size

Wysokość toru: 4, 6 lub 10 px.

"sm" | "default" | "lg""default"
format

Formatuje wartość wyświetlaną przez ProgressValue i odczytywaną przez czytniki ekranu.

Intl.NumberFormatOptionsBrak wartości domyślnej
locale

Ustawienia regionalne do formatowania liczb. Domyślnie te ze środowiska użytkownika.

Intl.LocalesArgumentBrak wartości domyślnej
getAriaValueText

Zwraca zrozumiały dla człowieka opis bieżącej wartości.

(formattedValue: string, value: number | null) => stringBrak wartości domyślnej

ProgressLabel

Dostępna nazwa paska. Renderuje <span>.

PropTypDomyślnie
className

Dodatkowe klasy.

stringBrak wartości domyślnej

ProgressValue

Wyświetla sformatowaną wartość wyrównaną do prawej. Renderuje <span>.

PropTypDomyślnie
children

Własny sposób renderowania wartości, np. w jednostkach innych niż procent.

(formattedValue: string | null, value: number | null) => ReactNodeBrak wartości domyślnej

ProgressTrack i ProgressIndicator

Progress renderuje je automatycznie. Są eksportowane na potrzeby własnych kompozycji — na przykład segmentowego miernika, który renderuje kilka wskaźników.