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

378 lines
9.6 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import { AlignLeft, Home } from "lucide-react";
2025-08-28 18:27:07 +02:00
import Link, { type LinkProps } from "next/link";
2025-08-04 17:45:44 +02:00
import { useRouter } from "next/navigation";
2025-08-28 18:27:07 +02:00
import type { ReactNode } from "react";
2025-08-04 17:45:44 +02:00
import { IconMatrix } from "~/components/IconComponents";
2025-08-28 18:27:07 +02:00
import { LangToggle } from "~/components/navbar/lang-toggle";
2025-08-04 17:45:44 +02:00
import { UserHeaderSection } from "~/components/navbar/login-button";
import { ASvg, LogoSvg } from "~/components/svgs";
2025-08-28 18:27:07 +02:00
import { Button } from "~/components/ui/button";
import { Separator } from "~/components/ui/separator";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "~/components/ui/sheet";
2025-08-04 17:45:44 +02:00
import { useMediaQuery } from "~/hooks/use-media-query";
2025-08-28 18:27:07 +02:00
import { cn } from "~/lib/utils";
import { useTranslation } from "~/providers/I18nProvider";
import { useSession } from "~/providers/SessionProvider";
import { ThemeSwitch } from "./theme-toggle";
2025-08-04 17:45:44 +02:00
type MobileNavProps = {
2025-08-28 18:27:07 +02:00
pathname: string;
open: boolean;
setOpen: (status: boolean) => void;
userOpen: boolean;
setUserOpen: (status: boolean) => void;
2025-08-04 17:45:44 +02:00
};
export function MobileNav({
2025-08-28 18:27:07 +02:00
pathname,
open,
setOpen,
userOpen,
setUserOpen,
2025-08-04 17:45:44 +02:00
}: MobileNavProps) {
2025-08-28 18:27:07 +02:00
const session = useSession();
const { t } = useTranslation();
const verySmallScreen = useMediaQuery("(max-width: 320px)");
return (
<>
2025-08-29 16:18:32 +02:00
<Sheet onOpenChange={setOpen} open={open}>
2025-08-28 18:27:07 +02:00
<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"
2025-08-29 16:18:32 +02:00
variant="ghost"
2025-08-28 18:27:07 +02:00
>
<AlignLeft />
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<span className="sr-only">Toggle Menu</span>
</Button>
</SheetTrigger>
2025-08-29 16:18:32 +02:00
<SheetContent className="z-500 p-0" side="left">
2025-08-28 18:27:07 +02:00
<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"
2025-08-29 16:18:32 +02:00
href="/"
2025-08-28 18:27:07 +02:00
onOpenChange={setOpen}
>
<Home className="size-6" />{" "}
2025-10-10 16:18:43 +02:00
<span className="font-medium text-xl">Home</span>
2025-08-28 18:27:07 +02:00
</MobileLink>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<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 />
</>
)}
2025-08-04 17:45:44 +02:00
<div className="flex h-full grow flex-col gap-3">
2025-08-28 18:27:07 +02:00
{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",
)}
2025-08-29 16:18:32 +02:00
href={item.href}
key={item.href}
onOpenChange={setOpen}
2025-08-28 18:27:07 +02:00
>
<IconMatrix
2025-08-29 16:18:32 +02:00
className="size-5"
2025-08-28 18:27:07 +02:00
key={item.title}
type={item.icon}
/>{" "}
{item.title}
</MobileLink>
))}
<Separator />
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<div className="flex h-fit flex-col gap-4 pb-2">
<UserHeaderSection
2025-08-29 16:18:32 +02:00
inSidebar
2025-08-28 18:27:07 +02:00
open={userOpen}
setOpen={setUserOpen}
setSidebar={setOpen}
/>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<div className="flex items-center justify-center gap-6">
<LangToggle />
<ThemeSwitch />
2025-08-28 18:27:07 +02:00
</div>
</div>
</div>
</div>
</SheetContent>
</Sheet>
<Link
aria-label="mainNavLink"
2025-08-29 16:18:32 +02:00
className="ml-3 flex shrink-0 items-center md:hidden"
href="/"
2025-08-28 18:27:07 +02:00
>
{verySmallScreen ? (
<ASvg className="h-8 w-auto" />
) : (
<LogoSvg className="h-6 xs:h-8 w-auto sm:h-10" />
2025-08-28 18:27:07 +02:00
)}
</Link>
</>
);
2025-08-04 17:45:44 +02:00
}
interface MobileLinkProps extends LinkProps {
2025-08-28 18:27:07 +02:00
onOpenChange?: (open: boolean) => void;
children: ReactNode;
className?: string;
disabled?: boolean;
2025-08-04 17:45:44 +02:00
}
function MobileLink({
2025-08-28 18:27:07 +02:00
href,
onOpenChange,
className,
children,
disabled,
...props
2025-08-04 17:45:44 +02:00
}: MobileLinkProps) {
2025-08-28 18:27:07 +02:00
const router = useRouter();
return (
<Link
aria-label="mobileLink"
2025-08-29 16:18:32 +02:00
className={cn(className)}
2025-08-28 18:27:07 +02:00
href={href}
onClick={(e) => {
if (disabled) {
e.preventDefault();
e.stopPropagation();
onOpenChange?.(false);
} else {
2025-08-29 16:18:32 +02:00
router.push(href.toString());
2025-08-28 18:27:07 +02:00
onOpenChange?.(false);
}
}}
{...props}
>
{children}
</Link>
);
2025-08-04 17:45:44 +02:00
}
type AreaRiservataLinksProps = {
2025-08-28 18:27:07 +02:00
isAdmin: boolean;
setOpen?: (status: boolean) => void;
pathname: string;
2025-08-04 17:45:44 +02:00
};
const AreaRiservataLinks = ({
2025-08-28 18:27:07 +02:00
isAdmin,
setOpen,
pathname,
2025-08-04 17:45:44 +02:00
}: AreaRiservataLinksProps) => {
2025-08-28 18:27:07 +02:00
const { t } = useTranslation();
return (
<div className="flex flex-col space-y-4 text-base">
2025-08-28 18:27:07 +02:00
{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",
)}
>
2025-08-29 16:18:32 +02:00
<IconMatrix className="size-5" type={item.icon} />
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
{item.title}
</div>
<div>
<ul>
{item.items.map((item) => {
return (
<MobileLink
className={cn(
2025-10-10 16:18:43 +02:00
`mx-4 block w-full border-l px-6 py-2 duration-150 hover:border-neutral-800 hover:text-accent-foreground`,
2025-08-28 18:27:07 +02:00
item.href === pathname &&
"border-neutral-900 text-neutral-900",
)}
2025-08-29 16:18:32 +02:00
href={item.href}
key={item.title}
onOpenChange={setOpen}
2025-08-28 18:27:07 +02:00
>
{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",
)}
2025-08-29 16:18:32 +02:00
href={item.href}
key={item.title}
onOpenChange={setOpen}
2025-08-28 18:27:07 +02:00
>
2025-08-29 16:18:32 +02:00
<IconMatrix className="size-5" type={item.icon} />
2025-08-28 18:27:07 +02:00
{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",
)}
2025-08-29 16:18:32 +02:00
href={item.href}
key={item.title}
onOpenChange={setOpen}
2025-08-28 18:27:07 +02:00
>
2025-08-29 16:18:32 +02:00
<IconMatrix className="size-5" type={item.icon} />
2025-08-28 18:27:07 +02:00
{item.title}
</MobileLink>
))}
</div>
);
2025-08-04 17:45:44 +02:00
};
export const ARMinimizableLinks = ({
isAnimating,
2025-08-28 18:27:07 +02:00
isAdmin,
pathname,
minimized,
2025-08-04 17:45:44 +02:00
}: {
isAnimating: boolean;
2025-08-28 18:27:07 +02:00
isAdmin: boolean;
pathname: string;
minimized: boolean;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
const { t } = useTranslation();
return (
<div
className={cn(
"flex w-full flex-col items-start gap-4 text-base",
2025-08-28 18:27:07 +02:00
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",
2025-08-28 18:27:07 +02:00
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>
2025-08-28 18:27:07 +02:00
</div>
<div>
<ul className="text-ellipsis">
2025-08-28 18:27:07 +02:00
{item.items.map((item) => {
return (
<MobileLink
className={cn(
`mx-4 block w-full rounded-r-md border-l px-6 py-2 duration-150 hover:border-neutral-800 hover:bg-muted hover:text-accent-foreground`,
2025-08-28 18:27:07 +02:00
item.href === pathname &&
"border-muted-foreground",
2025-08-28 18:27:07 +02:00
minimized && "hidden",
)}
disabled={minimized}
2025-08-29 16:18:32 +02:00
href={item.href}
key={item.title}
2025-08-28 18:27:07 +02:00
>
{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",
2025-08-28 18:27:07 +02:00
item.href === pathname && "bg-muted",
)}
disabled={minimized}
2025-08-29 16:18:32 +02:00
href={item.href}
key={item.title}
2025-08-28 18:27:07 +02:00
>
<IconMatrix className="size-5 shrink-0" type={item.icon} />
<span
className={cn(
"truncate duration-500",
minimized && "hidden",
isAnimating && "animate-expand",
)}
>
{!minimized && item.title}
</span>
2025-08-28 18:27:07 +02:00
</MobileLink>
);
})
: t.nav.areaRiservataUserNav.map((item) => (
<MobileLink
className={cn(
"flex h-8 items-center gap-3 rounded-lg px-2 py-1 font-medium",
2025-08-28 18:27:07 +02:00
item.href === pathname && "bg-muted",
)}
disabled={minimized}
2025-08-29 16:18:32 +02:00
href={item.href}
key={item.title}
2025-08-28 18:27:07 +02:00
>
<IconMatrix className="size-5 shrink-0" type={item.icon} />
<span
className={cn(
"truncate duration-500",
minimized && "hidden",
isAnimating && "animate-expand",
)}
>
{!minimized && item.title}
</span>
2025-08-28 18:27:07 +02:00
</MobileLink>
))}
</div>
);
2025-08-04 17:45:44 +02:00
};