tenseleyflow/shithub / 31691f6

Browse files

Restore trunk navbar files

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
31691f6bcfbc502fdb42fd4adbe8c4d04874ee91
Parents
53af555
Tree
42c89b7

5 changed files

StatusFile+-
M internal/web/nav_test.go 13 4
M internal/web/static/css/shithub.css 214 0
M internal/web/templates/_layout.html 55 0
M internal/web/templates/_nav.html 2 1
A internal/web/templates/_nav_offcanvas.html 69 0
internal/web/nav_test.gomodified
@@ -19,7 +19,7 @@ func TestNavRendersContextualRepoAndOrgHeaders(t *testing.T) {
1919
 		"_layout.html": {Data: []byte(`{{ define "layout" }}{{ template "nav" . }}{{ template "page" . }}{{ end }}`)},
2020
 		"page.html":    {Data: []byte(`{{ define "page" }}page{{ end }}`)},
2121
 	}
22
-	for _, name := range []string{"_nav.html", "_repo_subnav.html", "_org_subnav.html"} {
22
+	for _, name := range []string{"_nav.html", "_nav_offcanvas.html", "_repo_subnav.html", "_org_subnav.html"} {
2323
 		body, err := fs.ReadFile(TemplatesFS(), name)
2424
 		if err != nil {
2525
 			t.Fatalf("read %s: %v", name, err)
@@ -53,12 +53,15 @@ func TestNavRendersContextualRepoAndOrgHeaders(t *testing.T) {
5353
 			},
5454
 			want: []string{
5555
 				`class="shithub-nav has-context"`,
56
+				`data-offcanvas-open`,
57
+				`role="dialog" aria-modal="true" aria-label="Global navigation"`,
5658
 				`aria-label="Repository"`,
5759
 				`href="/tenseleyFlow/shithub" class="is-strong">shithub</a>`,
60
+				`href="/tenseleyFlow/shithub" class="shithub-offcanvas-repo-item"`,
5861
 				`href="/tenseleyFlow/shithub/issues"`,
5962
 				`Pull requests`,
6063
 			},
61
-			wantNot: []string{`href="/explore"`, `href="/about"`, `aria-label="Organization"`},
64
+			wantNot: []string{`class="shithub-nav-links"`, `href="/about"`, `aria-label="Organization"`, `Copilot`},
6265
 		},
6366
 		{
6467
 			name: "org",
@@ -73,12 +76,15 @@ func TestNavRendersContextualRepoAndOrgHeaders(t *testing.T) {
7376
 			},
7477
 			want: []string{
7578
 				`class="shithub-nav has-context"`,
79
+				`data-offcanvas-open`,
80
+				`role="dialog" aria-modal="true" aria-label="Global navigation"`,
7681
 				`aria-label="Organization"`,
7782
 				`href="/tenseleyFlow" class="is-strong">tenseleyFlow</a>`,
83
+				`href="/tenseleyFlow#org-repositories" class="shithub-offcanvas-repo-item"`,
7884
 				`href="/tenseleyFlow/teams"`,
7985
 				`href="/tenseleyFlow/people"`,
8086
 			},
81
-			wantNot: []string{`href="/explore"`, `href="/about"`, `Pull requests`},
87
+			wantNot: []string{`class="shithub-nav-links"`, `href="/about"`, `Copilot`},
8288
 		},
8389
 		{
8490
 			name: "global",
@@ -88,10 +94,13 @@ func TestNavRendersContextualRepoAndOrgHeaders(t *testing.T) {
8894
 			},
8995
 			want: []string{
9096
 				`class="shithub-nav"`,
97
+				`data-offcanvas-open`,
98
+				`role="dialog" aria-modal="true" aria-label="Global navigation"`,
99
+				`All pull requests`,
91100
 				`href="/explore"`,
92101
 				`href="/about"`,
93102
 			},
94
-			wantNot: []string{`shithub-nav-local`, `aria-label="Repository"`, `aria-label="Organization"`},
103
+			wantNot: []string{`shithub-nav-local`, `aria-label="Repository"`, `aria-label="Organization"`, `Copilot`},
95104
 		},
96105
 	}
97106
 	for _, tc := range cases {
internal/web/static/css/shithub.cssmodified
@@ -213,6 +213,220 @@ code {
213213
   border-bottom: 0;
214214
 }
215215
 
216
+.shithub-offcanvas-open {
217
+  overflow: hidden;
218
+}
219
+.shithub-offcanvas {
220
+  position: fixed;
221
+  inset: 0;
222
+  z-index: 120;
223
+}
224
+.shithub-offcanvas[hidden] {
225
+  display: none;
226
+}
227
+.shithub-offcanvas-backdrop {
228
+  position: absolute;
229
+  inset: 0;
230
+  width: 100%;
231
+  height: 100%;
232
+  padding: 0;
233
+  border: 0;
234
+  background: rgba(1, 4, 9, 0.58);
235
+  cursor: default;
236
+}
237
+.shithub-offcanvas-panel {
238
+  position: relative;
239
+  width: min(320px, calc(100vw - 14px));
240
+  min-height: 100vh;
241
+  max-height: 100vh;
242
+  display: flex;
243
+  flex-direction: column;
244
+  padding: 1rem 0.5rem;
245
+  color: var(--fg-default);
246
+  background: var(--canvas-inset);
247
+  border: 1px solid var(--border-default);
248
+  border-left: 0;
249
+  border-radius: 0 12px 12px 0;
250
+  box-shadow: 16px 0 48px rgba(1, 4, 9, 0.42);
251
+  overflow-y: auto;
252
+}
253
+.shithub-offcanvas-head {
254
+  display: flex;
255
+  align-items: center;
256
+  justify-content: space-between;
257
+  gap: 1rem;
258
+  padding: 0 0.5rem 1.1rem;
259
+}
260
+.shithub-offcanvas-mark {
261
+  display: inline-flex;
262
+  color: var(--fg-default);
263
+}
264
+.shithub-offcanvas-mark:hover {
265
+  text-decoration: none;
266
+}
267
+.shithub-offcanvas-mark svg {
268
+  width: 32px;
269
+  height: 32px;
270
+  color: var(--fg-default);
271
+}
272
+.shithub-offcanvas-close {
273
+  display: inline-flex;
274
+  align-items: center;
275
+  justify-content: center;
276
+  width: 32px;
277
+  height: 32px;
278
+  padding: 0;
279
+  border: 0;
280
+  border-radius: 6px;
281
+  color: var(--fg-muted);
282
+  background: transparent;
283
+  cursor: pointer;
284
+}
285
+.shithub-offcanvas-close:hover {
286
+  color: var(--fg-default);
287
+  background: var(--canvas-subtle);
288
+}
289
+.shithub-offcanvas-nav {
290
+  display: flex;
291
+  flex-direction: column;
292
+  gap: 0.125rem;
293
+  padding: 0 0.25rem 0.75rem;
294
+}
295
+.shithub-offcanvas-nav-secondary {
296
+  padding-top: 0.75rem;
297
+  border-top: 1px solid var(--border-default);
298
+}
299
+.shithub-offcanvas-link {
300
+  display: flex;
301
+  align-items: center;
302
+  gap: 0.65rem;
303
+  min-height: 32px;
304
+  padding: 0.35rem 0.5rem;
305
+  border-radius: 6px;
306
+  color: var(--fg-default);
307
+  font-size: 0.875rem;
308
+  font-weight: 600;
309
+}
310
+.shithub-offcanvas-link:hover,
311
+.shithub-offcanvas-link:focus-visible {
312
+  color: var(--fg-default);
313
+  background: var(--canvas-subtle);
314
+  text-decoration: none;
315
+}
316
+.shithub-offcanvas-link svg {
317
+  flex: 0 0 auto;
318
+  color: var(--fg-muted);
319
+}
320
+.shithub-offcanvas-repos {
321
+  margin: 0 0.25rem;
322
+  padding: 0.9rem 0.25rem 0.75rem;
323
+  border-top: 1px solid var(--border-default);
324
+}
325
+.shithub-offcanvas-section-head {
326
+  display: flex;
327
+  align-items: center;
328
+  justify-content: space-between;
329
+  gap: 1rem;
330
+  padding: 0 0.25rem 0.45rem;
331
+}
332
+.shithub-offcanvas-section-head h2 {
333
+  margin: 0;
334
+  color: var(--fg-muted);
335
+  font-size: 0.75rem;
336
+  font-weight: 700;
337
+}
338
+.shithub-offcanvas-section-head a {
339
+  display: inline-flex;
340
+  color: var(--fg-muted);
341
+}
342
+.shithub-offcanvas-section-head a:hover {
343
+  color: var(--fg-default);
344
+  text-decoration: none;
345
+}
346
+.shithub-offcanvas-repo-list {
347
+  display: flex;
348
+  flex-direction: column;
349
+  gap: 0.125rem;
350
+}
351
+.shithub-offcanvas-repo-item {
352
+  display: grid;
353
+  grid-template-columns: 20px minmax(0, 1fr);
354
+  align-items: center;
355
+  gap: 0.55rem;
356
+  min-height: 32px;
357
+  padding: 0.25rem;
358
+  border-radius: 6px;
359
+  color: var(--fg-default);
360
+  font-size: 0.875rem;
361
+  font-weight: 600;
362
+  line-height: 1.25;
363
+}
364
+.shithub-offcanvas-repo-item:hover,
365
+.shithub-offcanvas-repo-item:focus-visible {
366
+  background: var(--canvas-subtle);
367
+  text-decoration: none;
368
+}
369
+.shithub-offcanvas-repo-item img {
370
+  width: 20px;
371
+  height: 20px;
372
+  border-radius: 50%;
373
+  background: var(--canvas-default);
374
+}
375
+.shithub-offcanvas-repo-item span {
376
+  min-width: 0;
377
+  overflow-wrap: anywhere;
378
+}
379
+.shithub-offcanvas-show-more {
380
+  display: inline-flex;
381
+  margin: 0.35rem 0.25rem 0;
382
+  color: var(--fg-muted);
383
+  font-size: 0.75rem;
384
+}
385
+.shithub-offcanvas-show-more:hover {
386
+  color: var(--accent-fg);
387
+}
388
+.shithub-offcanvas-notice {
389
+  display: grid;
390
+  grid-template-columns: 16px minmax(0, 1fr) 24px;
391
+  align-items: center;
392
+  gap: 0.5rem;
393
+  margin: 0.75rem 0.5rem 0;
394
+  padding: 0.7rem 0.55rem;
395
+  border: 1px solid var(--accent-emphasis);
396
+  border-radius: 6px;
397
+  color: var(--fg-default);
398
+  background: rgba(9, 105, 218, 0.1);
399
+  font-size: 0.75rem;
400
+}
401
+.shithub-offcanvas-notice[hidden] {
402
+  display: none;
403
+}
404
+.shithub-offcanvas-notice > span {
405
+  display: inline-flex;
406
+  color: var(--accent-fg);
407
+}
408
+.shithub-offcanvas-notice p {
409
+  margin: 0;
410
+  min-width: 0;
411
+  font-weight: 600;
412
+}
413
+.shithub-offcanvas-notice button {
414
+  display: inline-flex;
415
+  align-items: center;
416
+  justify-content: center;
417
+  width: 24px;
418
+  height: 24px;
419
+  padding: 0;
420
+  border: 0;
421
+  border-radius: 6px;
422
+  color: var(--accent-fg);
423
+  background: transparent;
424
+  cursor: pointer;
425
+}
426
+.shithub-offcanvas-notice button:hover {
427
+  background: rgba(9, 105, 218, 0.16);
428
+}
429
+
216430
 /* User-menu dropdown — uses native <details>/<summary> so it works without JS. */
217431
 .shithub-user-menu { position: relative; }
218432
 .shithub-user-menu > summary {
internal/web/templates/_layout.htmlmodified
@@ -65,6 +65,61 @@
6565
     });
6666
   })();
6767
 
68
+  (function () {
69
+    var drawer = document.querySelector("[data-offcanvas]");
70
+    var opener = document.querySelector("[data-offcanvas-open]");
71
+    if (!drawer || !opener) return;
72
+    var panel = drawer.querySelector(".shithub-offcanvas-panel");
73
+    var closers = drawer.querySelectorAll("[data-offcanvas-close]");
74
+    var notice = drawer.querySelector("[data-offcanvas-notice]");
75
+    var noticeClose = drawer.querySelector("[data-offcanvas-notice-close]");
76
+    var lastFocus = null;
77
+
78
+    function openDrawer() {
79
+      lastFocus = document.activeElement;
80
+      drawer.hidden = false;
81
+      document.body.classList.add("shithub-offcanvas-open");
82
+      opener.setAttribute("aria-expanded", "true");
83
+      if (panel) panel.focus();
84
+    }
85
+
86
+    function closeDrawer() {
87
+      drawer.hidden = true;
88
+      document.body.classList.remove("shithub-offcanvas-open");
89
+      opener.setAttribute("aria-expanded", "false");
90
+      if (lastFocus && lastFocus.focus) lastFocus.focus();
91
+    }
92
+
93
+    opener.addEventListener("click", openDrawer);
94
+    closers.forEach(function (button) {
95
+      button.addEventListener("click", closeDrawer);
96
+    });
97
+    document.addEventListener("keydown", function (event) {
98
+      if (event.key === "Escape" && !drawer.hidden) closeDrawer();
99
+    });
100
+
101
+    var storage = null;
102
+    try { storage = window.localStorage; } catch (e) {}
103
+
104
+    if (notice && storage) {
105
+      try {
106
+        if (storage.getItem("shithubTeamsMovedNoticeDismissed") === "1") {
107
+          notice.hidden = true;
108
+        }
109
+      } catch (e) {}
110
+    }
111
+    if (noticeClose && notice) {
112
+      noticeClose.addEventListener("click", function () {
113
+        notice.hidden = true;
114
+        try {
115
+          if (storage) {
116
+            storage.setItem("shithubTeamsMovedNoticeDismissed", "1");
117
+          }
118
+        } catch (e) {}
119
+      });
120
+    }
121
+  })();
122
+
68123
   (function () {
69124
     var root = document.querySelector("[data-search-root]");
70125
     if (!root) return;
internal/web/templates/_nav.htmlmodified
@@ -2,7 +2,7 @@
22
 <header class="shithub-nav{{ if .Repo }} has-context{{ else if .Org }} has-context{{ end }}" role="banner">
33
   <div class="shithub-nav-global">
44
     <div class="shithub-nav-context">
5
-      <button type="button" class="shithub-nav-menu" aria-label="Open navigation menu">{{ octicon "three-bars" }}</button>
5
+      <button type="button" class="shithub-nav-menu" aria-label="Open navigation menu" aria-controls="shithub-global-navigation" aria-expanded="false" data-offcanvas-open>{{ octicon "three-bars" }}</button>
66
       <a href="/" class="shithub-nav-brand" aria-label="shithub home">
77
         {{ octicon "shithub" }}
88
         <span class="shithub-nav-brand-word">shithub</span>
@@ -100,4 +100,5 @@
100100
   </div>
101101
   {{ end }}
102102
 </header>
103
+{{ template "nav-offcanvas" . }}
103104
 {{- end }}
internal/web/templates/_nav_offcanvas.htmladded
@@ -0,0 +1,69 @@
1
+{{ define "nav-offcanvas" -}}
2
+<div class="shithub-offcanvas" data-offcanvas hidden>
3
+  <button type="button" class="shithub-offcanvas-backdrop" aria-label="Close navigation menu" data-offcanvas-close></button>
4
+  <aside class="shithub-offcanvas-panel" id="shithub-global-navigation" role="dialog" aria-modal="true" aria-label="Global navigation" tabindex="-1">
5
+    <div class="shithub-offcanvas-head">
6
+      <a href="/" class="shithub-offcanvas-mark" aria-label="shithub home">{{ octicon "shithub" }}</a>
7
+      <button type="button" class="shithub-offcanvas-close" aria-label="Close navigation menu" data-offcanvas-close>{{ octicon "x" }}</button>
8
+    </div>
9
+
10
+    <nav class="shithub-offcanvas-nav" aria-label="Global navigation">
11
+      <a href="/" class="shithub-offcanvas-link">{{ octicon "home" }} <span>Home</span></a>
12
+      <a href="/search?type=issues" class="shithub-offcanvas-link">{{ octicon "issue-opened" }} <span>All issues</span></a>
13
+      <a href="/search?type=pullrequests" class="shithub-offcanvas-link">{{ octicon "git-pull-request" }} <span>All pull requests</span></a>
14
+      <a href="/search?type=repositories" class="shithub-offcanvas-link">{{ octicon "repo" }} <span>All repositories</span></a>
15
+      <a href="/projects" class="shithub-offcanvas-link">{{ octicon "table" }} <span>Projects</span></a>
16
+      <a href="/discussions" class="shithub-offcanvas-link">{{ octicon "comment-discussion" }} <span>Discussions</span></a>
17
+      <a href="/codespaces" class="shithub-offcanvas-link">{{ octicon "code-square" }} <span>Codespaces</span></a>
18
+    </nav>
19
+
20
+    <nav class="shithub-offcanvas-nav shithub-offcanvas-nav-secondary" aria-label="Explore">
21
+      <a href="/explore" class="shithub-offcanvas-link">{{ octicon "rocket" }} <span>Explore</span></a>
22
+      <a href="/marketplace" class="shithub-offcanvas-link">{{ octicon "package" }} <span>Marketplace</span></a>
23
+      <a href="/mcp" class="shithub-offcanvas-link">{{ octicon "link" }} <span>MCP registry</span></a>
24
+    </nav>
25
+
26
+    {{ if or .Repo .Org .Viewer.ID }}
27
+    <section class="shithub-offcanvas-repos" aria-labelledby="shithub-offcanvas-repos-heading">
28
+      <div class="shithub-offcanvas-section-head">
29
+        <h2 id="shithub-offcanvas-repos-heading">Top repositories</h2>
30
+        <a href="/search?type=repositories" aria-label="Search repositories">{{ octicon "search" }}</a>
31
+      </div>
32
+      <div class="shithub-offcanvas-repo-list">
33
+        {{ if .Repo }}
34
+        <a href="/{{ .Owner }}/{{ .Repo.Name }}" class="shithub-offcanvas-repo-item">
35
+          <img src="/avatars/{{ .Owner }}" alt="" width="20" height="20">
36
+          <span>{{ .Owner }}/{{ .Repo.Name }}</span>
37
+        </a>
38
+        {{ else if .Org }}
39
+        <a href="/{{ .Org.Slug }}#org-repositories" class="shithub-offcanvas-repo-item">
40
+          <img src="/avatars/{{ .Org.Slug }}" alt="" width="20" height="20">
41
+          <span>{{ .Org.Slug }} repositories</span>
42
+        </a>
43
+        {{ else if .Viewer.ID }}
44
+        <a href="/{{ .Viewer.Username }}?tab=repositories" class="shithub-offcanvas-repo-item">
45
+          <img src="/avatars/{{ .Viewer.Username }}" alt="" width="20" height="20">
46
+          <span>{{ .Viewer.Username }} repositories</span>
47
+        </a>
48
+        {{ end }}
49
+      </div>
50
+      {{ if .Repo }}
51
+      <a href="/{{ .Owner }}?tab=repositories" class="shithub-offcanvas-show-more">Show more</a>
52
+      {{ else if .Org }}
53
+      <a href="/{{ .Org.Slug }}#org-repositories" class="shithub-offcanvas-show-more">Show more</a>
54
+      {{ else if .Viewer.ID }}
55
+      <a href="/{{ .Viewer.Username }}?tab=repositories" class="shithub-offcanvas-show-more">Show more</a>
56
+      {{ end }}
57
+    </section>
58
+    {{ end }}
59
+
60
+    {{ if .Viewer.ID }}
61
+    <div class="shithub-offcanvas-notice" data-offcanvas-notice>
62
+      <span>{{ octicon "issue-opened" }}</span>
63
+      <p>Teams have moved to <a href="/settings/organizations">settings</a></p>
64
+      <button type="button" aria-label="Dismiss" data-offcanvas-notice-close>{{ octicon "x" }}</button>
65
+    </div>
66
+    {{ end }}
67
+  </aside>
68
+</div>
69
+{{- end }}