UNPKG

muspe-cli

Version:

MusPE Advanced Framework v2.1.3 - Mobile User-friendly Simple Progressive Engine with Enhanced CLI Tools, Specialized E-Commerce Templates, Material Design 3, Progressive Enhancement, Mobile Optimizations, Performance Analysis, and Enterprise-Grade Develo

475 lines (390 loc) 11.4 kB
// MusPE UI - Action Sheet Component (iOS-style bottom sheet) class ActionSheet { constructor(options = {}) { this.options = { title: '', message: '', actions: [], cancelButton: true, destructiveButton: null, closeOnBackdrop: true, closeOnAction: true, ...options }; this.element = null; this.backdrop = null; this.isOpen = false; this.callbacks = { onOpen: null, onClose: null, onAction: null, ...options.callbacks }; } render() { // Create backdrop this.backdrop = document.createElement('div'); this.backdrop.className = 'muspe-actionsheet-backdrop'; // Create action sheet this.element = document.createElement('div'); this.element.className = 'muspe-actionsheet'; this.element.innerHTML = ` <div class="muspe-actionsheet__content"> ${this.renderHeader()} ${this.renderActions()} ${this.renderCancel()} </div> `; this.attachEvents(); return this.element; } renderHeader() { if (!this.options.title && !this.options.message) return ''; return ` <div class="muspe-actionsheet__header"> ${this.options.title ? `<h3 class="muspe-actionsheet__title">${this.options.title}</h3>` : ''} ${this.options.message ? `<p class="muspe-actionsheet__message">${this.options.message}</p>` : ''} </div> `; } renderActions() { if (this.options.actions.length === 0) return ''; const actions = this.options.actions.map((action, index) => { const classes = ['muspe-actionsheet__action']; if (action.destructive) { classes.push('muspe-actionsheet__action--destructive'); } if (action.disabled) { classes.push('muspe-actionsheet__action--disabled'); } return ` <button class="${classes.join(' ')}" data-action="${index}" ${action.disabled ? 'disabled' : ''}> ${action.icon ? `<span class="muspe-actionsheet__icon">${action.icon}</span>` : ''} <span class="muspe-actionsheet__text">${action.text}</span> </button> `; }).join(''); return `<div class="muspe-actionsheet__actions">${actions}</div>`; } renderCancel() { if (!this.options.cancelButton) return ''; const cancelText = typeof this.options.cancelButton === 'string' ? this.options.cancelButton : 'Cancel'; return ` <div class="muspe-actionsheet__cancel"> <button class="muspe-actionsheet__cancel-btn" data-action="cancel"> ${cancelText} </button> </div> `; } attachEvents() { if (!this.element) return; // Action buttons const actionButtons = this.element.querySelectorAll('[data-action]'); actionButtons.forEach(button => { button.addEventListener('click', (e) => { const actionIndex = e.target.closest('[data-action]').dataset.action; if (actionIndex === 'cancel') { this.close(); return; } const action = this.options.actions[parseInt(actionIndex)]; if (action && !action.disabled) { if (action.callback) { action.callback(); } if (this.callbacks.onAction) { this.callbacks.onAction(action, parseInt(actionIndex)); } if (this.options.closeOnAction !== false) { this.close(); } } }); }); // Backdrop click if (this.backdrop && this.options.closeOnBackdrop) { this.backdrop.addEventListener('click', (e) => { if (e.target === this.backdrop) { this.close(); } }); } // Swipe down to close this.attachSwipeGesture(); } attachSwipeGesture() { let startY = 0; let currentY = 0; let isDragging = false; const content = this.element.querySelector('.muspe-actionsheet__content'); if (!content) return; const handleTouchStart = (e) => { startY = e.touches[0].clientY; isDragging = true; content.style.transition = 'none'; }; const handleTouchMove = (e) => { if (!isDragging) return; currentY = e.touches[0].clientY; const deltaY = currentY - startY; if (deltaY > 0) { content.style.transform = `translateY(${deltaY}px)`; } }; const handleTouchEnd = () => { if (!isDragging) return; isDragging = false; content.style.transition = ''; const deltaY = currentY - startY; if (deltaY > 100) { this.close(); } else { content.style.transform = 'translateY(0)'; } }; content.addEventListener('touchstart', handleTouchStart, { passive: true }); content.addEventListener('touchmove', handleTouchMove, { passive: true }); content.addEventListener('touchend', handleTouchEnd, { passive: true }); } open() { if (this.isOpen) return; // Add to DOM document.body.appendChild(this.backdrop); document.body.appendChild(this.element); // Prevent body scroll document.body.style.overflow = 'hidden'; // Force reflow this.element.offsetHeight; // Add open classes this.backdrop.classList.add('muspe-actionsheet-backdrop--open'); this.element.classList.add('muspe-actionsheet--open'); this.isOpen = true; if (this.callbacks.onOpen) { this.callbacks.onOpen(); } } close() { if (!this.isOpen) return; // Remove open classes this.backdrop.classList.remove('muspe-actionsheet-backdrop--open'); this.element.classList.remove('muspe-actionsheet--open'); // Wait for animation setTimeout(() => { // Remove from DOM if (this.backdrop && this.backdrop.parentNode) { this.backdrop.parentNode.removeChild(this.backdrop); } if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); } // Restore body scroll document.body.style.overflow = ''; this.isOpen = false; if (this.callbacks.onClose) { this.callbacks.onClose(); } }, 300); } destroy() { if (this.isOpen) { this.close(); } this.element = null; this.backdrop = null; } } // CSS Styles for Action Sheet const actionSheetStyles = ` .muspe-actionsheet-backdrop { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.4); z-index: var(--muspe-z-modal); opacity: 0; transition: opacity var(--muspe-transition-normal); } .muspe-actionsheet-backdrop--open { opacity: 1; } .muspe-actionsheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: calc(var(--muspe-z-modal) + 1); transform: translateY(100%); transition: transform var(--muspe-transition-normal); } .muspe-actionsheet--open { transform: translateY(0); } .muspe-actionsheet__content { background: var(--muspe-white); border-radius: var(--muspe-radius-xl) var(--muspe-radius-xl) 0 0; padding: var(--muspe-space-4) var(--muspe-space-4) env(safe-area-inset-bottom); position: relative; } .muspe-actionsheet__content::before { content: ''; position: absolute; top: var(--muspe-space-2); left: 50%; transform: translateX(-50%); width: 36px; height: 4px; background: var(--muspe-gray-300); border-radius: 2px; } .muspe-actionsheet__header { text-align: center; padding: var(--muspe-space-4) 0; border-bottom: 1px solid var(--muspe-gray-200); margin-bottom: var(--muspe-space-4); } .muspe-actionsheet__title { font-size: var(--muspe-font-size-lg); font-weight: 600; color: var(--muspe-gray-900); margin: 0 0 var(--muspe-space-2) 0; } .muspe-actionsheet__message { font-size: var(--muspe-font-size-sm); color: var(--muspe-gray-600); margin: 0; line-height: 1.5; } .muspe-actionsheet__actions { display: flex; flex-direction: column; gap: 1px; background: var(--muspe-gray-200); border-radius: var(--muspe-radius-lg); overflow: hidden; margin-bottom: var(--muspe-space-3); } .muspe-actionsheet__action { display: flex; align-items: center; justify-content: center; gap: var(--muspe-space-3); background: var(--muspe-white); color: var(--muspe-primary); font-size: var(--muspe-font-size-lg); font-weight: 500; padding: var(--muspe-space-4); border: none; cursor: pointer; transition: background-color var(--muspe-transition-fast); min-height: 56px; width: 100%; } .muspe-actionsheet__action:active { background: var(--muspe-gray-100); } .muspe-actionsheet__action--destructive { color: var(--muspe-error); } .muspe-actionsheet__action--disabled { color: var(--muspe-gray-400); cursor: not-allowed; } .muspe-actionsheet__action--disabled:active { background: var(--muspe-white); } .muspe-actionsheet__icon { font-size: 1.2em; display: flex; align-items: center; } .muspe-actionsheet__text { flex: 1; text-align: left; } .muspe-actionsheet__cancel { background: var(--muspe-gray-200); border-radius: var(--muspe-radius-lg); overflow: hidden; } .muspe-actionsheet__cancel-btn { display: flex; align-items: center; justify-content: center; background: var(--muspe-white); color: var(--muspe-gray-700); font-size: var(--muspe-font-size-lg); font-weight: 600; padding: var(--muspe-space-4); border: none; cursor: pointer; transition: background-color var(--muspe-transition-fast); min-height: 56px; width: 100%; } .muspe-actionsheet__cancel-btn:active { background: var(--muspe-gray-100); } /* Dark mode */ @media (prefers-color-scheme: dark) { .muspe-actionsheet__content { background: var(--muspe-gray-800); } .muspe-actionsheet__content::before { background: var(--muspe-gray-600); } .muspe-actionsheet__header { border-bottom-color: var(--muspe-gray-700); } .muspe-actionsheet__title { color: var(--muspe-gray-100); } .muspe-actionsheet__message { color: var(--muspe-gray-400); } .muspe-actionsheet__actions { background: var(--muspe-gray-700); } .muspe-actionsheet__action { background: var(--muspe-gray-800); } .muspe-actionsheet__action:active { background: var(--muspe-gray-700); } .muspe-actionsheet__cancel { background: var(--muspe-gray-700); } .muspe-actionsheet__cancel-btn { background: var(--muspe-gray-800); color: var(--muspe-gray-300); } .muspe-actionsheet__cancel-btn:active { background: var(--muspe-gray-700); } } /* Safe area support */ @supports (padding: max(0px)) { .muspe-actionsheet__content { padding-bottom: max(var(--muspe-space-4), env(safe-area-inset-bottom)); padding-left: max(var(--muspe-space-4), env(safe-area-inset-left)); padding-right: max(var(--muspe-space-4), env(safe-area-inset-right)); } } /* Reduced motion */ @media (prefers-reduced-motion: reduce) { .muspe-actionsheet-backdrop, .muspe-actionsheet { transition: none; } } `; // Auto-inject styles if (typeof document !== 'undefined') { const styleSheet = document.createElement('style'); styleSheet.textContent = actionSheetStyles; document.head.appendChild(styleSheet); } export default ActionSheet;