diff --git a/src/RapidElement.ts b/src/RapidElement.ts index b473c2ae..7a3453c7 100644 --- a/src/RapidElement.ts +++ b/src/RapidElement.ts @@ -36,6 +36,7 @@ export interface EventHandler { event: string; method: EventListener; isDocument?: boolean; + isWindow?: boolean; } export class RapidElement extends LitElement { @@ -54,6 +55,8 @@ export class RapidElement extends LitElement { for (const handler of this.getEventHandlers()) { if (handler.isDocument) { document.addEventListener(handler.event, handler.method.bind(this)); + } else if (handler.isWindow) { + window.addEventListener(handler.event, handler.method.bind(this)); } else { this.addEventListener(handler.event, handler.method.bind(this)); } @@ -64,6 +67,8 @@ export class RapidElement extends LitElement { for (const handler of this.getEventHandlers()) { if (handler.isDocument) { document.removeEventListener(handler.event, handler.method); + } else if (handler.isWindow) { + window.removeEventListener(handler.event, handler.method); } else { this.removeEventListener(handler.event, handler.method); } @@ -173,4 +178,12 @@ export class RapidElement extends LitElement { event.preventDefault(); } } + + public isMobile() { + const win = window as any; + if (win.isMobile) { + return win.isMobile(); + } + return false; + } } diff --git a/src/ResizeElement.ts b/src/ResizeElement.ts new file mode 100644 index 00000000..f946c92a --- /dev/null +++ b/src/ResizeElement.ts @@ -0,0 +1,18 @@ +import { RapidElement } from './RapidElement'; +import { throttle } from './utils'; + +export class ResizeElement extends RapidElement { + public handleResize() { + this.requestUpdate(); + } + + public getEventHandlers() { + return [ + { + event: 'resize', + method: throttle(this.handleResize, 50), + isWindow: true, + }, + ]; + } +} diff --git a/src/dialog/Dialog.ts b/src/dialog/Dialog.ts index e72e5524..251d827c 100644 --- a/src/dialog/Dialog.ts +++ b/src/dialog/Dialog.ts @@ -5,6 +5,7 @@ import { RapidElement } from '../RapidElement'; import { CustomEventType } from '../interfaces'; import { styleMap } from 'lit-html/directives/style-map.js'; import { getClasses } from '../utils'; +import { ResizeElement } from '../ResizeElement'; export enum ButtonType { PRIMARY = 'primary', @@ -19,7 +20,7 @@ export class DialogButton { closes?: boolean; } -export class Dialog extends RapidElement { +export class Dialog extends ResizeElement { static get widths(): { [size: string]: string } { return { small: '400px', @@ -86,12 +87,14 @@ export class Dialog extends RapidElement { transform: scale(0.9) translatey(2em); background: white; margin: auto; + display: flex; + flex-direction: column; } .dialog-body { background: #fff; - max-height: 75vh; overflow-y: auto; + flex-grow: 1; } .dialog-mask.dialog-open { @@ -397,6 +400,12 @@ export class Dialog extends RapidElement { dialogStyle['width'] = Dialog.widths[this.size]; } + if (this.isMobile()) { + dialogStyle['width'] = '100%'; + dialogStyle['height'] = '100%'; + delete dialogStyle['maxWidth']; + } + const header = this.header ? html`
@@ -428,14 +437,18 @@ export class Dialog extends RapidElement {
-
+
${header} -
+
${this.body ? this.body : html``}
@@ -460,7 +473,9 @@ export class Dialog extends RapidElement { )}
-
+
diff --git a/src/dialog/Modax.ts b/src/dialog/Modax.ts index 5766bf95..8c005c2c 100644 --- a/src/dialog/Modax.ts +++ b/src/dialog/Modax.ts @@ -435,7 +435,10 @@ export class Modax extends RapidElement { @temba-button-clicked=${this.handleDialogClick.bind(this)} @temba-dialog-hidden=${this.handleDialogHidden.bind(this)} > -
+
${this.body}
diff --git a/src/dropdown/Dropdown.ts b/src/dropdown/Dropdown.ts index b26bdd70..1fea61f9 100644 --- a/src/dropdown/Dropdown.ts +++ b/src/dropdown/Dropdown.ts @@ -161,12 +161,6 @@ export class Dropdown extends RapidElement { } if (changedProperties.has('open')) { - if (this.open) { - this.classList.add('open'); - } else { - this.classList.remove('open'); - } - this.ensureOnScreen(); } } @@ -179,10 +173,29 @@ export class Dropdown extends RapidElement { if (dropdown) { // dropdown will go off the screen, let's push it up + const toggle = this.querySelector('div[slot="toggle"]'); if (dropdown.getBoundingClientRect().bottom > window.innerHeight) { - const toggle = this.querySelector('div[slot="toggle"]'); - dropdown.style.bottom = - this.offsetY + toggle.clientHeight + 10 + 'px'; + if (this.bottom) { + dropdown.style.top = toggle.clientHeight + 'px'; + } else { + dropdown.style.top = ''; + dropdown.style.bottom = toggle.clientHeight + 'px'; + } + } else if (dropdown.getBoundingClientRect().top < 0) { + if (this.bottom) { + dropdown.style.top = toggle.clientHeight + 'px'; + } else { + dropdown.style.top = toggle.clientHeight + 'px'; + dropdown.style.bottom = ''; + } + } + + if (dropdown.getBoundingClientRect().right > window.innerWidth) { + dropdown.style.left = ''; + dropdown.style.right = 0 + 'px'; + } else if (dropdown.getBoundingClientRect().left < 0) { + dropdown.style.left = 0 + 'px'; + dropdown.style.right = ''; } } }, 100); diff --git a/src/list/ContentMenu.ts b/src/list/ContentMenu.ts index 6b3afbee..85d0f1d4 100644 --- a/src/list/ContentMenu.ts +++ b/src/list/ContentMenu.ts @@ -160,11 +160,11 @@ export class ContentMenu extends RapidElement { `; })} ${this.items && this.items.length > 0 - ? html`
diff --git a/src/list/TembaMenu.ts b/src/list/TembaMenu.ts index df25aa76..f951f703 100644 --- a/src/list/TembaMenu.ts +++ b/src/list/TembaMenu.ts @@ -1,12 +1,19 @@ -import { css, html, TemplateResult } from 'lit'; +import { css, html, PropertyValueMap, TemplateResult } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import { property } from 'lit/decorators.js'; import { CustomEventType } from '../interfaces'; import { RapidElement } from '../RapidElement'; -import { debounce, fetchResults, getClasses, renderAvatar } from '../utils'; +import { + debounce, + fetchResults, + getClasses, + renderAvatar, + throttle, +} from '../utils'; import { Icon } from '../vectoricon'; import { Dropdown } from '../dropdown/Dropdown'; import { NotificationList } from './NotificationList'; +import { ResizeElement } from '../ResizeElement'; export interface MenuItem { id?: string; vanity_id?: string; @@ -53,7 +60,7 @@ const findItem = ( return { item: null, index: -1 }; }; -export class TembaMenu extends RapidElement { +export class TembaMenu extends ResizeElement { static get styles() { return css` :host { @@ -340,6 +347,78 @@ export class TembaMenu extends RapidElement { overflow-y: scroll; } + .mobile.root { + height: 100vh; + } + + .mobile.root.fully-collapsed { + height: initial; + } + + .root.fully-collapsed.mobile .level.level-0 { + flex-direction: row; + } + + .root.fully-collapsed.mobile .level.level-0 > .item { + display: none; + } + + .root.fully-collapsed.mobile .level.level-0 .expand-icon { + max-height: 100%; + padding: 1em; + } + + .mobile.fully-collapsed.root { + flex-direction: column; + } + + .mobile.fully-collapsed.root .level-0 { + padding-top: 0px !important; + } + + .mobile.fully-collapsed .level-1 { + display: none; + } + + .mobile .level-1 { + flex-grow: 1; + } + + .mobile .level-1 .item { + max-width: inherit; + min-width: inherit; + } + + .mobile .level-1 .section { + max-width: inherit; + min-width: inherit; + } + + .mobile.fully-collapsed .item { + } + + .mobile .expand-icon { + transition: none; + transform: rotate(-90deg); + align-self: center; + } + + .mobile.fully-collapsed .level-0 .empty { + flex-grow: 1; + } + + .mobile.fully-collapsed .top-spacer { + flex-grow: 0; + } + + .mobile.fully-collapsed #dd-workspace { + display: none; + } + + .mobile.fully-collapsed .expand-icon { + transform: none; + } + .level-2 { background: #fbfbfb; overflow-y: auto; @@ -733,6 +812,7 @@ export class TembaMenu extends RapidElement { parent, }); } + return; } @@ -760,10 +840,14 @@ export class TembaMenu extends RapidElement { this.handleItemClicked(null, parent); } - if (this.collapsed) { + if (this.collapsed && !this.isMobile()) { this.collapsed = false; } + if (this.isMobile()) { + this.collapsed = true; + } + if (menuItem.trigger) { this.fireCustomEvent(CustomEventType.ButtonClicked, { item: menuItem, @@ -1031,10 +1115,7 @@ export class TembaMenu extends RapidElement { ${menuItem.level === 0 ? menuItem.avatar ? icon - : html`${icon}` : html`${icon}${collapsedIcon}`} @@ -1079,6 +1160,7 @@ export class TembaMenu extends RapidElement { arrowSize="0" drop_align="left" mask + id="dd-${menuItem.id}" >
${item}
@@ -1103,18 +1185,20 @@ export class TembaMenu extends RapidElement { let items = this.root.items || []; const levels = []; + const expandIcon = this.isMobile() ? Icon.menu : Icon.menu_collapse; + levels.push( html`
+
${items .filter(item => !item.bottom) @@ -1153,6 +1237,8 @@ export class TembaMenu extends RapidElement { items = null; } + const collapseIcon = this.isMobile() ? Icon.close : Icon.menu_collapse; + if (items && items.length > 0 && !selected.inline) { levels.push( html`
` : null} @@ -1203,6 +1289,7 @@ export class TembaMenu extends RapidElement { class="${getClasses({ root: true, 'fully-collapsed': this.collapsed, + mobile: this.isMobile(), })}" > ${levels} diff --git a/src/resizer/Resizer.ts b/src/resizer/Resizer.ts index e7912dc5..c6af952c 100644 --- a/src/resizer/Resizer.ts +++ b/src/resizer/Resizer.ts @@ -1,10 +1,10 @@ import { PropertyValueMap, css, html } from 'lit'; -import { RapidElement } from '../RapidElement'; import { getClasses } from '../utils'; import { property } from 'lit/decorators.js'; import { CustomEventType } from '../interfaces'; +import { ResizeElement } from '../ResizeElement'; -export class Resizer extends RapidElement { +export class Resizer extends ResizeElement { static styles = css` :host { display: block;