Tabs
A set of layered sections of content—known as tab panels—that are displayed one at a time.
Account
Make changes to your account here. Click save when you're done.
Password
Change your password here. After saving, you'll be logged out.
package showcase
import (
"github.com/axzilla/goilerplate/internals/ui/components"
)
templ TabsShowcase() {
<div class="space-y-8 border border-border rounded-md p-4">
@components.Tabs(components.TabsProps{
Tabs: []components.Tab{
{
ID: "account",
Title: "Account",
Content: AccountTab(),
},
{
ID: "password",
Title: "Password",
Content: PasswordTab(),
},
},
TabsContainerClass: "w-full max-w-xs",
ContentContainerClass: "w-full",
})
</div>
}
templ AccountTab() {
<div class="border border-border rounded-lg shadow-sm bg-card text-card-foreground">
<div class="flex flex-col space-y-1.5 p-6">
<h3 class="text-lg font-semibold leading-none tracking-tight">Account</h3>
<p class="text-sm text-muted-foreground">Make changes to your account here. Click save when you're done.</p>
</div>
<div class="p-6 pt-0 space-y-2">
<div class="space-y-1">
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" for="name">Name</label>
<input type="text" placeholder="Name" id="name" value="John Doe" class="flex w-full h-10 px-3 py-2 text-sm bg-background border border-input rounded-md ring-offset-background placeholder:text-muted-foreground focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"/>
</div>
<div class="space-y-1">
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" for="username">Username</label>
<input type="text" placeholder="Username" id="username" value="@johndoe" class="flex w-full h-10 px-3 py-2 text-sm bg-background border border-input rounded-md ring-offset-background placeholder:text-muted-foreground focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"/>
</div>
</div>
<div class="flex items-center p-6 pt-0">
@components.Button(components.ButtonProps{Text: "Save changes"}, nil)
</div>
</div>
}
templ PasswordTab() {
<div class="border border-border rounded-lg shadow-sm bg-card text-card-foreground">
<div class="flex flex-col space-y-1.5 p-6">
<h3 class="text-lg font-semibold leading-none tracking-tight">Password</h3>
<p class="text-sm text-muted-foreground">Change your password here. After saving, you'll be logged out.</p>
</div>
<div class="p-6 pt-0 space-y-2">
<div class="space-y-1">
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" for="current_password">Current Password</label>
<input type="password" placeholder="Current Password" id="current_password" class="flex w-full h-10 px-3 py-2 text-sm bg-background border border-input rounded-md ring-offset-background placeholder:text-muted-foreground focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"/>
</div>
<div class="space-y-1">
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" for="new_password">New Password</label>
<input type="password" placeholder="New Password" id="new_password" class="flex w-full h-10 px-3 py-2 text-sm bg-background border border-input rounded-md ring-offset-background placeholder:text-muted-foreground focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"/>
</div>
</div>
<div class="flex items-center p-6 pt-0">
@components.Button(components.ButtonProps{Text: "Save password"}, nil)
</div>
</div>
}
package components
type Tab struct {
ID string
Title string
Content templ.Component
}
type TabsProps struct {
Tabs []Tab
TabsContainerClass string
ContentContainerClass string
}
templ Tabs(props TabsProps) {
<div
x-data="{
tabSelected: '1',
tabId: $id('tabs'),
tabButtonClicked(tabButton) {
this.tabSelected = tabButton.id.replace(this.tabId + '-', '');
this.tabRepositionMarker(tabButton);
},
tabRepositionMarker(tabButton) {
this.$refs.tabMarker.style.width = tabButton.offsetWidth + 'px';
this.$refs.tabMarker.style.height = tabButton.offsetHeight + 'px';
this.$refs.tabMarker.style.left = tabButton.offsetLeft + 'px';
},
tabContentActive(tabContent) {
return this.tabSelected === tabContent.id.replace(this.tabId + '-content-', '');
}
}"
x-init="$nextTick(() => tabRepositionMarker($refs.tabButtons.firstElementChild));"
class="relative w-full"
>
<div
x-ref="tabButtons"
class={ "relative flex items-center justify-center h-10 p-1 rounded-lg select-none",
"bg-muted text-muted-foreground",
props.TabsContainerClass }
>
for _, tab := range props.Tabs {
<button
:id="$id(tabId)"
@click="tabButtonClicked($el);"
type="button"
class="relative z-20 flex-1 inline-flex items-center justify-center h-8 px-3 text-sm font-medium transition-all rounded-md cursor-pointer whitespace-nowrap"
:class="{'text-foreground bg-background shadow-sm': tabSelected === '{tab.ID}', 'hover:text-foreground': tabSelected !== '{tab.ID}'}"
>
{ tab.Title }
</button>
}
<div x-ref="tabMarker" class="absolute left-0 z-10 h-full duration-300 ease-out" x-cloak>
<div class="w-full h-full bg-background rounded-md shadow-sm"></div>
</div>
</div>
<div class={ "relative mt-2 content", props.ContentContainerClass }>
for _, tab := range props.Tabs {
<div
:id="$id(tabId + '-content')"
x-show="tabContentActive($el)"
class="relative"
x-cloak
>
@tab.Content
</div>
}
</div>
</div>
}