1
0
mirror of https://github.com/axzilla/templui.git synced 2025-02-21 01:52:52 +00:00

change: update button component, use Templ attributes as button prop instead of seperate function value

This commit is contained in:
“axzilla” 2024-10-06 18:19:11 +02:00
parent 07eb76a007
commit 7a0ab8a620
10 changed files with 160 additions and 132 deletions

View File

@ -8,6 +8,7 @@
- Changed: Improve docs UI
- Changed: Make Product Hunt Badge smaller on footer
- Changed: Some UI changes on showcase section
- Changed: button attributes now as a prop
- Fixed: Width issue on tabs component
## 2024-10-05

View File

@ -662,6 +662,10 @@ body {
bottom: 0px;
}
.bottom-4 {
bottom: 1rem;
}
.left-0 {
left: 0px;
}
@ -674,6 +678,10 @@ body {
right: 0.5rem;
}
.right-4 {
right: 1rem;
}
.top-0 {
top: 0px;
}
@ -682,14 +690,6 @@ body {
top: 0.5rem;
}
.bottom-4 {
bottom: 1rem;
}
.right-4 {
right: 1rem;
}
.z-10 {
z-index: 10;
}
@ -707,11 +707,6 @@ body {
margin-right: auto;
}
.my-8 {
margin-top: 2rem;
margin-bottom: 2rem;
}
.mb-12 {
margin-bottom: 3rem;
}
@ -752,6 +747,10 @@ body {
margin-right: 0.5rem;
}
.mt-16 {
margin-top: 4rem;
}
.mt-2 {
margin-top: 0.5rem;
}
@ -764,10 +763,6 @@ body {
display: block;
}
.inline-block {
display: inline-block;
}
.flex {
display: flex;
}
@ -776,6 +771,10 @@ body {
display: inline-flex;
}
.table {
display: table;
}
.hidden {
display: none;
}
@ -856,14 +855,14 @@ body {
width: 16rem;
}
.w-full {
width: 100%;
}
.w-\[350px\] {
width: 350px;
}
.w-full {
width: 100%;
}
.max-w-3xl {
max-width: 48rem;
}
@ -880,6 +879,15 @@ body {
flex: 1 1 0%;
}
.border-collapse {
border-collapse: collapse;
}
.-translate-x-1\/4 {
--tw-translate-x: -25%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.-translate-x-full {
--tw-translate-x: -100%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@ -895,11 +903,6 @@ body {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-x-1\/4 {
--tw-translate-x: 25%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-x-full {
--tw-translate-x: 100%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@ -920,11 +923,6 @@ body {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.-translate-x-1\/4 {
--tw-translate-x: -25%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@ -1047,10 +1045,6 @@ body {
border-radius: calc(var(--radius) - 2px);
}
.rounded-full {
border-radius: 9999px;
}
.border {
border-width: 1px;
}
@ -1076,6 +1070,11 @@ body {
border-color: hsl(var(--border) / var(--tw-border-opacity));
}
.border-gray-300 {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
.border-input {
--tw-border-opacity: 1;
border-color: hsl(var(--input) / var(--tw-border-opacity));
@ -1100,6 +1099,11 @@ body {
background-color: hsl(var(--destructive) / var(--tw-bg-opacity));
}
.bg-gray-100 {
--tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
}
.bg-gray-700 {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
@ -1133,22 +1137,12 @@ body {
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}
.from-gray-900 {
--tw-gradient-from: #111827 var(--tw-gradient-from-position);
--tw-gradient-to: rgb(17 24 39 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-gray-100 {
--tw-gradient-from: #f3f4f6 var(--tw-gradient-from-position);
--tw-gradient-to: rgb(243 244 246 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-black {
--tw-gradient-to: #000 var(--tw-gradient-to-position);
}
.to-white {
--tw-gradient-to: #fff var(--tw-gradient-to-position);
}
@ -1182,10 +1176,6 @@ body {
padding: 1.5rem;
}
.p-8 {
padding: 2rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
@ -1201,11 +1191,6 @@ body {
padding-right: 1rem;
}
.px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.px-8 {
padding-left: 2rem;
padding-right: 2rem;
@ -1221,6 +1206,11 @@ body {
padding-bottom: 3rem;
}
.py-16 {
padding-top: 4rem;
padding-bottom: 4rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
@ -1246,15 +1236,14 @@ body {
padding-bottom: 2rem;
}
.py-16 {
padding-top: 4rem;
padding-bottom: 4rem;
}
.pb-4 {
padding-bottom: 1rem;
}
.pb-8 {
padding-bottom: 2rem;
}
.pl-6 {
padding-left: 1.5rem;
}
@ -1263,12 +1252,8 @@ body {
padding-top: 0px;
}
.pb-2 {
padding-bottom: 0.5rem;
}
.pb-8 {
padding-bottom: 2rem;
.text-left {
text-align: left;
}
.text-center {
@ -1363,11 +1348,6 @@ body {
color: hsl(var(--foreground) / var(--tw-text-opacity));
}
.text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
}
.text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
@ -1383,6 +1363,11 @@ body {
color: rgb(55 65 81 / var(--tw-text-opacity));
}
.text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.text-muted-foreground {
--tw-text-opacity: 1;
color: hsl(var(--muted-foreground) / var(--tw-text-opacity));
@ -1408,11 +1393,6 @@ body {
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.underline {
text-decoration-line: underline;
}
@ -1526,11 +1506,6 @@ body {
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
}
.hover\:bg-gray-200:hover {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}
.hover\:bg-gray-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(75 85 99 / var(--tw-bg-opacity));
@ -1726,16 +1701,6 @@ body {
padding-right: 1.5rem;
}
.sm\:px-8 {
padding-left: 2rem;
padding-right: 2rem;
}
.sm\:py-3 {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.sm\:text-2xl {
font-size: 1.5rem;
line-height: 2rem;
@ -1753,14 +1718,6 @@ body {
}
@media (min-width: 768px) {
.md\:block {
display: block;
}
.md\:inline {
display: inline;
}
.md\:flex {
display: flex;
}

View File

@ -1,30 +1,66 @@
package components
// ButtonVariant represents the visual style of the button.
type ButtonVariant string
// ButtonSize represents the size of the button.
type ButtonSize string
// Constants for button variants and sizes.
const (
Default ButtonVariant = "default"
Destructive ButtonVariant = "destructive"
Outline ButtonVariant = "outline"
Secondary ButtonVariant = "secondary"
Ghost ButtonVariant = "ghost"
Link ButtonVariant = "link"
Md ButtonSize = "md"
Sm ButtonSize = "sm"
Lg ButtonSize = "lg"
Icon ButtonSize = "icon"
)
// ButtonProps defines the properties for the Button component.
type ButtonProps struct {
Class string
Text string
Variant ButtonVariant
Size ButtonSize
// Class specifies additional CSS classes to apply to the button.
// Default: "" (empty string)
Class string
// Text is the content of the button.
// Default: "" (empty string)
Text string
// Variant determines the visual style of the button.
// Default: Default
Variant ButtonVariant
// Size sets the size of the button.
// Default: Md
Size ButtonSize
// FullWidth determines whether the button should take up the full width of its container.
// Default: false
FullWidth bool
Href string
Target string
// Href, if provided, renders the button as an anchor tag with this URL.
// Default: "" (empty string)
Href string
// Target specifies the target attribute for the anchor tag (only used when Href is provided).
// Default: "" (empty string)
Target string
// Disabled specifies whether the button is disabled.
// Default: "false"
Disabled string
// Attributes allows passing additional HTML attributes to the button or anchor element.
// Default: nil
Attributes templ.Attributes
}
// getVariantClasses returns the CSS classes for the given button variant.
func getVariantClasses(variant ButtonVariant) string {
switch variant {
case Destructive:
@ -42,6 +78,7 @@ func getVariantClasses(variant ButtonVariant) string {
}
}
// getSizeClasses returns the CSS classes for the given button size.
func getSizeClasses(size ButtonSize) string {
switch size {
case Sm:
@ -55,10 +92,35 @@ func getSizeClasses(size ButtonSize) string {
}
}
templ Button(props ButtonProps, attrs map[string]any) {
// Button renders a button or anchor component based on the provided props.
// It can be customized with various visual styles, sizes, and behaviors.
//
// Usage:
//
// @components.Button(components.ButtonProps{
// Text: "Click me",
// Variant: components.Primary,
// Size: components.Md,
// FullWidth: false,
// Attributes: templ.Attributes{
// "aria-label": "Click this button",
// "data-testid": "main-button",
// },
// })
//
// Props:
// - Class: Additional CSS classes to apply to the button. Default: "" (empty string)
// - Text: The text content of the button. Default: "" (empty string)
// - Variant: The visual style of the button (e.g., Default, Destructive, Outline). Default: Default
// - Size: The size of the button (Md, Sm, Lg, Icon). Default: Md
// - FullWidth: Whether the button should take up the full width of its container. Default: false
// - Href: If provided, renders the button as an anchor tag with this URL. Default: "" (empty string)
// - Target: The target attribute for the anchor tag (only used when Href is provided). Default: "" (empty string)
// - Disabled: Whether the button is disabled. Default: "false"
// - Attributes: Additional HTML attributes to apply to the button or anchor element. Default: nil
templ Button(props ButtonProps) {
if props.Href != "" {
<a
{ attrs... }
href={ templ.SafeURL(props.Href) }
target={ props.Target }
class={
@ -69,13 +131,13 @@ templ Button(props ButtonProps, attrs map[string]any) {
templ.KV("w-full", props.FullWidth),
props.Class,
}
{ props.Attributes... }
>
{ 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",
@ -85,6 +147,10 @@ templ Button(props ButtonProps, attrs map[string]any) {
templ.KV("w-full", props.FullWidth),
props.Class,
}
if props.Disabled == "true" {
disabled
}
{ props.Attributes... }
>
{ props.Text }
{ children... }

View File

@ -17,12 +17,12 @@ templ Navbar() {
<a href="/docs/components" class="text-sm hover:underline">Components</a>
</div>
<div class="flex items-center space-x-2">
@Button(ButtonProps{Size: "icon", Variant: "ghost", Href: "https://github.com/axzilla/goilerplate", Target: "_blank"}, nil) {
@Button(ButtonProps{Size: "icon", Variant: "ghost", Href: "https://github.com/axzilla/goilerplate", Target: "_blank"}) {
<svg class="fill-current h-6 w-6" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 30 30" fill="currentColor">
<path d="M15,3C8.373,3,3,8.373,3,15c0,5.623,3.872,10.328,9.092,11.63C12.036,26.468,12,26.28,12,26.047v-2.051 c-0.487,0-1.303,0-1.508,0c-0.821,0-1.551-0.353-1.905-1.009c-0.393-0.729-0.461-1.844-1.435-2.526 c-0.289-0.227-0.069-0.486,0.264-0.451c0.615,0.174,1.125,0.596,1.605,1.222c0.478,0.627,0.703,0.769,1.596,0.769 c0.433,0,1.081-0.025,1.691-0.121c0.328-0.833,0.895-1.6,1.588-1.962c-3.996-0.411-5.903-2.399-5.903-5.098 c0-1.162,0.495-2.286,1.336-3.233C9.053,10.647,8.706,8.73,9.435,8c1.798,0,2.885,1.166,3.146,1.481C13.477,9.174,14.461,9,15.495,9 c1.036,0,2.024,0.174,2.922,0.483C18.675,9.17,19.763,8,21.565,8c0.732,0.731,0.381,2.656,0.102,3.594 c0.836,0.945,1.328,2.066,1.328,3.226c0,2.697-1.904,4.684-5.894,5.097C18.199,20.49,19,22.1,19,23.313v2.734 c0,0.104-0.023,0.179-0.035,0.268C23.641,24.676,27,20.236,27,15C27,8.373,21.627,3,15,3z"></path>
</svg>
}
@Button(ButtonProps{Size: "icon", Variant: "ghost", Href: "https://x.com/_axzilla", Target: "_blank"}, nil) {
@Button(ButtonProps{Size: "icon", Variant: "ghost", Href: "https://x.com/_axzilla", Target: "_blank"}) {
<svg class="fill-current h-6 w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"></path>
</svg>

View File

@ -1,7 +1,11 @@
package components
templ ThemeSwitcher() {
@Button(ButtonProps{Size: "icon", Variant: "ghost"}, templ.Attributes{"@click": "toggleTheme()"}) {
@Button(ButtonProps{
Size: "icon",
Variant: "ghost",
Attributes: templ.Attributes{"@click": "toggleTheme()"},
}) {
<div x-show="theme === 'dark'">
@LightIcon()
</div>

View File

@ -30,7 +30,7 @@ templ Landing() {
Text: "Get Started",
Href: "/docs/getting-started",
Size: "lg",
}, nil)
})
</div>
<div class="absolute bottom-0 left-0 w-24 h-24 sm:w-32 sm:h-32 md:w-40 md:h-40">
<img src="/assets/img/gopher.svg" alt="Gopher" class="absolute bottom-0 left-0 w-full h-full object-contain transform translate-y-1/3 -translate-x-1/4"/>

View File

@ -8,21 +8,21 @@ templ ButtonShowcase() {
<div class="mb-8">
<h2 class="font-semibold mb-2">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)
@components.Button(components.ButtonProps{Text: "Default"})
@components.Button(components.ButtonProps{Text: "Secondary", Variant: components.Secondary})
@components.Button(components.ButtonProps{Text: "Destructive", Variant: components.Destructive})
@components.Button(components.ButtonProps{Text: "Outline", Variant: components.Outline})
@components.Button(components.ButtonProps{Text: "Ghost", Variant: components.Ghost})
@components.Button(components.ButtonProps{Text: "Link", Variant: components.Link})
</div>
</div>
<div class="mb-8">
<h2 class="font-semibold mb-2">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) {
@components.Button(components.ButtonProps{Text: "Default"})
@components.Button(components.ButtonProps{Text: "Small", Size: components.Sm})
@components.Button(components.ButtonProps{Text: "Large", Size: components.Lg})
@components.Button(components.ButtonProps{Size: components.Icon}) {
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
@ -43,19 +43,19 @@ templ ButtonShowcase() {
<div class="mb-8">
<h2 class="font-semibold mb-22">States</h2>
<div class="flex flex-wrap gap-2">
@components.Button(components.ButtonProps{Text: "Default"}, nil)
@components.Button(components.ButtonProps{Text: "Default"})
// Alpine.js example
@components.Button(components.ButtonProps{Text: "With Click"}, templ.Attributes{"@click": "alert('Hey Dude!')"})
@components.Button(components.ButtonProps{Text: "With Click", Attributes: 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)
// @components.Button(components.ButtonProps{Text: "With Click", Attributes: templ.Attributes{"onclick": "alert('Hey Dude!')"}})
@components.Button(components.ButtonProps{Text: "Disabled", Disabled: "true"})
@components.Button(components.ButtonProps{Text: "Full Width", Class: "w-full"})
</div>
</div>
<div class="mb-8">
<h2 class="font-semibold mb-2">With Icon</h2>
<div class="flex flex-wrap gap-2">
@components.Button(components.ButtonProps{}, nil) {
@components.Button(components.ButtonProps{}) {
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
@ -72,7 +72,7 @@ templ ButtonShowcase() {
</svg>
Icon Left
}
@components.Button(components.ButtonProps{}, nil) {
@components.Button(components.ButtonProps{}) {
Icon Right
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -17,7 +17,7 @@ templ CardShowcase() {
<p>Card Content</p>
}
@components.CardFooter() {
@components.Button(components.ButtonProps{Text: "Action"}, nil)
@components.Button(components.ButtonProps{Text: "Action"})
}
}
</div>

View File

@ -26,7 +26,7 @@ templ SheetShowcase() {
</div>
}
@components.SheetTrigger(string(side), side) {
@components.Button(components.ButtonProps{Text: string(side)}, nil)
@components.Button(components.ButtonProps{Text: string(side)})
}
}
}

View File

@ -42,7 +42,7 @@ templ AccountTab() {
</div>
</div>
<div class="flex items-center p-6 pt-0">
@components.Button(components.ButtonProps{Text: "Save changes"}, nil)
@components.Button(components.ButtonProps{Text: "Save changes"})
</div>
</div>
}
@ -64,7 +64,7 @@ templ PasswordTab() {
</div>
</div>
<div class="flex items-center p-6 pt-0">
@components.Button(components.ButtonProps{Text: "Save password"}, nil)
@components.Button(components.ButtonProps{Text: "Save password"})
</div>
</div>
}