Skip to content

Commit 0fae876

Browse files
authored
Add dropdown to switch between Flutter-related sites (#11659)
Adds a dropdown to the Flutter wordmark in the navbar, as well as a badge to distinguish the docs site from the marketing site. <img width="240" alt="Flutter site switcher expanded in light mode" src="https://github.com/user-attachments/assets/4393530e-2e48-4354-9358-8ccc2ec147da" /> --- Resolves #11658
1 parent 35bda67 commit 0fae876

File tree

6 files changed

+256
-22
lines changed

6 files changed

+256
-22
lines changed

src/_includes/header.html

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,47 @@
55
Skip to main content
66
</a>
77
<nav class="navbar">
8-
<button
9-
id="menu-toggle"
10-
class="icon-button"
11-
type="button"
12-
aria-controls="sidenav"
13-
aria-label="Toggle navigation"
14-
title="Toggle navigation">
15-
<span class="material-symbols" aria-hidden="true">menu</span>
16-
</button>
17-
18-
<a class="navbar-brand" href="/">
19-
<img
20-
src='/assets/images/branding/flutter/logo+text/horizontal/default.svg'
21-
alt='Flutter logo'
22-
height="37"
23-
width="129">
24-
</a>
8+
<div id="site-switcher" class="dropdown">
9+
<button class="dropdown-button site-wordmark" aria-expanded="false" aria-controls="site-switcher-menu" aria-label="Switch between Flutter and Dart sites">
10+
<img src="/assets/images/branding/flutter/logo/default.svg" alt="Flutter logo" width="28">
11+
<span>Flutter</span>
12+
<span class="subtype">Docs</span>
13+
<span class="material-symbols" aria-hidden="true">unfold_more</span>
14+
</button>
15+
<div class="dropdown-content" id="site-switcher-menu">
16+
<nav class="dropdown-menu" role="menu">
17+
<ul>
18+
<li role="presentation"><a href="{{site.main-url}}" class="site-wordmark" role="menuitem" title="Flutter homepage" aria-label="Go to the Flutter homepage">
19+
<img src="/assets/images/branding/flutter/logo/default.svg" alt="Flutter logo" width="28">
20+
<span>Flutter</span>
21+
</a></li>
22+
<li role="presentation"><a href="{{site.url}}" class="site-wordmark current-site" role="menuitem" aria-current="true" title="Flutter docs homepage" aria-label="Go to the Flutter docs homepage">
23+
<img src="/assets/images/branding/flutter/logo/default.svg" alt="Flutter logo" width="28">
24+
<span>Flutter</span>
25+
<span class="subtype">Docs</span>
26+
</a></li>
27+
<li role="presentation"><a href="{{site.api}}" class="site-wordmark" role="menuitem" title="Flutter API reference" aria-label="Go to the Flutter API reference">
28+
<img src="/assets/images/branding/flutter/logo/default.svg" alt="Flutter logo" width="28">
29+
<span>Flutter</span>
30+
<span class="subtype">API</span>
31+
</a></li>
32+
<li aria-hidden="true" class="dropdown-divider" role="separator"></li>
33+
<li role="presentation"><a href="{{site.dart-site}}" class="site-wordmark" role="menuitem" title="Dart homepage" aria-label="Go to the Dart homepage">
34+
<img src="/assets/images/branding/dart/logo.svg" alt="Dart logo" width="28" height="28">
35+
<span>Dart</span>
36+
</a></li>
37+
<li role="presentation"><a href="{{site.dartpad}}" class="site-wordmark" role="menuitem" title="DartPad playground" aria-label="Go to the DartPad playground">
38+
<img src="/assets/images/branding/dart/logo.svg" alt="Dart logo" width="28" height="28">
39+
<span>DartPad</span>
40+
</a></li>
41+
<li role="presentation"><a href="{{site.pub}}" class="site-wordmark" role="menuitem" title="pub.dev homepage" aria-label="Go to the pub.dev homepage">
42+
<img src="/assets/images/branding/dart/logo.svg" alt="Dart logo" width="28" height="28">
43+
<span>pub.dev</span>
44+
</a></li>
45+
</ul>
46+
</nav>
47+
</div>
48+
</div>
2549

2650
<div class="navbar-contents">
2751
<ul class="navbar-nav">
@@ -48,6 +72,15 @@
4872
<span class="material-symbols" aria-hidden="true">search</span>
4973
</a>
5074
<a id="call-to-action" class="filled-button" href="/get-started/install/">Get started</a>
75+
<button
76+
id="menu-toggle"
77+
class="icon-button"
78+
type="button"
79+
aria-controls="sidenav"
80+
aria-label="Toggle navigation"
81+
title="Toggle navigation">
82+
<span class="material-symbols" aria-hidden="true">menu</span>
83+
</button>
5184
</div>
5285
</nav>
5386
</header>

src/_sass/components/_header.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
flex-wrap: wrap;
1515
align-items: center;
1616
justify-content: space-between;
17-
padding: .5rem 1rem;
17+
padding: 0.5rem 0.75rem;
1818

1919
min-height: $site-header-height;
2020

2121
#menu-toggle {
22-
margin-right: 0.75rem;
22+
margin-left: 0.25rem;
2323

2424
@media (min-width: 1024px) {
2525
display: none;
@@ -72,7 +72,7 @@
7272
padding: 0.5rem 1rem !important;
7373
display: none;
7474

75-
@media (min-width: 768px) {
75+
@media (min-width: 1024px) {
7676
display: unset;
7777
}
7878
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
@use '../base/mixins';
2+
@use '../base/variables' as *;
3+
4+
.dropdown {
5+
position: relative;
6+
display: inline-flex;
7+
justify-content: center;
8+
}
9+
10+
button.dropdown-button {
11+
padding: 0.4rem 0.6rem;
12+
border-radius: 0.25rem;
13+
14+
.material-symbols:last-child {
15+
margin-left: 0.4rem;
16+
color: rgba(85, 85, 85, 0.6);
17+
font-size: 1rem;
18+
width: 0.7rem;
19+
}
20+
21+
&:hover {
22+
.material-symbols {
23+
color: rgba(85, 85, 85, 0.8);
24+
}
25+
}
26+
}
27+
28+
.dropdown-content {
29+
display: none;
30+
position: absolute;
31+
background-color: var(--site-switcher-bg, #ffffff);
32+
box-shadow: 0 6px 18px 0 rgba(0, 0, 0, 0.2);
33+
z-index: 1060;
34+
border-radius: 0.4rem;
35+
width: max-content;
36+
top: 2.25rem;
37+
transform: scale(0.9);
38+
39+
&.show {
40+
display: block;
41+
}
42+
43+
.dropdown-menu {
44+
padding: 0.4rem;
45+
46+
ul {
47+
display: flex;
48+
flex-direction: column;
49+
list-style: none;
50+
padding: 0;
51+
margin: 0;
52+
53+
li {
54+
padding: 0.3rem;
55+
56+
a {
57+
display: flex;
58+
align-items: center;
59+
flex-direction: row;
60+
61+
text-decoration: none;
62+
}
63+
}
64+
}
65+
66+
.dropdown-divider {
67+
background-color: #e7e8ed;
68+
border-radius: 0.5rem;
69+
height: 0.125rem;
70+
margin: 0.25rem;
71+
padding: 0;
72+
}
73+
}
74+
}
75+
76+
.site-wordmark {
77+
padding: 0.4rem 0.6rem;
78+
border-radius: 0.25rem;
79+
align-items: center;
80+
display: flex;
81+
flex-direction: row;
82+
cursor: pointer;
83+
84+
font-variant-ligatures: none;
85+
font-size: 1.75rem;
86+
line-height: 1.25em;
87+
letter-spacing: 0.015em;
88+
font-family: 'Google Sans', sans-serif;
89+
user-select: none;
90+
91+
> img {
92+
width: 28px;
93+
margin-right: 0.75rem;
94+
}
95+
96+
&.current-site {
97+
background-color: rgba(194, 229, 255, 0.4);
98+
}
99+
100+
&:hover {
101+
@include mixins.interaction-style(4%);
102+
}
103+
104+
&:active {
105+
@include mixins.interaction-style(6%);
106+
}
107+
108+
span {
109+
color: #4a4a4a;
110+
}
111+
112+
span.subtype {
113+
padding: 0 0.3rem;
114+
font-size: 1.25rem;
115+
font-weight: 500;
116+
color: $site-color-body;
117+
line-height: 1.3;
118+
border-radius: 0.25rem;
119+
background-color: rgb(194, 229, 255);
120+
margin-left: 0.4rem;
121+
letter-spacing: normal;
122+
}
123+
}

src/_sass/site.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
@use 'components/next-prev-nav';
2222
@use 'components/pill';
2323
@use 'components/sidebar';
24+
@use 'components/site-switcher';
2425
@use 'components/tabs';
2526
@use 'components/toc';
2627

Lines changed: 26 additions & 0 deletions
Loading

src/content/assets/js/main.js

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,15 +253,66 @@ function setUpCodeBlockButtons() {
253253
});
254254
}
255255

256+
function setupSiteSwitcher() {
257+
const siteSwitcher = document.getElementById('site-switcher');
258+
259+
if (!siteSwitcher) {
260+
return;
261+
}
262+
263+
const siteSwitcherButton = siteSwitcher.querySelector('.dropdown-button');
264+
const siteSwitcherMenu = siteSwitcher.querySelector('#site-switcher-menu');
265+
if (!siteSwitcherButton || !siteSwitcherMenu) {
266+
return;
267+
}
268+
269+
function _closeMenusAndToggle() {
270+
siteSwitcherMenu.classList.remove('show');
271+
siteSwitcherButton.ariaExpanded = 'false';
272+
}
273+
274+
siteSwitcherButton.addEventListener('click', (_) => {
275+
if (siteSwitcherMenu.classList.contains('show')) {
276+
_closeMenusAndToggle();
277+
} else {
278+
siteSwitcherMenu.classList.add('show');
279+
siteSwitcherButton.ariaExpanded = 'true';
280+
}
281+
});
282+
283+
document.addEventListener('keydown', (event) => {
284+
// If pressing the `esc` key in the menu area, close the menu.
285+
if (event.key === 'Escape' && event.target.closest('#site-switcher')) {
286+
_closeMenusAndToggle();
287+
}
288+
});
289+
290+
siteSwitcher.addEventListener('focusout', (e) => {
291+
// If focus leaves the site-switcher, hide the menu.
292+
if (e.relatedTarget && !e.relatedTarget.closest('#site-switcher')) {
293+
_closeMenusAndToggle();
294+
}
295+
});
296+
297+
document.addEventListener('click', (event) => {
298+
// If not clicking inside the site switcher, close the menu.
299+
if (!event.target.closest('#site-switcher')) {
300+
_closeMenusAndToggle();
301+
}
302+
});
303+
}
304+
256305
document.addEventListener("DOMContentLoaded", function(_) {
257-
adjustToc();
258-
setupInlineToc();
259306
scrollSidenavIntoView();
260307
initCookieNotice();
261308

262309
setupSidenavInteractivity();
263310
setUpCodeBlockButtons();
264311

265312
setupSearch();
313+
setupSiteSwitcher();
266314
setupTabs();
315+
316+
adjustToc();
317+
setupInlineToc();
267318
});

0 commit comments

Comments
 (0)