Files
Htmx/docs/Components/Progress.md
T
2026-05-04 19:57:48 +05:00

177 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Progress
A horizontal progress bar. Value is clamped to 0100. Three sizes control the bar height.
---
## HTML structure
```
div.w-full.bg-secondary.rounded-full.overflow-hidden.{size class}
div.bg-primary.rounded-full.h-full.transition-all[style="width: {value}%"]
```
---
## CSS mechanics
| Class | Effect |
|---|---|
| `bg-secondary` | Neutral track color |
| `bg-primary` | Filled indicator color |
| `rounded-full overflow-hidden` | Pill-shaped track; fills also become pill-shaped |
| `transition-all` | Smooth animation when `width` changes |
**Size classes applied to the outer track:**
| Size | Class | Height |
|---|---|---|
| `sm` | `h-1.5` | 6 px |
| `default` | `h-2.5` | 10 px |
| `lg` | `h-4` | 16 px |
---
## Constructor signature
```csharp
public Progress(int value, string size = "default")
```
| Parameter | Description |
|---|---|
| `value` | Fill percentage; clamped to 0100 |
| `size` | `"sm"` / `"default"` / `"lg"` |
---
## Usage examples
### Inline usage
```csharp
new Progress(value: 72)
new Progress(value: 40, size: "sm")
new Progress(value: 100, size: "lg")
```
### Inside a Card
```csharp
new Card(
title: "Disk usage",
content: $"""
<div class="mb-2 flex justify-between text-sm">
<span>Used</span>
<span>{used} GB / {total} GB</span>
</div>
{progressHtml}
""")
```
(Pre-render the `Progress` to a string using `HtmxRenderContext` and `ArrayBufferWriter<byte>`.)
### HTMX live update
```html
<div id="progress-bar"
hx-get="/job/42/progress"
hx-trigger="every 1s"
hx-swap="outerHTML">
$$ProgressBar$$
</div>
```
The endpoint returns a partial re-render of this fragment with the updated `value`.
---
## Tips and tricks
- Values below 0 are treated as 0; values above 100 are treated as 100 — no manual clamping needed.
- Use `size: "sm"` for compact UI areas such as table rows.
- To animate progress smoothly, let `transition-all` do the work: re-render the component via HTMX on a polling interval or push updates via SSE.
- For an indeterminate spinner, use `Skeleton` instead (it has `animate-pulse` built in).
- For an indeterminate spinner, use `Skeleton` instead (it has `animate-pulse` built in).
---
## 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");
}
}
```