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.
114 lines
5.1 KiB
Templ
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>
|
|
}
|