feat: flyout left nav (mobile)

closes #155
This commit is contained in:
kieran 2024-07-18 13:36:04 +01:00
parent e81a2f25b8
commit b033370322
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
4 changed files with 110 additions and 54 deletions

40
src/element/flyout.tsx Normal file
View File

@ -0,0 +1,40 @@
import { CSSProperties, ReactNode } from "react";
import { IconButton } from "./buttons";
import { createPortal } from "react-dom";
import classNames from "classnames";
export default function Flyout({
show,
children,
onClose,
side,
}: {
show: boolean;
children: ReactNode;
onClose: () => void;
side: "left" | "right";
}) {
const styles = {
"--flyout-w": "200px",
transition: "all 0.2s ease-in-out",
transform:
side === "right"
? `translate(${show ? "0" : "var(--flyout-w)"},0)`
: `translate(${show ? "0" : "calc(-1 * var(--flyout-w))"},0)`,
} as CSSProperties;
return createPortal(
<div
className={classNames("absolute z-20 top-0 overflow-hidden", {
"pointer-events-none": !show,
"right-0": side == "right",
"left-0": side === "left",
})}>
<div className="bg-layer-2/90 h-[100dvh] px-3 py-4" style={styles}>
<IconButton iconName="x" className="rounded-xl w-10 h-10 mb-6" iconSize={16} onClick={onClose} />
{children}
</div>
</div>,
document.body,
) as React.ReactNode;
}

View File

@ -142,7 +142,7 @@ export function HeaderNav() {
{layoutState.leftNav && (
<NavLinkIcon
name="hamburger"
className="!opacity-100 max-xl:hidden"
className="!opacity-100"
onClick={() => {
layoutState.update(c => {
c.leftNavExpand = !c.leftNavExpand;

View File

@ -1,41 +1,77 @@
import { useLayout } from "./context";
import { NavLinkIcon } from "./nav-icon";
import { FormattedMessage } from "react-intl";
import { useMediaQuery } from "usehooks-ts";
import Flyout from "@/element/flyout";
export function LeftNav() {
const layout = useLayout();
const isDesktop = useMediaQuery("(min-width: 1280px)");
const expandLabels = !isDesktop || layout.leftNavExpand;
function hideAfterMobileNav() {
if (isDesktop) return;
layout.update(c => {
c.leftNavExpand = false;
return { ...c };
});
}
if (layout.leftNav === false) return;
return (
<div className="flex flex-col gap-4 p-2 max-xl:hidden">
<NavLinkIcon name="signal" route="/streams" className="flex gap-2 items-center">
{layout.leftNavExpand && (
<span className="pr-3">
<FormattedMessage defaultMessage="Streams" />
</span>
)}
</NavLinkIcon>
<NavLinkIcon name="play-circle" route="/videos" className="flex gap-2 items-center">
{layout.leftNavExpand && (
<span className="pr-3">
<FormattedMessage defaultMessage="Videos" />
</span>
)}
</NavLinkIcon>
<NavLinkIcon name="film" route="/shorts" className="flex gap-2 items-center">
{layout.leftNavExpand && (
<span className="pr-3">
<FormattedMessage defaultMessage="Shorts" />
</span>
)}
</NavLinkIcon>
<NavLinkIcon name="grid" route="/category" className="flex gap-2 items-center">
{layout.leftNavExpand && (
<span className="pr-3">
<FormattedMessage defaultMessage="Categories" />
</span>
)}
</NavLinkIcon>
</div>
);
function navInner() {
return (
<div className="flex flex-col gap-4 p-2">
<NavLinkIcon name="signal" route="/streams" className="flex gap-2 items-center" onClick={hideAfterMobileNav}>
{expandLabels && (
<span className="pr-3">
<FormattedMessage defaultMessage="Streams" />
</span>
)}
</NavLinkIcon>
<NavLinkIcon
name="play-circle"
route="/videos"
className="flex gap-2 items-center"
onClick={hideAfterMobileNav}>
{expandLabels && (
<span className="pr-3">
<FormattedMessage defaultMessage="Videos" />
</span>
)}
</NavLinkIcon>
<NavLinkIcon name="film" route="/shorts" className="flex gap-2 items-center" onClick={hideAfterMobileNav}>
{expandLabels && (
<span className="pr-3">
<FormattedMessage defaultMessage="Shorts" />
</span>
)}
</NavLinkIcon>
<NavLinkIcon name="grid" route="/category" className="flex gap-2 items-center" onClick={hideAfterMobileNav}>
{expandLabels && (
<span className="pr-3">
<FormattedMessage defaultMessage="Categories" />
</span>
)}
</NavLinkIcon>
</div>
);
}
if (isDesktop) {
return navInner();
} else {
return (
<Flyout
side="left"
show={layout.leftNavExpand}
onClose={() => {
layout.update(c => {
c.leftNavExpand = !c.leftNavExpand;
return { ...c };
});
}}>
{navInner()}
</Flyout>
);
}
}

View File

@ -1,6 +1,6 @@
import { NostrLink, TaggedNostrEvent } from "@snort/system";
import { Helmet } from "react-helmet";
import { Suspense, lazy, useEffect } from "react";
import { Suspense, lazy } from "react";
import { useMediaQuery } from "usehooks-ts";
const LiveVideoPlayer = lazy(() => import("@/element/stream/live-video-player"));
@ -12,7 +12,6 @@ import { ContentWarningOverlay, useContentWarning } from "@/element/nsfw";
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
import { StreamState } from "@/const";
import { StreamInfo } from "@/element/stream/stream-info";
import { useLayout } from "./layout/context";
import { StreamContextProvider } from "@/element/stream/stream-state";
export function StreamPage({ link, evPreload }: { evPreload?: TaggedNostrEvent; link: NostrLink }) {
@ -33,25 +32,6 @@ export function StreamPage({ link, evPreload }: { evPreload?: TaggedNostrEvent;
const goal = useZapGoal(goalTag);
const isDesktop = useMediaQuery("(min-width: 1280px)");
const isGrownUp = useContentWarning();
const layout = useLayout();
useEffect(() => {
if (layout.leftNav) {
layout.update(c => {
c.leftNav = false;
return { ...c };
});
}
}, [layout]);
useEffect(() => {
return () => {
layout.update(c => {
c.leftNav = true;
return { ...c };
});
};
}, []);
if (contentWarning && !isGrownUp) {
return <ContentWarningOverlay />;