1
0
mirror of https://github.com/axzilla/templui.git synced 2025-03-14 05:17:59 +00:00

feat(spinner): initial implementation

This commit is contained in:
axzilla 2025-03-10 09:20:41 +07:00
parent 1bdd906ca4
commit 30492d9284
7 changed files with 921 additions and 0 deletions

View File

@ -66,6 +66,9 @@
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--animate-spin: spin 1s linear infinite;
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--animate-bounce: bounce 1s infinite;
--blur-xs: 4px;
--aspect-video: 16 / 9;
--default-transition-duration: 150ms;
@ -627,6 +630,9 @@
.w-5 {
width: calc(var(--spacing) * 5);
}
.w-6 {
width: calc(var(--spacing) * 6);
}
.w-7 {
width: calc(var(--spacing) * 7);
}
@ -642,6 +648,9 @@
.w-16 {
width: calc(var(--spacing) * 16);
}
.w-24 {
width: calc(var(--spacing) * 24);
}
.w-56 {
width: calc(var(--spacing) * 56);
}
@ -693,6 +702,9 @@
.max-w-xs {
max-width: var(--container-xs);
}
.min-w-\[120px\] {
min-width: 120px;
}
.flex-1 {
flex: 1;
}
@ -788,6 +800,15 @@
.transform {
transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y);
}
.animate-bounce {
animation: var(--animate-bounce);
}
.animate-pulse {
animation: var(--animate-pulse);
}
.animate-spin {
animation: var(--animate-spin);
}
.cursor-default {
cursor: default;
}
@ -845,6 +866,9 @@
.items-center {
align-items: center;
}
.items-end {
align-items: flex-end;
}
.items-start {
align-items: flex-start;
}
@ -872,6 +896,9 @@
.gap-8 {
gap: calc(var(--spacing) * 8);
}
.gap-12 {
gap: calc(var(--spacing) * 12);
}
.space-y-1 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
@ -917,6 +944,13 @@
.gap-x-4 {
column-gap: calc(var(--spacing) * 4);
}
.space-x-1 {
:where(& > :not(:last-child)) {
--tw-space-x-reverse: 0;
margin-inline-start: calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));
margin-inline-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)));
}
}
.space-x-2 {
:where(& > :not(:last-child)) {
--tw-space-x-reverse: 0;
@ -995,6 +1029,18 @@
border-style: var(--tw-border-style);
border-width: 2px;
}
.border-4 {
border-style: var(--tw-border-style);
border-width: 4px;
}
.border-\[3px\] {
border-style: var(--tw-border-style);
border-width: 3px;
}
.border-\[5px\] {
border-style: var(--tw-border-style);
border-width: 5px;
}
.border-t {
border-top-style: var(--tw-border-style);
border-top-width: 1px;
@ -1026,6 +1072,9 @@
.border-border {
border-color: var(--border);
}
.border-current {
border-color: currentColor;
}
.border-destructive {
border-color: var(--destructive);
}
@ -1041,6 +1090,15 @@
.border-transparent {
border-color: transparent;
}
.border-r-transparent {
border-right-color: transparent;
}
.border-b-transparent {
border-bottom-color: transparent;
}
.border-l-transparent {
border-left-color: transparent;
}
.bg-\(--theme-primary\) {
background-color: var(--theme-primary);
}
@ -1362,6 +1420,9 @@
.text-primary\/90 {
color: color-mix(in oklab, var(--primary) 90%, transparent);
}
.text-purple-500 {
color: var(--color-purple-500);
}
.text-red-500 {
color: var(--color-red-500);
}
@ -2663,3 +2724,23 @@
initial-value: "";
inherits: false;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes pulse {
50% {
opacity: 0.5;
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(-25%);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
50% {
transform: none;
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
}

View File

@ -119,6 +119,7 @@ func main() {
mux.Handle("GET /docs/components/separator", templ.Handler(pages.Separator()))
mux.Handle("GET /docs/components/sheet", templ.Handler(pages.Sheet()))
mux.Handle("GET /docs/components/slider", templ.Handler(pages.Slider()))
mux.Handle("GET /docs/components/spinner", templ.Handler(pages.Spinner()))
mux.Handle("GET /docs/components/tabs", templ.Handler(pages.Tabs()))
mux.Handle("GET /docs/components/textarea", templ.Handler(pages.Textarea()))
mux.Handle("GET /docs/components/time-picker", templ.Handler(pages.TimePicker()))

181
components/spinner.templ Normal file
View File

@ -0,0 +1,181 @@
package components
import (
"github.com/axzilla/templui/utils"
)
// SpinnerSize represents the available sizes for the Spinner component
type SpinnerSize string
const (
SpinnerSizeXs SpinnerSize = "xs" // Extra small (16px)
SpinnerSizeSm SpinnerSize = "sm" // Small (24px)
SpinnerSizeMd SpinnerSize = "md" // Medium (32px) - default
SpinnerSizeLg SpinnerSize = "lg" // Large (48px)
SpinnerSizeXl SpinnerSize = "xl" // Extra large (64px)
SpinnerSize2xl SpinnerSize = "2xl" // 2X Large (96px)
)
// SpinnerVariant defines the visual style of the spinner
type SpinnerVariant string
const (
SpinnerVariantBorder SpinnerVariant = "border" // Border style spinning animation
SpinnerVariantDots SpinnerVariant = "dots" // Bouncing dots animation
SpinnerVariantPulse SpinnerVariant = "pulse" // Pulsing circle animation
)
// SpinnerProps configures the Spinner component
type SpinnerProps struct {
Size SpinnerSize // Controls the size of the spinner
Variant SpinnerVariant // Visual style variant
Color string // Custom color - uses theme colors if empty
Text string // Optional text to display below the spinner
Class string // Additional CSS classes
Attributes templ.Attributes // Additional HTML attributes
}
// spinnerSizeClass returns the appropriate size class based on the size prop
func spinnerSizeClass(size SpinnerSize) string {
switch size {
case SpinnerSizeXs:
return "w-4 h-4"
case SpinnerSizeSm:
return "w-6 h-6"
case SpinnerSizeLg:
return "w-12 h-12"
case SpinnerSizeXl:
return "w-16 h-16"
case SpinnerSize2xl:
return "w-24 h-24"
default:
return "w-8 h-8" // Default to medium
}
}
// borderSpinnerClass returns the appropriate border-width class based on the size prop
func borderSpinnerClass(size SpinnerSize) string {
switch size {
case SpinnerSizeXs:
return "border-2"
case SpinnerSizeSm:
return "border-[3px]"
case SpinnerSizeLg, SpinnerSizeXl, SpinnerSize2xl:
return "border-[5px]"
default:
return "border-4" // Default to medium
}
}
// textSizeClass returns the appropriate text size class based on spinner size
func textSizeClass(size SpinnerSize) string {
switch size {
case SpinnerSizeXs:
return "text-xs"
case SpinnerSizeSm:
return "text-sm"
case SpinnerSizeLg:
return "text-lg"
case SpinnerSizeXl, SpinnerSize2xl:
return "text-xl"
default:
return "text-base" // Default to medium
}
}
// Spinner component for indicating loading states with customizable options
templ Spinner(props SpinnerProps) {
<div
class={ utils.TwMerge(
"inline-flex flex-col items-center justify-center",
props.Class,
) }
aria-label="Loading"
role="status"
{ props.Attributes... }
>
if props.Variant == SpinnerVariantDots {
<!-- Dots spinner variant -->
<div class="flex space-x-1">
<div
class={ utils.TwMerge(
"animate-bounce rounded-full",
spinnerSizeClass(props.Size),
utils.TwIfElse(
props.Color == "",
"bg-primary",
props.Color,
),
) }
style="animation-delay: 0ms;"
></div>
<div
class={ utils.TwMerge(
"animate-bounce rounded-full",
spinnerSizeClass(props.Size),
utils.TwIfElse(
props.Color == "",
"bg-primary",
props.Color,
),
) }
style="animation-delay: 150ms;"
></div>
<div
class={ utils.TwMerge(
"animate-bounce rounded-full",
spinnerSizeClass(props.Size),
utils.TwIfElse(
props.Color == "",
"bg-primary",
props.Color,
),
) }
style="animation-delay: 300ms;"
></div>
</div>
} else if props.Variant == SpinnerVariantPulse {
// Pulse spinner variant
<div
class={ utils.TwMerge(
"animate-pulse rounded-full",
spinnerSizeClass(props.Size),
utils.TwIfElse(
props.Color == "",
"bg-primary",
props.Color,
),
"opacity-75",
) }
></div>
} else {
/* <!-- Default border spinner variant --> */
<div
class={ utils.TwMerge(
"animate-spin rounded-full",
spinnerSizeClass(props.Size),
borderSpinnerClass(props.Size),
utils.TwIfElse(
props.Color == "",
"border-primary border-l-transparent border-r-transparent border-b-transparent",
"border-current border-l-transparent border-r-transparent border-b-transparent",
),
utils.TwIfElse(
props.Color != "",
props.Color,
"",
),
) }
></div>
}
if props.Text != "" {
<span
class={ utils.TwMerge(
"mt-2 text-center",
textSizeClass(props.Size),
"text-foreground",
) }
>{ props.Text }</span>
}
</div>
}

371
components/spinner_templ.go Normal file
View File

@ -0,0 +1,371 @@
// 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 (
"github.com/axzilla/templui/utils"
)
// SpinnerSize represents the available sizes for the Spinner component
type SpinnerSize string
const (
SpinnerSizeXs SpinnerSize = "xs" // Extra small (16px)
SpinnerSizeSm SpinnerSize = "sm" // Small (24px)
SpinnerSizeMd SpinnerSize = "md" // Medium (32px) - default
SpinnerSizeLg SpinnerSize = "lg" // Large (48px)
SpinnerSizeXl SpinnerSize = "xl" // Extra large (64px)
SpinnerSize2xl SpinnerSize = "2xl" // 2X Large (96px)
)
// SpinnerVariant defines the visual style of the spinner
type SpinnerVariant string
const (
SpinnerVariantBorder SpinnerVariant = "border" // Border style spinning animation
SpinnerVariantDots SpinnerVariant = "dots" // Bouncing dots animation
SpinnerVariantPulse SpinnerVariant = "pulse" // Pulsing circle animation
)
// SpinnerProps configures the Spinner component
type SpinnerProps struct {
Size SpinnerSize // Controls the size of the spinner
Variant SpinnerVariant // Visual style variant
Color string // Custom color - uses theme colors if empty
Text string // Optional text to display below the spinner
Class string // Additional CSS classes
Attributes templ.Attributes // Additional HTML attributes
}
// spinnerSizeClass returns the appropriate size class based on the size prop
func spinnerSizeClass(size SpinnerSize) string {
switch size {
case SpinnerSizeXs:
return "w-4 h-4"
case SpinnerSizeSm:
return "w-6 h-6"
case SpinnerSizeLg:
return "w-12 h-12"
case SpinnerSizeXl:
return "w-16 h-16"
case SpinnerSize2xl:
return "w-24 h-24"
default:
return "w-8 h-8" // Default to medium
}
}
// borderSpinnerClass returns the appropriate border-width class based on the size prop
func borderSpinnerClass(size SpinnerSize) string {
switch size {
case SpinnerSizeXs:
return "border-2"
case SpinnerSizeSm:
return "border-[3px]"
case SpinnerSizeLg, SpinnerSizeXl, SpinnerSize2xl:
return "border-[5px]"
default:
return "border-4" // Default to medium
}
}
// textSizeClass returns the appropriate text size class based on spinner size
func textSizeClass(size SpinnerSize) string {
switch size {
case SpinnerSizeXs:
return "text-xs"
case SpinnerSizeSm:
return "text-sm"
case SpinnerSizeLg:
return "text-lg"
case SpinnerSizeXl, SpinnerSize2xl:
return "text-xl"
default:
return "text-base" // Default to medium
}
}
// Spinner component for indicating loading states with customizable options
func Spinner(props SpinnerProps) 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(
"inline-flex flex-col items-center justify-center",
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/spinner.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, "\" aria-label=\"Loading\" role=\"status\"")
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, 3, ">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if props.Variant == SpinnerVariantDots {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<!-- Dots spinner variant --> <div class=\"flex space-x-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 = []any{utils.TwMerge(
"animate-bounce rounded-full",
spinnerSizeClass(props.Size),
utils.TwIfElse(
props.Color == "",
"bg-primary",
props.Color,
),
)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/spinner.templ`, Line: 1, Col: 0}
}
_, 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, "\" style=\"animation-delay: 0ms;\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 = []any{utils.TwMerge(
"animate-bounce rounded-full",
spinnerSizeClass(props.Size),
utils.TwIfElse(
props.Color == "",
"bg-primary",
props.Color,
),
)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/spinner.templ`, Line: 1, Col: 0}
}
_, 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, 8, "\" style=\"animation-delay: 150ms;\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 = []any{utils.TwMerge(
"animate-bounce rounded-full",
spinnerSizeClass(props.Size),
utils.TwIfElse(
props.Color == "",
"bg-primary",
props.Color,
),
)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/spinner.templ`, Line: 1, Col: 0}
}
_, 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, 10, "\" style=\"animation-delay: 300ms;\"></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if props.Variant == SpinnerVariantPulse {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 = []any{utils.TwMerge(
"animate-pulse rounded-full",
spinnerSizeClass(props.Size),
utils.TwIfElse(
props.Color == "",
"bg-primary",
props.Color,
),
"opacity-75",
)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var10).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/spinner.templ`, Line: 1, Col: 0}
}
_, 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, 13, "\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 = []any{utils.TwMerge(
"animate-spin rounded-full",
spinnerSizeClass(props.Size),
borderSpinnerClass(props.Size),
utils.TwIfElse(
props.Color == "",
"border-primary border-l-transparent border-r-transparent border-b-transparent",
"border-current border-l-transparent border-r-transparent border-b-transparent",
),
utils.TwIfElse(
props.Color != "",
props.Color,
"",
),
)}
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, 15, "<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/spinner.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, 16, "\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if props.Text != "" {
var templ_7745c5c3_Var14 = []any{utils.TwMerge(
"mt-2 text-center",
textSizeClass(props.Size),
"text-foreground",
)}
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, 17, "<span 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/spinner.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, 18, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(props.Text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/spinner.templ`, Line: 178, Col: 16}
}
_, 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, 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
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -150,6 +150,10 @@ var Sections = []Section{
Text: "Slider",
Href: "/docs/components/slider",
},
{
Text: "Spinner",
Href: "/docs/components/spinner",
},
{
Text: "Tabs",
Href: "/docs/components/tabs",

View File

@ -0,0 +1,62 @@
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 Spinner() {
@layouts.DocsLayout(
"Spinner",
"Visual indicators for loading states and processes in progress.",
) {
@modules.PageWrapper(modules.PageWrapperProps{
Name: "Spinner",
Description: templ.Raw("Visual indicators for loading states and processes in progress."),
Tailwind: true,
}) {
@modules.ExampleWrapper(modules.ExampleWrapperProps{
ShowcaseFile: showcase.SpinnerDefault(),
PreviewCodeFile: "spinner_default.templ",
ComponentCodeFile: "spinner.templ",
})
@modules.Usage(modules.UsageProps{
ComponentName: "Spinner",
PropsExample: "Size: SpinnerSizeMd, Variant: SpinnerVariantBorder",
})
@modules.ContainerWrapper(modules.ContainerWrapperProps{Title: "Examples"}) {
@modules.ExampleWrapper(modules.ExampleWrapperProps{
SectionName: "Sizes",
ShowcaseFile: showcase.SpinnerSizes(),
PreviewCodeFile: "spinner_sizes.templ",
ComponentCodeFile: "spinner.templ",
})
@modules.ExampleWrapper(modules.ExampleWrapperProps{
SectionName: "Variants",
ShowcaseFile: showcase.SpinnerVariants(),
PreviewCodeFile: "spinner_variants.templ",
ComponentCodeFile: "spinner.templ",
})
@modules.ExampleWrapper(modules.ExampleWrapperProps{
SectionName: "Colors",
ShowcaseFile: showcase.SpinnerColors(),
PreviewCodeFile: "spinner_colors.templ",
ComponentCodeFile: "spinner.templ",
})
@modules.ExampleWrapper(modules.ExampleWrapperProps{
SectionName: "With Text",
ShowcaseFile: showcase.SpinnerWithText(),
PreviewCodeFile: "spinner_with_text.templ",
ComponentCodeFile: "spinner.templ",
})
@modules.ExampleWrapper(modules.ExampleWrapperProps{
SectionName: "In Button",
ShowcaseFile: showcase.SpinnerInButton(),
PreviewCodeFile: "spinner_in_button.templ",
ComponentCodeFile: "spinner.templ",
})
}
}
}
}

View File

@ -0,0 +1,221 @@
package showcase
import "github.com/axzilla/templui/components"
templ SpinnerDefault() {
<div class="flex justify-center">
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
})
</div>
}
templ SpinnerSizes() {
<div class="flex flex-wrap items-end justify-center gap-8">
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">XS</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeXs,
Variant: components.SpinnerVariantBorder,
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">SM</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeSm,
Variant: components.SpinnerVariantBorder,
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">MD (Default)</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">LG</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeLg,
Variant: components.SpinnerVariantBorder,
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">XL</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeXl,
Variant: components.SpinnerVariantBorder,
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">2XL</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSize2xl,
Variant: components.SpinnerVariantBorder,
})
</div>
</div>
}
templ SpinnerVariants() {
<div class="flex flex-wrap items-end justify-center gap-12">
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">Border (Default)</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">Dots</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantDots,
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">Pulse</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantPulse,
})
</div>
</div>
}
templ SpinnerColors() {
<div class="flex flex-wrap items-end justify-center gap-8">
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">Default (Primary)</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">Red</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
Color: "text-red-500",
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">Green</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
Color: "text-green-500",
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">Blue</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
Color: "text-blue-500",
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">Yellow</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
Color: "text-yellow-500",
})
</div>
<div class="flex flex-col items-center">
<span class="text-sm text-muted-foreground mb-2">Purple</span>
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
Color: "text-purple-500",
})
</div>
</div>
}
templ SpinnerWithText() {
<div class="flex flex-wrap items-end justify-center gap-8">
<!-- Border spinner with text -->
<div class="flex flex-col items-center">
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantBorder,
Text: "Loading...",
})
</div>
<!-- Dots spinner with text -->
<div class="flex flex-col items-center">
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeMd,
Variant: components.SpinnerVariantDots,
Text: "Please wait",
})
</div>
<!-- Pulse spinner with text -->
<div class="flex flex-col items-center">
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeLg,
Variant: components.SpinnerVariantPulse,
Text: "Processing your request",
Color: "text-blue-500",
})
</div>
</div>
}
templ SpinnerInButton() {
<div class="flex flex-wrap items-center justify-center gap-8">
<!-- Primary button with spinner -->
@components.Button(components.ButtonProps{
Attributes: templ.Attributes{
"disabled": "true",
},
Class: "min-w-[120px]",
}) {
<div class="flex items-center gap-2">
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeXs,
Variant: components.SpinnerVariantBorder,
Color: "text-primary-foreground",
})
<span>Loading</span>
</div>
}
<!-- Secondary button with spinner -->
@components.Button(components.ButtonProps{
Variant: components.ButtonVariantSecondary,
Attributes: templ.Attributes{
"disabled": "true",
},
Class: "min-w-[120px]",
}) {
<div class="flex items-center gap-2">
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeXs,
Variant: components.SpinnerVariantBorder,
Color: "text-secondary-foreground",
})
<span>Processing</span>
</div>
}
<!-- Outline button with spinner -->
@components.Button(components.ButtonProps{
Variant: components.ButtonVariantOutline,
Attributes: templ.Attributes{
"disabled": "true",
},
Class: "min-w-[120px]",
}) {
<div class="flex items-center gap-2">
@components.Spinner(components.SpinnerProps{
Size: components.SpinnerSizeXs,
Variant: components.SpinnerVariantDots,
})
<span>Submitting</span>
</div>
}
</div>
}