171 lines
4.4 KiB
Svelte
171 lines
4.4 KiB
Svelte
<!-- Fixed header that is shown when scrolling down -->
|
|
<script lang="ts">
|
|
import LL from "$i18n/i18n-svelte";
|
|
import { page } from "$app/stores";
|
|
import { beforeNavigate } from "$app/navigation";
|
|
import { mdiBellOutline, mdiClose, mdiHistory, mdiPlay } from "@mdi/js";
|
|
import { kbd } from "@melt-ui/svelte/internal/helpers";
|
|
|
|
import { clamp, isExcluded } from "$lib/util/functions";
|
|
import { headerColor, headerHeight, searchTerm } from "$lib/stores/layout";
|
|
import { iconSearch } from "$lib/util/icons";
|
|
|
|
import IconButton from "$lib/components/ui/IconButton.svelte";
|
|
import Icon from "$lib/components/ui/Icon.svelte";
|
|
import { SearchCtx } from "$lib/util/types";
|
|
import { CLEAR_SEARCH, registerHandler } from "$lib/util/events";
|
|
import UserMenuButton from "./UserMenuButton.svelte";
|
|
|
|
export let scrollPos = 0;
|
|
|
|
let headerElm: HTMLElement;
|
|
|
|
$: homeScreen = $page.route.id === "/" || $page.route.id === null;
|
|
let title: string;
|
|
$: {
|
|
const t = $page.data.header?.title;
|
|
if (typeof t === "function") {
|
|
title = t();
|
|
} else if (t) {
|
|
title = t;
|
|
} else {
|
|
title = $LL.app_name();
|
|
}
|
|
}
|
|
$: searchCtx = $page.data.header?.searchCtx;
|
|
|
|
let fading = false;
|
|
$: fading = $page.data.header?.fading;
|
|
|
|
let hh = 0;
|
|
$: hh = Math.max($headerHeight - 30, 0);
|
|
let hthresh = 0;
|
|
$: hthresh = hh / 2;
|
|
|
|
let searchActivate = false;
|
|
let searchShow = false;
|
|
$: searchShow =
|
|
Boolean($searchTerm) || searchActivate || searchCtx === SearchCtx.Global;
|
|
|
|
let opacity = 0;
|
|
$: opacity = fading
|
|
? Math.max(
|
|
(clamp(scrollPos, hthresh, hh) - hthresh) / Math.max(hthresh, 1),
|
|
Number(searchShow) * 0.5
|
|
)
|
|
: 1;
|
|
|
|
let bgcolor = "oklch(var(--b2)";
|
|
$: if ($headerColor) {
|
|
bgcolor = `rgb(${$headerColor.r}, ${$headerColor.g}, ${$headerColor.b}, ${opacity})`;
|
|
} else {
|
|
bgcolor = `oklch(var(--b2) / ${opacity})`;
|
|
}
|
|
|
|
let inputElm: HTMLInputElement;
|
|
|
|
$: if (inputElm && searchShow) inputElm.focus();
|
|
|
|
let searchLabel: string;
|
|
$: if (searchCtx) {
|
|
if (searchCtx === SearchCtx.Global) searchLabel = $LL.search();
|
|
else searchLabel = $LL.search_item(title);
|
|
}
|
|
|
|
function searchOnBlur(e: FocusEvent) {
|
|
// Hide the search bar on unfocus, unless it is active or
|
|
// the click happened within the header bar
|
|
if (!$searchTerm && !isExcluded(e.relatedTarget, headerElm)) {
|
|
searchActivate = false;
|
|
}
|
|
}
|
|
|
|
function clearSearch() {
|
|
$searchTerm = "";
|
|
searchActivate = false;
|
|
}
|
|
|
|
beforeNavigate(clearSearch);
|
|
|
|
registerHandler(CLEAR_SEARCH, () => clearSearch());
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>{title}</title>
|
|
</svelte:head>
|
|
|
|
<div
|
|
class="w-full h-full px-2 flex gap-2 items-center relative"
|
|
style="background-color: {bgcolor}"
|
|
bind:this={headerElm}
|
|
>
|
|
<div class="flex gap-2 items-center flex-grow">
|
|
{#if searchShow}
|
|
<Icon path={iconSearch} />
|
|
<input
|
|
type="text"
|
|
placeholder={searchLabel}
|
|
aria-label={searchLabel}
|
|
bind:this={inputElm}
|
|
bind:value={$searchTerm}
|
|
on:keydown={(e) => {
|
|
if (e.key === kbd.ESCAPE) {
|
|
clearSearch();
|
|
}
|
|
}}
|
|
on:blur={searchOnBlur}
|
|
/>
|
|
{:else if !fading || scrollPos > 0.8 * hh}
|
|
{#if $page.data.header?.playLink}
|
|
<IconButton
|
|
path={mdiPlay}
|
|
ariaLabel={$LL.play_item(title)}
|
|
color="primary"
|
|
on:click={() => alert("Playing " + title)}
|
|
/>
|
|
{/if}
|
|
|
|
{#if homeScreen}
|
|
<img src="/favicon.svg" alt="" height="32" width="32" />
|
|
{/if}
|
|
|
|
<h1 class="text-2xl font-bold ellipsis-ol">{title}</h1>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="flex gap-1 items-center">
|
|
{#if homeScreen}
|
|
<IconButton path={mdiBellOutline} ariaLabel={$LL.notifications()} />
|
|
<IconButton path={mdiHistory} ariaLabel={$LL.playback_history()} />
|
|
{/if}
|
|
{#if searchCtx}
|
|
<IconButton
|
|
path={searchShow ? mdiClose : iconSearch}
|
|
ariaLabel={searchShow ? $LL.clear_search() : searchLabel}
|
|
on:click={() => {
|
|
if (searchShow) {
|
|
clearSearch();
|
|
} else {
|
|
searchActivate = true;
|
|
}
|
|
}}
|
|
/>
|
|
{/if}
|
|
<UserMenuButton />
|
|
</div>
|
|
</div>
|
|
|
|
<style lang="postcss">
|
|
input {
|
|
width: 100%;
|
|
|
|
background-color: transparent;
|
|
outline: none;
|
|
border-bottom: solid 2px;
|
|
@apply border-base-content;
|
|
}
|
|
|
|
::placeholder {
|
|
@apply text-base-content/70;
|
|
}
|
|
</style>
|