p-stream/src/components/layout/Navigation.tsx

210 lines
7.3 KiB
TypeScript

import classNames from "classnames";
import { useEffect, useState } from "react";
import { Link, To, useNavigate } from "react-router-dom";
import { NoUserAvatar, UserAvatar } from "@/components/Avatar";
import { IconPatch } from "@/components/buttons/IconPatch";
import { Icons } from "@/components/Icon";
import { LinksDropdown } from "@/components/LinksDropdown";
import { useNotifications } from "@/components/overlays/notificationsModal";
import { Lightbar } from "@/components/utils/Lightbar";
import { useAuth } from "@/hooks/auth/useAuth";
import { BlurEllipsis } from "@/pages/layouts/SubPageLayout";
import { conf } from "@/setup/config";
import { useBannerSize } from "@/stores/banner";
import { usePreferencesStore } from "@/stores/preferences";
import { BrandPill } from "./BrandPill";
export interface NavigationProps {
bg?: boolean;
noLightbar?: boolean;
doBackground?: boolean;
clearBackground?: boolean;
}
export function Navigation(props: NavigationProps) {
const bannerHeight = useBannerSize();
const navigate = useNavigate();
const { loggedIn } = useAuth();
const [scrollPosition, setScrollPosition] = useState(0);
const { openNotifications, getUnreadCount } = useNotifications();
useEffect(() => {
const handleScroll = () => {
setScrollPosition(window.scrollY);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const handleClick = (path: To) => {
window.scrollTo(0, 0);
navigate(path);
};
// Calculate mask length based on scroll position
const getMaskLength = () => {
// When at top (0), use longer mask (200px)
// When scrolled down (300px+), use shorter mask (100px)
const maxScroll = 300;
const minLength = 100;
const maxLength = 180;
const scrollFactor = Math.min(scrollPosition, maxScroll) / maxScroll;
return minLength + (maxLength - minLength) * (1 - scrollFactor);
};
const enableLowPerformanceMode = usePreferencesStore(
(s) => s.enableLowPerformanceMode,
);
return (
<>
{/* lightbar */}
{!props.noLightbar ? (
<div
className="absolute inset-x-0 top-0 flex h-[88px] items-center justify-center"
style={{
top: `${bannerHeight}px`,
}}
>
<div className="absolute inset-x-0 -mt-[22%] flex items-center sm:mt-0">
<Lightbar noParticles={enableLowPerformanceMode} />
</div>
</div>
) : null}
{/* backgrounds - these are seperate because of z-index issues */}
<div
className="top-content fixed z-[20] pointer-events-none left-0 right-0 top-0 min-h-[150px]"
style={{
top: `${bannerHeight}px`,
}}
>
<div
className={classNames(
"fixed left-0 right-0 top-0 flex items-center", // border-b border-utils-divider border-opacity-50
"transition-[background-color,backdrop-filter] duration-300 ease-in-out",
props.doBackground
? props.clearBackground
? "backdrop-blur-md bg-transparent"
: "bg-background-main"
: "bg-transparent",
)}
>
{props.doBackground ? (
<div className="absolute w-full h-full inset-0 overflow-hidden">
<BlurEllipsis positionClass="absolute" />
</div>
) : null}
<div className="opacity-0 absolute inset-0 block h-20 pointer-events-auto" />
<div
className={classNames(
"transition-[background-color,backdrop-filter,opacity] duration-300 ease-in-out",
props.bg ? "opacity-100" : "opacity-0",
"absolute inset-0 block h-[11rem]",
props.clearBackground
? "backdrop-blur-md bg-transparent"
: "bg-background-main",
)}
style={{
maskImage: `linear-gradient(
to bottom,
rgba(0, 0, 0, 1),
rgba(0, 0, 0, 1) calc(100% - ${getMaskLength()}px),
rgba(0, 0, 0, 0) 100%
)`,
WebkitMaskImage: `linear-gradient(
to bottom,
rgba(0, 0, 0, 1),
rgba(0, 0, 0, 1) calc(100% - ${getMaskLength()}px),
rgba(0, 0, 0, 0) 100%
)`,
}}
/>
</div>
</div>
{/* content */}
<div
className="top-content fixed pointer-events-none left-0 right-0 z-[60] top-0 min-h-[150px]"
style={{
top: `${bannerHeight}px`,
}}
>
<div className={classNames("fixed left-0 right-0 flex items-center")}>
<div className="px-7 py-5 relative z-[60] flex flex-1 items-center justify-between">
<div className="flex items-center space-x-1.5 ssm:space-x-3 pointer-events-auto">
<Link
className="block tabbable rounded-full text-xs ssm:text-base"
to="/"
onClick={() => window.scrollTo(0, 0)}
>
<BrandPill clickable header />
</Link>
<a
href={conf().DISCORD_LINK}
target="_blank"
rel="noreferrer"
className="text-xl text-white tabbable rounded-full backdrop-blur-lg"
>
<IconPatch
icon={Icons.DISCORD}
clickable
downsized
navigation
/>
</a>
{!enableLowPerformanceMode &&
(window.location.pathname !== "/discover" ? (
<a
onClick={() => handleClick("/discover")}
rel="noreferrer"
className="text-xl text-white tabbable rounded-full backdrop-blur-lg"
>
<IconPatch
icon={Icons.RISING_STAR}
clickable
downsized
navigation
/>
</a>
) : (
<a
onClick={() => handleClick("/")}
rel="noreferrer"
className="text-lg text-white tabbable rounded-full backdrop-blur-lg"
>
<IconPatch
icon={Icons.SEARCH}
clickable
downsized
navigation
/>
</a>
))}
<a
onClick={() => openNotifications()}
rel="noreferrer"
className="text-xl text-white tabbable rounded-full backdrop-blur-lg relative"
>
<IconPatch icon={Icons.BELL} clickable downsized navigation />
{getUnreadCount() > 0 && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
{getUnreadCount()}
</span>
)}
</a>
</div>
<div className="relative pointer-events-auto">
<LinksDropdown>
{loggedIn ? <UserAvatar withName /> : <NoUserAvatar />}
</LinksDropdown>
</div>
</div>
</div>
</div>
</>
);
}