249 lines
6 KiB
Text
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>
|
|
}
|
|
}
|