// 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);