Apply the new design-system primitives across the remaining pages so the whole app shares one visual language: - Auth/entry: redesigned index, login, register (centered card layout, SVG icons, semantic tokens) - Admin: sidebar nav with icons, cohesive admin dashboard/users/library/ processing-issues/settings (tables use border-line + hover states; scan/websocket attributes preserved) - Secondary: analytics (stat cards), devices, progress, conflicts, queue, profile + form/modal all migrated to .card/.btn/.input primitives and SVG action icons - Docs + API explorer + setup wizard + collection/rules/modals + unlinked books + book-detail modals: consistent chrome, modal scrims use --surface-overlay, close buttons use icon-btn - reader.templ and error.templ intentionally left unchanged
111 lines
4.2 KiB
Templ
111 lines
4.2 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 bg-surface text-content font-sans antialiased" }>
|
||
@Header(user, currentPath)
|
||
<!-- Mobile Menu Button -->
|
||
<button
|
||
class="icon-btn lg:hidden fixed top-4 left-4 z-50"
|
||
onclick="toggleSidebar()"
|
||
aria-label="Toggle menu"
|
||
>
|
||
@Icon("menu", "h-6 w-6")
|
||
</button>
|
||
<!-- Search Results Overlay -->
|
||
<div id="search-results" class="hidden fixed inset-0 bg-black/95 z-50 overflow-y-auto p-8 transition-opacity duration-200 ease-in-out"></div>
|
||
<!-- Sidebar -->
|
||
<div class="sidebar fixed left-0 top-0 bottom-0 w-72 bg-surface-raised border-r border-line overflow-y-auto mt-16 lg:translate-x-0 transition-transform duration-300 ease-in-out">
|
||
<!-- Search -->
|
||
<div class="p-4 border-b border-line">
|
||
<input
|
||
type="text"
|
||
class="search-input input text-sm"
|
||
placeholder="Search documentation..."
|
||
id="docs-search"
|
||
/>
|
||
</div>
|
||
<!-- Navigation Sections -->
|
||
for _, section := range nav.Sections {
|
||
<div class="nav-section mb-6">
|
||
<div
|
||
class="nav-section-title font-semibold text-xs uppercase tracking-wide text-content-muted px-4 py-3 cursor-pointer select-none flex items-center justify-between"
|
||
@click="toggleSection($el)"
|
||
>
|
||
<span>{ section.Title }</span>
|
||
<svg class="w-4 h-4 transform transition-transform section-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||
</svg>
|
||
</div>
|
||
if section.Collapsed {
|
||
<div class="nav-items hidden" data-collapsed="true">
|
||
for _, item := range section.Items {
|
||
<a href={ item.URL } class="nav-item block py-2 px-4 pl-8 text-content-muted hover:text-brand text-sm transition-colors no-underline">
|
||
<span class="mr-2">{ item.Icon }</span>{ item.Title }
|
||
</a>
|
||
}
|
||
</div>
|
||
} else {
|
||
<div class="nav-items" data-collapsed="false">
|
||
for _, item := range section.Items {
|
||
<a href={ item.URL } class="nav-item block py-2 px-4 pl-8 text-content-muted hover:text-brand text-sm transition-colors no-underline">
|
||
<span class="mr-2">{ item.Icon }</span>{ item.Title }
|
||
</a>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
<!-- Main Content -->
|
||
<main class="main-content lg:ml-72 p-8 max-w-4xl mx-auto">
|
||
<!-- Breadcrumb -->
|
||
if len(doc.Breadcrumb) > 0 {
|
||
<nav class="breadcrumb flex gap-2 text-sm text-content-muted mb-8" aria-label="Breadcrumb">
|
||
for i, crumb := range doc.Breadcrumb {
|
||
if i > 0 {
|
||
<span class="text-content-muted/50">›</span>
|
||
}
|
||
<a href={ crumb.URL } class="text-brand hover:underline no-underline">{ crumb.Title }</a>
|
||
}
|
||
</nav>
|
||
}
|
||
<!-- Title -->
|
||
<h1 class="text-3xl font-bold tracking-tight mb-8 text-content">{ doc.Title }</h1>
|
||
<!-- Table of Contents -->
|
||
if len(doc.TOC) > 0 {
|
||
<details class="toc bg-surface-raised p-4 rounded-lg mb-8 border border-line">
|
||
<summary class="cursor-pointer hover:opacity-80 transition-opacity">
|
||
<strong class="text-content font-semibold">On this page</strong>
|
||
<span class="ml-2 text-content-muted text-xs">▼</span>
|
||
</summary>
|
||
for _, item := range doc.TOC {
|
||
<a
|
||
href={ "#" + item.Anchor }
|
||
class="toc-item block py-1 text-content-muted hover:text-brand text-sm no-underline"
|
||
style={ "margin-left: " + fmt.Sprintf("%drem", item.Level) }
|
||
>
|
||
{ item.Title }
|
||
</a>
|
||
}
|
||
</details>
|
||
}
|
||
<!-- Content -->
|
||
<div class="prose prose-invert max-w-none">
|
||
@UnsafeHTML(doc.Content).ToComponent()
|
||
</div>
|
||
</main>
|
||
</body>
|
||
</html>
|
||
}
|