f6ae86617c
Co-authored-by: Copilot <copilot@github.com>
158 lines
4.6 KiB
Markdown
158 lines
4.6 KiB
Markdown
# Progress
|
|
|
|
A horizontal bar that fills from left to right to show how complete something is. Use it for upload progress, onboarding checklists, storage usage, or anything that has a percentage value.
|
|
|
|
---
|
|
|
|
## Quick example
|
|
|
|
```csharp
|
|
new Progress(value: 72)
|
|
```
|
|
|
|
---
|
|
|
|
## All the options
|
|
|
|
```csharp
|
|
public Progress(int value, string size = "default")
|
|
```
|
|
|
|
| Parameter | What it does |
|
|
|---|---|
|
|
| `value` | How filled the bar is, from 0 to 100. Values outside this range are clamped automatically. |
|
|
| `size` | Height of the bar: `"sm"` (6px), `"default"` (10px), or `"lg"` (16px). |
|
|
|
|
---
|
|
|
|
## Real-world examples
|
|
|
|
### Disk usage inside a Card
|
|
|
|
```csharp
|
|
// Pre-render the Progress bar to HTML
|
|
var w = new System.Buffers.ArrayBufferWriter<byte>();
|
|
new Progress(value: usedPercent, size: "lg").Render(new HtmxRenderContext(w));
|
|
var progressHtml = System.Text.Encoding.UTF8.GetString(w.WrittenSpan);
|
|
|
|
new Card(
|
|
title: "Storage",
|
|
content: $"""
|
|
<div class="mb-2 flex justify-between text-sm">
|
|
<span>Used</span>
|
|
<span>{usedGb} GB / {totalGb} GB</span>
|
|
</div>
|
|
{progressHtml}
|
|
""")
|
|
```
|
|
|
|
### Live progress bar (HTMX polling)
|
|
|
|
Wrap the component in a polling `<div>` that swaps the fragment every second:
|
|
|
|
```html
|
|
<div id="job-progress"
|
|
hx-get="/jobs/42/progress"
|
|
hx-trigger="every 1s"
|
|
hx-swap="outerHTML">
|
|
$$ProgressBar$$
|
|
</div>
|
|
```
|
|
|
|
The handler returns a fresh render of the component with the updated value. The `transition-all` CSS on the fill makes the change smooth.
|
|
|
|
### Three sizes side by side
|
|
|
|
```csharp
|
|
new Progress(value: 40, size: "sm") // compact, good for table rows
|
|
new Progress(value: 60) // standard
|
|
new Progress(value: 80, size: "lg") // prominent
|
|
```
|
|
|
|
---
|
|
|
|
## How it works
|
|
|
|
Progress is two nested `<div>` elements. The outer one is the grey track; the inner one is the filled bar. The fill width is set as an inline `style="width: {value}%"` so no JavaScript is required. The `transition-all` class makes the bar animate smoothly when the value changes via an HTMX swap.
|
|
|
|
---
|
|
|
|
## Complete page example
|
|
|
|
**`Templates/JobStatusPage.htmx`**
|
|
```html
|
|
<div class="max-w-md mx-auto py-10">
|
|
<h1 class="text-2xl font-bold mb-2">Processing</h1>
|
|
<p class="text-sm text-muted-foreground mb-6">$$StatusText$$</p>
|
|
<div class="mb-2 flex justify-between text-sm">
|
|
<span>Progress</span>
|
|
<span>$$ProgressLabel$$</span>
|
|
</div>
|
|
$$ProgressBar$$
|
|
$$DoneAlert$$
|
|
</div>
|
|
```
|
|
|
|
**`Templates/JobStatusPage.htmx.cs`**
|
|
```csharp
|
|
namespace Htmx.ApiDemo.Templates;
|
|
|
|
public sealed class JobStatusPage : JobStatusPageBase
|
|
{
|
|
private readonly byte[] _statusText;
|
|
private readonly byte[] _progressLabel;
|
|
private readonly IHtmxComponent _progressBar;
|
|
private readonly IHtmxComponent _doneAlert;
|
|
|
|
public JobStatusPage(int percent, string statusText)
|
|
{
|
|
_statusText = System.Net.WebUtility.HtmlEncode(statusText).ToUtf8Bytes();
|
|
_progressLabel = $"{percent}%".ToUtf8Bytes();
|
|
_progressBar = new Components.Progress(value: percent);
|
|
_doneAlert = percent >= 100
|
|
? new Components.Alert(title: "Complete!", description: "Your export is ready.")
|
|
: HtmxEmpty.Instance;
|
|
}
|
|
|
|
protected override void RenderStatusText(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_statusText);
|
|
protected override void RenderProgressLabel(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_progressLabel);
|
|
protected override void RenderProgressBar(HtmxRenderContext ctx) => _progressBar.Render(ctx.Next());
|
|
protected override void RenderDoneAlert(HtmxRenderContext ctx) => _doneAlert.Render(ctx.Next());
|
|
}
|
|
```
|
|
|
|
**GET handler with HTMX polling**
|
|
```csharp
|
|
[Handler]
|
|
[MapGet("/jobs/{jobId}/status")]
|
|
public static partial class GetJobStatusHandler
|
|
{
|
|
public record Query([property: FromRoute] string JobId);
|
|
|
|
private static async Task<IResult> HandleAsync(
|
|
Query q, HttpContext ctx, JobQueue jobs, CancellationToken ct)
|
|
{
|
|
var job = await jobs.GetAsync(q.JobId, ct);
|
|
if (job is null) return Results.NotFound();
|
|
|
|
var page = new JobStatusPage(job.PercentComplete, job.StatusText);
|
|
|
|
// If polling (HTMX partial), only return the progress fragment
|
|
if (ctx.Request.Headers.ContainsKey("HX-Request"))
|
|
{
|
|
// Stop polling when done
|
|
if (job.PercentComplete >= 100)
|
|
ctx.Response.Headers.Append("HX-Trigger", "jobComplete");
|
|
|
|
return await ctx.WriteHtmxPage(page, title: "Processing");
|
|
}
|
|
|
|
// Full page load — include polling trigger
|
|
ctx.Response.Headers.Append("HX-Trigger-After-Settle",
|
|
"""{"startPolling": {"interval": 1000, "target": "#progress-region"}}""");
|
|
|
|
return await ctx.WriteHtmxPage(page, title: "Processing");
|
|
}
|
|
}
|
|
```
|