diff --git a/book.html b/book.html index 711493d..7e590ff 100644 --- a/book.html +++ b/book.html @@ -18,10 +18,16 @@ +
- diff --git a/index.html b/index.html index c23ee1d..eae99f9 100644 --- a/index.html +++ b/index.html @@ -18,10 +18,16 @@ +
- diff --git a/js/app.js b/js/app.js index 5d025f4..4f71f28 100644 --- a/js/app.js +++ b/js/app.js @@ -31,6 +31,7 @@ import "./components/image-gallery.js"; import "./components/book-description.js"; import "./components/book-reviews.js"; import "./components/book-review-item.js"; +import "./components/mobile-drawer.js"; // Import icon components import "./icons/menu-icon.js"; @@ -40,6 +41,7 @@ import "./icons/arrow-circle-right-icon.js"; import "./icons/book-open-icon.js"; import "./icons/clipboard-icon.js"; import "./icons/chevron-down-icon.js"; +import "./icons/close-icon.js"; // App initialization document.addEventListener("DOMContentLoaded", () => { diff --git a/js/components/mobile-drawer.js b/js/components/mobile-drawer.js new file mode 100644 index 0000000..991bca0 --- /dev/null +++ b/js/components/mobile-drawer.js @@ -0,0 +1,379 @@ +/** + * Mobile Drawer Component + * Slide-in navigation drawer from the left with backdrop + */ +class MobileDrawer extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this.isOpen = false; + this.handleBackdropClick = this.handleBackdropClick.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + } + + connectedCallback() { + this.render(); + this.setupEventListeners(); + } + + disconnectedCallback() { + document.removeEventListener("keydown", this.handleKeyDown); + } + + setupEventListeners() { + // Close button + const closeBtn = this.shadowRoot.querySelector(".close-button"); + closeBtn?.addEventListener("click", () => this.close()); + + // Backdrop click + const backdrop = this.shadowRoot.querySelector(".backdrop"); + backdrop?.addEventListener("click", this.handleBackdropClick); + + // Escape key + document.addEventListener("keydown", this.handleKeyDown); + + // Listen for global open event + window.addEventListener("open-mobile-drawer", () => this.open()); + window.addEventListener("close-mobile-drawer", () => this.close()); + window.addEventListener("toggle-mobile-drawer", () => this.toggle()); + } + + handleBackdropClick() { + this.close(); + } + + handleKeyDown(e) { + if (e.key === "Escape" && this.isOpen) { + this.close(); + } + } + + open() { + this.isOpen = true; + this.shadowRoot.querySelector(".drawer-container").classList.add("open"); + document.body.style.overflow = "hidden"; + + // Focus trap - focus first focusable element + setTimeout(() => { + const closeBtn = this.shadowRoot.querySelector(".close-button"); + closeBtn?.focus(); + }, 100); + } + + close() { + this.isOpen = false; + this.shadowRoot.querySelector(".drawer-container").classList.remove("open"); + document.body.style.overflow = ""; + } + + toggle() { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } + } + + render() { + this.shadowRoot.innerHTML = ` + + + + `; + } +} + +customElements.define("mobile-drawer", MobileDrawer); diff --git a/js/icons/close-icon.js b/js/icons/close-icon.js new file mode 100644 index 0000000..a0cebac --- /dev/null +++ b/js/icons/close-icon.js @@ -0,0 +1,65 @@ +/** + * Close Icon Web Component + * A reusable close/X icon element + */ +class CloseIcon extends HTMLElement { + static get observedAttributes() { + return ["size", "color", "stroke-width"]; + } + + constructor() { + super(); + this.attachShadow({ mode: "open" }); + } + + connectedCallback() { + this.render(); + } + + attributeChangedCallback() { + this.render(); + } + + get size() { + return this.getAttribute("size") || "24"; + } + + get color() { + return this.getAttribute("color") || "currentColor"; + } + + get strokeWidth() { + return this.getAttribute("stroke-width") || "2"; + } + + render() { + this.shadowRoot.innerHTML = ` + + + + + + `; + } +} + +customElements.define("close-icon", CloseIcon);