component · 15/108
Progress Summary
Progress, elapsed time, remaining work, and cancellation in one summary.
Install
Install manuallyView source on GitHub
Anatomy
- ProgressSummary
- ProgressSummaryHeader
- ProgressSummaryTitle
- ProgressSummaryStatusText
- ProgressSummaryValue
- ProgressSummaryBar
- ProgressSummaryStats
- ProgressSummaryStat
- ProgressSummaryStatLabel
- ProgressSummaryStatValue
- ProgressSummaryActions
- ProgressSummaryCancel
Accessibility3 checks
- ProgressSummaryBar is a role="progressbar" labelled by the title, with aria-valuenow and a readable aria-valuetext. Omit value for indeterminate progress.
- Elapsed and remaining figures are a description list, so labels and values stay paired for screen readers.
- Status text is a polite live region. Cancel is disabled once work is complete, cancelled, or failed; the consumer owns stopping the job.
Feedback and state
Importing contacts from HubSpot
96 of 240 records
- Elapsed
- 1m 24s
- Remaining
- 144 records
- Skipped
- 3 duplicates
Code example
usage.tsx"use client";import { useEffect, useState } from "react";import { ProgressSummary, ProgressSummaryActions, ProgressSummaryBar, ProgressSummaryCancel, ProgressSummaryHeader, ProgressSummaryStat, ProgressSummaryStatLabel, ProgressSummaryStats, type ProgressSummaryStatus, ProgressSummaryStatusText, ProgressSummaryStatValue, ProgressSummaryTitle, ProgressSummaryValue, type ProgressSummaryVariant,} from "@/components/ui/uai/progress-summary";const TOTAL = 240;function formatSeconds(seconds: number) { const minutes = Math.floor(seconds / 60); return `${minutes}m ${String(seconds % 60).padStart(2, "0")}s`;}export function ProgressSummaryPreview({ variant = "card" }: { variant?: ProgressSummaryVariant }) { const [done, setDone] = useState(96); const [elapsed, setElapsed] = useState(84); const [cancelled, setCancelled] = useState(false); const status: ProgressSummaryStatus = cancelled ? "cancelled" : done === TOTAL ? "complete" : "running"; useEffect(() => { if (status !== "running") return; const timer = window.setInterval(() => { setElapsed((seconds) => seconds + 1); setDone((count) => Math.min(count + 2, TOTAL)); }, 1000); return () => window.clearInterval(timer); }, [status]); const remaining = TOTAL - done; return ( <ProgressSummary variant={variant} value={done} max={TOTAL} status={status}> <ProgressSummaryHeader> <ProgressSummaryTitle>Importing contacts from HubSpot</ProgressSummaryTitle> <ProgressSummaryStatusText> {status === "running" ? `${done} of ${TOTAL} records` : undefined} </ProgressSummaryStatusText> </ProgressSummaryHeader> <ProgressSummaryValue /> <ProgressSummaryBar /> <ProgressSummaryStats> <ProgressSummaryStat> <ProgressSummaryStatLabel>Elapsed</ProgressSummaryStatLabel> <ProgressSummaryStatValue>{formatSeconds(elapsed)}</ProgressSummaryStatValue> </ProgressSummaryStat> <ProgressSummaryStat> <ProgressSummaryStatLabel>Remaining</ProgressSummaryStatLabel> <ProgressSummaryStatValue>{remaining} records</ProgressSummaryStatValue> </ProgressSummaryStat> <ProgressSummaryStat> <ProgressSummaryStatLabel>Skipped</ProgressSummaryStatLabel> <ProgressSummaryStatValue>3 duplicates</ProgressSummaryStatValue> </ProgressSummaryStat> </ProgressSummaryStats> <ProgressSummaryActions> {status === "running" ? ( <ProgressSummaryCancel onClick={() => setCancelled(true)}> Cancel import </ProgressSummaryCancel> ) : ( <button type="button" onClick={() => { setDone(0); setElapsed(0); setCancelled(false); }} style={{ height: 28, padding: "0 12px", border: 0, borderRadius: 999, background: "var(--uai-surface-raised)", color: "var(--uai-text)", fontSize: 12.5, fontWeight: 500, cursor: "pointer", }} > Run again </button> )} </ProgressSummaryActions> </ProgressSummary> );}Manual installation
progress-summary.tsxLoading component source…