145 lines
3.5 KiB
JavaScript
145 lines
3.5 KiB
JavaScript
/**
|
|
* Content Tabs Component
|
|
* Tabbed interface for displaying different content sections
|
|
*/
|
|
class ContentTabs extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.attachShadow({ mode: "open" });
|
|
this.activeTab = 0;
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.render();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
get tabs() {
|
|
const tabsAttr = this.getAttribute("tabs");
|
|
return tabsAttr ? tabsAttr.split(",").map((t) => t.trim()) : [];
|
|
}
|
|
|
|
setupEventListeners() {
|
|
const tabButtons = this.shadowRoot.querySelectorAll(".tab-button");
|
|
tabButtons.forEach((button, index) => {
|
|
button.addEventListener("click", () => {
|
|
this.setActiveTab(index);
|
|
});
|
|
});
|
|
}
|
|
|
|
setActiveTab(index) {
|
|
this.activeTab = index;
|
|
this.updateTabs();
|
|
this.dispatchEvent(
|
|
new CustomEvent("tab-change", {
|
|
bubbles: true,
|
|
composed: true,
|
|
detail: { index, tab: this.tabs[index] },
|
|
})
|
|
);
|
|
}
|
|
|
|
updateTabs() {
|
|
// Update tab buttons
|
|
const tabButtons = this.shadowRoot.querySelectorAll(".tab-button");
|
|
tabButtons.forEach((button, index) => {
|
|
button.classList.toggle("active", index === this.activeTab);
|
|
});
|
|
|
|
// Update panels
|
|
const panels = this.querySelectorAll("[slot^='panel-']");
|
|
panels.forEach((panel, index) => {
|
|
panel.style.display = index === this.activeTab ? "block" : "none";
|
|
});
|
|
}
|
|
|
|
render() {
|
|
const tabsHtml = this.tabs
|
|
.map(
|
|
(tab, index) => `
|
|
<button
|
|
class="tab-button ${index === this.activeTab ? "active" : ""}"
|
|
type="button"
|
|
role="tab"
|
|
aria-selected="${index === this.activeTab}"
|
|
>
|
|
${tab}
|
|
</button>
|
|
`
|
|
)
|
|
.join("");
|
|
|
|
this.shadowRoot.innerHTML = `
|
|
<style>
|
|
:host {
|
|
display: block;
|
|
}
|
|
|
|
.tabs-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.tab-list {
|
|
display: flex;
|
|
gap: var(--spacing-lg, 1.5rem);
|
|
border-bottom: 1px solid var(--color-border, #e2e8f0);
|
|
margin-bottom: var(--spacing-md, 1rem);
|
|
}
|
|
|
|
.tab-button {
|
|
padding: var(--spacing-sm, 0.5rem) 0;
|
|
background: none;
|
|
border: none;
|
|
font-family: var(--font-family-outfit, "Outfit", sans-serif);
|
|
font-size: 16px;
|
|
font-weight: 400;
|
|
line-height: 24px;
|
|
color: var(--color-purple, #951d51);
|
|
text-decoration: underline;
|
|
text-underline-offset: 3px;
|
|
cursor: pointer;
|
|
transition: opacity var(--transition-fast, 150ms ease);
|
|
position: relative;
|
|
}
|
|
|
|
.tab-button:first-child {
|
|
color: var(--color-text, #1e293b);
|
|
text-decoration: none;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.tab-button.active {
|
|
color: var(--color-text, #1e293b);
|
|
text-decoration: none;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.tab-button:hover {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.tab-panels {
|
|
min-height: 100px;
|
|
}
|
|
</style>
|
|
<div class="tabs-container">
|
|
<div class="tab-list" role="tablist">
|
|
${tabsHtml}
|
|
</div>
|
|
<div class="tab-panels">
|
|
<slot name="panel-0"></slot>
|
|
<slot name="panel-1"></slot>
|
|
<slot name="panel-2"></slot>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Initialize panel visibility
|
|
setTimeout(() => this.updateTabs(), 0);
|
|
}
|
|
}
|
|
|
|
customElements.define("content-tabs", ContentTabs);
|