import { FASTElement, Observable, html, css, repeat, when } from '@microsoft/fast-element'; export interface NavItem { label: string; href: string; } const hamburgerSvg = html` `; const closeSvg = html` `; /** * * Top navigation bar with site title, nav links, and dark/light theme toggle. */ export class StarlightHeader extends FASTElement { siteTitle = ''; nav: NavItem[] = []; currentPath = ''; navOpen = false; hasSidebar = false; _theme = 'light'; private _initialized = false; override connectedCallback(): void { super.connectedCallback(); if (!this._initialized && typeof localStorage !== 'undefined') { const stored = localStorage.getItem('sl-theme') ?? 'light'; this._theme = stored; if (typeof document !== 'undefined') { document.documentElement.setAttribute('data-theme', stored); } this._initialized = true; } } toggleTheme() { const next = this._theme === 'light' ? 'dark' : 'light'; this._theme = next; if (typeof localStorage !== 'undefined') { localStorage.setItem('sl-theme', next); } if (typeof document !== 'undefined') { document.documentElement.setAttribute('data-theme', next); } } toggleNav() { this.dispatchEvent( new CustomEvent('sl-nav-toggle', { bubbles: true, composed: true }), ); } } Observable.defineProperty(StarlightHeader.prototype, 'siteTitle'); Observable.defineProperty(StarlightHeader.prototype, 'nav'); Observable.defineProperty(StarlightHeader.prototype, 'currentPath'); Observable.defineProperty(StarlightHeader.prototype, 'navOpen'); Observable.defineProperty(StarlightHeader.prototype, 'hasSidebar'); Observable.defineProperty(StarlightHeader.prototype, '_theme'); const template = html`
${when(x => x.hasSidebar, html` `)} ${x => x.siteTitle}
`; const styles = css` :host { display: block; position: sticky; top: 0; z-index: 100; } header { height: var(--sl-nav-height, 3.5rem); background-color: var(--sl-color-bg-nav, #fff); border-bottom: 1px solid var(--sl-color-border, #e8e8e8); display: flex; align-items: center; padding: 0 var(--sl-content-pad-x, 1.5rem); gap: 1rem; } .menu-btn { display: none; appearance: none; background: none; border: 1px solid var(--sl-color-border, #e8e8e8); border-radius: var(--sl-border-radius, 0.375rem); width: 2.25rem; height: 2.25rem; align-items: center; justify-content: center; cursor: pointer; color: var(--sl-color-text, #23262f); transition: background-color 0.15s; flex-shrink: 0; padding: 0; } .menu-btn:hover { background-color: var(--sl-color-gray-2, #e8e8e8); } .menu-btn svg { width: 1.1rem; height: 1.1rem; } @media (max-width: 72rem) { .menu-btn { display: flex; } } .site-title { font-size: var(--sl-text-lg, 1.125rem); font-weight: 700; color: var(--sl-color-text, #23262f); text-decoration: none; white-space: nowrap; } .site-title:hover { opacity: 0.85; } nav { display: flex; align-items: center; gap: 0.25rem; flex: 1; } nav a { padding: 0.35rem 0.75rem; font-size: var(--sl-text-sm, 0.875rem); font-weight: 500; color: var(--sl-color-gray-5, #4b4b4b); text-decoration: none; border-radius: var(--sl-border-radius, 0.375rem); transition: color 0.15s, background-color 0.15s; } nav a:hover { color: var(--sl-color-text, #23262f); background-color: var(--sl-color-gray-2, #e8e8e8); } nav a[aria-current='page'] { color: var(--sl-color-accent, #7c3aed); background-color: var(--sl-color-accent-low, #ede9fe); } .theme-toggle { margin-left: auto; appearance: none; background: none; border: 1px solid var(--sl-color-border, #e8e8e8); border-radius: var(--sl-border-radius, 0.375rem); width: 2.25rem; height: 2.25rem; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 1rem; color: var(--sl-color-text, #23262f); transition: background-color 0.15s; flex-shrink: 0; } .theme-toggle:hover { background-color: var(--sl-color-gray-2, #e8e8e8); } `; StarlightHeader.define({ name: 'starlight-header', template, styles }); export default StarlightHeader;