// Helpers
const compose =
(...fns) =>
(value) =>
fns.reverse().reduce((acc, fn) => fn(acc), value);
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const els = (sel, par) => (par || document).querySelectorAll(sel);
const createEl = (tag, prop = {}, children) => {
const el = document.createElement(tag);
Object.assign(el, prop);
if (children) {
el.append(...children);
}
return el;
};
const attr = (el, attr) =>
Object.entries(attr).map(([k, v]) => el.setAttribute(k, v) || true) && el;
const css = (el, styles) => Object.assign(el.style, styles);
// Rename createEl to h for simplicity
const h = createEl;
// Icon Custom Element
class NYCIcon extends HTMLElement {
connectedCallback() {
if (this.isConnected) {
const src = this.getAttribute("src");
const objectEl = h("object", {
type: "image/svg+xml",
data: src,
ariaLabel: this.getAttribute("aria-label") || "icon",
innerHTML: "Your browser does not support SVGs",
});
this.appendChild(objectEl);
}
}
}
customElements.define("nyc-icon", NYCIcon);
// Tab Bar Custom Element
class NYCTabBar extends HTMLElement {
constructor() {
super();
// document.URL refers to the current url
this._urlHash = new URL(document.URL).hash.split("#")[1] || null;
this._defaultTab = "#what-we-do";
const buttonProps = {
type: "button",
innerHTML:
'',
};
this._prevArrowBtn = h("button", {
...buttonProps,
onclick: () => this.shiftContainer(0),
});
this._prevArrowBtn.setAttribute("aria-label", "Previous");
this._nextArrowBtn = h("button", {
...buttonProps,
onclick: () => {
const { offsetWidth } = this._container;
const { innerWidth } = window;
const shift =
offsetWidth > innerWidth ? -(offsetWidth - innerWidth) : 0;
this.shiftContainer(shift);
},
});
this._nextArrowBtn.setAttribute("aria-label", "Next");
this._container = h("nav", { style: `left: ${this._shiftValue}%` });
this._shiftValue = 0;
this.shiftContainer(0);
this._tabPanes = [];
this.handleBackClick = this.handleBackClick.bind(this);
}
connectedCallback() {
if (this.isConnected) {
const children = this.children;
// Collect and process tabs
this._tabs = [...children]
.filter((el) => el.getAttribute("role") === "tab")
.map(this.processTab.bind(this));
this._container.append(...this._tabs);
this.append(this._container, this._prevArrowBtn, this._nextArrowBtn);
this._tabLinks = document.querySelectorAll("[data-tab-link]");
if (this._tabLinks.length > 0) {
this._tabLinks.forEach((tabLink) =>
tabLink.addEventListener("click", () => {
const id = tabLink.dataset.tabLink;
this.showTab(id);
this.setURL(id);
})
);
}
// If URL hash matches tab pane, show tab
if (
this._urlHash &&
this._tabs
.map((t) => t.getAttribute("aria-controls"))
.includes(this._urlHash)
) {
this.showTab(this._urlHash);
}
window.addEventListener("popstate", this.handleBackClick);
}
}
disconnectedCallback() {
window.removeEventListener("popstate", this.handleBackClick);
}
processTab(tab) {
// Collect corresponding pane
let tabPane = null;
const tabPaneId = tab.getAttribute("aria-controls");
try {
tabPane = document.getElementById(tabPaneId);
this._tabPanes.push(tabPane);
} catch (e) {
console.log(e);
return;
}
// if pane exists add click handler for show/hide
if (tabPane) {
tab.addEventListener("click", () => {
this.setURL(tabPaneId);
this.resetAllTabs();
// Set clicked tab and pane to selected/active
tab.setAttribute("aria-selected", true);
tabPane.classList.add("active");
});
}
return tab;
}
resetAllTabs() {
// Reset all tabs and panes
this._tabs.forEach((tab) => tab.setAttribute("aria-selected", false));
this._tabPanes.forEach((pane) => pane.classList.remove("active"));
}
showTab(id) {
this.resetAllTabs();
const tab = this._tabs.filter(
(t) => t.getAttribute("aria-controls") === id
)[0];
const tabPane = this._tabPanes.filter((p) => p.id === id)[0];
// Set clicked tab and pane to selected/active
tab.setAttribute("aria-selected", true);
tabPane.classList.add("active");
tabPane.scrollIntoView({
behavior: "smooth",
});
}
setURL(id) {
// Set the URL hash to the tab ID
const hash = `#${id}`;
history.pushState(true, "", hash);
}
handleBackClick(e) {
e.preventDefault();
const { hash } = e.target.location;
this._urlHash = hash.length > 0 ? hash : this._defaultTab;
this.showTab(this._urlHash.split('#')[1]);
}
shiftContainer(value) {
this._shiftValue = value;
this._prevArrowBtn.style.display = this._shiftValue === 0 ? "none" : "flex";
this._nextArrowBtn.style.display = this._shiftValue < 0 ? "none" : "flex";
this._container.style.left = `${this._shiftValue}px`;
}
}
customElements.define("nyc-tab-bar", NYCTabBar);
class NYCSlider extends HTMLElement {
constructor() {
super();
this._pageIndex = 0;
this._itemIndex = 0;
this._itemsPerPage = this.getItemsPerPage();
const buttonProps = {
type: "button",
innerHTML:
'',
};
this._prevArrowBtn = h("button", {
...buttonProps,
ariaLabel: "Previous",
onclick: () => this.shiftTrack(true),
});
this._nextArrowBtn = h("button", {
...buttonProps,
ariaLabel: "Next",
onclick: () => this.shiftTrack(),
});
this._pageCounter = h("span", {
className: "nyc-slider__page-counter",
});
this._navContainer = h("div", { className: "nyc-slider__navigation" }, [
this._prevArrowBtn,
this._pageCounter,
this._nextArrowBtn,
]);
this._sliderTrack = h("div", { className: "nyc-slider__track" });
this._container = h("div", { className: "nyc-slider__container" });
this._navClicked = false;
}
connectedCallback() {
if (this.isConnected) {
this._items = [...this.children];
this._sliderTrack.append(...this._items);
this._container.append(this._sliderTrack);
this.append(this._container, this._navContainer);
this.setPageCounter();
window.addEventListener("resize", () => {
this._itemsPerPage = this.getItemsPerPage();
this.setPageCounter();
});
this._observer = new IntersectionObserver(() => {
this.setPageCounter();
});
this._observer.observe(this);
}
}
getItemsPerPage() {
if (window.innerWidth > 960) {
return 3;
}
if (window.innerWidth > 768) {
return 2;
}
return 1;
}
setPageCounter() {
const containerWidth = this._container.offsetWidth;
const trackWidth = this._sliderTrack.offsetWidth;
const pages =
trackWidth > containerWidth
? Math.ceil(this._items.length / this._itemsPerPage)
: 1;
// console.log(containerWidth, trackWidth);
const content = `Page ${this._pageIndex + 1} of ${pages}`;
this._pageCounter.innerHTML = content;
if (this._pageIndex === 0) {
this._prevArrowBtn.setAttribute("disabled", true);
} else {
this._prevArrowBtn.removeAttribute("disabled");
}
if (this._pageIndex + 1 === pages) {
this._nextArrowBtn.setAttribute("disabled", true);
} else {
this._nextArrowBtn.removeAttribute("disabled");
}
}
shiftTrack(reverse = false) {
this._itemIndex = reverse
? this._itemIndex - this._itemsPerPage
: this._itemIndex + this._itemsPerPage;
this._pageIndex = reverse ? this._pageIndex - 1 : this._pageIndex + 1;
const shiftToItem = this._items[this._itemIndex];
// console.log(this._items[0].offsetLeft);
const containerWidth = this._container.offsetWidth;
const trackWidth = this._sliderTrack.offsetWidth;
// const shift = -containerWidth * 0.8;
const shift = this._navClicked
? shiftToItem.offsetLeft
: shiftToItem.offsetLeft - 20;
// console.log(shift);
this._sliderTrack.style.transform = "translateX(" + -shift + "px)";
// "translateX(" + this._pageIndex * -shift + "px)";
this.setPageCounter();
this._navClicked = true;
}
}
customElements.define("nyc-slider", NYCSlider);