diff options
| author | joonhoekim <26rote@gmail.com> | 2025-12-04 21:05:28 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-12-04 21:05:28 +0900 |
| commit | e5b36fa6a1b12446883f51fc5e7cd56d8df8d8f5 (patch) | |
| tree | c8f9fb50eb593dd5322d26d9276947c155997858 /components/layout/MobileMenuV2.tsx | |
| parent | 240f4f31b3b6ff6a46436978fb988588a1972721 (diff) | |
| parent | 04ed774ff60a83c00711d4e8615cb4122954dba5 (diff) | |
Merge branch 'jh-auth-menu' into dujinkim
Diffstat (limited to 'components/layout/MobileMenuV2.tsx')
| -rw-r--r-- | components/layout/MobileMenuV2.tsx | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/components/layout/MobileMenuV2.tsx b/components/layout/MobileMenuV2.tsx new file mode 100644 index 00000000..c83ba779 --- /dev/null +++ b/components/layout/MobileMenuV2.tsx @@ -0,0 +1,160 @@ +"use client"; + +import * as React from "react"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { X, ChevronDown, ChevronRight } from "lucide-react"; +import type { MenuTreeNode } from "@/lib/menu-v2/types"; + +interface MobileMenuV2Props { + lng: string; + onClose: () => void; + tree: MenuTreeNode[]; + getTitle: (node: MenuTreeNode) => string; + getDescription: (node: MenuTreeNode) => string | null; +} + +export function MobileMenuV2({ + lng, + onClose, + tree, + getTitle, + getDescription, +}: MobileMenuV2Props) { + const [expandedGroups, setExpandedGroups] = React.useState<Set<number>>( + new Set() + ); + + const toggleGroup = (groupId: number) => { + setExpandedGroups((prev) => { + const next = new Set(prev); + if (next.has(groupId)) { + next.delete(groupId); + } else { + next.add(groupId); + } + return next; + }); + }; + + // 드롭다운 메뉴인지 판단 + const isDropdownMenu = (node: MenuTreeNode) => + node.nodeType === 'menu_group' && node.children && node.children.length > 0; + + return ( + <div className="fixed inset-0 z-50 bg-background md:hidden"> + {/* 헤더 */} + <div className="flex items-center justify-between px-4 h-14 border-b"> + <span className="font-semibold">메뉴</span> + <Button variant="ghost" size="icon" onClick={onClose}> + <X className="h-5 w-5" /> + <span className="sr-only">닫기</span> + </Button> + </div> + + {/* 스크롤 영역 */} + <ScrollArea className="h-[calc(100vh-56px)]"> + <div className="px-4 py-4 space-y-2"> + {tree.map((node) => { + // 드롭다운 메뉴 (menu_group with children) + if (isDropdownMenu(node)) { + return ( + <div key={node.id} className="space-y-2"> + {/* 메뉴그룹 헤더 */} + <button + onClick={() => toggleGroup(node.id)} + className="flex items-center justify-between w-full py-2 text-left font-semibold" + > + <span>{getTitle(node)}</span> + {expandedGroups.has(node.id) ? ( + <ChevronDown className="h-4 w-4" /> + ) : ( + <ChevronRight className="h-4 w-4" /> + )} + </button> + + {/* 하위 메뉴 */} + {expandedGroups.has(node.id) && ( + <div className="pl-4 space-y-1"> + {node.children?.map((item) => { + if (item.nodeType === "group") { + // 그룹인 경우 + return ( + <div key={item.id} className="space-y-1"> + <div className="text-xs text-muted-foreground font-medium py-1"> + {getTitle(item)} + </div> + <div className="pl-2 space-y-1"> + {item.children?.map((menu) => ( + <MobileMenuLink + key={menu.id} + href={`/${lng}${menu.menuPath}`} + title={getTitle(menu)} + onClick={onClose} + /> + ))} + </div> + </div> + ); + } else if (item.nodeType === "menu") { + // 직접 메뉴인 경우 + return ( + <MobileMenuLink + key={item.id} + href={`/${lng}${item.menuPath}`} + title={getTitle(item)} + onClick={onClose} + /> + ); + } + return null; + })} + </div> + )} + </div> + ); + } + + // 단일 링크 메뉴 (최상위 menu) + if (node.nodeType === 'menu' && node.menuPath) { + return ( + <MobileMenuLink + key={node.id} + href={`/${lng}${node.menuPath}`} + title={getTitle(node)} + onClick={onClose} + /> + ); + } + + return null; + })} + </div> + </ScrollArea> + </div> + ); +} + +interface MobileMenuLinkProps { + href: string; + title: string; + onClick: () => void; +} + +function MobileMenuLink({ href, title, onClick }: MobileMenuLinkProps) { + return ( + <Link + href={href} + onClick={onClick} + className={cn( + "block py-2 px-2 rounded-md text-sm", + "hover:bg-accent hover:text-accent-foreground", + "transition-colors" + )} + > + {title} + </Link> + ); +} |
