40
src/element/flyout.tsx
Normal file
40
src/element/flyout.tsx
Normal 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;
|
||||||
|
}
|
@ -142,7 +142,7 @@ export function HeaderNav() {
|
|||||||
{layoutState.leftNav && (
|
{layoutState.leftNav && (
|
||||||
<NavLinkIcon
|
<NavLinkIcon
|
||||||
name="hamburger"
|
name="hamburger"
|
||||||
className="!opacity-100 max-xl:hidden"
|
className="!opacity-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
layoutState.update(c => {
|
layoutState.update(c => {
|
||||||
c.leftNavExpand = !c.leftNavExpand;
|
c.leftNavExpand = !c.leftNavExpand;
|
||||||
|
@ -1,41 +1,77 @@
|
|||||||
import { useLayout } from "./context";
|
import { useLayout } from "./context";
|
||||||
import { NavLinkIcon } from "./nav-icon";
|
import { NavLinkIcon } from "./nav-icon";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { useMediaQuery } from "usehooks-ts";
|
||||||
|
import Flyout from "@/element/flyout";
|
||||||
|
|
||||||
export function LeftNav() {
|
export function LeftNav() {
|
||||||
const layout = useLayout();
|
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;
|
if (layout.leftNav === false) return;
|
||||||
return (
|
function navInner() {
|
||||||
<div className="flex flex-col gap-4 p-2 max-xl:hidden">
|
return (
|
||||||
<NavLinkIcon name="signal" route="/streams" className="flex gap-2 items-center">
|
<div className="flex flex-col gap-4 p-2">
|
||||||
{layout.leftNavExpand && (
|
<NavLinkIcon name="signal" route="/streams" className="flex gap-2 items-center" onClick={hideAfterMobileNav}>
|
||||||
<span className="pr-3">
|
{expandLabels && (
|
||||||
<FormattedMessage defaultMessage="Streams" />
|
<span className="pr-3">
|
||||||
</span>
|
<FormattedMessage defaultMessage="Streams" />
|
||||||
)}
|
</span>
|
||||||
</NavLinkIcon>
|
)}
|
||||||
<NavLinkIcon name="play-circle" route="/videos" className="flex gap-2 items-center">
|
</NavLinkIcon>
|
||||||
{layout.leftNavExpand && (
|
<NavLinkIcon
|
||||||
<span className="pr-3">
|
name="play-circle"
|
||||||
<FormattedMessage defaultMessage="Videos" />
|
route="/videos"
|
||||||
</span>
|
className="flex gap-2 items-center"
|
||||||
)}
|
onClick={hideAfterMobileNav}>
|
||||||
</NavLinkIcon>
|
{expandLabels && (
|
||||||
<NavLinkIcon name="film" route="/shorts" className="flex gap-2 items-center">
|
<span className="pr-3">
|
||||||
{layout.leftNavExpand && (
|
<FormattedMessage defaultMessage="Videos" />
|
||||||
<span className="pr-3">
|
</span>
|
||||||
<FormattedMessage defaultMessage="Shorts" />
|
)}
|
||||||
</span>
|
</NavLinkIcon>
|
||||||
)}
|
<NavLinkIcon name="film" route="/shorts" className="flex gap-2 items-center" onClick={hideAfterMobileNav}>
|
||||||
</NavLinkIcon>
|
{expandLabels && (
|
||||||
<NavLinkIcon name="grid" route="/category" className="flex gap-2 items-center">
|
<span className="pr-3">
|
||||||
{layout.leftNavExpand && (
|
<FormattedMessage defaultMessage="Shorts" />
|
||||||
<span className="pr-3">
|
</span>
|
||||||
<FormattedMessage defaultMessage="Categories" />
|
)}
|
||||||
</span>
|
</NavLinkIcon>
|
||||||
)}
|
<NavLinkIcon name="grid" route="/category" className="flex gap-2 items-center" onClick={hideAfterMobileNav}>
|
||||||
</NavLinkIcon>
|
{expandLabels && (
|
||||||
</div>
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { Suspense, lazy, useEffect } from "react";
|
import { Suspense, lazy } from "react";
|
||||||
import { useMediaQuery } from "usehooks-ts";
|
import { useMediaQuery } from "usehooks-ts";
|
||||||
|
|
||||||
const LiveVideoPlayer = lazy(() => import("@/element/stream/live-video-player"));
|
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 { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
import { StreamState } from "@/const";
|
import { StreamState } from "@/const";
|
||||||
import { StreamInfo } from "@/element/stream/stream-info";
|
import { StreamInfo } from "@/element/stream/stream-info";
|
||||||
import { useLayout } from "./layout/context";
|
|
||||||
import { StreamContextProvider } from "@/element/stream/stream-state";
|
import { StreamContextProvider } from "@/element/stream/stream-state";
|
||||||
|
|
||||||
export function StreamPage({ link, evPreload }: { evPreload?: TaggedNostrEvent; link: NostrLink }) {
|
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 goal = useZapGoal(goalTag);
|
||||||
const isDesktop = useMediaQuery("(min-width: 1280px)");
|
const isDesktop = useMediaQuery("(min-width: 1280px)");
|
||||||
const isGrownUp = useContentWarning();
|
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) {
|
if (contentWarning && !isGrownUp) {
|
||||||
return <ContentWarningOverlay />;
|
return <ContentWarningOverlay />;
|
||||||
|
Reference in New Issue
Block a user