Loading...

Button

Displays a button or a component that looks like a button.

Variants

Sizes

States

With Icon

package showcase

import "github.com/axzilla/goilerplate/internals/ui/components"

templ ButtonShowcase() {
	<div class="space-y-8 border rounded-md p-4">
		<h2 class="font-semibold mb-4">Variants</h2>
		<div class="flex flex-wrap gap-2">
			@components.Button(components.ButtonProps{Text: "Default"}, nil)
			@components.Button(components.ButtonProps{Text: "Secondary", Variant: components.Secondary}, nil)
			@components.Button(components.ButtonProps{Text: "Destructive", Variant: components.Destructive}, nil)
			@components.Button(components.ButtonProps{Text: "Outline", Variant: components.Outline}, nil)
			@components.Button(components.ButtonProps{Text: "Ghost", Variant: components.Ghost}, nil)
			@components.Button(components.ButtonProps{Text: "Link", Variant: components.Link}, nil)
		</div>
		<h2 class="font-semibold mb-4">Sizes</h2>
		<div class="flex flex-wrap items-center gap-2">
			@components.Button(components.ButtonProps{Text: "Default"}, nil)
			@components.Button(components.ButtonProps{Text: "Small", Size: components.Sm}, nil)
			@components.Button(components.ButtonProps{Text: "Large", Size: components.Lg}, nil)
			@components.Button(components.ButtonProps{Size: components.Icon}, nil) {
				<svg
					xmlns="http://www.w3.org/2000/svg"
					width="24"
					height="24"
					viewBox="0 0 24 24"
					fill="none"
					stroke="currentColor"
					stroke-width="2"
					stroke-linecap="round"
					stroke-linejoin="round"
					class="h-4 w-4"
				>
					<path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path>
				</svg>
			}
		</div>
		<h2 class="font-semibold mb-4">States</h2>
		<div class="flex flex-wrap gap-2">
			@components.Button(components.ButtonProps{Text: "Default"}, nil)
			// Alpine.js example
			@components.Button(components.ButtonProps{Text: "With Click"}, templ.Attributes{"@click": "alert('Hey Dude!')"})
			// Vanilla JS example
			// @components.Button(components.ButtonProps{Text: "With Click"}, templ.Attributes{"onclick": "alert('Hey Dude!')"})
			@components.Button(components.ButtonProps{Text: "Disabled"}, templ.Attributes{"disabled": "true"})
			@components.Button(components.ButtonProps{Text: "Full Width", Class: "w-full"}, nil)
		</div>
		<h2 class="font-semibold mb-4">With Icon</h2>
		<div class="flex flex-wrap gap-2">
			@components.Button(components.ButtonProps{}, nil) {
				<svg
					xmlns="http://www.w3.org/2000/svg"
					width="24"
					height="24"
					viewBox="0 0 24 24"
					fill="none"
					stroke="currentColor"
					stroke-width="2"
					stroke-linecap="round"
					stroke-linejoin="round"
					class="mr-2 h-4 w-4"
				>
					<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3"></path>
				</svg>
				Icon Left
			}
			@components.Button(components.ButtonProps{}, nil) {
				Icon Right
				<svg
					xmlns="http://www.w3.org/2000/svg"
					width="24"
					height="24"
					viewBox="0 0 24 24"
					fill="none"
					stroke="currentColor"
					stroke-width="2"
					stroke-linecap="round"
					stroke-linejoin="round"
					class="ml-2 h-4 w-4"
				>
					<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3"></path>
				</svg>
			}
		</div>
	</div>
}
package components

type ButtonVariant string
type ButtonSize string

const (
	Destructive ButtonVariant = "destructive"
	Outline     ButtonVariant = "outline"
	Secondary   ButtonVariant = "secondary"
	Ghost       ButtonVariant = "ghost"
	Link        ButtonVariant = "link"

	Sm   ButtonSize = "sm"
	Lg   ButtonSize = "lg"
	Icon ButtonSize = "icon"
)

type ButtonProps struct {
	Class     string
	Text      string
	Variant   ButtonVariant
	Size      ButtonSize
	FullWidth bool
	Href      string
	Target    string
}

func getVariantClasses(variant ButtonVariant) string {
	switch variant {
	case Destructive:
		return "bg-destructive text-destructive-foreground hover:bg-destructive/90"
	case Outline:
		return "border border-input bg-background hover:bg-accent hover:text-accent-foreground"
	case Secondary:
		return "bg-secondary text-secondary-foreground hover:bg-secondary/80"
	case Ghost:
		return "hover:bg-accent hover:text-accent-foreground"
	case Link:
		return "text-primary underline-offset-4 hover:underline"
	default:
		return "bg-primary text-primary-foreground hover:bg-primary/90"
	}
}

func getSizeClasses(size ButtonSize) string {
	switch size {
	case Sm:
		return "h-9 px-3 rounded-md"
	case Lg:
		return "h-11 px-8 rounded-md"
	case Icon:
		return "h-10 w-10"
	default:
		return "h-10 px-4 py-2 rounded-md"
	}
}

templ Button(props ButtonProps, attrs map[string]any) {
	if props.Href != "" {
		<a
			{ attrs... }
			href={ templ.SafeURL(props.Href) }
			target={ props.Target }
			class={
				"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors",
				"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
				getVariantClasses(props.Variant),
				getSizeClasses(props.Size),
				templ.KV("w-full", props.FullWidth),
				props.Class,
			}
		>
			{ props.Text }
			{ children... }
		</a>
	} else {
		<button
			{ attrs... }
			class={
				"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors",
				"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
				"disabled:pointer-events-none disabled:opacity-50",
				getVariantClasses(props.Variant),
				getSizeClasses(props.Size),
				templ.KV("w-full", props.FullWidth),
				props.Class,
			}
		>
			{ props.Text }
			{ children... }
		</button>
	}
}