UNPKG

mbio-ui

Version:

Web components library containing lightweight, ready-to-use and framework-agnostic User Interface elements.

201 lines (176 loc) 6.35 kB
class MbioBottomSheet extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.sheetHeight = 50; this.dragPosition = undefined; } connectedCallback() { this.render(); this.setupEventListeners(); this.updateVisibility(); } static get observedAttributes() { return ['visible', 'label']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'visible') { this.updateVisibility(); } } updateVisibility() { if (this.hasAttribute('visible')) { this.shadowRoot.querySelector('#bottom-sheet').classList.remove('hide'); } else { this.shadowRoot.querySelector('#bottom-sheet').classList.add('hide'); } if (this.hasAttribute('label')) { this.label = this.getAttribute('label'); this.shadowRoot.querySelector('#bottom-sheet-header').querySelector('.label').textContent = this.label; } } render() { this.shadowRoot.innerHTML = ` <style> :host { z-index:9; display:block; position:fixed; bottom:0px; left:0px; right:0px;} .fullscreen { top: 0px; } #bottom-sheet-header { width: 100%; height: 40px; display: flex; flex-direction: row; flex-wrap: wrap; align-content: center; justify-content: center; align-items: center; border-bottom:1px solid rgb(var(--neutral-1), .1); } #drag svg { top: -13px; position: relative; } .not-selectable > #bottom-sheet-header > #drag { opacity: 1; } #drag { background-color: var(--text-1); height: 4px; border-radius: 10px; opacity: 0.7; } #bottom-sheet{ display: block; position: fixed; z-index: 100; bottom: 0; left: 0; right: 0; height: ${this.sheetHeight}vh; background: rgb(var(--base-0), 1); box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1), 0 0px 40px rgba(0, 0, 0, 0.1); padding: 0px 1rem 1rem 1rem; border-radius: 1rem 1rem 0 0; transition: bottom 0.3s ease; } .bottom-sheet-content { top: 10px; position: relative; height: 100%; overflow-y: auto; display: flex; flex-direction: column; flex-wrap: wrap; align-content: flex-start; justify-content: flex-start; align-items: flex-start; gap: 10px;} #bottom-sheet.hide { bottom: -100%; transition: bottom 0.3s ease; } .label { position:absolute; left:10px;} </style> <div id="bottom-sheet" class="hide"> <div id="bottom-sheet-header"> <slot name="label"><p class="label"></p></slot> <div id="drag"><svg width="36" height="4" viewBox="0 0 36 4" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="36" height="4" rx="2" fill="grey" fill-opacity="0.22"></rect></svg></div> </div> <div class="bottom-sheet-content"> <slot></slot> </div> </div> `; } setupEventListeners() { const bottomSheet = this.shadowRoot.getElementById('bottom-sheet'); const draggableArea = this.shadowRoot.getElementById('bottom-sheet-header'); const dragElement = this.shadowRoot.getElementById('drag'); dragElement.addEventListener('click', () => { this.setIsSheetShownFalse(); setTimeout(() => { this.setSheetHeight(50); }, 250); }); const touchPosition = (event) => event.touches ? event.touches[0] : event; const onDragStart = (event) => { this.dragPosition = touchPosition(event).pageY; bottomSheet.classList.add('not-selectable'); draggableArea.style.cursor = document.body.style.cursor = 'grabbing'; }; const setSheetHeight = (value) => { this.sheetHeight = Math.max(0, Math.min(100, value)); bottomSheet.style.height = `${this.sheetHeight}vh`; if (this.sheetHeight === 100) { bottomSheet.classList.add('fullscreen'); } else { bottomSheet.classList.remove('fullscreen'); } }; this.setSheetHeight = setSheetHeight; const setIsSheetShownFalse = () => { bottomSheet.classList.add('hide'); this.removeAttribute('visible'); }; this.setIsSheetShownFalse = setIsSheetShownFalse; const onDragMove = (event) => { if (this.dragPosition === undefined) return; const y = touchPosition(event).pageY; const deltaY = this.dragPosition - y; const deltaHeight = deltaY / window.innerHeight * 120; this.setSheetHeight(this.sheetHeight + deltaHeight); this.dragPosition = y; }; const onDragEnd = () => { this.dragPosition = undefined; bottomSheet.classList.remove('not-selectable'); draggableArea.style.cursor = document.body.style.cursor = ''; if (this.sheetHeight < 20) { this.setIsSheetShownFalse(); setTimeout(() => { this.setSheetHeight(50); }, 250); } else if (this.sheetHeight > 90) { this.setSheetHeight(100); } else { this.setSheetHeight(this.sheetHeight); } }; draggableArea.addEventListener('mousedown', onDragStart); draggableArea.addEventListener('touchstart', onDragStart); bottomSheet.addEventListener('mousemove', onDragMove); bottomSheet.addEventListener('touchmove', onDragMove); bottomSheet.addEventListener('mouseup', onDragEnd); bottomSheet.addEventListener('touchend', onDragEnd); } } customElements.define('mbio-bottom-sheet', MbioBottomSheet);