Files
bookhoard/templates/docs.templ
T
john-okeefe 8ea70ea7f8 feat(ui): migrate remaining pages to the new shell
Apply the sidebar shell and bold primitives across the rest of the app so
the whole experience shares one visual language:

- Browse/organize: series (keeps stacked-covers/series-card CSS),
  collections (keeps wood-paneling + carousel classes), browse_detail.
- Account/stats: profile + form + modal, progress, analytics (stat-card
  tiles), devices, conflicts (status semantics), queue (status/priority
  badges).
- Admin: admin sidebar restyled with icons, dashboard/users/library/
  processing-issues/settings migrated to .card/.btn/.input primitives.
- Docs/setup/misc: docs (keeps prose/highlight.js), api_explorer, setup
  wizard, unlinked_books, custom_section builder.
- Modals/fragments: collection_modal, collection_rules,
  restore_system_collection_modal, filter_item, book_detail_modals — all
  overlays use --surface-overlay + --shadow-pop.

Every Alpine handler, HTMX attribute, id, name, and data-* is preserved;
only presentation changes.
2026-08-05 16:45:02 -04:00

114 lines
5.1 KiB
Templ

package templates
import "fmt"
templ DocsLayout(nav Navigation, doc Document, user User, currentPath string) {
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{ doc.Title } - Bookhoard Documentation</title>
<link href="/static/style.css" rel="stylesheet"/>
<link rel="stylesheet" href="/static/highlight-dark.min.css"/>
</head>
<body x-data="docs" x-init="initializeSearch(); highlightCurrentPage(); initializeCodeCopyButtons()" class={ "theme-" + user.Theme + " page-docs font-sans antialiased" }>
@Header(user, currentPath)
<button
class="lg:hidden icon-btn fixed top-[4.75rem] left-4 z-50 bg-surface-raised"
onclick="toggleSidebar()"
aria-label="Toggle documentation menu"
>
@Icon("menu", "h-5 w-5")
</button>
<div id="docs-overlay" class="hidden fixed inset-0 z-40 lg:hidden" style="background: var(--surface-overlay);" onclick="toggleSidebar()"></div>
<div id="search-results" class="hidden fixed inset-0 z-50 overflow-y-auto p-8" style="background: var(--surface-overlay);"></div>
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex gap-8 py-8">
<aside id="docs-sidebar" class="sidebar fixed lg:sticky top-16 lg:top-20 bottom-0 lg:bottom-auto left-0 lg:left-auto z-40 lg:z-10 w-64 shrink-0 lg:h-[calc(100vh-6rem)] overflow-y-auto -translate-x-full lg:translate-x-0 transition-transform duration-300 ease-in-out bg-surface-raised border-r border-line lg:border-r-0">
<div class="p-4 border-b border-line">
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none" style="color: var(--text-secondary);">
@Icon("search", "h-4 w-4")
</span>
<input
type="text"
class="search-input input pl-9"
placeholder="Search documentation…"
id="docs-search"
/>
</div>
</div>
for _, section := range nav.Sections {
<div class="nav-section border-b border-line">
<div
class="nav-section-title flex items-center justify-between px-4 py-3 cursor-pointer select-none text-xs uppercase tracking-wider font-semibold transition-colors hover:bg-surface-hover"
style="color: var(--text-secondary);"
@click="toggleSection($el)"
>
<span>{ section.Title }</span>
@Icon("chevron-down", "h-4 w-4 section-arrow transition-transform")
</div>
if section.Collapsed {
<div class="nav-items hidden" data-collapsed="true">
for _, item := range section.Items {
<a href={ item.URL } class="nav-item flex items-center gap-2 py-2 pl-8 pr-4 text-sm no-underline transition-colors hover:bg-surface-hover" style="color: var(--text-secondary);">
<span class="text-xs" style="color: var(--accent);">{ item.Icon }</span>
<span>{ item.Title }</span>
</a>
}
</div>
} else {
<div class="nav-items" data-collapsed="false">
for _, item := range section.Items {
<a href={ item.URL } class="nav-item flex items-center gap-2 py-2 pl-8 pr-4 text-sm no-underline transition-colors hover:bg-surface-hover" style="color: var(--text-secondary);">
<span class="text-xs" style="color: var(--accent);">{ item.Icon }</span>
<span>{ item.Title }</span>
</a>
}
</div>
}
</div>
}
</aside>
<main class="main-content flex-1 min-w-0 max-w-4xl">
if len(doc.Breadcrumb) > 0 {
<nav class="breadcrumb flex flex-wrap items-center gap-1.5 text-sm mb-8" aria-label="Breadcrumb" style="color: var(--text-secondary);">
for i, crumb := range doc.Breadcrumb {
if i > 0 {
@Icon("chevron-right", "h-4 w-4")
}
<a href={ crumb.URL } class="no-underline transition-colors hover:underline" style="color: var(--accent);">{ crumb.Title }</a>
}
</nav>
}
<h1 class="text-4xl font-bold mb-8 tracking-tight" style="color: var(--text-primary);">{ doc.Title }</h1>
if len(doc.TOC) > 0 {
<details class="toc card p-4 mb-8">
<summary class="cursor-pointer list-none [&::-webkit-details-marker]:hidden flex items-center gap-2 font-semibold transition-colors hover:bg-surface-hover -m-4 p-4 rounded-2xl" style="color: var(--text-primary);">
<span>On this page</span>
@Icon("chevron-down", "h-4 w-4 ml-auto")
</summary>
<div class="mt-3">
for _, item := range doc.TOC {
<a
href={ "#" + item.Anchor }
class="toc-item block py-1 text-sm no-underline transition-colors hover:text-brand"
style={ "margin-left: " + fmt.Sprintf("%drem", item.Level) + "; color: var(--text-secondary);" }
>
{ item.Title }
</a>
}
</div>
</details>
}
<div class="prose prose-invert max-w-none">
@UnsafeHTML(doc.Content).ToComponent()
</div>
</main>
</div>
</div>
</body>
</html>
}