ux: move support and theme buttons to topnav (#23243)

Move the contact support button and the theme switch button to the top
nav bar.
This commit is contained in:
Arthur
2025-08-15 10:51:55 +02:00
committed by GitHub
parent 5e8b1da6b1
commit 39ff715091
15 changed files with 258 additions and 187 deletions

View File

@@ -201,6 +201,7 @@
--color-yellow-outlinedborder: rgba(235, 156, 0, 0.56);
--color-yellow-selected: rgba(235, 156, 0, 0.16);
--topnav-button-bg: #4878f3;
--tw-prose-code-bg: var(--color-gray-100);
--tw-prose-code-bg-dark: var(--color-gray-800);
}

View File

@@ -249,10 +249,17 @@
@utility breadcrumbs {
font-size: 90%;
}
@utility topbar-button {
@apply text-white font-semibold min-h-10 px-2 bg-blue-300/50 rounded-md border-1 border-blue-300 inline-flex justify-center items-center gap-1.5 hover:bg-blue-400/70 hover:border-blue-200 transition-colors;
@apply text-center max-w-40 text-white font-semibold min-h-10 px-2 bg-(--topnav-button-bg) rounded-md border-1 border-blue-300;
@apply inline-flex justify-center items-center gap-1.5 hover:bg-blue-400 hover:border-blue-300 transition-colors;
svg {
font-size: 19px;
}
}
@utility topbar-button-clear {
@apply text-center text-white/95 font-semibold min-h-9 px-0 hover:text-white/85 transition-colors;
svg {
font-size: 19px;
}
}

3
assets/icons/headset.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke="currentColor" stroke-width="0.5" d="M8.55237 2.00563C8.09228 1.53094 7.54504 1.15362 6.94199 0.895266C6.33893 0.636909 5.69191 0.50259 5.03796 0.5H4.99998C3.6739 0.5 2.40214 1.03807 1.46446 1.99585C0.526782 2.95362 0 4.25264 0 5.60714V8.35714C0 8.66972 0.121565 8.96949 0.337953 9.19052C0.55434 9.41154 0.847824 9.53571 1.15384 9.53571H1.92307C2.22909 9.53571 2.52257 9.41154 2.73896 9.19052C2.95535 8.96949 3.07691 8.66972 3.07691 8.35714V6.39286C3.07691 6.08028 2.95535 5.78051 2.73896 5.55948C2.52257 5.33846 2.22909 5.21429 1.92307 5.21429H0.786536C0.86071 4.39515 1.16194 3.61457 1.65491 2.96405C2.14788 2.31353 2.81218 1.82002 3.56994 1.54135C4.3277 1.26268 5.14752 1.21041 5.93333 1.39066C6.71913 1.57091 7.43835 1.97621 8.0067 2.55906C8.70136 3.27225 9.12864 4.21204 9.21391 5.21429H8.07689C7.77088 5.21429 7.47739 5.33846 7.261 5.55948C7.04462 5.78051 6.92305 6.08028 6.92305 6.39286V8.35714C6.92305 8.66972 7.04462 8.96949 7.261 9.19052C7.47739 9.41154 7.77088 9.53571 8.07689 9.53571H9.23074C9.23074 9.84829 9.10917 10.1481 8.89278 10.3691C8.6764 10.5901 8.38291 10.7143 8.07689 10.7143H5.3846C5.28259 10.7143 5.18476 10.7557 5.11263 10.8294C5.0405 10.903 4.99998 11.003 4.99998 11.1071C4.99998 11.2113 5.0405 11.3113 5.11263 11.3849C5.18476 11.4586 5.28259 11.5 5.3846 11.5H8.07689C8.58692 11.5 9.07606 11.293 9.43671 10.9247C9.79736 10.5563 9.99996 10.0567 9.99996 9.53571V5.60714C10.0025 4.93908 9.87589 4.27707 9.62749 3.65906C9.37908 3.04104 9.01373 2.47917 8.55237 2.00563ZM1.92307 6C2.02508 6 2.1229 6.04139 2.19503 6.11506C2.26716 6.18874 2.30768 6.28866 2.30768 6.39286V8.35714C2.30768 8.46133 2.26716 8.56126 2.19503 8.63494C2.1229 8.70861 2.02508 8.75 1.92307 8.75H1.15384C1.05184 8.75 0.954008 8.70861 0.881879 8.63494C0.80975 8.56126 0.769228 8.46133 0.769228 8.35714V6H1.92307ZM8.07689 8.75C7.97489 8.75 7.87706 8.70861 7.80493 8.63494C7.7328 8.56126 7.69228 8.46133 7.69228 8.35714V6.39286C7.69228 6.28866 7.7328 6.18874 7.80493 6.11506C7.87706 6.04139 7.97489 6 8.07689 6H9.23074V8.75H8.07689Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

8
assets/icons/moon.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- moon icon using path, stroke currentColor -->
<path d="M22 15.8442C20.6866 16.4382 19.2286 16.7688 17.6935 16.7688C11.9153 16.7688 7.23116 12.0847 7.23116 6.30654C7.23116 4.77135 7.5618 3.3134 8.15577 2C4.52576 3.64163 2 7.2947 2 11.5377C2 17.3159 6.68414 22 12.4623 22C16.7053 22 20.3584 19.4742 22 15.8442Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>
<rect width="24" height="24" fill="currentColor" fill-opacity="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 608 B

View File

@@ -1,8 +1,8 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_206_64" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="1" y="1" width="16" height="16">
<path d="M15.75 15.75L12.4875 12.4875M14.25 8.25C14.25 11.5637 11.5637 14.25 8.25 14.25C4.93629 14.25 2.25 11.5637 2.25 8.25C2.25 4.93629 4.93629 2.25 8.25 2.25C11.5637 2.25 14.25 4.93629 14.25 8.25Z" stroke="#6C7E9D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</mask>
<g mask="url(#mask0_206_64)">
<!-- search icon using path, stroke currentColor -->
<path d="M15.75 15.75L12.4875 12.4875M14.25 8.25C14.25 11.5637 11.5637 14.25 8.25 14.25C4.93629 14.25 2.25 11.5637 2.25 8.25C2.25 4.93629 4.93629 2.25 8.25 2.25C11.5637 2.25 14.25 4.93629 14.25 8.25Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>
<rect width="24" height="24" fill="white" fill-opacity="0.85"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 605 B

After

Width:  |  Height:  |  Size: 541 B

View File

@@ -1,8 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_5432_1814" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="1" y="0" width="22" height="23">
<path d="M18.5 7.84656V2.84656M5.5 20.8466V15.8466M16 5.34656H21M3 18.3466H8M6.5 1.84656L5.71554 3.41547C5.45005 3.94645 5.31731 4.21194 5.13997 4.442C4.98261 4.64615 4.79959 4.82917 4.59545 4.98653C4.36538 5.16387 4.0999 5.29661 3.56892 5.5621L2 6.34656L3.56892 7.13102C4.0999 7.39651 4.36538 7.52925 4.59545 7.70659C4.79959 7.86395 4.98261 8.04696 5.13997 8.25111C5.31731 8.48117 5.45005 8.74666 5.71554 9.27764L6.5 10.8466L7.28446 9.27764C7.54995 8.74666 7.68269 8.48117 7.86003 8.25111C8.01739 8.04696 8.20041 7.86395 8.40455 7.70659C8.63462 7.52925 8.9001 7.3965 9.43108 7.13102L11 6.34656L9.43108 5.5621C8.9001 5.29661 8.63462 5.16387 8.40455 4.98653C8.20041 4.82917 8.01739 4.64615 7.86003 4.442C7.68269 4.21194 7.54995 3.94645 7.28446 3.41547L6.5 1.84656ZM17 11.8466L16.0489 13.7488C15.7834 14.2798 15.6506 14.5453 15.4733 14.7753C15.3159 14.9795 15.1329 15.1625 14.9288 15.3199C14.6987 15.4972 14.4332 15.6299 13.9023 15.8954L12 16.8466L13.9023 17.7977C14.4332 18.0632 14.6987 18.1959 14.9288 18.3733C15.1329 18.5306 15.3159 18.7136 15.4733 18.9178C15.6506 19.1478 15.7834 19.4133 16.0489 19.9443L17 21.8466L17.9511 19.9443C18.2166 19.4133 18.3494 19.1478 18.5267 18.9178C18.6841 18.7136 18.8671 18.5306 19.0712 18.3733C19.3013 18.1959 19.5668 18.0632 20.0977 17.7977L22 16.8466L20.0977 15.8954C19.5668 15.6299 19.3013 15.4972 19.0712 15.3199C18.8671 15.1625 18.6841 14.9795 18.5267 14.7753C18.3494 14.5453 18.2166 14.2798 17.9511 13.7488L17 11.8466Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</mask>
<g mask="url(#mask0_5432_1814)">
<!-- sparkle icon using path, stroke currentColor -->
<path d="M18.5 7.84656V2.84656M5.5 20.8466V15.8466M16 5.34656H21M3 18.3466H8M6.5 1.84656L5.71554 3.41547C5.45005 3.94645 5.31731 4.21194 5.13997 4.442C4.98261 4.64615 4.79959 4.82917 4.59545 4.98653C4.36538 5.16387 4.0999 5.29661 3.56892 5.5621L2 6.34656L3.56892 7.13102C4.0999 7.39651 4.36538 7.52925 4.59545 7.70659C4.79959 7.86395 4.98261 8.04696 5.13997 8.25111C5.31731 8.48117 5.45005 8.74666 5.71554 9.27764L6.5 10.8466L7.28446 9.27764C7.54995 8.74666 7.68269 8.48117 7.86003 8.25111C8.01739 8.04696 8.20041 7.86395 8.40455 7.70659C8.63462 7.52925 8.9001 7.3965 9.43108 7.13102L11 6.34656L9.43108 5.5621C8.9001 5.29661 8.63462 5.16387 8.40455 4.98653C8.20041 4.82917 8.01739 4.64615 7.86003 4.442C7.68269 4.21194 7.54995 3.94645 7.28446 3.41547L6.5 1.84656ZM17 11.8466L16.0489 13.7488C15.7834 14.2798 15.6506 14.5453 15.4733 14.7753C15.3159 14.9795 15.1329 15.1625 14.9288 15.3199C14.6987 15.4972 14.4332 15.6299 13.9023 15.8954L12 16.8466L13.9023 17.7977C14.4332 18.0632 14.6987 18.1959 14.9288 18.3733C15.1329 18.5306 15.3159 18.7136 15.4733 18.9178C15.6506 19.1478 15.7834 19.4133 16.0489 19.9443L17 21.8466L17.9511 19.9443C18.2166 19.4133 18.3494 19.1478 18.5267 18.9178C18.6841 18.7136 18.8671 18.5306 19.0712 18.3733C19.3013 18.1959 19.5668 18.0632 20.0977 17.7977L22 16.8466L20.0977 15.8954C19.5668 15.6299 19.3013 15.4972 19.0712 15.3199C18.8671 15.1625 18.6841 14.9795 18.5267 14.7753C18.3494 14.5453 18.2166 14.2798 17.9511 13.7488L17 11.8466Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>
<rect width="24" height="24" fill="currentColor" fill-opacity="1"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

8
assets/icons/sun.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- sun icon using path, stroke currentColor -->
<path d="M12 2V4M12 20V22M4 12H2M6.31412 6.31412L4.8999 4.8999M17.6859 6.31412L19.1001 4.8999M6.31412 17.69L4.8999 19.1042M17.6859 17.69L19.1001 19.1042M22 12H20M17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 9.23858 9.23858 7 12 7C14.7614 7 17 9.23858 17 12Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>
<rect width="24" height="24" fill="currentColor" fill-opacity="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 619 B

View File

@@ -330,7 +330,6 @@
"hidden",
"hidden'",
"highlight",
"hover:bg-blue",
"hover:bg-blue-400",
"hover:bg-blue-400/90",
"hover:bg-blue-500",
@@ -338,7 +337,6 @@
"hover:bg-gray-200",
"hover:bg-gray-50",
"hover:border-white/20",
"hover:dark:bg-blue-500",
"hover:dark:bg-gray-800",
"hover:dark:text-blue-400",
"hover:dark:text-blue-700",
@@ -375,6 +373,7 @@
"lg:grid-cols-3",
"lg:grid-cols-4",
"lg:hidden",
"lg:inline",
"lg:no-underline",
"lg:pb-2",
"lg:scale-100",
@@ -446,8 +445,6 @@
"object-cover",
"open-kapa-widget",
"openSUSE-and-SLES",
"order-1",
"order-2",
"origin-bottom-right",
"origin-top-right",
"ot-sdk-show-settings",
@@ -563,14 +560,13 @@
"top-16",
"top-6",
"topbar-button",
"topbar-button-clear",
"transition",
"transition-colors",
"transition-transform",
"truncate",
"underline-offset-2",
"w-0",
"w-2",
"w-32",
"w-5",
"w-65",
"w-8",

View File

@@ -49,6 +49,40 @@
>
Back
</button>
<!-- Top nav buttons appear here on sm width -->
<div class="flex flex-col gap-2 md:hidden mb-4">
<button
@click="open = false"
class="topbar-button open-kapa-widget w-full flex items-center gap-2"
>
<span>Ask&nbsp;AI</span>
<span class="icon-svg">
{{ partialCached "utils/svg.html" "/icons/sparkle.svg" }}
</span>
</button>
<div class="topbar-button w-full flex items-center gap-2" id="search-bar-container">
{{ partialCached "search-bar.html" "-" }}
</div>
{{ partial "contact-support-button.html" .}}
<button
aria-label="Theme switch"
id="theme-switch"
class="topbar-button flex items-center"
x-data="{ theme: localStorage.getItem('theme-preference') }"
x-init="$watch('theme', value => {
localStorage.setItem('theme-preference', value);
document.firstElementChild.className = value;
})"
@click="theme = (theme === 'dark' ? 'light' : 'dark')"
>
<span class="icon-svg icon-sm dark:hidden"
>{{ partialCached "icon" "icons/sun.svg" "sun" }}
</span>
<span class="icon-svg icon-sm hidden dark:block">
{{ partialCached "icon" "icons/moon.svg" "moon" }}
</span>
</button>
</div>
<!-- Actual Sidebar Content -->
{{ block "left" . }}
{{ partial "sidebar/mainnav.html" . }}

View File

@@ -11,7 +11,7 @@
<input type="search" id="search-page-input"
class="ring-3-gray-light-200 dark:ring-3-gray-dark-400 dark:bg-background-dark focus:ring-3-blue-light dark:focus:ring-3-blue-dark ring-3-[1.5px] w-full max-w-xl min-w-0 rounded-sm bg-white px-4 py-2 outline-hidden"
placeholder="Search…" tabindex="0" />
<div class="admonition flex flex-row min-w-fit items-center w-0 gap-1">
<div class="admonition flex flex-row min-w-fit items-center gap-1">
<p>Not finding what you're looking for? Try</p>
<button onclick="askAI('search-page-input')" class="topbar-button bg-blue-400/95 border-blue-300 hover:bg-blue-400/90">
<span>Ask&nbsp;AI</span>
@@ -19,7 +19,6 @@
{{ partial "utils/svg.html" "/icons/sparkle.svg" }}
</span>
</button>
</div>
</div>
<hr class="border-divider-light dark:border-divider-dark" />
<div id="search-page-results">

View File

@@ -1,7 +0,0 @@
<a
href="https://hub.docker.com/support/contact"
class="hover:bg-blue bg-blue z-50 flex items-center rounded-sm px-4 py-2 text-white dark:bg-blue-400 hover:dark:bg-blue-500"
aria-label="Contact support"
>
<span>Contact support</span>
</a>

View File

@@ -0,0 +1,12 @@
{{ $class := "" }}
{{ if eq . "notext" }}
{{ $class = "hidden lg:inline" }}
{{ end }}
<a class="topbar-button w-full flex items-center gap-2"
href="https://hub.docker.com/support/contact"
aria-label="Contact support">
<span class="{{ $class }}">Contact support</span>
<span class="icon-svg icon-sm">
{{ partial "utils/svg.html" "/icons/headset.svg" }}
</span>
</a>

View File

@@ -1,8 +1,4 @@
<div
class="relative z-10 flex justify-center bg-gray-100 px-4 py-8 dark:bg-gray-900"
>
{{ partialCached "components/support-button.html" . }}
</div>
<div class="flex justify-center bg-gray-100 px-4 pt-10 pb-20 dark:bg-gray-900">
<div class="flex w-full max-w-[840px] flex-col gap-10">
<div class="flex flex-col items-center justify-evenly gap-4 md:flex-row">
@@ -95,27 +91,6 @@
<button type="button" id="ot-sdk-btn" class="ot-sdk-show-settings">
Cookies Settings
</button>
<div class="flex items-center gap-2">
<span>Theme:</span>
<button
aria-label="Theme switch"
id="theme-switch"
class="bg-blue rounded-sm px-4 py-1 text-white transition hover:bg-blue-400 dark:bg-blue-400 dark:hover:bg-blue-500"
x-data="{ theme: localStorage.getItem('theme-preference') }"
x-init="$watch('theme', value => {
localStorage.setItem('theme-preference', value);
document.firstElementChild.className = value;
})"
@click="theme = (theme === 'dark' ? 'light' : 'dark')"
>
<span class="icon-svg icon-sm dark:hidden"
>Light {{ partialCached "icon" "light_mode" "light_mode" }}</span
>
<span class="icon-svg icon-sm hidden dark:block"
>Dark {{ partialCached "icon" "dark_mode" "dark_mode" }}</span
>
</button>
</div>
</div>
</div>
</div>

View File

@@ -45,19 +45,51 @@
</ul>
</nav>
</div>
<div id="buttons" class="flex min-w-0 items-center justify-end gap-4 flex-shrink-0">
<button
@click="open = false"
class="topbar-button open-kapa-widget"
>
<span>Ask&nbsp;AI</span>
<span class="icon-svg-stroke">
{{ partial "utils/svg.html" "/icons/sparkle.svg" }}
</span>
</button>
<div class="topbar-button" id="search-bar-container">
{{ partialCached "search-bar.html" "-" }}
<div id="buttons" class="flex min-w-0 items-center justify-end flex-shrink-0">
<!-- Hide on small screens, show on md+ -->
<div class="hidden md:flex items-center gap-3">
<button
@click="open = false"
class="topbar-button open-kapa-widget"
>
<span class="hidden lg:inline">Ask&nbsp;AI</span>
<span class="icon-svg">
{{ partialCached "utils/svg.html" "/icons/sparkle.svg" }}
</span>
</button>
<div class="topbar-button" id="search-bar-container">
{{ partialCached "search-bar.html" "-" }}
</div>
{{ partial "contact-support-button.html" "notext" }}
<button
aria-label="Theme switch"
id="theme-switch"
class="topbar-button-clear"
x-data="{ theme: localStorage.getItem('theme-preference') }"
x-init="$watch('theme', value => {
localStorage.setItem('theme-preference', value);
document.firstElementChild.className = value;
})"
@click="theme = (theme === 'dark' ? 'light' : 'dark')"
>
<span class="icon-svg icon-sm dark:hidden"
>{{ partialCached "icon" "icons/sun.svg" "sun" }}
</span>
<span class="icon-svg icon-sm hidden dark:block">
{{ partialCached "icon" "icons/moon.svg" "moon" }}
</span>
</button>
</div>
<!-- On sm screens, hide all buttons here (they will be in sidebar) -->
</div>
</div>
</header>
</button>
</div>
<!-- On sm screens, hide all buttons here (they will be in sidebar) -->
</div>
</div>
</header>

View File

@@ -1,129 +1,132 @@
<div
x-ref="searchBarRef"
x-data="{ open: false }"
@click.outside="open = false;"
@keyup.escape.window="open = false"
id="search-bar"
class="relative w-32 items-center flex"
>
<input
x-ref="searchBarInput"
type="search"
id="search-bar-input"
placeholder="Search"
@focus="open = true;"
@keyup.enter.prevent="window.location.href = '/search/?q=' + $event.target.value;"
@keyup.escape.prevent="open = false;"
@keydown.window="(e) => {
switch(e.key) {
case 'k':
if (e.metaKey || e.ctrlKey) {
e.preventDefault();
$el.focus();
x-ref="searchBarRef"
x-data="{ open: false }"
@click.outside="open = false;"
@keyup.escape.window="open = false"
id="search-bar"
class="relative items-center flex w-full max-w-full overflow-x-auto"
>
<input
class="border-none outline-none focus:outline-none min-w-0"
x-ref="searchBarInput"
type="search"
id="search-bar-input"
placeholder="Search"
@focus="open = true;"
@keyup.enter.prevent="window.location.href = '/search/?q=' + $event.target.value;"
@keyup.escape.prevent="open = false;"
@keydown.window="(e) => {
switch(e.key) {
case 'k':
if (e.metaKey || e.ctrlKey) {
e.preventDefault();
$el.focus();
}
break;
}
break;
}
}"
class="flex-grow order-1 min-w-0 border-none outline-none focus:outline-none"
tabindex="0"
/>
<div id="search-bar-icon" class="order-2">
<span class="icon-svg-stroke">
{{ partial "utils/svg.html" "/icons/search.svg" }}
</span>
</div>
<div id="search-bar-dropdown" x-show="open" x-cloak x-ref="dropdown"
class="font-medium text-gray-400 dark:text-gray-200 bg-gray-50 dark:bg-gray-900 rounded-sm mt-4 border-1 border-gray-100 dark:border-gray-700 fixed z-[999] w-[500px] p-6 shadow-md"
x-effect="if (open) {
const containerRect = document.getElementById('search-bar-container').getBoundingClientRect();
const rect = $refs.searchBarRef.getBoundingClientRect();
$el.style.top = (rect.bottom + window.scrollY - 2) + 'px';
$el.style.right = (window.innerWidth - containerRect.right - 15) + 'px';
}">
<div id="search-bar-results">
{{- $emptyState := `<div>Start typing to search or try <button onclick="askAI('search-bar-input')" class="link">Ask
AI</button>.</div>` }}
{{- $emptyState | safe.HTML }}
<!-- results -->
}"
tabindex="0"
/>
<div id="search-bar-icon">
<span class="icon-svg-stroke">
{{ partial "utils/svg.html" "/icons/search.svg" }}
</span>
</div>
</div>
<script type="module">
window.addEventListener("load", async function () {
const pagefind = await import("/pagefind/pagefind.js");
await pagefind.options({
ranking: {
termFrequency: 0.2,
pageLength: 0.75,
termSaturation: 1.4,
termSimilarity: 6.0
},
});
const searchBarInput = document.querySelector("#search-bar-input");
const searchBarResults = document.querySelector(
"#search-bar-results",
);
async function search(e) {
const query = e.target.value;
if (query === "") {
searchBarResults.innerHTML = `{{ $emptyState | safe.HTML }}`;
return;
}
const search = await pagefind.debouncedSearch(query);
if (search === null) {
return;
} else {
const resultsLength = search.results.length
const resultsData = await Promise.all(search.results.slice(0, 5).map(r => r.data()));
const results = resultsData.map((item, index) => ({...item, index: index + 1}));
if (query) {
searchBarResults.classList.remove("hidden");
} else {
searchBarResults.classList.add("hidden");
}
let resultsHTML = `<div class="p-2 text-gray-400 dark:text-gray-500">${resultsLength} results</div>`;
resultsHTML += results
.map((item) => {
return `<div class="p-2">
<div class="flex flex-col">
<a class="link" href="${item.url}" data-query="${query}" data-index="${item.index}">${item.meta.title}</a>
<p class="text-black dark:text-white overflow-hidden">…${item.excerpt}…</p>
</div>
</div>`;
})
.join("");
if (resultsLength > 5) {
resultsHTML += `<div class="w-fit ml-auto px-4 py-2"><a href="/search/?q=${query}" class="link">Show all results</a></div>`;
}
searchBarResults.innerHTML = resultsHTML;
}
}
searchBarInput.addEventListener("input", search);
// Event delegation for tracking link clicks
if (window.heap !== undefined) {
searchBarResults.addEventListener('click', function (event) {
if (event.target.tagName === 'A' && event.target.closest('.link')) {
const searchQuery = event.target.getAttribute('data-query');
const resultIndex = event.target.getAttribute('data-index');
const url = new URL(event.target.href);
const properties = {
docs_search_target_path: url.pathname,
docs_search_target_title: event.target.textContent,
docs_search_query_text: searchQuery,
docs_search_target_index: resultIndex,
docs_search_source_path: window.location.pathname,
docs_search_source_title: document.title,
};
heap.track("Docs - Search - Click - Result Link", properties);
}
<div id="search-bar-dropdown"
x-show="open"
x-cloak
x-ref="dropdown"
class="hidden md:block font-medium text-gray-400 dark:text-gray-200 bg-gray-50 dark:bg-gray-900 rounded-sm mt-4 border-1 border-gray-100 dark:border-gray-700 fixed z-[999] w-[500px] p-6 shadow-md"
x-effect="if (open) {
const containerRect = document.getElementById('search-bar-container').getBoundingClientRect();
const rect = $refs.searchBarRef.getBoundingClientRect();
$el.style.top = (rect.bottom + window.scrollY - 2) + 'px';
$el.style.right = (window.innerWidth - containerRect.right - 15) + 'px';
}">
<div id="search-bar-results">
{{- $emptyState := `<div>Start typing to search or try <button onclick="askAI('search-bar-input')" class="link">Ask
AI</button>.</div>` }}
{{- $emptyState | safe.HTML }}
<!-- results -->
</div>
</div>
<script type="module">
window.addEventListener("load", async function () {
const pagefind = await import("/pagefind/pagefind.js");
await pagefind.options({
ranking: {
termFrequency: 0.2,
pageLength: 0.75,
termSaturation: 1.4,
termSimilarity: 6.0
},
});
}
});
</script>
const searchBarInput = document.querySelector("#search-bar-input");
const searchBarResults = document.querySelector(
"#search-bar-results",
);
async function search(e) {
const query = e.target.value;
if (query === "") {
searchBarResults.innerHTML = `{{ $emptyState | safe.HTML }}`;
return;
}
const search = await pagefind.debouncedSearch(query);
if (search === null) {
return;
} else {
const resultsLength = search.results.length
const resultsData = await Promise.all(search.results.slice(0, 5).map(r => r.data()));
const results = resultsData.map((item, index) => ({...item, index: index + 1}));
if (query) {
searchBarResults.classList.remove("hidden");
} else {
searchBarResults.classList.add("hidden");
}
let resultsHTML = `<div class="p-2 text-gray-400 dark:text-gray-500">${resultsLength} results</div>`;
resultsHTML += results
.map((item) => {
return `<div class="p-2">
<div class="flex flex-col">
<a class="link" href="${item.url}" data-query="${query}" data-index="${item.index}">${item.meta.title}</a>
<p class="text-black dark:text-white overflow-hidden">…${item.excerpt}…</p>
</div>
</div>`;
})
.join("");
if (resultsLength > 5) {
resultsHTML += `<div class="w-fit ml-auto px-4 py-2"><a href="/search/?q=${query}" class="link">Show all results</a></div>`;
}
searchBarResults.innerHTML = resultsHTML;
}
}
searchBarInput.addEventListener("input", search);
// Event delegation for tracking link clicks
if (window.heap !== undefined) {
searchBarResults.addEventListener('click', function (event) {
if (event.target.tagName === 'A' && event.target.closest('.link')) {
const searchQuery = event.target.getAttribute('data-query');
const resultIndex = event.target.getAttribute('data-index');
const url = new URL(event.target.href);
const properties = {
docs_search_target_path: url.pathname,
docs_search_target_title: event.target.textContent,
docs_search_query_text: searchQuery,
docs_search_target_index: resultIndex,
docs_search_source_path: window.location.pathname,
docs_search_source_title: document.title,
};
heap.track("Docs - Search - Click - Result Link", properties);
}
});
}
});
</script>
</div>