From 04ed774ff60a83c00711d4e8615cb4122954dba5 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Thu, 4 Dec 2025 19:46:55 +0900
Subject: (김준회) 메뉴 관리기능 초안 개발 (시딩 필요)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/layout/MobileMenuV2.tsx | 160 +++++++++++++++++++++++++++++++++++++
 1 file changed, 160 insertions(+)
 create mode 100644 components/layout/MobileMenuV2.tsx

(limited to 'components/layout/MobileMenuV2.tsx')

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>
+  );
+}
-- 
cgit v1.2.3

