mbio-ui
Version:
Web components library containing lightweight, ready-to-use and framework-agnostic User Interface elements.
201 lines (176 loc) • 6.35 kB
JavaScript
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);