1
0
mirror of https://github.com/axzilla/templui.git synced 2025-03-13 10:53:35 +00:00

feat(progress): initial implementation

This commit is contained in:
axzilla 2025-03-10 15:03:22 +07:00
parent 624601ac18
commit 68f28b33dc
10 changed files with 874 additions and 9 deletions

@ -461,6 +461,9 @@
.ml-2 {
margin-left: calc(var(--spacing) * 2);
}
.ml-4 {
margin-left: calc(var(--spacing) * 4);
}
.ml-auto {
margin-left: auto;
}
@ -523,6 +526,9 @@
.h-2 {
height: calc(var(--spacing) * 2);
}
.h-2\.5 {
height: calc(var(--spacing) * 2.5);
}
.h-3 {
height: calc(var(--spacing) * 3);
}
@ -679,15 +685,6 @@
.w-\[30rem\] {
width: 30rem;
}
.w-\[200px\] {
width: 200px;
}
.w-\[230px\] {
width: 230px;
}
.w-\[250px\] {
width: 250px;
}
.w-auto {
width: auto;
}
@ -712,6 +709,9 @@
.max-w-md {
max-width: var(--container-md);
}
.max-w-none {
max-width: none;
}
.max-w-sm {
max-width: var(--container-sm);
}
@ -834,6 +834,9 @@
.resize {
resize: both;
}
.list-inside {
list-style-position: inside;
}
.list-disc {
list-style-type: disc;
}
@ -1171,6 +1174,9 @@
.bg-secondary {
background-color: var(--secondary);
}
.bg-secondary\/30 {
background-color: color-mix(in oklab, var(--secondary) 30%, transparent);
}
.bg-white {
background-color: var(--color-white);
}

@ -10,6 +10,7 @@ import (
"github.com/axzilla/templui/assets"
"github.com/axzilla/templui/components"
"github.com/axzilla/templui/internal/config"
"github.com/axzilla/templui/internal/handlers"
"github.com/axzilla/templui/internal/middleware"
"github.com/axzilla/templui/internal/ui/pages"
mw "github.com/axzilla/templui/middleware"
@ -112,6 +113,7 @@ func main() {
mux.Handle("GET /docs/components/label", templ.Handler(pages.Label()))
mux.Handle("GET /docs/components/modal", templ.Handler(pages.Modal()))
mux.Handle("GET /docs/components/pagination", templ.Handler(pages.Pagination()))
mux.Handle("GET /docs/components/progress", templ.Handler(pages.Progress()))
mux.Handle("GET /docs/components/radio", templ.Handler(pages.Radio()))
mux.Handle("GET /docs/components/radio-card", templ.Handler(pages.RadioCard()))
mux.Handle("GET /docs/components/rating", templ.Handler(pages.Rating()))
@ -130,6 +132,11 @@ func main() {
// Showcase API
mux.Handle("POST /docs/toast/demo", http.HandlerFunc(toastDemoHandler))
mux.Handle("POST /docs/button/htmx-loading", http.HandlerFunc(buttonHtmxLoadingHandler))
//
// Add these routes to your main.go where routes are defined
// mux.Handle("GET /docs/components/progress", templ.Handler(pages.Progress()))
mux.Handle("GET /api/progress", http.HandlerFunc(handlers.ProgressHandler))
mux.Handle("POST /api/progress/reset", http.HandlerFunc(handlers.ProgressResetHandler))
fmt.Println("Server is running on http://localhost:8090")
http.ListenAndServe(":8090", wrappedMux)

151
components/progress.templ Normal file

@ -0,0 +1,151 @@
package components
import (
"fmt"
"github.com/axzilla/templui/utils"
)
// ProgressSize defines available sizes for the Progress component
type ProgressSize string
const (
ProgressSizeDefault ProgressSize = "" // Default size
ProgressSizeSm ProgressSize = "sm" // Small progress bar
ProgressSizeLg ProgressSize = "lg" // Large progress bar
)
// ProgressVariant defines color variants
type ProgressVariant string
const (
ProgressVariantDefault ProgressVariant = "default" // Default styling
ProgressVariantSuccess ProgressVariant = "success" // Success state
ProgressVariantDanger ProgressVariant = "danger" // Error/danger state
ProgressVariantWarning ProgressVariant = "warning" // Warning state
)
// ProgressProps configures the Progress component
type ProgressProps struct {
Value int // Current progress value (0-100)
Max int // Maximum value (default: 100)
Label string // Optional text label
ShowValue bool // Whether to show percentage
Size ProgressSize // Size variant (sm, md, lg)
Variant ProgressVariant // Color variant
Class string // Additional CSS classes
BarClass string // Additional CSS classes for the bar itself
HxGet string // HTMX endpoint for updates
HxTrigger string // HTMX trigger for updates
HxTarget string // HTMX target for updates
HxSwap string // HTMX swap method
Attributes templ.Attributes // Additional HTML attributes
}
// Progress renders a progress bar component
templ Progress(props ProgressProps) {
<div
class={
utils.TwMerge(
"w-full",
props.Class,
),
}
if props.HxGet != "" {
hx-get={ props.HxGet }
}
if props.HxTrigger != "" {
hx-trigger={ props.HxTrigger }
}
if props.HxTarget != "" {
hx-target={ props.HxTarget }
}
if props.HxSwap != "" {
hx-swap={ props.HxSwap }
}
aria-valuemin="0"
aria-valuemax={ fmt.Sprintf("%d", getMaxValue(props.Max)) }
aria-valuenow={ fmt.Sprintf("%d", props.Value) }
role="progressbar"
{ props.Attributes... }
>
if props.Label != "" || props.ShowValue {
<div class="flex justify-between items-center mb-1">
if props.Label != "" {
<span class="text-sm font-medium">{ props.Label }</span>
}
if props.ShowValue {
<span class="text-sm font-medium">
{ fmt.Sprintf("%d%%", getProgressPercentage(props)) }
</span>
}
</div>
}
<div
class={
utils.TwMerge(
"w-full overflow-hidden rounded-full bg-secondary",
),
}
>
<div
class={
utils.TwMerge(
"h-full rounded-full transition-all",
getProgressSizeClasses(props.Size),
getProgressVariantClasses(props.Variant),
props.BarClass,
),
}
style={ fmt.Sprintf("width: %d%%", getProgressPercentage(props)) }
></div>
</div>
</div>
}
// Helper functions
func getMaxValue(max int) int {
if max <= 0 {
return 100
}
return max
}
func getProgressPercentage(props ProgressProps) int {
max := getMaxValue(props.Max)
// Ensure value is within bounds
value := props.Value
if value < 0 {
value = 0
}
if value > max {
value = max
}
// Calculate percentage
return (value * 100) / max
}
func getProgressSizeClasses(size ProgressSize) string {
switch size {
case ProgressSizeSm:
return "h-1"
case ProgressSizeLg:
return "h-4"
default:
return "h-2.5" // Default (md) size
}
}
func getProgressVariantClasses(variant ProgressVariant) string {
switch variant {
case ProgressVariantSuccess:
return "bg-green-500"
case ProgressVariantDanger:
return "bg-destructive"
case ProgressVariantWarning:
return "bg-yellow-500"
default:
return "bg-primary" // Default to primary
}
}

@ -0,0 +1,383 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.833
package components
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"github.com/axzilla/templui/utils"
)
// ProgressSize defines available sizes for the Progress component
type ProgressSize string
const (
ProgressSizeDefault ProgressSize = "" // Default size
ProgressSizeSm ProgressSize = "sm" // Small progress bar
ProgressSizeLg ProgressSize = "lg" // Large progress bar
)
// ProgressVariant defines color variants
type ProgressVariant string
const (
ProgressVariantDefault ProgressVariant = "default" // Default styling
ProgressVariantSuccess ProgressVariant = "success" // Success state
ProgressVariantDanger ProgressVariant = "danger" // Error/danger state
ProgressVariantWarning ProgressVariant = "warning" // Warning state
)
// ProgressProps configures the Progress component
type ProgressProps struct {
Value int // Current progress value (0-100)
Max int // Maximum value (default: 100)
Label string // Optional text label
ShowValue bool // Whether to show percentage
Size ProgressSize // Size variant (sm, md, lg)
Variant ProgressVariant // Color variant
Class string // Additional CSS classes
BarClass string // Additional CSS classes for the bar itself
HxGet string // HTMX endpoint for updates
HxTrigger string // HTMX trigger for updates
HxTarget string // HTMX target for updates
HxSwap string // HTMX swap method
Attributes templ.Attributes // Additional HTML attributes
}
// Progress renders a progress bar component
func Progress(props ProgressProps) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var2 = []any{
utils.TwMerge(
"w-full",
props.Class,
),
}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if props.HxGet != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(props.HxGet)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 54, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if props.HxTrigger != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " hx-trigger=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(props.HxTrigger)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 57, Col: 31}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if props.HxTarget != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(props.HxTarget)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 60, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if props.HxSwap != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " hx-swap=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(props.HxSwap)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 63, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " aria-valuemin=\"0\" aria-valuemax=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", getMaxValue(props.Max)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 66, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" aria-valuenow=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", props.Value))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 67, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" role=\"progressbar\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, props.Attributes)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, ">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if props.Label != "" || props.ShowValue {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"flex justify-between items-center mb-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if props.Label != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<span class=\"text-sm font-medium\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(props.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 74, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if props.ShowValue {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<span class=\"text-sm font-medium\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d%%", getProgressPercentage(props)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 78, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
var templ_7745c5c3_Var12 = []any{
utils.TwMerge(
"w-full overflow-hidden rounded-full bg-secondary",
),
}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 = []any{
utils.TwMerge(
"h-full rounded-full transition-all",
getProgressSizeClasses(props.Size),
getProgressVariantClasses(props.Variant),
props.BarClass,
),
}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var14).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("width: %d%%", getProgressPercentage(props)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/progress.templ`, Line: 99, Col: 68}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\"></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Helper functions
func getMaxValue(max int) int {
if max <= 0 {
return 100
}
return max
}
func getProgressPercentage(props ProgressProps) int {
max := getMaxValue(props.Max)
// Ensure value is within bounds
value := props.Value
if value < 0 {
value = 0
}
if value > max {
value = max
}
// Calculate percentage
return (value * 100) / max
}
func getProgressSizeClasses(size ProgressSize) string {
switch size {
case ProgressSizeSm:
return "h-1"
case ProgressSizeLg:
return "h-4"
default:
return "h-2.5" // Default (md) size
}
}
func getProgressVariantClasses(variant ProgressVariant) string {
switch variant {
case ProgressVariantSuccess:
return "bg-green-500"
case ProgressVariantDanger:
return "bg-destructive"
case ProgressVariantWarning:
return "bg-yellow-500"
default:
return "bg-primary" // Default to primary
}
}
var _ = templruntime.GeneratedTemplate

@ -19,4 +19,5 @@ templ ComponentScripts() {
@components.TabsScript()
@components.TextareaScript()
@components.TimePickerScript()
<!-- @components.ProgressScript() -->
}

@ -92,6 +92,10 @@ func ComponentScripts() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!-- @components.ProgressScript() -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}

@ -0,0 +1,60 @@
package handlers
import (
"net/http"
"sync"
"github.com/axzilla/templui/components"
)
// Simple in-memory progress storage
var (
progressValue = 0
progressValueMutex sync.RWMutex
)
// ProgressHandler returns the current progress
func ProgressHandler(w http.ResponseWriter, r *http.Request) {
// Get current progress
progressValueMutex.RLock()
currentProgress := progressValue
progressValueMutex.RUnlock()
// Increment progress for next request (simulate progress)
progressValueMutex.Lock()
if progressValue < 100 {
progressValue += 10
if progressValue > 100 {
progressValue = 100
}
}
progressValueMutex.Unlock()
// Render the progress component
components.Progress(components.ProgressProps{
Value: currentProgress,
Label: "Processing data...",
ShowValue: true,
HxGet: "/api/progress",
HxTrigger: "every 2s",
HxTarget: "#progress-container",
}).Render(r.Context(), w)
}
// ProgressResetHandler resets the progress
func ProgressResetHandler(w http.ResponseWriter, r *http.Request) {
// Reset progress
progressValueMutex.Lock()
progressValue = 0
progressValueMutex.Unlock()
// Render the reset progress component
components.Progress(components.ProgressProps{
Value: 0,
Label: "Processing data...",
ShowValue: true,
HxGet: "/api/progress",
HxTrigger: "every 2s",
HxTarget: "#progress-container",
}).Render(r.Context(), w)
}

@ -121,6 +121,10 @@ var Sections = []Section{
Text: "Pagination",
Href: "/docs/components/pagination",
},
{
Text: "Progress",
Href: "/docs/components/progress",
},
{
Text: "Radio",
Href: "/docs/components/radio",

@ -0,0 +1,141 @@
package pages
import (
"github.com/axzilla/templui/internal/ui/layouts"
"github.com/axzilla/templui/internal/ui/modules"
"github.com/axzilla/templui/internal/ui/showcase"
)
templ Progress() {
@layouts.DocsLayout(
"Progress",
"Progress indicators inform users about the status of ongoing processes.",
) {
@modules.PageWrapper(modules.PageWrapperProps{
Name: "Progress",
Description: templ.Raw("Progress indicators inform users about the status of ongoing processes."),
Tailwind: true,
}) {
@modules.ExampleWrapper(modules.ExampleWrapperProps{
ShowcaseFile: showcase.ProgressVariants(),
PreviewCodeFile: "progress_variants.templ",
ComponentCodeFile: "progress.templ",
})
@modules.Usage(modules.UsageProps{
ComponentName: "Progress",
PropsExample: "Value: 75, Max: 100, Label: \"Uploading...\", ShowValue: true",
})
@modules.ContainerWrapper(modules.ContainerWrapperProps{Title: "Examples"}) {
@modules.ExampleWrapper(modules.ExampleWrapperProps{
SectionName: "Sizes",
ShowcaseFile: showcase.ProgressSizes(),
PreviewCodeFile: "progress_sizes.templ",
ComponentCodeFile: "progress.templ",
})
@modules.ExampleWrapper(modules.ExampleWrapperProps{
SectionName: "HTMX Integration",
ShowcaseFile: showcase.ProgressHTMX(),
PreviewCodeFile: "progress_htmx.templ",
ComponentCodeFile: "progress.templ",
})
}
@modules.ContainerWrapper(modules.ContainerWrapperProps{Title: "Integration Patterns"}) {
<div class="prose max-w-none">
<p>
The Progress component can be integrated in different ways depending on your requirements:
</p>
<h3 class="text-lg font-medium mt-6 mb-2">File Uploads</h3>
<p>
For file uploads, use the browser's XMLHttpRequest or fetch API with progress events:
</p>
<pre class="bg-secondary/30 p-4 rounded-md overflow-x-auto text-sm">
{ `// Client-side JavaScript
const form = document.querySelector("#upload-form");
form.addEventListener("submit", (e) => {
e.preventDefault();
const formData = new FormData(form);
const xhr = new XMLHttpRequest();
// Update progress bar with upload progress
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
// Update progress bar width
document.querySelector("#progress-bar").style.width = percent + "%";
// Update percentage text
document.querySelector("#progress-value").textContent = percent + "%";
}
});
xhr.open("POST", "/upload");
xhr.send(formData);
});` }
</pre>
<h3 class="text-lg font-medium mt-6 mb-2">Multi-step Forms</h3>
<p>
For multi-step forms, you can use either client-side calculations or HTMX for server-side validation between steps:
</p>
<pre class="bg-secondary/30 p-4 rounded-md overflow-x-auto text-sm">
{ `<!-- Client-side approach -->
<div data-current-step="1" data-total-steps="4">
@components.Progress(components.ProgressProps{
Value: 25, // 1 of 4 steps = 25%
Label: "Step 1 of 4",
ShowValue: true,
})
<!-- Form steps -->
</div>
<!-- HTMX approach -->
<button
hx-get="/form/step/2"
hx-target="#form-container"
class="px-4 py-2 bg-primary text-white rounded"
>
Next
</button>` }
</pre>
<h3 class="text-lg font-medium mt-6 mb-2">Background Processes</h3>
<p>
For tracking background processes, choose between:
</p>
<ul class="list-disc list-inside space-y-2 ml-4">
<li>
<strong>HTMX Polling</strong>: Simple approach, good for processes under a few minutes
<pre class="bg-secondary/30 p-4 rounded-md overflow-x-auto text-sm mt-2">
{ `@components.Progress(components.ProgressProps{
Value: initialValue,
Label: "Processing...",
ShowValue: true,
HxGet: "/api/job/123/progress",
HxTrigger: "every 2s",
HxTarget: "#progress-container",
})` }
</pre>
</li>
<li>
<strong>Server-Sent Events (SSE)</strong>: For real-time updates and longer processes
<pre class="bg-secondary/30 p-4 rounded-md overflow-x-auto text-sm mt-2">
{ `// Server-side Go code
func SSEHandler(w http.ResponseWriter, r *http.Request) {
// Set SSE headers
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
// Send progress updates
for i := 0; i <= 100; i += 10 {
fmt.Fprintf(w, "data: {\"progress\": %d}\n\n", i)
w.(http.Flusher).Flush()
time.Sleep(1 * time.Second)
}
}` }
</pre>
</li>
</ul>
</div>
}
}
}
}

@ -0,0 +1,108 @@
package showcase
import (
"github.com/axzilla/templui/components"
)
// Basic progress bar variants
templ ProgressVariants() {
<div class="space-y-6 w-full max-w-xl">
<!-- Default variant (25%) -->
@components.Progress(components.ProgressProps{
Value: 25,
Label: "Default",
ShowValue: true,
})
<!-- Success variant (50%) -->
@components.Progress(components.ProgressProps{
Value: 50,
Label: "Success",
ShowValue: true,
Variant: components.ProgressVariantSuccess,
})
<!-- Danger variant (75%) -->
@components.Progress(components.ProgressProps{
Value: 75,
Label: "Danger",
ShowValue: true,
Variant: components.ProgressVariantDanger,
})
<!-- Warning variant (90%) -->
@components.Progress(components.ProgressProps{
Value: 90,
Label: "Warning",
ShowValue: true,
Variant: components.ProgressVariantWarning,
})
</div>
}
// Size variations
templ ProgressSizes() {
<div class="space-y-6 w-full max-w-xl">
<!-- Small size -->
@components.Progress(components.ProgressProps{
Value: 65,
Label: "Small size",
ShowValue: true,
Size: components.ProgressSizeSm,
})
<!-- Default/medium size -->
@components.Progress(components.ProgressProps{
Value: 65,
Label: "Default size",
ShowValue: true,
})
<!-- Large size -->
@components.Progress(components.ProgressProps{
Value: 65,
Label: "Large size",
ShowValue: true,
Size: components.ProgressSizeLg,
})
<!-- Without label -->
@components.Progress(components.ProgressProps{
Value: 65,
})
<!-- Without percentage -->
@components.Progress(components.ProgressProps{
Value: 65,
Label: "Without percentage",
})
</div>
}
// HTMX integration example
templ ProgressHTMX() {
<div class="space-y-4 w-full max-w-xl">
<p class="text-sm text-muted-foreground mb-2">
This example demonstrates how to use HTMX to automatically update a progress bar
by polling an endpoint.
</p>
<div id="progress-container">
@components.Progress(components.ProgressProps{
Value: 30, // Initial value
Label: "Processing data...",
ShowValue: true,
HxGet: "/api/progress",
HxTrigger: "every 2s",
HxTarget: "#progress-container",
})
</div>
<!-- Reset button -->
<button
class="mt-4 px-4 py-2 bg-secondary rounded-md text-sm"
hx-post="/api/progress/reset"
hx-target="#progress-container"
>
Reset Demo
</button>
<div class="mt-4 p-4 bg-secondary/30 rounded-md">
<p class="text-sm text-muted-foreground italic">
When integrated with your backend, this progress bar will automatically update
by polling the server every 2 seconds. The server would replace this component
with an updated version showing the current progress.
</p>
</div>
</div>
}