Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 139 additions & 33 deletions packages/openscd/src/addons/menu-tabs/menu-tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import {
property,
query,
state,
TemplateResult,
css
} from 'lit-element';

import '@material/mwc-list';
import '@material/mwc-tab';
import '@material/mwc-tab-bar';
import '@material/mwc-menu';
import type { Menu } from '@material/mwc-menu';
import '@material/mwc-button';
import '@material/mwc-icon-button';
import '@material/mwc-icon';

import {
Plugin,
Expand All @@ -33,47 +34,152 @@ export class OscdMenuTabs extends LitElement {
};

@state() private activeTabIndex = 0;
@state() private visibleTabs: Plugin[] = [];
@state() private hiddenTabs: Plugin[] = [];
@query('.app-bar-container') private appBarContainer!: HTMLElement;
@query('mwc-menu') private overflowMenu!: Menu;

render(){
if(this.editors.length === 0){ return html``; }
firstUpdated() {
this.#calculateVisibleTabs();
window.addEventListener('resize', () => this.#calculateVisibleTabs());
}

disconnectedCallback() {
window.removeEventListener('resize', () => this.#calculateVisibleTabs());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this won't remove the original one.
I would recommend creating a function that we can reference.
something along these lines:

private calculateVisibleTabs = () => {
...
}

By using an arrow function we make sure that we have the correct context (this) without binding the function.
With that you can add and remove listeners:

window.addEventListener('resize', this.calculateVisibleTabs)
window.RemoveEventListener('resize', this.calculateVisibleTabs)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Thanks for the hint that removing the resize listener did not work. I fixed it now.
  2. The overflow menu is now placed at the end of the row - I like this solution better as well.
  3. I find styling Material Web Components quite painful, because there are many fixed/internal styles. I've adjusted the buttons to look more like mwc-tab-bar, but I couldn’t increase the icon size or vertical padding. Any ideas?
  4. My idea for improving the UI: If the active plugin is hidden in the overflow menu, move its button to the right-most visible position, and move the button currently there into the menu instead. This could be repeated if needed (e.g. if the active button is wider).

super.disconnectedCallback();
}

async #calculateVisibleTabs() {
await this.updateComplete;

const visibleTabs: Plugin[] = [];
const hiddenTabs: Plugin[] = [];
let totalWidth = 0;

const measurer = document.createElement('div');
Object.assign(measurer.style, {
position: 'absolute',
visibility: 'hidden',
whiteSpace: 'nowrap',
fontSize: '14px',
padding: '0 20 px',
});
document.body.appendChild(measurer);

try {
for (let i = 0; i < this.editors.length; i++) {
measurer.textContent = this.editors[i].name;
const approximateWidthOtherThanText = 112;
const buttonWidth =
measurer.offsetWidth + approximateWidthOtherThanText;

var availableWidth = this.appBarContainer.offsetWidth;
const isMenuButtonVisible = this.hiddenTabs.length > 0;
if (isMenuButtonVisible) {
availableWidth -= 48;
}
if (totalWidth + buttonWidth <= availableWidth) {
totalWidth += buttonWidth;
visibleTabs.push(this.editors[i]);
} else {
hiddenTabs.push(this.editors[i]);
}
}

this.visibleTabs = visibleTabs;
this.hiddenTabs = hiddenTabs;
} finally {
document.body.removeChild(measurer);
}
}

render() {
if (this.activeEditor === undefined && this.editors.length > 0) {
this.#activateTab(0);
}

return html`
<mwc-tab-bar
@MDCTabBar:activated=${this.handleActivatedEditorTab}
activeIndex=${this.activeTabIndex}
>
${ this.editors.map( EditorTab ) }
</mwc-tab-bar>
`
<div class="app-bar-container">
${this.hiddenTabs.length > 0
? html`
<mwc-icon-button
icon="more_vert"
@click=${() => this.overflowMenu.show()}
></mwc-icon-button>

<mwc-menu>
${this.hiddenTabs.map(
(editor, index) => html`
<mwc-list-item
graphic="icon"
@click=${() =>
this.#activateTab(this.visibleTabs.length + index)}
>
<span>${editor.name}</span>
<mwc-icon slot="graphic">${editor.icon}</mwc-icon>
</mwc-list-item>
`
)}
</mwc-menu>
`
: ''}
${this.visibleTabs.map(
(editor, index) => html`
<mwc-button
label=${editor.name}
icon=${editor.icon}
?active=${this.activeTabIndex === index}
@click=${() => this.#activateTab(index)}
></mwc-button>
`
)}
</div>
`;
}

#activateTab(index: number) {
this.activeTabIndex = index;
this._activeEditor = this.editors[index];
this.dispatchEvent(
new CustomEvent(TabActivatedEventKey, {
detail: { editor: this.editors[index] },
composed: true,
bubbles: true,
})
);
}

static styles = css`
mwc-tab {
background-color: var(--primary);
.app-bar-container {
display: flex;
align-items: center;
height: 48px;
background-color: var(--mdc-theme-primary, #6200ee);
position: relative;
}

mwc-button {
--mdc-theme-on-primary: #174b46;
--mdc-theme-primary: var(--mdc-theme-on-primary);
--mdc-shape-small: 0px;
white-space: nowrap;
margin: 0 10px;
}
`

private handleActivatedEditorTab(e: CustomEvent): void {
const tabIndex = e.detail.index;
const editor = this.editors[tabIndex];
this.activeTabIndex = tabIndex;
this.dispatchActivateEditor(editor);
}
mwc-button[active] {
background: #42a99f;
--mdc-theme-on-primary: white;
}

private dispatchActivateEditor( editor: Plugin ){
const newEvent = new CustomEvent(TabActivatedEventKey, {
detail: { editor },
composed: true,
bubbles: true
})
this.dispatchEvent(newEvent)
}
}
mwc-icon-button {
color: #174b46;
}

function EditorTab({ name, icon }: Plugin): TemplateResult {
return html`
<mwc-tab label=${name} icon=${icon || 'edit'}> </mwc-tab>
mwc-menu {
position: absolute;
left: 0;
top: 100%;
}
`;
}

Expand Down
Loading