infoalloggi-monorepo/apps/infoalloggi/src/components/navbar/mobile-nav.tsx

377 lines
9.5 KiB
TypeScript

import { AlignLeft, Home } from "lucide-react";
import Link, { type LinkProps } from "next/link";
import { useRouter } from "next/navigation";
import type { ReactNode } from "react";
import { IconMatrix } from "~/components/IconComponents";
import { LangToggle } from "~/components/navbar/lang-toggle";
import { UserHeaderSection } from "~/components/navbar/login-button";
import { ASvg, LogoSvg } from "~/components/svgs";
import { Button } from "~/components/ui/button";
import { Separator } from "~/components/ui/separator";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "~/components/ui/sheet";
import { useMediaQuery } from "~/hooks/use-media-query";
import { cn } from "~/lib/utils";
import { useTranslation } from "~/providers/I18nProvider";
import { useSession } from "~/providers/SessionProvider";
import { ThemeSwitch } from "./theme-toggle";
type MobileNavProps = {
pathname: string;
open: boolean;
setOpen: (status: boolean) => void;
userOpen: boolean;
setUserOpen: (status: boolean) => void;
};
export function MobileNav({
pathname,
open,
setOpen,
userOpen,
setUserOpen,
}: MobileNavProps) {
const session = useSession();
const { t } = useTranslation();
const verySmallScreen = useMediaQuery("(max-width: 280px)");
return (
<>
<Sheet onOpenChange={setOpen} open={open}>
<SheetTrigger asChild>
<Button
aria-label="Toggle Mobile Menu"
className="mr-2 size-8 p-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden [&_svg]:size-6"
variant="ghost"
>
<AlignLeft />
<span className="sr-only">Toggle Menu</span>
</Button>
</SheetTrigger>
<SheetContent className="z-500 p-0" side="left">
<SheetHeader>
<SheetTitle className="sr-only">Mobile Menu</SheetTitle>
<SheetDescription className="sr-only">
This is the mobile menu
</SheetDescription>
</SheetHeader>
<div className="flex h-full flex-col space-y-2 overflow-y-auto px-3 pb-4">
<MobileLink
aria-label="mainNavLinkHome"
className="ml-2 flex items-center gap-3"
href="/"
onOpenChange={setOpen}
>
<Home className="size-6" />{" "}
<span className="font-medium text-xl">Home</span>
</MobileLink>
<div className="flex h-full flex-col space-y-2 pt-4">
{pathname.startsWith("/area-riservata") && (
<>
{session?.user && (
<AreaRiservataLinks
isAdmin={session.user.isAdmin}
pathname={pathname}
setOpen={setOpen}
/>
)}
<Separator />
</>
)}
<div className="flex h-full grow flex-col gap-3">
{t.nav.mainNav.map((item) => (
<MobileLink
className={cn(
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted",
)}
href={item.href}
key={item.href}
onOpenChange={setOpen}
>
<IconMatrix
className="size-5"
key={item.title}
type={item.icon}
/>{" "}
{item.title}
</MobileLink>
))}
<Separator />
</div>
<div className="flex h-fit flex-col gap-4 pb-2">
<UserHeaderSection
inSidebar
open={userOpen}
setOpen={setUserOpen}
setSidebar={setOpen}
/>
<div className="flex items-center justify-center gap-6">
<LangToggle />
<ThemeSwitch />
</div>
</div>
</div>
</div>
</SheetContent>
</Sheet>
<Link
aria-label="mainNavLink"
className="flex shrink-0 items-center md:hidden"
href="/"
>
{verySmallScreen ? (
<ASvg className="h-8 w-auto" />
) : (
<LogoSvg className="h-6 xs:h-8 w-auto sm:h-10" />
)}
</Link>
</>
);
}
interface MobileLinkProps extends LinkProps {
onOpenChange?: (open: boolean) => void;
children: ReactNode;
className?: string;
disabled?: boolean;
}
function MobileLink({
href,
onOpenChange,
className,
children,
disabled,
...props
}: MobileLinkProps) {
const router = useRouter();
return (
<Link
aria-label="mobileLink"
className={cn(className)}
href={href}
onClick={(e) => {
if (disabled) {
e.preventDefault();
e.stopPropagation();
onOpenChange?.(false);
} else {
router.push(href.toString());
onOpenChange?.(false);
}
}}
{...props}
>
{children}
</Link>
);
}
type AreaRiservataLinksProps = {
isAdmin: boolean;
setOpen?: (status: boolean) => void;
pathname: string;
};
const AreaRiservataLinks = ({
isAdmin,
setOpen,
pathname,
}: AreaRiservataLinksProps) => {
const { t } = useTranslation();
return (
<div className="flex flex-col space-y-4 text-base">
{isAdmin
? t.nav.areaRiservataAdminNav.map((item) => {
if (item.items !== undefined) {
return (
<div key={item.title}>
<div
className={cn(
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted",
)}
>
<IconMatrix className="size-5" type={item.icon} />
{item.title}
</div>
<div>
<ul>
{item.items.map((item) => {
return (
<MobileLink
className={cn(
`mx-4 block w-full border-muted-foreground/20 border-l-2 px-6 py-2 duration-150`,
item.href === pathname &&
"border-accent-foreground",
)}
href={item.href}
key={item.title}
onOpenChange={setOpen}
>
{item.title}
</MobileLink>
);
})}
</ul>
</div>
</div>
);
}
return (
<MobileLink
className={cn(
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted",
)}
href={item.href}
key={item.title}
onOpenChange={setOpen}
>
<IconMatrix className="size-5" type={item.icon} />
{item.title}
</MobileLink>
);
})
: t.nav.areaRiservataUserNav.map((item) => (
<MobileLink
className={cn(
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted",
)}
href={item.href}
key={item.title}
onOpenChange={setOpen}
>
<IconMatrix className="size-5" type={item.icon} />
{item.title}
</MobileLink>
))}
</div>
);
};
export const ARMinimizableLinks = ({
isAnimating,
isAdmin,
pathname,
minimized,
}: {
isAnimating: boolean;
isAdmin: boolean;
pathname: string;
minimized: boolean;
}) => {
const { t } = useTranslation();
return (
<div
className={cn(
"flex w-full flex-col items-start gap-4 text-base",
minimized && "justify-center",
)}
>
{isAdmin
? t.nav.areaRiservataAdminNav.map((item) => {
if (item.items !== undefined) {
return (
<div key={item.title}>
<div
className={cn(
"flex h-8 items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted",
)}
>
<IconMatrix className="size-5 shrink-0" type={item.icon} />
<span
className={cn(
"truncate duration-500",
minimized && "hidden",
isAnimating && "animate-expand",
)}
>
{!minimized && item.title}
</span>
</div>
<div>
<ul className="text-ellipsis">
{item.items.map((item) => {
return (
<MobileLink
className={cn(
`mx-4 block w-full rounded-r-md border-muted-foreground/20 border-l-2 px-6 py-2 duration-150 hover:border-accent-foreground hover:bg-muted hover:text-accent-foreground`,
item.href === pathname &&
"border-muted-foreground",
minimized && "hidden",
)}
disabled={minimized}
href={item.href}
key={item.title}
>
{item.title}
</MobileLink>
);
})}
</ul>
</div>
</div>
);
}
return (
<MobileLink
className={cn(
"flex h-8 w-full items-center gap-3 rounded-lg px-2 py-1 font-medium hover:bg-muted",
item.href === pathname && "bg-muted",
)}
disabled={minimized}
href={item.href}
key={item.title}
>
<IconMatrix className="size-5 shrink-0" type={item.icon} />
<span
className={cn(
"truncate duration-500",
minimized && "hidden",
isAnimating && "animate-expand",
)}
>
{!minimized && item.title}
</span>
</MobileLink>
);
})
: t.nav.areaRiservataUserNav.map((item) => (
<MobileLink
className={cn(
"flex h-8 items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted",
)}
disabled={minimized}
href={item.href}
key={item.title}
>
<IconMatrix className="size-5 shrink-0" type={item.icon} />
<span
className={cn(
"truncate duration-500",
minimized && "hidden",
isAnimating && "animate-expand",
)}
>
{!minimized && item.title}
</span>
</MobileLink>
))}
</div>
);
};