diff --git a/packages/common/components/src/ui/lnb/index.tsx b/packages/common/components/src/ui/lnb/index.tsx index e3b65dc63..99c7402d7 100644 --- a/packages/common/components/src/ui/lnb/index.tsx +++ b/packages/common/components/src/ui/lnb/index.tsx @@ -57,6 +57,7 @@ export function LNB({ const [currentMenuCode, setCurrentMenuCode] = useState(null); const prevPathRef = useRef(currentPath); const { browser } = useDetectDevice(); + const { isMobile, setOpenMobile } = useSidebar(); const appSiteId = getCookie("site-id-key") ?? ""; const siteId = propSiteId ?? appSiteId; @@ -90,6 +91,38 @@ export function LNB({ return currentPath?.startsWith(path); } + function handleSidebarKeyDown(event: React.KeyboardEvent) { + if (event.key !== "Tab") return; + + const menuControls = Array.from( + event.currentTarget.querySelectorAll( + '[data-sidebar="menu-button"], [data-sidebar="menu-sub-button"]', + ), + ).filter( + (control) => + control.tabIndex >= 0 && + !control.hasAttribute("disabled") && + control.getAttribute("aria-disabled") !== "true" && + !control.closest('[hidden], [aria-hidden="true"]'), + ); + const currentIndex = menuControls.findIndex( + (control) => + control === event.target || + control.contains(event.target as Node), + ); + + if (currentIndex < 0) return; + + const nextIndex = currentIndex + (event.shiftKey ? -1 : 1); + const nextControl = menuControls[nextIndex]; + + if (nextControl) { + event.preventDefault(); + nextControl.focus(); + nextControl.scrollIntoView({ block: "nearest" }); + } + } + useEffect(() => { if (!menuList || !currentPath) return; @@ -127,6 +160,7 @@ export function LNB({ if (prevPathRef.current !== currentPath) { setDefaultOpenMenuIds(matchedMenuIds); prevPathRef.current = currentPath; + if (isMobile) setOpenMobile(false); } if (matchedMenuCode) { @@ -142,7 +176,7 @@ export function LNB({ siteId, urlPath: currentPath, }); - }, [menuList, currentPath]); + }, [menuList, currentPath, isMobile, setOpenMobile]); function MenuItem({ item, @@ -151,65 +185,104 @@ export function LNB({ item: MenuItem; isSubMenu?: boolean; }) { - const { open: isSidebarOpen, setOpen } = useSidebar(); + const { + open: isSidebarOpen, + setOpen, + isMobile: isMobileView, + setOpenMobile: closeMobileSidebar, + } = useSidebar(); const isMatched = isPathMatched(item.urlPath) && currentMenuCode === item.menuCd; + const hideLabel = !isSidebarOpen && !isMobileView; - const handleClick = () => { - if (!isSidebarOpen) setOpen(true); + const handleClick = (event: React.MouseEvent) => { setCookie("menu-code-key", item.menuCd.toString()); - // newWndwYn이 Y인 경우 새창으로 열기 - if (item.newWndwYn === "Y") { - window.open(item.urlPath, "_blank"); + if (item.newWndwYn !== "Y" && handleMenuClick) { + event.preventDefault(); + handleMenuClick(item); + if (isMobileView && currentPath === item.urlPath) { + closeMobileSidebar(false); + } + } else if (isMobileView) { + closeMobileSidebar(false); } - - handleMenuClick?.(item); }; - if (item.children) { + if (item.children?.length) { return ; } - const MenuItem = isSubMenu ? SidebarMenuSubItem : SidebarMenuItem; - const MenuButton = isSubMenu ? SidebarMenuSubButton : SidebarMenuButton; + const menuContent = ( + <> + + + {item.menuKornNm} + + + ); + const linkProps = { + href: item.urlPath, + target: item.newWndwYn === "Y" ? "_blank" : undefined, + rel: item.newWndwYn === "Y" ? "noopener noreferrer" : undefined, + "aria-current": isMatched ? ("page" as const) : undefined, + onClick: handleClick, + }; + const menuClassName = cn( + "h-auto cursor-pointer py-4 text-[1.4rem] text-label transition-colors hover:bg-accent", + isMatched && "bg-accent !text-primary", + !isSidebarOpen && "justify-center", + ); + + if (isSubMenu) { + return ( + + + {menuContent} + + + ); + } return ( - - + - - - {item.menuKornNm} - - - + {menuContent} + + ); } function MenuGroup({ item }: { item: MenuItem }) { - const { open: isSidebarOpen, setOpen: setSidebarOpen } = useSidebar(); + const { + open: isSidebarOpen, + setOpen: setSidebarOpen, + isMobile, + } = useSidebar(); + const hideLabel = !isSidebarOpen && !isMobile; const [isOpen, setIsOpen] = useState( defaultOpenMenuIds.includes(item.menuCd), ); const handleOpenChange = (open: boolean) => { setIsOpen(open); - if (!isSidebarOpen) setSidebarOpen(true); + if (!isMobile && !isSidebarOpen) setSidebarOpen(true); }; return ( @@ -233,13 +306,13 @@ export function LNB({ className={cn( "diveicon text-label", `di-${item.iconNm}`, - isSidebarOpen && "ml-2", + !hideLabel && "ml-2", )} /> {item.menuKornNm} @@ -249,7 +322,7 @@ export function LNB({ className={cn( "ml-auto !size-6 text-label transition-transform", isOpen && "rotate-90", - !isSidebarOpen && "hidden", + hideLabel && "hidden", )} /> @@ -270,11 +343,14 @@ export function LNB({ - + {menuList?.map((item) => ( diff --git a/packages/common/components/src/ui/sheet/index.tsx b/packages/common/components/src/ui/sheet/index.tsx index 10a35d74c..f2672b822 100644 --- a/packages/common/components/src/ui/sheet/index.tsx +++ b/packages/common/components/src/ui/sheet/index.tsx @@ -67,7 +67,7 @@ const SheetContent = React.forwardRef< {children} - Close + 닫기 diff --git a/packages/common/components/src/ui/sidebar/index.tsx b/packages/common/components/src/ui/sidebar/index.tsx index 4333e9f92..c9a945ccf 100644 --- a/packages/common/components/src/ui/sidebar/index.tsx +++ b/packages/common/components/src/ui/sidebar/index.tsx @@ -211,7 +211,7 @@ const Sidebar = React.forwardRef< Sidebar Displays the mobile sidebar. -
{children}
+
+ {children} +
); @@ -281,12 +288,22 @@ const SidebarTrigger = React.forwardRef< React.ElementRef, React.ComponentProps >(({ className, onClick, ...props }, ref) => { - const { toggleSidebar } = useSidebar(); + const { toggleSidebar, isMobile, openMobile, state } = useSidebar(); return ( ); }); @@ -422,7 +439,7 @@ const SidebarContent = React.forwardRef< ref={ref} data-sidebar="content" className={cn( - "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", + "flex min-h-0 flex-1 flex-col gap-2 overflow-x-hidden overflow-y-auto overscroll-y-contain touch-pan-y group-data-[collapsible=icon]:overflow-hidden", className, )} {...props}