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:
parent
624601ac18
commit
68f28b33dc
assets/css
cmd/server
components
helpers
internal
@ -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
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
|
||||
}
|
||||
}
|
383
components/progress_templ.go
Normal file
383
components/progress_templ.go
Normal file
@ -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
|
||||
})
|
||||
}
|
||||
|
60
internal/handlers/progress.go
Normal file
60
internal/handlers/progress.go
Normal file
@ -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",
|
||||
|
141
internal/ui/pages/progress.templ
Normal file
141
internal/ui/pages/progress.templ
Normal file
@ -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>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
internal/ui/showcase/progress.templ
Normal file
108
internal/ui/showcase/progress.templ
Normal file
@ -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>
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user