1
0
mirror of https://github.com/axzilla/templui.git synced 2025-02-22 13:22:38 +00:00
templui/pgk/components/tabs.templ

108 lines
3.5 KiB
Plaintext

package components
// Tab represents a single tab in the Tabs component.
type Tab struct {
// ID is the unique identifier for the tab.
ID string
// Title is the text displayed on the tab button.
Title string
// Content is the templ.Component to be rendered when the tab is active.
Content templ.Component
}
// TabsProps defines the properties for the Tabs component.
type TabsProps struct {
// Tabs is an array of Tab structs representing each tab in the component.
Tabs []Tab
// TabsContainerClass specifies additional CSS classes for the tabs container.
// Default: "" (empty string)
TabsContainerClass string
// ContentContainerClass specifies additional CSS classes for the content container.
// Default: "" (empty string)
ContentContainerClass string
}
// Tabs renders a tabbed interface component based on the provided props.
// It uses Alpine.js for interactivity and state management.
//
// Usage:
//
// @components.Tabs(components.TabsProps{
// Tabs: []components.Tab{
// {ID: "1", Title: "Tab 1", Content: Tab1Content()},
// {ID: "2", Title: "Tab 2", Content: Tab2Content()},
// },
// TabsContainerClass: "w-full max-w-md",
// ContentContainerClass: "mt-4",
// })
//
// Props:
// - Tabs: An array of Tab structs, each representing a tab in the interface.
// - TabsContainerClass: Additional CSS classes for the tabs container. Default: "" (empty string)
// - ContentContainerClass: Additional CSS classes for the content container. Default: "" (empty 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"
>
<!-- Tabs buttons container -->
<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 }
>
<!-- Individual tab buttons -->
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>
}
<!-- Active tab marker -->
<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>
<!-- Tab content container -->
<div class={ "relative mt-2 content", props.ContentContainerClass }>
<!-- Individual tab content -->
for _, tab := range props.Tabs {
<div
:id="$id(tabId + '-content')"
x-show="tabContentActive($el)"
class="relative"
x-cloak
>
@tab.Content
</div>
}
</div>
</div>
}