Loading...

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>
}