diff --git a/src/sidebar.core.ts b/src/sidebar.core.ts index 5a9a4bb..7076ea4 100644 --- a/src/sidebar.core.ts +++ b/src/sidebar.core.ts @@ -8,6 +8,18 @@ export const IS_MOVING = `${SIDEBARJS}--is-moving`; export const POSITIONS: SidebarPosition[] = [SidebarPosition.Left, SidebarPosition.Right]; export const EVENT_LISTENER_OPTIONS: AddEventListenerOptions = {passive: true}; +export const MediaQuery = { + pattern: new RegExp(/((\d*\.?\d+\s*)(px|em|rem))/), + DEFAULT_BREAKPOINT: '1025px', + getValidBreakpoint(value: SidebarConfig['responsive']) { + if (this.pattern.test(value as any)) { + return value as string; + } + console.error(`Provided invalid breakpoint value: ${value}, fallback to ${this.DEFAULT_BREAKPOINT}`); + return this.DEFAULT_BREAKPOINT; + }, +}; + export const enum SidebarPosition { Left = 'left', Right = 'right', @@ -51,7 +63,7 @@ export interface SidebarConfig { documentSwipeRange?: number; nativeSwipe?: boolean; nativeSwipeOpen?: boolean; - responsive?: boolean; + responsive?: boolean | string; mainContent?: HTMLElement; position?: SidebarPosition; backdropOpacity?: number; diff --git a/src/sidebar.element.ts b/src/sidebar.element.ts index 8889796..a36fecf 100644 --- a/src/sidebar.element.ts +++ b/src/sidebar.element.ts @@ -8,6 +8,7 @@ import { IS_VISIBLE, isStyleMapSupported, MapGestureEvent, + MediaQuery, POSITIONS, shouldDefineMainContent, shouldInvokeFunction, @@ -52,6 +53,7 @@ export class SidebarElement implements SidebarBase { private readonly _emitOnOpen?: CallableFunction; private readonly _emitOnClose?: CallableFunction; private readonly _emitOnChangeVisibility?: (changes: SidebarChangeEvent) => void; + private readonly destroyMediaQuery?: () => void; constructor(options: SidebarConfig = {}) { const config = {...DEFAULT_CONFIG, ...options}; @@ -83,7 +85,7 @@ export class SidebarElement implements SidebarBase { this.setSwipeGestures(true); if (this.responsive || this.mainContent) { - this.setResponsive(); + this.destroyMediaQuery = this.setResponsive(config.responsive); } this.setPosition(config.position!); @@ -123,6 +125,7 @@ export class SidebarElement implements SidebarBase { } this.component.removeChild(this.container); this.component.removeChild(this.backdrop); + this.destroyMediaQuery?.(); Object.keys(this).forEach((key) => ((this as any)[key] = null)); } @@ -343,7 +346,7 @@ export class SidebarElement implements SidebarBase { this.applyStyle(this.backdrop, 'opacity', opacity.toString()); } - private setResponsive(): void { + private setResponsive(breakpoint?: SidebarConfig['responsive']) { if (!this.responsive && this.mainContent) { throw new Error(`You provide a [${SIDEBARJS_CONTENT}] element without set {responsive: true}`); } @@ -351,6 +354,20 @@ export class SidebarElement implements SidebarBase { throw new Error(`You have set {responsive: true} without provide a [${SIDEBARJS_CONTENT}] element`); } this.addComponentClass('sidebarjs--responsive'); + return this.setMediaQuery(breakpoint); + } + + private setMediaQuery(breakpoint?: SidebarConfig['responsive']) { + const value = MediaQuery.getValidBreakpoint(breakpoint); + const mediaQuery = window.matchMedia(`(min-width: ${value})`); + const toggleMediaQueryClass = (e: MediaQueryList | MediaQueryListEvent) => this.toggleDesktopBreakpointClass(e.matches); + mediaQuery.addEventListener('change', toggleMediaQueryClass, EVENT_LISTENER_OPTIONS); + toggleMediaQueryClass(mediaQuery); + return () => mediaQuery.removeEventListener('change', toggleMediaQueryClass); + } + + private toggleDesktopBreakpointClass(isDesktop: boolean) { + document.body.classList.toggle('sidebarjs--desktop-breakpoint', isDesktop); } private applyStyle(el: HTMLElement, prop: CSSStyleProperties, val: CSSStyleValues, vendorify?: boolean): void { diff --git a/src/sidebarjs.scss b/src/sidebarjs.scss index aafaf61..29cfdb4 100644 --- a/src/sidebarjs.scss +++ b/src/sidebarjs.scss @@ -37,6 +37,7 @@ $width: 300px; [sidebarjs].sidebarjs--left { @extend %component--left; + [sidebarjs-container] { @extend %component--left; @include shadow('left'); @@ -45,6 +46,7 @@ $width: 300px; [sidebarjs].sidebarjs--right { @extend %component--right; + [sidebarjs-container] { @extend %component--right; @include shadow('right'); @@ -87,6 +89,7 @@ $width: 300px; &.sidebarjs--is-moving { transition: none; transform: translate(0, 0); + [sidebarjs-container], [sidebarjs-backdrop] { transition: none; } @@ -98,34 +101,41 @@ $width: 300px; width: 100%; min-height: 100%; transition: width $duration $timing; + &.sidebarjs-content--left { margin-left: auto; margin-right: 0; } + &.sidebarjs-content--right { margin-left: 0; margin-right: auto; } } -@media (min-width: 1025px) { +.sidebarjs--desktop-breakpoint { [sidebarjs].sidebarjs--responsive { @include component--is-visible(!important); width: $width; + &.sidebarjs--left { left: 0; right: auto; + [sidebarjs-container] { box-shadow: 1px 0 0 rgba(black, .1); } } + &.sidebarjs--right { right: 0; left: auto; + [sidebarjs-container] { box-shadow: -1px 0 0 rgba(black, .1); } } + [sidebarjs-container] { max-width: none; width: 100%; @@ -135,6 +145,7 @@ $width: 300px; [sidebarjs-content] { width: calc(100% - #{$width}); + &.sidebarjs-content--left.sidebarjs-content--right { width: calc(100% - #{$width*2}); margin: 0 auto;