This repository has no description
1{{ define "layouts/fragments/topbar" }}
2 <nav class="mx-auto space-x-4 px-6 py-2">
3 <div class="flex justify-between p-0 items-center">
4 <div id="left-items">
5 <a href="/" hx-boost="true" class="text-2xl no-underline hover:no-underline flex items-center gap-2">
6 <span class="hidden sm:inline-flex">{{ template "fragments/logotypeSmall" }}</span>
7 <span class="inline-flex sm:hidden">{{ template "fragments/dolly/logo" (dict "Classes" "size-8" ) }}</span>
8 </a>
9 </div>
10
11 <div id="right-items" class="flex items-center gap-4">
12 {{ with .LoggedInUser }}
13 <div class="flex items-center order-1 md:order-3">{{ block "newButton" . }} {{ end }}</div>
14 <div class="flex items-center order-2 md:order-4">{{ template "notifications/fragments/bell" }}</div>
15 <div class="flex items-center order-3 md:order-1">{{ block "search" . }} {{ end }}</div>
16 <div class="hidden md:block md:order-2 w-px h-6 bg-gray-200 dark:bg-gray-700 self-center"></div>
17 <div class="flex items-center order-4 md:order-5">{{ block "profileDropdown" . }} {{ end }}</div>
18 {{ else }}
19 <a href="/login">Login</a>
20 <span class="text-gray-500 dark:text-gray-400">or</span>
21 <a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2">
22 Join now {{ i "arrow-right" "size-4" }}
23 </a>
24 {{ end }}
25 </div>
26 </div>
27 </nav>
28{{ end }}
29
30{{ define "newButton" }}
31<details class="relative inline-block text-left nav-dropdown">
32 <summary class="btn py-0 cursor-pointer list-none flex items-center gap-2">
33 {{ i "plus" "w-4 h-4" }} <span class="hidden md:inline">New</span>
34 </summary>
35 <div class="absolute flex flex-col right-0 mt-3 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700">
36 <a href="/repo/new" class="flex items-center gap-2">
37 {{ i "book-plus" "w-4 h-4" }}
38 New repository
39 </a>
40 <a href="/strings/new" class="flex items-center gap-2">
41 {{ i "line-squiggle" "w-4 h-4" }}
42 New string
43 </a>
44 </div>
45</details>
46{{ end }}
47
48{{ define "search" }}
49<div class="relative hidden md:block" id="topbar-search-container">
50 <div id="topbar-search-box" class="relative flex items-center gap-2 px-2 pr-1.5 py-4 max-h-[30px] md:w-80 lg:w-96 border border-gray-200 dark:border-gray-700 rounded bg-white dark:bg-gray-900 focus-within:z-[51] before:content-[''] before:absolute before:inset-0 before:rounded-md before:invisible focus-within:before:visible before:ring-2 before:ring-transparent before:ring-offset-transparent before:ring-offset-1 before:pointer-events-none focus-within:before:ring-gray-300 dark:focus-within:before:ring-gray-600">
51 {{ i "search" "size-4 text-gray-400" }}
52 <input
53 type="text"
54 id="topbar-search-input"
55 name="q"
56 placeholder="Search..."
57 autocomplete="off"
58 class="flex-1 border-none bg-transparent p-0 focus:outline-none focus:[box-shadow:none] text-sm text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 peer"
59 hx-get="/search/quick"
60 hx-trigger="input changed delay:10ms"
61 hx-target="#topbar-search-results"
62 hx-swap="innerHTML"
63 hx-indicator="#topbar-search-indicator"
64 />
65 <kbd id="search-kbd" class="pointer-events-none flex items-center text-xs border border-gray-200 dark:border-gray-600 rounded border-b-2 px-1 font-sans leading-5 text-gray-400 dark:text-gray-500 peer-focus:hidden peer-[:not(:placeholder-shown)]:hidden"></kbd>
66 <script>
67 (function() {
68 var kbd = document.getElementById('search-kbd');
69 if (!kbd) return;
70 var ua = navigator.userAgent;
71 var isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(ua);
72 if (isMobile) {
73 kbd.style.display = 'none';
74 } else if (/Macintosh|Mac OS X/.test(ua)) {
75 kbd.textContent = '⌘K';
76 } else {
77 kbd.textContent = 'Ctrl+K';
78 }
79 })();
80 </script>
81 <span id="topbar-search-indicator" class="shrink-0">
82 {{ i "loader-circle" "size-4 text-gray-400 animate-spin" }}
83 </span>
84 <button
85 type="button"
86 class="hidden peer-[:not(:placeholder-shown)]:block text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
87 onclick="const i=document.getElementById('topbar-search-input');i.value='';htmx.trigger(i,'input');i.focus()"
88 >{{ i "x" "size-4" }}</button>
89 </div>
90 <div id="topbar-search-results" class="absolute w-full z-50"></div>
91</div>
92
93<button id="mobile-search-btn" class="md:hidden" type="button" data-action="open-mobile-search">
94 {{ i "search" "size-5 text-gray-500 dark:text-gray-400" }}
95</button>
96
97<div id="mobile-search-overlay"
98 class="md:hidden fixed inset-x-0 top-0 h-dvh z-50
99 bg-white dark:bg-gray-900
100 flex flex-col
101 opacity-0 pointer-events-none
102 transition-opacity duration-150
103 pt-[env(safe-area-inset-top)]"
104 aria-hidden="true">
105 <div class="flex items-center gap-3 px-4 py-3 border-b border-gray-200 dark:border-gray-700 shrink-0">
106 {{ i "search" "size-4 text-gray-400 shrink-0" }}
107 <input
108 type="text"
109 id="mobile-search-input"
110 name="q"
111 placeholder="Search..."
112 autocomplete="off"
113 inputmode="search"
114 class="flex-1 min-w-0 border-none bg-transparent p-0 focus:outline-none focus:[box-shadow:none] text-[16px] text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-600 peer"
115 hx-get="/search/quick/mobile"
116 hx-trigger="input changed delay:10ms"
117 hx-target="#mobile-search-results"
118 hx-swap="innerHTML"
119 hx-indicator="#mobile-search-indicator"
120 />
121 <span id="mobile-search-indicator" class="shrink-0">
122 {{ i "loader-circle" "size-4 text-gray-400 animate-spin" }}
123 </span>
124 <button type="button" class="p-1 -mr-1 hit-area hit-area-4" data-action="close-mobile-search">
125 {{ i "x" "size-5 text-gray-400" }}
126 </button>
127 </div>
128 <div id="mobile-search-results" class="flex-1 flex flex-col overflow-hidden pb-[env(safe-area-inset-bottom)]"></div>
129 <div id="mobile-search-spacer" class="bg-gray-50 dark:bg-gray-800 hidden"></div>
130</div>
131<style>
132 #topbar-search-indicator,
133 #mobile-search-indicator {
134 display: none;
135 opacity: 0;
136 transition-property: opacity, display;
137 transition-duration: 150ms, 150ms;
138 transition-timing-function: ease-out;
139 transition-behavior: allow-discrete;
140 }
141 #topbar-search-indicator.htmx-request,
142 #mobile-search-indicator.htmx-request {
143 display: block;
144 opacity: 1;
145 transition-duration: 0s, 0s;
146 }
147</style>
148{{ end }}
149
150{{ define "profileDropdown" }}
151{{ $handle := resolve .Did }}
152<details class="relative inline-block text-left nav-dropdown">
153 <summary class="cursor-pointer list-none flex items-center gap-1">
154 {{ template "user/fragments/pic" (list .Did "size-8 md:size-6") }}
155 <span class="hidden md:inline">{{ $handle | truncateAt30 }}</span>
156 </summary>
157 <div class="absolute right-0 mt-4 rounded bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 shadow-lg z-50 text-sm" style="width: 14rem;">
158 {{ $linkStyle := "flex items-center gap-3 px-4 py-2 hover:no-underline hover:bg-gray-50 hover:dark:bg-gray-700/50" }}
159
160 {{ $others := .Accounts | otherAccounts .Did }}
161 {{ if $others }}
162 <div class="text-sm text-gray-500 dark:text-gray-400 px-3 py-1 pt-2">Switch account</div>
163 {{ range $others }}
164 <button
165 type="button"
166 hx-post="/account/switch"
167 hx-vals='{"did": "{{ .Did }}"}'
168 hx-swap="none"
169 class="{{$linkStyle}} w-full text-left pl-3"
170 >
171 {{ template "user/fragments/pic" (list .Did "size-6") }}
172 <span class="truncate flex-1">{{ .Did | resolve }}</span>
173 </button>
174 {{ end }}
175 {{ end }}
176
177 <a href="/login?mode=add_account" class="{{$linkStyle}} pl-3">
178 <div class="size-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
179 {{ i "plus" "size-3" }}
180 </div>
181
182 <div class="text-left flex-1 min-w-0 block truncate">
183 Add account
184 </div>
185 </a>
186
187 <div class="border-t border-gray-200 dark:border-gray-700">
188 <a href="/{{ $handle }}" class="{{$linkStyle}}">
189 {{ i "user" "size-4" }}
190 Profile
191 </a>
192 <a href="/{{ $handle }}?tab=repos" class="{{$linkStyle}}">
193 {{ i "book-marked" "size-4" }}
194 Repositories
195 </a>
196 <a href="/{{ $handle }}?tab=strings" class="{{$linkStyle}}">
197 {{ i "line-squiggle" "size-4" }}
198 Strings
199 </a>
200 <a href="/settings" class="{{$linkStyle}}">
201 {{ i "cog" "size-4" }}
202 Settings
203 </a>
204 <a href="#"
205 hx-post="/logout"
206 hx-swap="none"
207 class="{{$linkStyle}} text-red-400 hover:text-red-400 hover:bg-red-100 dark:hover:bg-red-700/20 pb-2">
208 {{ i "log-out" "size-4" }}
209 Logout
210 </a>
211 </div>
212 </div>
213</details>
214
215<script>
216document.addEventListener('click', function(event) {
217 const dropdowns = document.querySelectorAll('.nav-dropdown');
218 dropdowns.forEach(function(dropdown) {
219 if (!dropdown.contains(event.target)) {
220 dropdown.removeAttribute('open');
221 }
222 });
223});
224</script>
225{{ end }}