diff options
Diffstat (limited to 'components/layout/DynamicMenuRender.tsx')
| -rw-r--r-- | components/layout/DynamicMenuRender.tsx | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/components/layout/DynamicMenuRender.tsx b/components/layout/DynamicMenuRender.tsx new file mode 100644 index 00000000..f94223ae --- /dev/null +++ b/components/layout/DynamicMenuRender.tsx @@ -0,0 +1,146 @@ +"use client"; + +import Link from "next/link"; +import { cn } from "@/lib/utils"; +import { NavigationMenuLink } from "@/components/ui/navigation-menu"; +import type { MenuTreeNode } from "@/lib/menu-v2/types"; + +interface DynamicMenuRenderProps { + groups: MenuTreeNode[] | undefined; + lng: string; + getTitle: (node: MenuTreeNode) => string; + getDescription: (node: MenuTreeNode) => string | null; + onItemClick?: () => void; +} + +export default function DynamicMenuRender({ + groups, + lng, + getTitle, + getDescription, + onItemClick, +}: DynamicMenuRenderProps) { + if (!groups || groups.length === 0) { + return ( + <div className="p-4 text-sm text-muted-foreground"> + 메뉴가 없습니다. + </div> + ); + } + + // 그룹별로 메뉴 분류 + const groupedMenus = new Map<string, MenuTreeNode[]>(); + const ungroupedMenus: MenuTreeNode[] = []; + + for (const item of groups) { + if (item.nodeType === "group") { + // 그룹인 경우, 그룹의 children을 해당 그룹에 추가 + const groupTitle = getTitle(item); + if (!groupedMenus.has(groupTitle)) { + groupedMenus.set(groupTitle, []); + } + if (item.children) { + groupedMenus.get(groupTitle)!.push(...item.children); + } + } else if (item.nodeType === "menu") { + // 직접 메뉴인 경우 (그룹 없이 직접 메뉴그룹에 속한 경우) + ungroupedMenus.push(item); + } + } + + // 그룹이 없고 메뉴만 있는 경우 - 단순 그리드 렌더링 + if (groupedMenus.size === 0 && ungroupedMenus.length > 0) { + return ( + <ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]"> + {ungroupedMenus.map((menu) => ( + <MenuListItem + key={menu.id} + href={`/${lng}${menu.menuPath}`} + title={getTitle(menu)} + onClick={onItemClick} + > + {getDescription(menu)} + </MenuListItem> + ))} + </ul> + ); + } + + // 그룹별 렌더링 - 가로 스크롤 지원 + // 컨텐츠가 85vw를 초과할 때만 스크롤 발생 + return ( + <div className="p-4 max-w-[85vw]"> + <div className="flex gap-6 overflow-x-auto"> + {/* 그룹화되지 않은 메뉴 (있는 경우) */} + {ungroupedMenus.length > 0 && ( + <div className="w-[200px] flex-shrink-0"> + <ul className="space-y-2"> + {ungroupedMenus.map((menu) => ( + <MenuListItem + key={menu.id} + href={`/${lng}${menu.menuPath}`} + title={getTitle(menu)} + onClick={onItemClick} + > + {getDescription(menu)} + </MenuListItem> + ))} + </ul> + </div> + )} + + {/* 그룹별 메뉴 - 순서대로 가로 배치 */} + {Array.from(groupedMenus.entries()).map(([groupTitle, menus]) => ( + <div key={groupTitle} className="w-[200px] flex-shrink-0"> + <h4 className="mb-2 text-sm font-semibold text-muted-foreground whitespace-nowrap"> + {groupTitle} + </h4> + <ul className="space-y-2"> + {menus.map((menu) => ( + <MenuListItem + key={menu.id} + href={`/${lng}${menu.menuPath}`} + title={getTitle(menu)} + onClick={onItemClick} + > + {getDescription(menu)} + </MenuListItem> + ))} + </ul> + </div> + ))} + </div> + </div> + ); +} + +interface MenuListItemProps { + href: string; + title: string; + children?: React.ReactNode; + onClick?: () => void; +} + +function MenuListItem({ href, title, children, onClick }: MenuListItemProps) { + return ( + <li> + <NavigationMenuLink asChild> + <Link + href={href} + onClick={onClick} + className={cn( + "block select-none space-y-1 rounded-md p-2 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground" + )} + > + <div className="text-sm font-medium leading-none">{title}</div> + {children && ( + <p className="line-clamp-2 text-xs leading-snug text-muted-foreground"> + {children} + </p> + )} + </Link> + </NavigationMenuLink> + </li> + ); +} + |
