Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
167 linesCodeBlameRaw
1---
2// The docs header: the wordmark, search in the middle and links on the
3// right, with the tabs that divide the docs beneath it.
4//
5// Its own layout, not Starlight's: Starlight lines the search up with the
6// page's text column, which moves when a page has an "On this page" list
7// and when it does not. Here the row is three columns, the outer two always
8// the same width, so the search sits at the header's centre on every page.
9import Search from '@astrojs/starlight/components/Search.astro';
10
11import { NAV } from '../route-data';
12import SiteTitle from './SiteTitle.astro';
13import SocialIcons from './SocialIcons.astro';
14// Auto, Light or Dark, kept in this browser; Auto follows the system.
15// On tablets and phones it is at the foot of the menu instead.
16import ThemeToggle from './ThemeToggle.astro';
17
18const tabs = NAV.get(Astro.locals.starlightRoute)?.tabs ?? [];
19---
20
21<div class="g1t-header">
22 <div class="g1t-header-top">
23 <div class="g1t-header-title"><SiteTitle /></div>
24 <div class="g1t-header-search print:hidden"><Search /></div>
25 <div class="g1t-header-links print:hidden">
26 <SocialIcons />
27 <div class="g1t-header-theme"><ThemeToggle /></div>
28 </div>
29 </div>
30 <nav class="g1t-tabs" aria-label="Sections">
31 {
32 tabs.map((tab) => (
33 <a href={tab.href} aria-current={tab.current ? 'page' : undefined}>
34 {tab.label}
35 </a>
36 ))
37 }
38 </nav>
39</div>
40
41<script>
42 // On a phone the tabs scroll sideways: start with the current one in view.
43 const current = document.querySelector<HTMLElement>('.g1t-tabs [aria-current]');
44 const tabs = current?.parentElement;
45 if (current && tabs && current.offsetLeft + current.offsetWidth > tabs.clientWidth) {
46 tabs.scrollLeft = current.offsetLeft - (tabs.clientWidth - current.offsetWidth) / 2;
47 }
48</script>
49
50<style>
51 .g1t-header {
52 display: flex;
53 flex-direction: column;
54 height: 100%;
55 }
56 /* The same bar as the app's: 3.5rem, and a line under it. */
57 .g1t-header-top {
58 /* Each side column is at least as wide as the widest thing that goes
59 in either, so the two stay equal and the search stays centred. */
60 --g1t-header-side: 13.5rem;
61 box-sizing: border-box;
62 display: grid;
63 grid-template-columns:
64 minmax(var(--g1t-header-side), 1fr)
65 minmax(0, 26rem)
66 minmax(var(--g1t-header-side), 1fr);
67 align-items: center;
68 gap: 1rem;
69 height: 3.5rem;
70 flex-shrink: 0;
71 padding: 0 var(--sl-nav-pad-x);
72 border-bottom: 1px solid var(--g1t-line);
73 }
74 .g1t-header-title {
75 display: flex;
76 min-width: 0;
77 /* Room for the link's focus ring. */
78 padding: 0.25rem;
79 margin: -0.25rem;
80 }
81 .g1t-header-search {
82 display: flex;
83 justify-content: center;
84 min-width: 0;
85 }
86 .g1t-header-links {
87 display: flex;
88 align-items: center;
89 justify-content: flex-end;
90 gap: 0.5rem;
91 min-width: 0;
92 }
93 .g1t-header-theme {
94 display: flex;
95 align-items: center;
96 }
97 /* Desktop: the links and the theme button together are wider. */
98 @media (min-width: 50rem) {
99 .g1t-header-top {
100 --g1t-header-side: 17.5rem;
101 }
102 }
103 @media (min-width: 64rem) {
104 .g1t-header-top {
105 --g1t-header-side: 21rem;
106 }
107 }
108 /* Tablets and phones: the theme select is at the foot of the menu. */
109 @media (max-width: 49.99rem) {
110 .g1t-header-theme {
111 display: none;
112 }
113 }
114 /* Tablets: the sidebar is a menu, and its button (fixed in place by
115 Starlight) takes the row's end. The right column makes room for it
116 inside itself, so the two columns stay equal and the search centred. */
117 @media (max-width: 49.99rem) {
118 .g1t-header-top {
119 --g1t-header-side: 13rem;
120 }
121 :global([data-has-sidebar]) .g1t-header-links {
122 padding-inline-end: calc(var(--sl-menu-button-size) + 0.5rem);
123 }
124 }
125 /* Phones: the wordmark on the left; search, the links and the menu
126 button together on the right. */
127 @media (max-width: 44.99rem) {
128 .g1t-header-top {
129 grid-template-columns: minmax(0, 1fr) auto auto;
130 gap: 0.25rem;
131 }
132 }
133 .g1t-tabs {
134 /* The tabs' own offsets are measured from here. */
135 position: relative;
136 display: flex;
137 gap: 1.5rem;
138 height: 2.75rem;
139 padding: 0 var(--sl-nav-pad-x);
140 overflow-x: auto;
141 overscroll-behavior-x: contain;
142 scrollbar-width: none;
143 }
144 .g1t-tabs::-webkit-scrollbar {
145 display: none;
146 }
147 .g1t-tabs a {
148 display: flex;
149 flex-shrink: 0;
150 align-items: center;
151 border-bottom: 2px solid transparent;
152 font-size: 0.875rem;
153 font-weight: 500;
154 white-space: nowrap;
155 color: var(--g1t-muted);
156 text-decoration: none;
157 transition: color 0.15s;
158 }
159 .g1t-tabs a:hover {
160 color: var(--g1t-fg);
161 }
162 .g1t-tabs a[aria-current='page'] {
163 border-color: var(--g1t-accent);
164 font-weight: 600;
165 color: var(--g1t-fg);
166 }
167</style>