summaryrefslogtreecommitdiff
path: root/components/layout/DynamicMenuRender.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout/DynamicMenuRender.tsx')
-rw-r--r--components/layout/DynamicMenuRender.tsx146
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>
+ );
+}
+