mirror of
https://github.com/axzilla/templui.git
synced 2025-02-22 13:22:38 +00:00
108 lines
3.5 KiB
Plaintext
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>
|
|
}
|