Skip to content
142 changes: 110 additions & 32 deletions src/components/navigation/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
PUBLIC_NAV_BAR_LINKS
} from '$env/static/public';
import { onMount } from 'svelte';
import ThemeSwitcher from '$lib/ThemeSwitch/ThemeSwitcher.svelte';
import MobileMenu from './MobileMenu.svelte';
let isMobileMenuOpen = $state(false);
let shouldShowMobile = $state(false);
let navContainer;
let linksContainer = $state(null);
let linksWidthCache = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename this to cachedLinksWidth

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

let headerLinks = $state(null);
Expand All @@ -19,13 +24,80 @@
function toggleNavbar() {
isMobileMenuOpen = !isMobileMenuOpen;
}
function checkOverflow() {
if (!navContainer) return;
const navWidth = navContainer.clientWidth;
const logoWidth = navContainer.querySelector('.logo-container')?.clientWidth || 0;
const themeWidth = 48;
const availableWidth = navWidth - logoWidth - themeWidth - 32;
let linksWidth = 0;
if (linksContainer) {
linksWidth = linksContainer.scrollWidth;
linksWidthCache = linksWidth;
} else {
linksWidth = linksWidthCache;
}
const newShouldShowMobileMenu = linksWidth > availableWidth;
if (shouldShowMobile !== newShouldShowMobileMenu) {
shouldShowMobile = newShouldShowMobileMenu;
}
}
function measureLinksWidth() {
if (headerLinks && !linksContainer) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an early return instead of deep nesting:

if (!headerLinks || linksContainer) {
  return;
}

Note: this logic ^^ may be incorrect! You'll need to test and validate!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, correct. I have updated the function to use an early return to avoid deep nesting and make the code more readable.

const tempDiv = document.createElement('div');
tempDiv.style.position = 'absolute';
tempDiv.style.visibility = 'hidden';
tempDiv.style.display = 'flex';
tempDiv.style.gap = '1rem';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does the gap here and the padding on line 62 come from? Are these derived from the current style values on the page? If so, those should be reused somehow in case the styling changes later.

Copy link
Collaborator Author

@tarunsinghofficial tarunsinghofficial Mar 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gap and padding are derived from actual style values used in the page here (<a href={value} class="block px-2 py-1 font-semibold text-gray-900 dark:text-white" >{key}</a >).

For ease, I've now defined constants in the file with clear comments explaining which Tailwind classes they correspond to.

Note: If the styles change later, we just have to update these constants according to the corresponding Tailwind classes from the template. That's the best & easiest approach to implement, I think :)

if (Object.keys(headerLinks).length > 0) {
Object.keys(headerLinks).forEach((key) => {
const linkDiv = document.createElement('div');
linkDiv.style.padding = '0.25rem 0.5rem';
linkDiv.style.flexShrink = '0';
linkDiv.textContent = key;
tempDiv.appendChild(linkDiv);
});
} else {
const linkDiv = document.createElement('div');
linkDiv.style.width = '600px';
tempDiv.appendChild(linkDiv);
}
document.body.appendChild(tempDiv);
linksWidthCache = tempDiv.scrollWidth;
document.body.removeChild(tempDiv);
}
}
onMount(() => {
measureLinksWidth();
checkOverflow();
const headerResizeObserver = new ResizeObserver(() => {
checkOverflow();
});
headerResizeObserver.observe(navContainer);
return () => {
headerResizeObserver.disconnect();
};
});
</script>
<div
class="bg-blur-md flex items-center justify-between border-b border-gray-500 bg-white/80 px-4 dark:bg-black dark:text-white md:flex-row md:px-8"
bind:this={navContainer}
>
<div class="flex flex-1 items-center justify-between md:flex-none">
<div class="flex w-full justify-between gap-4 px-2 py-2 md:w-auto">
<div class="logo-container flex w-full items-center gap-4 px-2 py-2 md:w-auto">
<div class="flex items-center justify-center gap-x-2">
<a href="/" class="block">
<img src={PUBLIC_OBA_LOGO_URL} alt={PUBLIC_OBA_REGION_NAME} class="h-10 rounded-sm" />
Expand All @@ -34,42 +106,48 @@
{PUBLIC_OBA_REGION_NAME}
</a>
</div>

<div class="flex items-center justify-end md:hidden">
<button onclick={toggleNavbar} aria-label="Toggle navigation menu">
<svg
class="burger-icon h-6 w-6 text-gray-900 dark:text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16m-7 6h7"
></path>
</svg>
</button>
</div>
</div>
<div class="hidden items-center gap-4 px-2 py-2 md:flex">
<div class="flex gap-x-4">
{#each Object.entries(headerLinks) as [key, value]}
<div class="rounded-md border bg-white/80 dark:bg-gray-800">
<a href={value} class="block px-2 py-1 font-semibold text-gray-900 dark:text-white"
>{key}</a
>
</div>
{/each}
{#if !shouldShowMobile}
<div class="flex items-center px-2 py-2" bind:this={linksContainer}>
<div class="no-scrollbar flex gap-x-4 overflow-x-auto">
{#if headerLinks && Object.keys(headerLinks).length > 0}
{#each Object.entries(headerLinks) as [key, value]}
<div class="flex-shrink-0 rounded-md border bg-white/80 dark:bg-gray-800">
<a href={value} class="block px-2 py-1 font-semibold text-gray-900 dark:text-white"
>{key}</a
>
</div>
{/each}
{/if}
</div>
</div>
</div>
{/if}
</div>
<div class="hidden md:flex">
<ThemeSwitcher />
<div class="flex items-center">
{#if shouldShowMobile}
<button onclick={toggleNavbar} aria-label="Toggle navigation menu" class="mr-2">
<svg
class="burger-icon h-6 w-6 text-gray-900 dark:text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16m-7 6h7"
></path>
</svg>
</button>
{:else}
<div class={shouldShowMobile ? '' : 'flex'}>
<ThemeSwitcher />
</div>
{/if}
</div>
</div>
Expand Down