@panoramax/web-viewer
Version:
Panoramax web viewer for geolocated pictures
141 lines (126 loc) • 3.56 kB
JavaScript
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 ; }
.contents ::slotted(.active) { display: block ; }
`;
/**
* 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);