This repository has been archived on 2026-08-10. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
drizzli/src/lib/components/navigation/header.svelte
T

157 lines
4.3 KiB
Svelte

<script lang="ts">
import { get } from 'svelte/store';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { type GeoLocation, type Theme, storedLocation, storedTheme } from '$lib/stores/settings';
import { buildLocationRoute } from '$lib/utils/location';
import LocationSearch from '$lib/components/location/location-search.svelte';
interface Props {
onMenuToggle?: () => void;
}
let { onMenuToggle }: Props = $props();
let location = $state(get(storedLocation));
storedLocation.subscribe((value) => {
location = value;
});
const themeCycle: Theme[] = ['system', 'light', 'dark'];
const themeTitles: Record<Theme, string> = {
system: 'Theme: follow system',
light: 'Theme: light',
dark: 'Theme: dark'
};
function cycleTheme() {
storedTheme.update(
(current) => themeCycle[(themeCycle.indexOf(current) + 1) % themeCycle.length]
);
}
function navigateToLocation(newLocation: GeoLocation) {
storedLocation.set(newLocation);
const locationRoute = buildLocationRoute(newLocation);
const currentPath = get(page).url.pathname;
if (currentPath.startsWith('/weather/compare')) {
goto(resolve('/weather/compare/[location]', { location: locationRoute }));
} else if (currentPath.startsWith('/weather/14-day')) {
goto(resolve('/weather/14-day/[location]', { location: locationRoute }));
} else {
goto(resolve('/weather/week/[location]', { location: locationRoute }));
}
}
</script>
<header
class="topbar flex h-14 shrink-0 items-center gap-3 border-b border-topbar-border bg-topbar px-3 md:px-4"
>
<!-- Mobile menu toggle -->
<button
class="flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-black/5 md:hidden dark:hover:bg-white/10"
onclick={onMenuToggle}
aria-label="Toggle menu"
>
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.75">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<!-- Current location display -->
{#if location}
<div
class="hidden items-center gap-2 rounded-full border border-border/70 bg-muted/40 py-1 ps-1 pe-3 sm:flex"
>
<img
class="h-6 w-6 rounded-full ring-1 ring-border"
src="/images/country-flags/{(location.country_code || 'united_nations').toLowerCase()}.svg"
alt={location.country}
/>
<!-- the region lives here; the page hero carries the place name -->
<span class="text-sm font-semibold text-foreground">
{#if location.admin1}{location.admin1}, {location.country}{:else}{location.country ??
location.name}{/if}
</span>
</div>
{/if}
<!-- Spacer -->
<div class="flex-1"></div>
<!-- Location search: primary way to switch places, so keep it loud -->
<div class="w-full max-w-sm md:max-w-md">
<LocationSearch
label="Search location..."
on:location={(event) => {
navigateToLocation(event.detail);
}}
/>
</div>
<!-- Theme toggle: system → light → dark -->
<button
class="flex h-9 w-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-border/70 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
onclick={cycleTheme}
title={themeTitles[$storedTheme]}
aria-label={themeTitles[$storedTheme]}
>
{#if $storedTheme === 'light'}
<!-- sun -->
<svg
class="h-4.5 w-4.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="1.75"
>
<circle cx="12" cy="12" r="4" />
<path
stroke-linecap="round"
d="M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32 1.41 1.41M2 12h2m16 0h2M4.93 19.07l1.41-1.41m11.32-11.32 1.41-1.41"
/>
</svg>
{:else if $storedTheme === 'dark'}
<!-- moon -->
<svg
class="h-4.5 w-4.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="1.75"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"
/>
</svg>
{:else}
<!-- monitor (system) -->
<svg
class="h-4.5 w-4.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="1.75"
>
<rect x="3" y="4" width="18" height="13" rx="2" />
<path stroke-linecap="round" d="M8 21h8m-4-4v4" />
</svg>
{/if}
</button>
</header>
<style>
.topbar {
z-index: 40;
}
</style>