Window Management UI

by Nicholas Berlette

HTML

<window-component id="window1">
    <tab-view-component slot="views">
        <tab-component slot="tabs">Explorer</tab-component>
        <tab-component slot="tabs">Search</tab-component>
        <tab-component slot="tabs">Extensions</tab-component>
        <div slot="content" name="panel-0">
            <tab-view-component>
                <tab-component slot="tabs">File 1</tab-component>
                <tab-component slot="tabs">File 2</tab-component>
                <div slot="content" name="panel-0">Content for File 1</div>
                <div slot="content" name="panel-1">Content for File 2</div>
            </tab-view-component>
        </div>
        <div slot="content" name="panel-1">
            <tab-view-component>
                <tab-component slot="tabs">Match 1</tab-component>
                <tab-component slot="tabs">Match 2</tab-component>
                <div slot="content" name="panel-0">Search Result 1</div>
                <div slot="content" name="panel-1">Search Result 2</div>
            </tab-view-component>
        </div>
        <div slot="content" name="panel-2">
            <tab-view-component>
                <tab-component slot="tabs">Installed</tab-component>
                <tab-component slot="tabs">Disabled</tab-component>
                <tab-component slot="tabs">Built-in</tab-component>
                <div slot="content" name="panel-0">Installed Extensions</div>
                <div slot="content" name="panel-1">Disabled Extensions</div>
                <div slot="content" name="panel-2">Built-in Extensions</div>
            </tab-view-component>
        </div>
    </tab-view-component>
</window-component>

<window-component id="window2">
    <tab-view-component slot="views">
        <tab-component slot="tabs">Home</tab-component>
        <tab-component slot="tabs">Settings</tab-component>
        <div slot="content" name="panel-0">Home Content</div>
        <div slot="content" name="panel-1">Settings Content</div>
   ...

Tailwind CSS

body {
    margin: 0;
    height: 100vh;
    width: 100vw;
    position: relative;
    background: #111827;
    overflow: hidden;
    --window-border-color: #000;
    --window-focused-shadow: 0 4px 25px -8px rgba(0,0,0,0.2);
    --window-blurred-filter: blur(2px);
    font-family: var(--font-sans);
    color: #ddd;
}

JavaScript

// BaseComponent.js
class BaseComponent extends HTMLElement {
    static tagName = 'base-component';
    static styles = `
        :host {
            --background-color: white;
            --border-color: #ccc;
            --focused-shadow: 0 4px 20px rgba(0,0,0,0.3);
            --blurred-filter: blur(2px);
            --transition-duration: 0.3s;
            --transition-ease: ease;
            display: block;
        }
    `;
    static template = `<slot></slot>`;

    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `
            <style>${this.constructor.styles}</style>
            ${this.constructor.template}
        `;
    }

    /**
     * Shorthand for querySelector within shadow DOM
     * @param {string} selector 
     * @returns {Element}
     */
    $(selector) {
        return this.shadowRoot.querySelector(selector);
    }

    /**
     * Shorthand for querySelectorAll within shadow DOM
     * @param {string} selector 
     * @returns {NodeList}
     */
    $$(selector) {
        return this.shadowRoot.querySelectorAll(selector);
    }

    /**
     * Set multiple attributes at once
     * @param {Object} attributes 
     * @returns {BaseComponent} for chaining
     */
    setAttributes(attributes) {
        for (let key in attributes) {
            this.setAttribute(key, attributes[key]);
        }
        return this;
    }

    /**
     * Emit a custom event with optional detail
     * @param {string} eventName 
     * @param {Object} detail 
     * @returns {BaseComponent} for chaining
     */
    emit(eventName, detail = {}) {
        this.dispatchEvent(new CustomEvent(eventName, { detail, bubbles: true, composed: true }));
        return this;
    }

    /**
     * Add an event listener
     * @param {string} eventName 
     * @param {Function} callback 
     * @returns {BaseComponent} for chaining
     */
    on(eventName, callback) {
        this.addEventListener(eventName,...