scanfile/server/web/templui/components/tabs/tabs.templ
2025-06-03 15:44:56 +02:00

249 lines
6 KiB
Text

// templui component tabs - version: main installed by templui v0.71.0
package tabs
import (
"context"
"git.jmbit.de/jmb/scanfile/server/web/templui/utils"
)
type Props struct {
ID string
Class string
Attributes templ.Attributes
}
type ListProps struct {
ID string
Class string
Attributes templ.Attributes
}
type TriggerProps struct {
ID string
Class string
Attributes templ.Attributes
Value string
IsActive bool
TabsID string
}
type ContentProps struct {
ID string
Class string
Attributes templ.Attributes
Value string
IsActive bool
TabsID string
}
templ Tabs(props ...Props) {
@Script()
{{ var p Props }}
if len(props) > 0 {
{{ p = props[0] }}
}
{{ tabsID := p.ID }}
if tabsID == "" {
{{ tabsID = utils.RandomID() }}
}
<div
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("relative", p.Class) }
data-tabs
data-tabs-id={ tabsID }
{ p.Attributes... }
>
{{ ctx = context.WithValue(ctx, "tabsId", tabsID) }}
{ children... }
</div>
}
templ List(props ...ListProps) {
{{ var p ListProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
{{ tabsID := IDFromContext(ctx) }}
<div
if p.ID != "" {
id={ p.ID }
}
class={
utils.TwMerge(
"relative flex items-center justify-center h-10 p-1 rounded-lg select-none bg-muted text-muted-foreground",
p.Class,
),
}
{ p.Attributes... }
>
{ children... }
<div
data-tabs-marker
data-tabs-id={ tabsID }
class="absolute left-0 z-10 h-full duration-300 ease-out"
>
<div class="w-full h-full bg-background rounded-md shadow-xs"></div>
</div>
</div>
}
templ Trigger(props ...TriggerProps) {
{{ var p TriggerProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
{{ tabsID := p.TabsID }}
if tabsID == "" {
{{ tabsID = IDFromContext(ctx) }}
}
if p.Value == "" {
<span class="text-xs text-destructive">Error: Tab Trigger missing required 'Value' attribute.</span>
}
<button
if p.ID != "" {
id={ p.ID }
}
type="button"
class={
utils.TwMerge(
"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 hover:text-foreground",
p.Class,
),
}
data-tabs-trigger
data-tabs-id={ tabsID }
data-tabs-value={ p.Value }
data-state={ utils.IfElse(p.IsActive, "active", "inactive") }
{ p.Attributes... }
>
{ children... }
</button>
}
templ Content(props ...ContentProps) {
{{ var p ContentProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
{{ tabsID := p.TabsID }}
if tabsID == "" {
{{ tabsID = IDFromContext(ctx) }}
}
if p.Value == "" {
<span class="text-xs text-destructive">Error: Tab Content missing required 'Value' attribute.</span>
return templ.NopComponent
}
<div
if p.ID != "" {
id={ p.ID }
}
class={
utils.TwMerge(
"relative",
utils.If(!p.IsActive, "hidden"),
p.Class,
),
}
data-tabs-content
data-tabs-id={ tabsID }
data-tabs-value={ p.Value }
data-state={ utils.IfElse(p.IsActive, "active", "inactive") }
{ p.Attributes... }
>
{ children... }
</div>
}
func IDFromContext(ctx context.Context) string {
if tabsID, ok := ctx.Value("tabsId").(string); ok {
return tabsID
}
return ""
}
var handle = templ.NewOnceHandle()
templ Script() {
@handle.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
(function() { // IIFE
function initTabs(container) {
if (container.hasAttribute('data-initialized')) return;
container.setAttribute('data-initialized', 'true');
const tabsId = container.dataset.tabsId;
if (!tabsId) return;
const triggers = Array.from(container.querySelectorAll(`[data-tabs-trigger][data-tabs-id="${tabsId}"]`));
const contents = Array.from(container.querySelectorAll(`[data-tabs-content][data-tabs-id="${tabsId}"]`));
const marker = container.querySelector(`[data-tabs-marker][data-tabs-id="${tabsId}"]`);
function updateMarker(activeTrigger) {
if (!marker || !activeTrigger) return;
marker.style.width = activeTrigger.offsetWidth + 'px';
marker.style.height = activeTrigger.offsetHeight + 'px';
marker.style.left = activeTrigger.offsetLeft + 'px';
}
function setActiveTab(value) {
let activeTrigger = null;
for (const trigger of triggers) {
const isActive = trigger.dataset.tabsValue === value;
trigger.dataset.state = isActive ? "active" : "inactive";
trigger.classList.toggle('text-foreground', isActive);
trigger.classList.toggle('bg-background', isActive);
trigger.classList.toggle('shadow-xs', isActive);
if (isActive) activeTrigger = trigger;
}
for (const content of contents) {
const isActive = content.dataset.tabsValue === value;
content.dataset.state = isActive ? "active" : "inactive";
content.classList.toggle('hidden', !isActive);
}
updateMarker(activeTrigger);
}
const defaultTrigger = triggers.find(t => t.dataset.state === 'active') || triggers[0];
if (defaultTrigger) {
setActiveTab(defaultTrigger.dataset.tabsValue);
}
for (const trigger of triggers) {
trigger.addEventListener('click', () => {
setActiveTab(trigger.dataset.tabsValue);
});
}
}
function initAllComponents(root = document) {
if (root instanceof Element && root.matches('[data-tabs]')) {
initTabs(root);
}
for (const tabs of root.querySelectorAll('[data-tabs]:not([data-initialized])')) {
initTabs(tabs);
}
}
const handleHtmxSwap = (event) => {
const target = event.detail.target || event.detail.elt;
if (target instanceof Element) {
requestAnimationFrame(() => initAllComponents(target));
}
};
initAllComponents();
document.addEventListener('DOMContentLoaded', () => initAllComponents());
document.body.addEventListener('htmx:afterSwap', handleHtmxSwap);
document.body.addEventListener('htmx:oobAfterSwap', handleHtmxSwap);
})(); // End of IIFE
</script>
}
}