summaryrefslogtreecommitdiff
path: root/components/layout/MobileMenuV2.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-12-04 21:05:28 +0900
committerjoonhoekim <26rote@gmail.com>2025-12-04 21:05:28 +0900
commite5b36fa6a1b12446883f51fc5e7cd56d8df8d8f5 (patch)
treec8f9fb50eb593dd5322d26d9276947c155997858 /components/layout/MobileMenuV2.tsx
parent240f4f31b3b6ff6a46436978fb988588a1972721 (diff)
parent04ed774ff60a83c00711d4e8615cb4122954dba5 (diff)
Merge branch 'jh-auth-menu' into dujinkim
Diffstat (limited to 'components/layout/MobileMenuV2.tsx')
-rw-r--r--components/layout/MobileMenuV2.tsx160
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>
+ );
+}