TirayaFrontend/src/lib/components/header/FixedHeader.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>