sober
Version:
Sober is a library of UI components designed with reference to Material You.
211 lines (210 loc) • 6.93 kB
JavaScript
import { useElement, supports } from './core/element.js';
import { convertCSSDuration } from './core/utils/CSSUtils.js';
const name = 's-drawer';
const style = /*css*/ `
:host{
display: flex;
height: 100%;
overflow: hidden;
position: relative;
container-name: s-drawer;
container-type: inline-size;
}
.start,
.end{
flex-shrink: 0;
height: 100%;
display: none;
overflow: hidden;
}
.start{
order: -1;
}
.scrim{
position: absolute;
inset: 0;
width: 100%;
height: 100%;
opacity: 0;
backdrop-filter: saturate(180%) blur(2px);
pointer-events: none;
}
.scrim::before{
content: '';
display: block;
width: 100%;
height: 100%;
opacity: .75;
background: var(--s-color-scrim, ${"#000000" /* Theme.colorScrim */});
}
.view{
display: flex;
flex-direction: column;
flex-grow: 1;
min-width: 0;
height: 100%;
position: relative;
}
::slotted(:is([slot=start], [slot=end])){
width: 280px;
border-width: 1px;
height: 100%;
box-sizing: border-box;
pointer-events: auto;
position: relative;
background: var(--s-color-surface-container-low, ${"#F2F4F5" /* Theme.colorSurfaceContainerLow */});
border-color: var(--s-color-surface-variant, ${"#DCE4E8" /* Theme.colorSurfaceVariant */});
}
::slotted([slot=start]){
border-right-style: solid;
}
::slotted([slot=end]){
border-left-style: solid;
}
::slotted(s-scroll-view:not([slot])){
flex-grow: 1;
}
.start.show,
.end.show{
display: block;
}
.scrim.s-laptop{
display: block;
z-index: 3;
}
.scrim.s-laptop.show-laptop{
opacity: 1;
pointer-events: auto;
}
.start.s-laptop,
.end.s-laptop{
position: absolute;
z-index: 3;
max-width: 75%;
display: none;
}
.end.s-laptop{
left: auto;
right: 0;
}
.start.s-laptop.show,
.end.s-laptop.show{
display: none;
}
.start.s-laptop.show-laptop,
.end.s-laptop.show-laptop{
display: block;
}
.s-laptop ::slotted(:is([slot=start], [slot=end])){
border-left-style: none;
border-right-style: none;
box-shadow: var(--s-elevation-level-3, ${"0 5px 5px -3px rgba(0, 0, 0, .2), 0 8px 10px 1px rgba(0, 0, 0, .14), 0 3px 14px 2px rgba(0, 0, 0, .12)" /* Theme.elevationLevel3 */});
}
s-drawer (max-width: ${1024 /* mediaQueries.laptop */}px){
.scrim{
display: block;
z-index: 3;
}
.scrim.show-laptop{
opacity: 1;
pointer-events: auto;
}
.start,
.end{
position: absolute;
z-index: 3;
max-width: 75%;
display: none;
}
.end{
left: auto;
right: 0;
}
.start.show,
.end.show{
display: none;
}
.start.show-laptop,
.end.show-laptop{
display: block;
}
::slotted(:is([slot=start], [slot=end])){
border-left-style: none;
border-right-style: none;
box-shadow: var(--s-elevation-level-3, ${"0 5px 5px -3px rgba(0, 0, 0, .2), 0 8px 10px 1px rgba(0, 0, 0, .14), 0 3px 14px 2px rgba(0, 0, 0, .12)" /* Theme.elevationLevel3 */});
}
}
`;
const template = /*html*/ `
<slot class="view" part="view"></slot>
<div class="scrim" part="scrim show"></div>
<slot name="start" class="start show" part="start"></slot>
<slot name="end" class="end show" part="end"></slot>
`;
export class Drawer extends useElement({
style, template,
setup(shadowRoot) {
const scrim = shadowRoot.querySelector('.scrim');
const slots = {
start: shadowRoot.querySelector('.start'),
end: shadowRoot.querySelector('.end')
};
const containerStyle = getComputedStyle(this);
const getAnimateOptions = () => {
const easing = containerStyle.getPropertyValue('--s-motion-easing-standard') || "cubic-bezier(0.2, 0, 0, 1.0)" /* Theme.motionEasingStandard */;
const duration = containerStyle.getPropertyValue('--s-motion-duration-medium4') || "400ms" /* Theme.motionDurationMedium4 */;
return { easing: easing, duration: convertCSSDuration(duration) };
};
const getElement = (name = 'start') => slots[name];
const getClassName = (folded) => folded ?? this.offsetWidth <= 1024 /* mediaQueries.laptop */ ? 'show-laptop' : 'show';
const getOffset = (name = 'start') => ({ start: -1, end: 1 }[name]);
const show = (slot, folded) => {
const element = getElement(slot);
const className = getClassName(folded);
if (element.classList.contains(className))
return;
const offset = getOffset(slot);
const animateOptions = getAnimateOptions();
element.classList.add(className);
scrim.classList.add(className);
const keyframes = this.offsetWidth <= 1024 /* mediaQueries.laptop */ ? { transform: [`translateX(${element.offsetWidth * offset}px)`, `translateX(0)`] } : { width: ['0', element.offsetWidth + 'px'] };
this.offsetWidth <= 1024 /* mediaQueries.laptop */ && scrim.animate({ opacity: [0, 1] }, animateOptions);
element.animate(keyframes, animateOptions);
};
const close = (slot, folded) => {
const element = getElement(slot);
const className = getClassName(folded);
if (!element.classList.contains(className))
return;
const offset = getOffset(slot);
const animateOptions = getAnimateOptions();
const keyframes = { ...this.offsetWidth <= 1024 /* mediaQueries.laptop */ ? { transform: [`translateX(0)`, `translateX(${element.offsetWidth * offset}px)`] } : { width: [element.offsetWidth + 'px', '0px'] } };
element.style.display = 'block';
element.animate(keyframes, animateOptions).finished.then(() => element.style.removeProperty('display'));
this.offsetWidth <= 1024 /* mediaQueries.laptop */ && scrim.animate({ opacity: [1, 0] }, animateOptions);
element.classList.remove(className);
scrim.classList.remove(className);
};
const toggle = (slot, folded) => {
const element = getElement(slot);
const className = getClassName(folded);
element.classList.contains(className) ? close(slot, folded) : show(slot, folded);
};
scrim.addEventListener('click', () => {
close('start', true);
close('end', true);
});
if (!supports.CSSContainer) {
new ResizeObserver(() => {
scrim.classList.toggle('s-laptop', this.offsetWidth <= 1024 /* mediaQueries.laptop */);
slots.start.classList.toggle('s-laptop', this.offsetWidth <= 1024 /* mediaQueries.laptop */);
slots.end.classList.toggle('s-laptop', this.offsetWidth <= 1024 /* mediaQueries.laptop */);
}).observe(this);
}
return {
expose: { show, close, toggle }
};
}
}) {
}
Drawer.define(name);