ionian/packages/client/src/components/layout/nav.tsx

306 lines
No EOL
10 KiB
TypeScript

import { Link } from "wouter"
import { SearchIcon, SettingsIcon, TagsIcon, ArchiveIcon, UserIcon, LayoutListIcon, PanelLeftIcon, PanelLeftCloseIcon, MenuIcon, XIcon } from "lucide-react"
import { Button, buttonVariants } from "@/components/ui/button.tsx"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip.tsx"
import { useLogin } from "@/state/user.ts";
import { useNavItems } from "./navAtom";
import { Separator } from "../ui/separator";
import { cn } from "@/lib/utils";
interface NavItemProps {
icon: React.ReactNode;
to: string;
name: string;
}
export function NavItem({
icon,
to,
name
}: NavItemProps) {
return <Tooltip>
<TooltipTrigger asChild>
<Link
href={to}
className={buttonVariants({ variant: "ghost" })}
>
{icon}
<span className="sr-only">{name}</span>
</Link>
</TooltipTrigger>
<TooltipContent side="right">{name}</TooltipContent>
</Tooltip>
}
interface NavItemButtonProps {
icon: React.ReactNode;
onClick: () => void;
name: string;
className?: string;
}
export function NavItemButton({
icon,
onClick,
name,
className
}: NavItemButtonProps) {
return <Tooltip>
<TooltipTrigger asChild>
<Button
onClick={onClick}
variant="ghost"
className={className}
>
{icon}
<span className="sr-only">{name}</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">{name}</TooltipContent>
</Tooltip>
}
export function NavList() {
const loginInfo = useLogin();
const navItems = useNavItems();
return <aside className="h-dvh flex flex-col">
<nav className="flex flex-col items-center gap-4 px-2 sm:py-5 flex-1">
{navItems && <>{navItems} <Separator /> </>}
<NavItem icon={<SearchIcon className="h-5 w-5" />} to="/search" name="Search" />
<NavItem icon={<TagsIcon className="h-5 w-5" />} to="/tags" name="Tags" />
<NavItem icon={<ArchiveIcon className="h-5 w-5" />} to="/difference" name="Difference" />
</nav>
<nav className="mt-auto flex flex-col items-center gap-4 px-2 sm:py-5 flex-grow-0">
<NavItem icon={<UserIcon className="h-5 w-5" />} to={loginInfo ? "/profile" : "/login"} name={loginInfo ? "Profiles" : "Login"} />
<NavItem icon={<SettingsIcon className="h-5 w-5" />} to="/setting" name="Settings" />
</nav>
</aside>
}
// 사이드바 토글 버튼
export function SidebarToggle({ isOpen, onToggle }: { isOpen: boolean; onToggle: () => void }) {
return (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
onClick={onToggle}
className="h-8 w-8 p-0"
>
{isOpen ? (
<PanelLeftCloseIcon className="h-4 w-4" />
) : (
<PanelLeftIcon className="h-4 w-4" />
)}
<span className="sr-only">{isOpen ? "Close sidebar" : "Open sidebar"}</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">
{isOpen ? "Close sidebar" : "Open sidebar"}
</TooltipContent>
</Tooltip>
);
}
// 모바일용 사이드바 토글 버튼
export function MobileSidebarToggle({ isOpen, onToggle }: { isOpen: boolean; onToggle: () => void }) {
return (
<Button
variant="ghost"
size="sm"
onClick={onToggle}
className="h-8 w-8 p-0"
>
{isOpen ? (
<XIcon className="h-5 w-5" />
) : (
<MenuIcon className="h-5 w-5" />
)}
<span className="sr-only">{isOpen ? "Close menu" : "Open menu"}</span>
</Button>
);
}
// 데스크탑용 사이드바 네비게이션
export function SidebarNav({ isCollapsed, onNavigate }: { isCollapsed: boolean; onNavigate?: () => void }) {
const loginInfo = useLogin();
const navItems = useNavItems();
return (
<div className="flex flex-col h-full">
<nav className="flex flex-col gap-2 p-3 flex-1 min-h-0">
{navItems && (
<>
<div className={cn("space-y-2", isCollapsed && "items-center")}>
{navItems}
</div>
<Separator className="my-3" />
</>
)}
<div className="flex flex-col gap-2">
<SidebarNavItem
icon={<SearchIcon className="h-5 w-5" />}
to="/search"
name="Search"
isCollapsed={isCollapsed}
onNavigate={onNavigate}
/>
<SidebarNavItem
icon={<TagsIcon className="h-5 w-5" />}
to="/tags"
name="Tags"
isCollapsed={isCollapsed}
onNavigate={onNavigate}
/>
<SidebarNavItem
icon={<ArchiveIcon className="h-5 w-5" />}
to="/difference"
name="Difference"
isCollapsed={isCollapsed}
onNavigate={onNavigate}
/>
<SidebarNavItem
icon={<LayoutListIcon className="h-5 w-5" />}
to="/queue"
name="Task Queue"
isCollapsed={isCollapsed}
onNavigate={onNavigate}
/>
</div>
</nav>
<div className="border-t p-3 flex flex-col gap-2 flex-shrink-0">
<SidebarNavItem
icon={<UserIcon className="h-5 w-5" />}
to={loginInfo ? "/profile" : "/login"}
name={loginInfo ? "Profiles" : "Login"}
isCollapsed={isCollapsed}
onNavigate={onNavigate}
/>
<SidebarNavItem
icon={<SettingsIcon className="h-5 w-5" />}
to="/setting"
name="Settings"
isCollapsed={isCollapsed}
onNavigate={onNavigate}
/>
</div>
</div>
);
}
// 사이드바 네비게이션 아이템
interface SidebarNavItemProps {
icon: React.ReactNode;
to: string;
name: string;
isCollapsed: boolean;
onNavigate?: () => void;
}
function SidebarNavItem({ icon, to, name, isCollapsed, onNavigate }: SidebarNavItemProps) {
if (isCollapsed) {
return (
<Tooltip>
<TooltipTrigger asChild>
<Link
href={to}
className={cn(
buttonVariants({ variant: "ghost", size: "sm" }),
"justify-center h-10 w-10 p-0"
)}
onClick={onNavigate}
>
{icon}
<span className="sr-only">{name}</span>
</Link>
</TooltipTrigger>
<TooltipContent side="right">{name}</TooltipContent>
</Tooltip>
);
}
return (
<Link
href={to}
className={cn(
buttonVariants({ variant: "ghost", size: "sm" }),
"justify-start gap-3 h-10 px-3"
)}
onClick={onNavigate}
>
{icon}
<span>{name}</span>
</Link>
);
}
// 모바일용 하단 네비게이션
export function BottomNav() {
const loginInfo = useLogin();
const navItems = useNavItems();
return (
<nav className="mb-1">
<div className="flex justify-around items-center max-w-md mx-auto
overflow-hidden
bg-background/50 backdrop-blur-md border rounded-full">
<BottomNavItem
icon={<SearchIcon className="h-5 w-5" />}
to="/search"
name="Search"
className="flex-1"
/>
{navItems ? navItems : <>
<BottomNavItem
icon={<TagsIcon className="h-5 w-5" />}
to="/tags"
name="Tags"
className="flex-1"
/>
<BottomNavItem
icon={<ArchiveIcon className="h-5 w-5" />}
to="/difference"
name="Diff"
className="flex-1"
/>
<BottomNavItem
icon={<UserIcon className="h-5 w-5" />}
to={loginInfo ? "/profile" : "/login"}
name={loginInfo ? "Profile" : "Login"}
className="flex-1"
/>
<BottomNavItem
icon={<SettingsIcon className="h-5 w-5" />}
to="/setting"
name="Settings"
className="flex-1"
/>
</>}
</div>
</nav>
);
}
// 하단 네비게이션 아이템
interface BottomNavItemProps {
icon: React.ReactNode;
to: string;
name: string;
className?: string;
}
function BottomNavItem({ icon, to, name, className }: BottomNavItemProps) {
return (
<Link
href={to}
className={cn("flex flex-col items-center gap-1 p-2 hover:bg-accent text-xs min-w-0", className)}
>
{icon}
<span className="text-xs truncate leading-normal">{name}</span>
</Link>
);
}