Newer
Older
TelosDB / src / frontend / components / app-sidebar.js
@楽曲作りまくりおじさん 楽曲作りまくりおじさん 21 hours ago 4 KB chore: add frontend components, scripts, and journal files
class AppSidebar extends HTMLElement {
  connectedCallback() {
    if (this._initialized) return;
    this._initialized = true;
    this.className = 'sidebar';
    this.innerHTML = `
      <div class="sidebar-inner">
        <div class="sidebar-panel">
          <div class="sidebar-top">
            <nav class="sidebar-nav">
              <button class="nav-item" data-panel="search">検索</button>
              <button class="nav-item" data-panel="docs">文書管理</button>
              <button class="nav-item" data-panel="settings">設定</button>
            </nav>
          </div>
        </div>

        <!-- accordion moved outside the inner panel, remains in sidebar bottom area -->
        <div class="sidebar-bottom">
          <div class="accordion">
            <button class="accordion-header" aria-expanded="false">
              <span class="accordion-title">Info</span>
              <span class="accordion-toggle">▸</span>
            </button>
            <div class="accordion-content" hidden>
              <div class="version">v0.3.0</div>
              <div class="settings-list">
                <button class="nav-item small">Settings</button>
                <button class="nav-item small">Advanced</button>
              </div>
            </div>
          </div>
        </div>
      </div>
    `;

    // accordion behavior
    const header = this.querySelector('.accordion-header');
    const content = this.querySelector('.accordion-content');
    if (header && content) {
      header.addEventListener('click', () => {
        const expanded = header.getAttribute('aria-expanded') === 'true';
        header.setAttribute('aria-expanded', String(!expanded));
        if (expanded) {
          content.hidden = true;
          header.querySelector('.accordion-toggle').textContent = '▸';
        } else {
          content.hidden = false;
          header.querySelector('.accordion-toggle').textContent = '▾';
        }
      });
    }

    // Add a resizer handle to allow dragging to change sidebar width
    const resizer = document.createElement('div');
    resizer.className = 'sidebar-resizer';
    this.appendChild(resizer);

    let isResizing = false;
    let startX = 0;
    let startWidth = 0;

    const onPointerMove = (e) => {
      if (!isResizing) return;
      const dx = e.clientX - startX;
      const minW = 160;
      const maxW = 520;
      let newW = Math.max(minW, Math.min(maxW, startWidth + dx));
      document.documentElement.style.setProperty('--sidebar-width', `${newW}px`);
    };

    const onPointerUp = () => {
      if (!isResizing) return;
      isResizing = false;
      document.body.style.userSelect = '';
      window.removeEventListener('pointermove', onPointerMove);
      window.removeEventListener('pointerup', onPointerUp);
      try { localStorage.setItem('telos_sidebar_width', getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width')); } catch (e) {}
    };

    resizer.addEventListener('pointerdown', (e) => {
      isResizing = true;
      startX = e.clientX;
      const current = getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width').trim();
      startWidth = parseInt(current) || this.offsetWidth || 244;
      document.body.style.userSelect = 'none';
      window.addEventListener('pointermove', onPointerMove);
      window.addEventListener('pointerup', onPointerUp);
    });

    // Restore saved width from localStorage if present
    try {
      const saved = localStorage.getItem('telos_sidebar_width');
      if (saved) document.documentElement.style.setProperty('--sidebar-width', saved.trim());
    } catch (e) {}

    // Navigation: dispatch custom event when a nav item is clicked
    const navButtons = this.querySelectorAll('.sidebar-nav .nav-item');
    navButtons.forEach(btn => {
      btn.addEventListener('click', (ev) => {
        const panel = btn.getAttribute('data-panel') || btn.textContent.trim();
        // toggle active class
        navButtons.forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
        // dispatch event to document so main panel can listen
        const e = new CustomEvent('navigate-panel', { detail: panel, bubbles: true });
        document.dispatchEvent(e);
      });
    });
  }
}

customElements.define('app-sidebar', AppSidebar);