feature/book-page (#4)
Co-authored-by: Tim Rijkse <trijkse@gmail.com> Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
144
js/components/content-tabs.js
Normal file
144
js/components/content-tabs.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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);
|
||||
Reference in New Issue
Block a user