157 lines
4.3 KiB
Svelte
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>
|