@manufosela/util-lola

@manufosela/util-lola

Loading overlay (LoLa) web component with message, timeout, and CSS custom properties for theming.

Basic

Show overlay

Click to show the loading overlay. It stays until you dismiss it.

Code

+ Show HTML + JS
<util-lola id="lola" message="Loading..."></util-lola>

<script type="module">
  import "@manufosela/util-lola";
  document.getElementById("lola").active = true;
</script>
Timeout

Auto-close

The overlay closes automatically after the specified seconds.

Code

+ Show HTML
<util-lola
  message="Please wait..."
  timeout="3"
  active
></util-lola>
Custom

Custom message

Pass a message via the message attribute.

Events

Toggle event

Listens for lola-toggle events.

Demo code (CodePen-ready HTML, CSS, JS)
HTML (html)
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
    href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&display=swap"
    rel="stylesheet"
  />
<main>
    <div class="container demo-shell">

      <section class="demo-section">
        <div class="demo-card">
          <span class="badge">Basic</span>
          <h2>Show overlay</h2>
          <p class="note">Click to show the loading overlay. It stays until you dismiss it.</p>
          <div class="btn-row">
            <button class="btn" id="btn-basic-show">Show overlay</button>
            <button class="btn-outline" id="btn-basic-hide">Hide overlay</button>
          </div>
        </div>
        <div class="demo-card">
          <h3>Code</h3>
          <details class="code-details">
            <summary>+ Show HTML + JS</summary>
            <div class="code-block"><pre><code>&lt;util-lola id="lola" message="Loading..."&gt;&lt;/util-lola&gt;

&lt;script type="module"&gt;
  import "@manufosela/util-lola";
  document.getElementById("lola").active = true;
&lt;/script&gt;</code></pre></div>
          </details>
        </div>
      </section>

      <section class="demo-section">
        <div class="demo-card">
          <span class="badge">Timeout</span>
          <h2>Auto-close</h2>
          <p class="note">The overlay closes automatically after the specified seconds.</p>
          <div class="btn-row">
            <button class="btn" id="btn-timeout-2">2 second timeout</button>
            <button class="btn" id="btn-timeout-5">5 second timeout</button>
          </div>
        </div>
        <div class="demo-card">
          <h3>Code</h3>
          <details class="code-details">
            <summary>+ Show HTML</summary>
            <div class="code-block"><pre><code>&lt;util-lola
  message="Please wait..."
  timeout="3"
  active
&gt;&lt;/util-lola&gt;</code></pre></div>
          </details>
        </div>
      </section>

      <section class="demo-section">
        <div class="demo-card">
          <span class="badge">Custom</span>
          <h2>Custom message</h2>
          <p class="note">Pass a message via the <code>message</code> attribute.</p>
          <div class="btn-row">
            <button class="btn" id="btn-custom">Show with custom message</button>
          </div>
        </div>
        <div class="demo-card">
          <span class="badge">Events</span>
          <h2>Toggle event</h2>
          <p class="note">Listens for <code>lola-toggle</code> events.</p>
          <div id="event-log" class="code-block" style="min-height: 60px; font-family: monospace;"></div>
        </div>
      </section>

    </div>
  </main>

  <util-lola id="demo-lola"></util-lola>
CSS (css)
:root {
  --bg: #0c0f14;
  --bg-elevated: #141923;
  --bg-panel: #171d28;
  --border: #262f3f;
  --text: #f4f6fb;
  --text-muted: #a7b0c2;
  --text-dim: #7d879b;
  --accent: #ff8a3d;
  --accent-strong: #ff6a00;
  --accent-soft: rgba(255, 138, 61, 0.16);
  --shadow: 0 20px 50px rgba(5, 8, 14, 0.45);
  --radius-lg: 22px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --max-width: 1160px;
}

* { box-sizing: border-box; }
a { color: inherit; text-decoration: none; }

header {
  padding: 32px 24px 16px;
  border-bottom: 1px solid var(--line);
}

.container { max-width: 1100px; margin: 0 auto; }

.top-links {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.top-links a {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 12px;
  color: var(--muted);
  font-size: 0.85rem;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.top-links a:hover { color: var(--text); border-color: var(--accent-2); }

.hero h1 { margin: 0; font-size: clamp(28px, 4vw, 44px); letter-spacing: -0.02em; }
.hero p { margin: 8px 0 0; color: var(--muted); }

main { padding: 24px 24px 64px; }

.demo-shell { display: grid; gap: 20px; }

.demo-section {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  align-items: start;
}

.demo-card {
  background: color-mix(in srgb, var(--card) 85%, transparent);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 18px;
}

.demo-card h2 { margin: 0 0 10px; }
.badge {
  display: inline-flex;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.75rem;
}

.note { color: var(--muted); font-size: 0.9rem; margin: 0 0 12px; }

.btn-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; }

.btn {
  padding: 8px 18px;
  border-radius: 999px;
  border: none;
  background: linear-gradient(120deg, var(--accent), #ff6a00);
  color: #111;
  font-weight: 600;
  cursor: pointer;
  font-size: 0.9rem;
}

.btn-outline {
  padding: 8px 18px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-size: 0.9rem;
}

.code-details summary { cursor: pointer; color: var(--muted); margin-top: 10px; }
.code-details[open] summary { color: var(--text); }
.code-block {
  background: var(--code-bg);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px;
  overflow: auto;
  font-size: 0.85rem;
  color: var(--code-text);
}
.code-block pre { margin: 0; white-space: pre-wrap; }
  


:root {
  --bg: #0f1117;
  --bg-2: #151a26;
  --bg-spot-1: #1a2136;
  --bg-spot-2: #1d1b34;
  --card: #1c2233;
  --text: #f3f6ff;
  --muted: #b8c0d9;
  --line: #2b3247;
  --accent: #ffb000;
  --accent-2: #00d0ff;
  --surface: #0b0f1a;
  --code-bg: #0b0f1a;
  --code-text: #d6d9e6;
}

:root.light {
  --bg: #f5f5f7;
  --bg-2: #ffffff;
  --bg-spot-1: #f8e9d0;
  --bg-spot-2: #e8eef8;
  --card: #ffffff;
  --text: #1d1d1f;
  --muted: #6b7280;
  --line: #e5e7eb;
  --accent: #ffb000;
  --accent-2: #00a7d6;
  --surface: #f3f4f6;
  --code-bg: #111827;
  --code-text: #f9fafb;
}

:root.dark {
  --bg: #0f1117;
  --bg-2: #151a26;
  --bg-spot-1: #1a2136;
  --bg-spot-2: #1d1b34;
  --card: #1c2233;
  --text: #f3f6ff;
  --muted: #b8c0d9;
  --line: #2b3247;
  --accent: #ffb000;
  --accent-2: #00d0ff;
  --surface: #0b0f1a;
  --code-bg: #0b0f1a;
  --code-text: #d6d9e6;
}

h1 {
  color: var(--accent) !important;
}

.top-links a,
.topbar a {
  color: var(--muted) !important;
  border-color: var(--line) !important;
}

.top-links a:hover,
.topbar a:hover {
  color: var(--text) !important;
}

.card,
.section,
.demo-section,
.panel {
  background: var(--card) !important;
  border-color: var(--line) !important;
  color: var(--text) !important;
}

.code-block,
pre,
code,
.output,
.current-url,
.event-log,
.result-card,
.log {
  background: var(--code-bg) !important;
  color: var(--code-text) !important;
  border-color: var(--line) !important;
}

input,
select,
textarea {
  background: var(--surface) !important;
  color: var(--text) !important;
  border-color: var(--line) !important;
}

button {
  background: linear-gradient(120deg, var(--accent), #ff6a00) !important;
  color: #111 !important;
}

footer {
  color: var(--muted) !important;
}

footer a {
  color: var(--text) !important;
}
JS (js)
import "https://esm.sh/@manufosela/util-lola";

    const lola = document.getElementById("demo-lola");
    const log = document.getElementById("event-log");

    lola.addEventListener("lola-toggle", (e) => {
      const line = document.createElement("div");
      line.textContent = `lola-toggle → active: ${e.detail.active}`;
      log.prepend(line);
    });

    document.getElementById("btn-basic-show").addEventListener("click", () => {
      lola.message = "Loading...";
      lola.timeout = 0;
      lola.active = true;
    });

    document.getElementById("btn-basic-hide").addEventListener("click", () => {
      lola.active = false;
    });

    document.getElementById("btn-timeout-2").addEventListener("click", () => {
      lola.message = "Closing in 2 seconds...";
      lola.timeout = 2;
      lola.active = true;
    });

    document.getElementById("btn-timeout-5").addEventListener("click", () => {
      lola.message = "Closing in 5 seconds...";
      lola.timeout = 5;
      lola.active = true;
    });

    document.getElementById("btn-custom").addEventListener("click", () => {
      lola.message = "Fetching data from API...";
      lola.timeout = 3;
      lola.active = true;
    });
  

class DemoThemeToggle extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.handleClick = this.handleClick.bind(this);
  }

  connectedCallback() {
    const saved = localStorage.getItem('utils-demo-theme');
    document.documentElement.classList.remove('dark');
    document.documentElement.classList.toggle('light', saved === 'light');

    this.render();
  }

  render() {
    const isLight = document.documentElement.classList.contains('light');
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: inline-flex;
        }
        .toggle {
          display: inline-flex;
          align-items: center;
          border: 1px solid var(--line);
          border-radius: 999px;
          overflow: hidden;
          background: var(--surface);
        }
        button {
          border: none;
          background: transparent;
          color: var(--muted);
          padding: 6px 12px;
          font-size: 0.8rem;
          cursor: pointer;
          font-family: "Space Grotesk", "Trebuchet MS", Arial, sans-serif;
        }
        button.active {
          background: linear-gradient(120deg, var(--accent), #ff6a00);
          color: #111;
          font-weight: 600;
        }
      </style>
      <div class="toggle" role="group" aria-label="Theme toggle">
        <button class="${isLight ? 'active' : ''}" data-theme="light">Light</button>
        <button class="${isLight ? '' : 'active'}" data-theme="dark">Dark</button>
      </div>
    `;

    this.shadowRoot.querySelectorAll('button').forEach((btn) => {
      btn.addEventListener('click', this.handleClick);
    });
  }

  handleClick(event) {
    const theme = event.currentTarget.dataset.theme;
    const isLight = theme === 'light';
    document.documentElement.classList.toggle('light', isLight);
    document.documentElement.classList.remove('dark');
    localStorage.setItem('utils-demo-theme', isLight ? 'light' : 'dark');
    this.render();
  }
}

customElements.define('demo-theme-toggle', DemoThemeToggle);