renderDocsPage function

String renderDocsPage(
  1. List<Library> libraries, {
  2. DocPageOptions options = const DocPageOptions(),
})

Renders the shared, complete LuaLike documentation HTML page.

Packages that embed LuaLike or add libraries should call this instead of carrying their own HTML shell. The library list supplies the content; the UI behavior, layout, and JSON-friendly class names stay centralized here.

Implementation

String renderDocsPage(
  List<Library> libraries, {
  DocPageOptions options = const DocPageOptions(),
}) {
  final fragments = renderDocs(libraries);
  final homeLink = options.homeHref == null
      ? ''
      : '<a href="${_escapeAttribute(options.homeHref!)}">'
            '${_escape(options.homeLabel)}</a>';

  return '''<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${_escape(options.title)}</title>
  <style>
    :root {
      color-scheme: dark;
      --bg: #101214;
      --panel: #181b1f;
      --line: #2b3138;
      --text: #f4f1ea;
      --muted: #a5adb8;
      --soft: #717b88;
      --accent: #5fb3a1;
      --code: #e2c06d;
      --type: #f08d6c;
      --sidebar-width: 18rem;
      --font-ui: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
        sans-serif;
      --font-code: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    }
    * { box-sizing: border-box; }
    html { scroll-behavior: smooth; }
    body {
      margin: 0;
      min-height: 100vh;
      display: flex;
      background: var(--bg);
      color: var(--text);
      font-family: var(--font-ui);
      font-size: 15px;
      line-height: 1.5;
    }
    .sidebar-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: rgb(0 0 0 / 0.48);
      z-index: 8;
    }
    .sidebar-overlay.open { display: block; }
    .sidebar {
      position: sticky;
      top: 0;
      width: var(--sidebar-width);
      height: 100vh;
      overflow-y: auto;
      flex-shrink: 0;
      background: var(--panel);
      border-right: 1px solid var(--line);
      z-index: 9;
    }
    .brand {
      min-height: 4rem;
      padding: 1rem 1.1rem;
      display: flex;
      align-items: center;
      gap: 0.75rem;
      border-bottom: 1px solid var(--line);
    }
    .brand-mark {
      width: 1.6rem;
      height: 1.6rem;
      border-radius: 50%;
      background: linear-gradient(135deg, var(--accent), var(--code));
      box-shadow: inset 0 0 0 4px rgb(16 18 20 / 0.42);
      flex: 0 0 auto;
    }
    .brand-title {
      min-width: 0;
      flex: 1;
      font-weight: 700;
      overflow-wrap: anywhere;
    }
    .brand a {
      color: var(--muted);
      font-size: 0.78rem;
      text-decoration: none;
    }
    .brand a:hover { color: var(--accent); }
    .sidebar-links { padding: 0.8rem 0; }
    .section-group { margin: 0 0 0.4rem; }
    .section-toggle {
      width: 100%;
      border: 0;
      background: transparent;
      color: var(--soft);
      cursor: pointer;
      display: flex;
      align-items: center;
      gap: 0.45rem;
      font: inherit;
      font-weight: 700;
      text-align: left;
      user-select: none;
    }
    .sidebar .section-toggle {
      padding: 0.7rem 1.1rem 0.3rem;
      font-size: 0.72rem;
      letter-spacing: 0.06em;
      text-transform: uppercase;
    }
    .content .section-toggle {
      margin-top: 1.5rem;
      padding: 0.8rem 0;
      border-bottom: 1px solid var(--line);
      color: var(--text);
      font-size: 1.35rem;
    }
    .section-toggle:hover { color: var(--accent); }
    .toggle-icon {
      font-family: var(--font-code);
      font-size: 0.75em;
      transition: transform 0.16s ease;
    }
    .section-toggle.collapsed .toggle-icon {
      transform: rotate(-90deg);
    }
    .section-items {
      overflow: hidden;
      transition: max-height 0.2s ease;
    }
    .section-toggle.collapsed + .section-items {
      max-height: 0 !important;
    }
    .sidebar a.func {
      display: block;
      padding: 0.25rem 1.1rem 0.25rem 1.75rem;
      border-left: 2px solid transparent;
      color: var(--muted);
      font-family: var(--font-code);
      font-size: 0.8rem;
      overflow-wrap: anywhere;
      text-decoration: none;
    }
    .sidebar a.func:hover {
      color: var(--text);
      background: rgb(255 255 255 / 0.04);
      border-left-color: var(--accent);
    }
    .menu-toggle {
      display: none;
      width: 2rem;
      height: 2rem;
      border: 1px solid var(--line);
      border-radius: 6px;
      background: transparent;
      color: var(--text);
      cursor: pointer;
      font: 700 1.2rem var(--font-code);
    }
    .content {
      width: min(100%, 64rem);
      padding: 2rem 3rem 4rem;
    }
    .library-desc {
      max-width: 54rem;
      color: var(--muted);
      margin: 0.9rem 0 1.1rem;
    }
    .func-entry {
      margin: 1rem 0 1.35rem;
      padding: 1rem;
      border: 1px solid var(--line);
      border-radius: 8px;
      background: rgb(255 255 255 / 0.025);
    }
    .func-signature {
      margin-bottom: 0.5rem;
      color: var(--code);
      font-family: var(--font-code);
      overflow-wrap: anywhere;
    }
    .func-summary { color: #d8dbe0; margin-bottom: 0.75rem; }
    .params-table {
      width: 100%;
      border-collapse: collapse;
      margin: 0.65rem 0;
      font-size: 0.86rem;
    }
    .params-table th {
      color: var(--soft);
      font-weight: 700;
      text-align: left;
      border-bottom: 1px solid var(--line);
    }
    .params-table th,
    .params-table td {
      padding: 0.4rem 0.5rem;
      vertical-align: top;
    }
    .params-table td { border-bottom: 1px solid rgb(255 255 255 / 0.06); }
    .p-name, .p-type { font-family: var(--font-code); }
    .p-name { color: var(--code); white-space: nowrap; }
    .p-type { color: var(--type); font-size: 0.8rem; }
    .p-desc, .returns { color: #d8dbe0; }
    .returns { margin: 0.6rem 0; font-size: 0.86rem; }
    .returns strong { color: var(--soft); }
    pre {
      overflow: auto;
      margin: 0.7rem 0 0;
      padding: 0.9rem;
      border-radius: 6px;
      background: #0b0d0f;
    }
    code {
      font-family: var(--font-code);
      font-size: 0.9rem;
    }
    @media (max-width: 820px) {
      body { display: block; }
      .sidebar {
        position: fixed;
        left: -100%;
        transition: left 0.24s ease;
      }
      .sidebar.open { left: 0; }
      .menu-toggle { display: inline-block; }
      .content { padding: 1.25rem; }
    }
    @media (max-width: 520px) {
      .content { padding: 1rem; }
      .params-table th:nth-child(3),
      .params-table td:nth-child(3) { display: none; }
    }
  </style>
</head>
<body>
  <div class="sidebar-overlay" id="sidebarOverlay"></div>
  <nav class="sidebar" id="sidebar">
    <div class="brand">
      <span class="brand-mark" aria-hidden="true"></span>
      <span class="brand-title">${_escape(options.brandName)}</span>
      <button class="menu-toggle" id="menuToggle" type="button"
        aria-label="Toggle navigation">=</button>
      $homeLink
    </div>
    <div class="sidebar-links" id="sidebarLinks">${fragments.sidebar}</div>
  </nav>
  <main class="content" id="content">${fragments.content}</main>
  <script>
    function setExpanded(button, expanded) {
      const group = button.closest('.section-group');
      const items = group.querySelector('.section-items');
      button.classList.toggle('collapsed', !expanded);
      button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
      items.style.maxHeight = expanded ? items.scrollHeight + 'px' : '0px';
    }
    document.querySelectorAll('.section-toggle').forEach((button) => {
      button.addEventListener('click', () => {
        setExpanded(button, button.classList.contains('collapsed'));
      });
    });
    document.querySelectorAll('.section-items').forEach((items) => {
      items.style.maxHeight = items.scrollHeight + 'px';
    });
    const sidebar = document.getElementById('sidebar');
    const overlay = document.getElementById('sidebarOverlay');
    const menuToggle = document.getElementById('menuToggle');
    function closeSidebar() {
      sidebar.classList.remove('open');
      overlay.classList.remove('open');
    }
    menuToggle.addEventListener('click', () => {
      sidebar.classList.toggle('open');
      overlay.classList.toggle('open');
    });
    overlay.addEventListener('click', closeSidebar);
    document.querySelectorAll('.sidebar a.func').forEach((link) => {
      link.addEventListener('click', closeSidebar);
    });
  </script>
</body>
</html>''';
}