|
1 | 1 | <?php declare(strict_types=1); |
2 | | - |
3 | 2 | // Copyright (C) 2015-2026 Mark Constable <mc@netserva.org> (MIT License) |
4 | 3 |
|
5 | 4 | namespace SPE\Session\Core; |
6 | 5 |
|
7 | | -abstract class Theme |
| 6 | +final class Theme |
8 | 7 | { |
9 | | - public function __construct( |
10 | | - protected Ctx $ctx, |
11 | | - protected array $out, |
12 | | - ) {} |
| 8 | + public function __construct(private Ctx $ctx, private array $out) {} |
13 | 9 |
|
14 | | - abstract public function render(): string; |
| 10 | + public function render(): string |
| 11 | + { |
| 12 | + return $this->html($this->topnav() . $this->sidebar('left') . $this->sidebar('right') . "<main>{$this->out['main']}</main>"); |
| 13 | + } |
15 | 14 |
|
16 | | - protected function nav(): string |
| 15 | + private function navLinks(): string |
17 | 16 | { |
18 | | - ['o' => $o, 't' => $t] = $this->ctx->in; |
19 | | - return $this->ctx->nav |
20 | | - |> (static fn($n) => array_map(static fn($p) => sprintf( |
21 | | - '<a href="?o=%s"%s><i data-lucide="%s"></i> %s</a>', |
22 | | - $p[2], |
23 | | - $o === $p[2] ? ' class="active"' : '', |
24 | | - $p[0], |
25 | | - $p[1], |
26 | | - ), $n)) |
27 | | - |> (static fn($a) => implode(' ', $a)); |
| 17 | + return implode('', array_map(fn($p) => sprintf( |
| 18 | + '<a href="?o=%s"%s data-icon="%s"><i data-lucide="%s"></i> %s</a>', |
| 19 | + $p[2], $this->ctx->in['o'] === $p[2] ? ' class="active"' : '', $p[0], $p[0], $p[1] |
| 20 | + ), $this->ctx->nav)); |
28 | 21 | } |
29 | 22 |
|
30 | | - protected function dropdown(): string |
| 23 | + private function colorLinks(): string |
31 | 24 | { |
32 | | - ['o' => $o, 't' => $t] = $this->ctx->in; |
33 | | - $links = $this->ctx->themes |
34 | | - |> (static fn($n) => array_map(static fn($p) => sprintf( |
35 | | - '<a href="?t=%s"%s><i data-lucide="%s"></i> %s</a>', |
36 | | - $p[2], |
37 | | - $t === $p[2] ? ' class="active"' : '', |
38 | | - $p[0], |
39 | | - $p[1], |
40 | | - ), $n)) |
41 | | - |> (static fn($a) => implode('', $a)); |
42 | | - return "<div class=\"dropdown\"><span class=\"dropdown-toggle\"><i data-lucide=\"layout-grid\"></i> Layout</span><div class=\"dropdown-menu\">$links</div></div>"; |
| 25 | + return implode('', array_map(fn($p) => sprintf( |
| 26 | + '<a href="#" data-scheme="%s" data-icon="%s"><i data-lucide="%s"></i> %s</a>', $p[2], $p[0], $p[0], $p[1] |
| 27 | + ), $this->ctx->colors)); |
| 28 | + } |
| 29 | + |
| 30 | + private function topnav(): string |
| 31 | + { |
| 32 | + return <<<HTML |
| 33 | +<nav class="topnav"> |
| 34 | + <button class="menu-toggle" data-sidebar="left"><i data-lucide="menu"></i></button> |
| 35 | + <h1><a class="brand" href="/"><span>{$this->out['page']}</span></a></h1> |
| 36 | + <button class="menu-toggle" data-sidebar="right"><i data-lucide="menu"></i></button> |
| 37 | +</nav> |
| 38 | +HTML; |
43 | 39 | } |
44 | 40 |
|
45 | | - protected function colors(): string |
| 41 | + private function sidebar(string $side): string |
46 | 42 | { |
| 43 | + [$nav, $title, $icon] = $side === 'left' |
| 44 | + ? [$this->navLinks(), 'Navigation', 'compass'] |
| 45 | + : [$this->colorLinks() . '<div class="sidebar-divider"></div><a href="#" onclick="Base.toggleTheme();return false" data-icon="moon"><i data-lucide="moon"></i> Toggle Theme</a>', 'Settings', 'sliders-horizontal']; |
47 | 46 | return <<<HTML |
48 | | - <div class="dropdown"><span class="dropdown-toggle"><i data-lucide="swatch-book"></i> Colors</span><div class="dropdown-menu"> |
49 | | - <a href="#" data-scheme="default"><i data-lucide="circle"></i> Stone</a> |
50 | | - <a href="#" data-scheme="ocean"><i data-lucide="waves"></i> Ocean</a> |
51 | | - <a href="#" data-scheme="forest"><i data-lucide="trees"></i> Forest</a> |
52 | | - <a href="#" data-scheme="sunset"><i data-lucide="sunset"></i> Sunset</a> |
53 | | - </div></div> |
54 | | - HTML; |
| 47 | +<aside class="sidebar sidebar-{$side}"> |
| 48 | + <div class="sidebar-header"><span><i data-lucide="{$icon}"></i> {$title}</span><button class="pin-toggle" data-sidebar="{$side}" title="Pin sidebar"><i data-lucide="pin"></i></button></div> |
| 49 | + <nav>{$nav}</nav> |
| 50 | +</aside> |
| 51 | +HTML; |
55 | 52 | } |
56 | 53 |
|
57 | | - protected function flash(): string |
| 54 | + private function flash(): string |
58 | 55 | { |
59 | 56 | $msg = $this->ctx->flash('msg'); |
60 | 57 | $type = $this->ctx->flash('type') ?? 'success'; |
61 | | - return $msg ? "<script>showToast('$msg', '$type');</script>" : ''; |
| 58 | + return $msg ? "<script>showToast('{$msg}', '{$type}');</script>" : ''; |
62 | 59 | } |
63 | 60 |
|
64 | | - protected function html(string $theme, string $body): string |
| 61 | + private function html(string $body): string |
65 | 62 | { |
66 | 63 | $flash = $this->flash(); |
67 | 64 | return <<<HTML |
68 | | - <!DOCTYPE html> |
69 | | - <html lang="en"> |
70 | | - <head> |
71 | | - <meta charset="utf-8"> |
72 | | - <meta name="viewport" content="width=device-width, initial-scale=1"> |
73 | | - <title>{$this->out['doc']} [$theme]</title> |
74 | | - <link rel="stylesheet" href="/base.css"> |
75 | | - <link rel="stylesheet" href="/site.css"> |
76 | | - <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script> |
77 | | - <script>(function(){const t=localStorage.getItem("base-theme"),s=localStorage.getItem("base-scheme"),c=t||(matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light");document.documentElement.className=c+(s&&s!=="default"?" scheme-"+s:"")})();</script> |
78 | | - </head> |
79 | | - <body> |
80 | | - $body |
81 | | - <script src="/base.js"></script> |
82 | | - $flash |
83 | | - </body> |
84 | | - </html> |
85 | | - HTML; |
| 65 | +<!DOCTYPE html> |
| 66 | +<html lang="en"> |
| 67 | +<head> |
| 68 | + <meta charset="utf-8"> |
| 69 | + <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 70 | + <title>{$this->out['doc']}</title> |
| 71 | + <link rel="stylesheet" href="/base.css"> |
| 72 | + <link rel="stylesheet" href="/site.css"> |
| 73 | + <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script> |
| 74 | + <script>(function(){var s=JSON.parse(localStorage.getItem('base-state')||'{}'),t=s.theme,c=s.scheme,h=document.documentElement;h.className='preload '+(t||(matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light'))+(c&&c!=='default'?' scheme-'+c:'');})()</script> |
| 75 | +</head> |
| 76 | +<body> |
| 77 | +{$body} |
| 78 | +<div class="overlay"></div> |
| 79 | +<script src="/base.js"></script> |
| 80 | +<script>if(location.search)history.replaceState(null,'',location.pathname);</script> |
| 81 | +{$flash} |
| 82 | +</body> |
| 83 | +</html> |
| 84 | +HTML; |
86 | 85 | } |
87 | 86 | } |
0 commit comments