joelniklaus's picture
joelniklaus HF Staff
switched from accordion to newly created tab widget
3742813
---
const { class: className, ...props } = Astro.props;
const wrapperClass = ["tabs", className].filter(Boolean).join(" ");
---
<div class={wrapperClass} {...props}>
<div class="tabs__nav" role="tablist"></div>
<div class="tabs__panels">
<slot />
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.tabs').forEach((tabsEl) => {
const nav = tabsEl.querySelector('.tabs__nav') as HTMLElement;
const panels = Array.from(tabsEl.querySelectorAll(':scope > .tabs__panels > .tab-panel')) as HTMLElement[];
if (!nav || panels.length === 0) return;
panels.forEach((panel, i) => {
const title = panel.dataset.tabTitle || `Tab ${i + 1}`;
const btn = document.createElement('button');
btn.className = 'tabs__btn';
btn.role = 'tab';
btn.textContent = title;
btn.setAttribute('aria-selected', 'false');
btn.addEventListener('click', () => activate(i));
nav.appendChild(btn);
});
const buttons = Array.from(nav.querySelectorAll('.tabs__btn')) as HTMLButtonElement[];
function activate(index: number) {
buttons.forEach((b, i) => {
const active = i === index;
b.classList.toggle('tabs__btn--active', active);
b.setAttribute('aria-selected', String(active));
});
panels.forEach((p, i) => {
p.hidden = i !== index;
});
}
// Keyboard navigation
nav.addEventListener('keydown', (e) => {
const current = buttons.findIndex(b => b === document.activeElement);
if (current < 0) return;
let next = current;
if (e.key === 'ArrowRight') next = (current + 1) % buttons.length;
else if (e.key === 'ArrowLeft') next = (current - 1 + buttons.length) % buttons.length;
else if (e.key === 'Home') next = 0;
else if (e.key === 'End') next = buttons.length - 1;
else return;
e.preventDefault();
buttons[next].focus();
activate(next);
});
activate(0);
});
});
</script>
<style>
.tabs {
margin: 0 0 var(--spacing-4);
border: 1px solid var(--border-color);
border-radius: var(--table-border-radius);
background: var(--surface-bg);
}
.tabs__nav {
display: flex;
flex-wrap: wrap;
gap: 0;
border-bottom: 1px solid var(--border-color);
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.tabs__nav::-webkit-scrollbar {
display: none;
}
.tabs__nav :global(.tabs__btn) {
flex: 1 1 0;
padding: var(--spacing-2) var(--spacing-3);
text-align: center;
border: none !important;
border-bottom: 2px solid transparent !important;
border-radius: 0 !important;
background: none !important;
font: inherit;
font-size: 0.9em;
font-weight: 600;
color: var(--text-muted) !important;
cursor: pointer;
white-space: nowrap;
margin-bottom: -1px;
transition: color 150ms ease, border-color 150ms ease;
filter: none !important;
}
.tabs__nav :global(.tabs__btn:hover) {
color: var(--text-color) !important;
filter: none !important;
}
.tabs__nav :global(.tabs__btn--active) {
color: var(--primary-color) !important;
border-bottom-color: var(--primary-color) !important;
}
.tabs__nav :global(.tabs__btn:focus-visible) {
outline: 2px solid var(--primary-color);
outline-offset: -2px;
border-radius: 2px;
}
.tabs__panels :global(.tab-panel) {
padding: 8px;
}
.tabs__panels :global(.tab-panel > *:first-child) {
margin-top: 0 !important;
}
.tabs__panels :global(.tab-panel > *:last-child) {
margin-bottom: 0 !important;
padding-bottom: 0 !important;
}
</style>