UNPKG

@rxxuzi/gumi

Version:

Clean & minimal design system with delightful interactions

198 lines 6.62 kB
// sidebar.ts // Sidebar component with hamburger menu import * as dom from '../core/dom'; export class Sidebar { constructor(element, options = {}) { this.overlay = null; this.hamburger = null; this.isOpen = false; this.element = dom.$(element); if (!this.element) { throw new Error('Sidebar element not found'); } this.options = { overlay: true, closeOnOutsideClick: true, closeOnEscape: true, push: false, pushTarget: 'body', ...options }; this.boundKeyHandler = this.handleKeyPress.bind(this); this.boundOutsideClickHandler = this.handleOutsideClick.bind(this); this.init(); } init() { // Create overlay if needed if (this.options.overlay) { this.createOverlay(); } // Find and setup hamburger menu this.setupHamburger(); // Setup event listeners this.bindEvents(); } createOverlay() { this.overlay = dom.createElement('div', { className: 'sidebar-overlay' }); document.body.appendChild(this.overlay); dom.on(this.overlay, 'click', () => { if (this.options.closeOnOutsideClick) { this.close(); } }); } setupHamburger() { // Look for hamburger with data-sidebar attribute const hamburgerSelector = `[data-sidebar="#${this.element.id}"], [data-sidebar="${this.element.id}"]`; this.hamburger = dom.$(hamburgerSelector); if (this.hamburger) { dom.on(this.hamburger, 'click', (e) => { e.preventDefault(); this.toggle(); }); } } bindEvents() { // Close on escape key if (this.options.closeOnEscape) { dom.on(document, 'keydown', this.boundKeyHandler); } // Close on outside click if (this.options.closeOnOutsideClick) { dom.on(document, 'click', this.boundOutsideClickHandler); } } handleKeyPress(e) { const keyEvent = e; if (keyEvent.key === 'Escape' && this.isOpen) { this.close(); } } handleOutsideClick(e) { if (!this.isOpen) return; const target = e.target; // Don't close if clicking inside sidebar or hamburger if (this.element.contains(target) || (this.hamburger && this.hamburger.contains(target))) { return; } this.close(); } open() { if (this.isOpen) return; this.isOpen = true; dom.addClass(this.element, 'active'); if (this.overlay) { dom.addClass(this.overlay, 'active'); } if (this.hamburger) { dom.addClass(this.hamburger, 'active'); } // Push content if enabled if (this.options.push && this.options.pushTarget) { const pushTarget = dom.$(this.options.pushTarget); if (pushTarget) { const pushClass = this.element.classList.contains('sidebar-right') ? 'pushed-right' : 'pushed'; dom.addClass(pushTarget, 'sidebar-push', pushClass); } } // Trigger event const event = new CustomEvent('gumi-sidebar-open', { detail: { sidebar: this } }); this.element.dispatchEvent(event); } close() { if (!this.isOpen) return; this.isOpen = false; dom.removeClass(this.element, 'active'); if (this.overlay) { dom.removeClass(this.overlay, 'active'); } if (this.hamburger) { dom.removeClass(this.hamburger, 'active'); } // Remove push effect if (this.options.push && this.options.pushTarget) { const pushTarget = dom.$(this.options.pushTarget); if (pushTarget) { dom.removeClass(pushTarget, 'sidebar-push', 'pushed', 'pushed-right'); } } // Trigger event const event = new CustomEvent('gumi-sidebar-close', { detail: { sidebar: this } }); this.element.dispatchEvent(event); } toggle() { if (this.isOpen) { this.close(); } else { this.open(); } } isOpened() { return this.isOpen; } destroy() { // Remove event listeners dom.off(document, 'keydown', this.boundKeyHandler); dom.off(document, 'click', this.boundOutsideClickHandler); // Remove overlay if (this.overlay) { this.overlay.remove(); } // Remove classes dom.removeClass(this.element, 'active'); if (this.hamburger) { dom.removeClass(this.hamburger, 'active'); } // Remove push effect if (this.options.push && this.options.pushTarget) { const pushTarget = dom.$(this.options.pushTarget); if (pushTarget) { dom.removeClass(pushTarget, 'sidebar-push', 'pushed', 'pushed-right'); } } } static initFromAttributes(selector = '[data-sidebar]') { const triggers = dom.$$(selector); const sidebars = []; triggers.forEach(trigger => { const sidebarSelector = trigger.getAttribute('data-sidebar'); if (!sidebarSelector) return; const sidebarElement = dom.$(sidebarSelector); if (!sidebarElement) return; // Check if sidebar already initialized if (sidebarElement.__gumi_sidebar) return; const options = { overlay: trigger.getAttribute('data-overlay') !== 'false', closeOnOutsideClick: trigger.getAttribute('data-close-outside') !== 'false', closeOnEscape: trigger.getAttribute('data-close-escape') !== 'false', push: trigger.getAttribute('data-push') === 'true', pushTarget: trigger.getAttribute('data-push-target') || 'body' }; const sidebar = new Sidebar(sidebarElement, options); sidebarElement.__gumi_sidebar = sidebar; sidebars.push(sidebar); }); return sidebars; } static init(selector) { const element = dom.$(selector); if (!element) return null; return new Sidebar(element); } } //# sourceMappingURL=sidebar.js.map