UNPKG

@panoramax/web-viewer

Version:

Panoramax web viewer for geolocated pictures

141 lines (126 loc) 3.56 kB
import { LitElement, html, css, nothing } from "lit"; /** * Tabs offers a nice paged content rendering based on tabs buttons. * The list of tab names are passed through `title` slots, and content using `content` slots. * Note that tab names and contents should respect a coherent order. * @class Panoramax.components.layout.Tabs * @element pnx-tabs * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/) * @slot `title` A single tab name * @slot `content` A single tab content * @example * ```html * <pnx-tabs> * <h4 slot="title">Tab 1</h4> * <div slot="content">Tab 1 content</div> * * <h4 slot="title">Tab 2</h4> * <div slot="content">Tab 2 content</div> * * <h4 slot="title">Tab 3</h4> * <div slot="content">Tab 3 content</div> * </pnx-tabs> * ``` */ export default class Tabs extends LitElement { /** @private */ static styles = css` /* Tabs */ nav { display: flex; gap: 5px; align-items: stretch; overflow-x: auto; touch-action: pan-x; position: sticky; top: 0; background: white; z-index: 125; } nav ::slotted(*) { color: var(--grey-dark); border-bottom: 2px solid rgba(0,0,0,0); cursor: pointer; transition: all 0.1s; flex: 1; } nav ::slotted(*:hover:not(.active)) { background-color: var(--blue-pale); } nav ::slotted(*.active) { color: var(--blue-dark); border-bottom: 2px solid var(--blue-dark); } /* Content */ .contents ::slotted(div) { display: none !important; } .contents ::slotted(.active) { display: block !important; } `; /** * Component properties. * @memberof Panoramax.components.layout.Tabs# * @type {Object} * @property {string} [activeTabIndex=0] The selected tab index * @property {boolean} [hideNav=false] Hide nav */ static properties = { activeTabIndex: {type: Number, reflect: true}, hideNav: {type: Boolean}, }; constructor() { super(); this.activeTabIndex = 0; this.hideNav = false; } /** @private */ _getTabs() { return this.shadowRoot.querySelector("slot[name='title']")?.assignedNodes() || []; } /** @private */ _getContents() { return this.shadowRoot.querySelector("slot[name='content']")?.assignedNodes() || []; } /** @private */ _changeTab(tabTarget, tabIndex) { const tabs = this._getTabs(); const contents = this._getContents(); // Check if tab change is possible if(tabTarget !== undefined || tabIndex !== undefined) { // For tab target, check if a nav tab has really been clicked if(tabTarget) { tabIndex = tabs.findIndex(tab => ( tab === tabTarget || tab === tabTarget.parentNode )); } if(!isNaN(tabIndex) && tabIndex >= 0 && tabIndex < tabs.length) { tabs.forEach((tab, index) => { if (index == tabIndex) { this.activeTabIndex = index; contents[index].classList.add("active"); tab.classList.add("active"); } else { contents[index].classList.remove("active"); tab.classList.remove("active"); } }); } } } /** @private */ _handleTabClick(event) { this._changeTab(event.target); } /** @private */ updated(changedProperties) { if(changedProperties.has("activeTabIndex")) { this._changeTab(undefined, this.activeTabIndex); } } /** @private */ render() { return html` ${!this.hideNav ? html`<nav @click="${this._handleTabClick}"> <slot name="title" @slotchange=${() => this.activeTabIndex = 0}></slot> </nav>` : nothing} <div class="contents"> <slot name="content"></slot> </div> `; } } customElements.define("pnx-tabs", Tabs);