TenantAtlas/apps/website/src/components/sections/navbar&footer/Navbar.astro
ahmido b9c128163b feat: public website launch readiness updates (#394)
## Summary
- apply public website launch readiness updates across the Astro site shell, content, and navigation
- refine website components, metadata, and localization-related structure for launch prep
- update docs/content paths and smoke coverage to match the launch-ready public site state

## Scope
- touches the website app and related spec artifacts for feature 403
- does not modify `apps/platform`

## Testing
- not run in this step

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #394
2026-05-21 21:41:33 +00:00

214 lines
7.0 KiB
Plaintext

---
//Import relevant dependencies
import ThemeIcon from '@components/ThemeIcon.astro';
import NavLink from '@components/ui/links/NavLink.astro';
import Authentication from '../misc/Authentication.astro';
import BrandLogo from '@components/BrandLogo.astro';
import LanguagePicker from '@components/ui/LanguagePicker.astro';
import { getLocaleFromPath, localizeHref } from '@/i18n';
import { siteCopy } from '@data/site-copy';
const locale = getLocaleFromPath(Astro.url.pathname);
const strings = siteCopy[locale];
const homeUrl = localizeHref('/', locale);
type NavItem = {
name: string;
url: string;
};
---
{/* Main header component */}
<header
class="sticky inset-x-0 top-4 z-50 flex w-full flex-wrap text-sm md:flex-nowrap md:justify-start"
>
{/* Navigation container */}
<nav
class="relative mx-2 w-full rounded-[36px] border border-yellow-100/40 bg-yellow-50/60 px-4 py-3 backdrop-blur-md md:flex md:items-center md:justify-between md:px-6 md:py-0 lg:px-8 xl:mx-auto dark:border-neutral-700/40 dark:bg-neutral-800/80 dark:backdrop-blur-md"
aria-label="Global"
>
<div class="flex items-center justify-between">
{/* Brand logo */}
<a
class="flex-none rounded-lg text-xl font-bold ring-zinc-500 outline-hidden focus-visible:ring-3 dark:ring-zinc-200 dark:focus:outline-hidden"
href={homeUrl}
aria-label="Brand"
>
<BrandLogo class="h-auto w-36 sm:w-40" />
</a>
{/* Collapse toggle for smaller screens */}
<div class="mr-5 ml-auto md:hidden">
<button
type="button"
class="hs-collapse-toggle flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold text-neutral-600 transition duration-300 hover:bg-neutral-200 disabled:pointer-events-none disabled:opacity-50 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:focus:outline-hidden"
data-hs-collapse="#navbar-collapse-with-animation"
aria-controls="navbar-collapse-with-animation"
aria-label="Toggle navigation"
>
<svg
class="hs-collapse-open:hidden h-[1.25rem] w-[1.25rem] shrink-0"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="3" x2="21" y1="6" y2="6"></line>
<line x1="3" x2="21" y1="12" y2="12"></line>
<line x1="3" x2="21" y1="18" y2="18"></line>
</svg>
<svg
class="hs-collapse-open:block hidden h-[1.25rem] w-[1.25rem] shrink-0"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</div>
{/* ThemeIcon component specifically for smaller screens */}
<span class="inline-block md:hidden">
<ThemeIcon />
</span>
</div>
{/* Contains navigation links */}
<div
id="navbar-collapse-with-animation"
class="hs-collapse hidden grow basis-full overflow-hidden transition-all duration-300 md:block"
>
{/* Navigation links container */}
<div
class="mt-5 flex flex-col gap-x-0 gap-y-4 md:mt-0 md:flex-row md:items-center md:justify-end md:gap-x-4 md:gap-y-0 md:ps-7 lg:gap-x-7"
>
{/* Navigation links and Authentication component */}
{
strings.nav.map((link: NavItem) => (
<NavLink url={localizeHref(link.url, locale)} name={link.name} />
))
}
<Authentication locale={locale} />
<LanguagePicker />
{/* ThemeIcon component specifically for larger screens */}
<span class="hidden md:inline-block">
<ThemeIcon />
</span>
</div>
</div>
</nav>
</header>
{/* Theme Appearance script to manage light/dark modes */}
<script is:inline>
const HSThemeAppearance = {
init() {
const defaultTheme = 'default';
let theme = localStorage.getItem('hs_theme') || defaultTheme;
if (document.querySelector('html').classList.contains('dark')) return;
this.setAppearance(theme);
},
_resetStylesOnLoad() {
const $resetStyles = document.createElement('style');
$resetStyles.innerText = `*{transition: unset !important;}`;
$resetStyles.setAttribute('data-hs-appearance-onload-styles', '');
document.head.appendChild($resetStyles);
return $resetStyles;
},
setAppearance(theme, saveInStore = true, dispatchEvent = true) {
const $resetStylesEl = this._resetStylesOnLoad();
if (saveInStore) {
localStorage.setItem('hs_theme', theme);
}
if (theme === 'auto') {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'default';
}
document.querySelector('html').classList.remove('dark');
document.querySelector('html').classList.remove('default');
document.querySelector('html').classList.remove('auto');
document
.querySelector('html')
.classList.add(this.getOriginalAppearance());
setTimeout(() => {
$resetStylesEl.remove();
});
if (dispatchEvent) {
window.dispatchEvent(
new CustomEvent('on-hs-appearance-change', { detail: theme })
);
}
},
getAppearance() {
let theme = this.getOriginalAppearance();
if (theme === 'auto') {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'default';
}
return theme;
},
getOriginalAppearance() {
const defaultTheme = 'default';
return localStorage.getItem('hs_theme') || defaultTheme;
},
};
HSThemeAppearance.init();
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => {
if (HSThemeAppearance.getOriginalAppearance() === 'auto') {
HSThemeAppearance.setAppearance('auto', false);
}
});
window.addEventListener('load', () => {
const $clickableThemes = document.querySelectorAll(
'[data-hs-theme-click-value]'
);
const $switchableThemes = document.querySelectorAll(
'[data-hs-theme-switch]'
);
$clickableThemes.forEach($item => {
$item.addEventListener('click', () =>
HSThemeAppearance.setAppearance(
$item.getAttribute('data-hs-theme-click-value'),
true,
$item
)
);
});
$switchableThemes.forEach($item => {
$item.addEventListener('change', e => {
HSThemeAppearance.setAppearance(e.target.checked ? 'dark' : 'default');
});
$item.checked = HSThemeAppearance.getAppearance() === 'dark';
});
window.addEventListener('on-hs-appearance-change', e => {
$switchableThemes.forEach($item => {
$item.checked = e.detail === 'dark';
});
});
});
</script>