feat: 사이드바 토글 기능 적용

This commit is contained in:
2025-06-19 17:13:47 +09:00
parent 0c76036529
commit 4ef352f770
4 changed files with 95 additions and 35 deletions
@@ -20,6 +20,7 @@ import {
DropdownMenuItem,
} from "../dropdown-menu";
import { Button } from "../button";
import { SidebarTrigger } from "../sidebar";
const { SITE_ID } = getConfig();
@@ -91,7 +92,8 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
return (
<header className="sticky top-0 z-30 box-border flex h-[5rem] w-full items-center justify-between border-b border-solid border-border bg-background">
<div className="flex items-center">
<div className="flex items-center px-6">
<SidebarTrigger className="size-12 !bg-transparent p-2 hover:!bg-accent [&>svg]:!size-full [&>svg]:text-foreground" />
<div className="MobMenuBtn">
<img src={images.menuBtn} />
</div>
+72 -28
View File
@@ -12,6 +12,9 @@ import {
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
useSidebar,
} from "../sidebar";
import {
Collapsible,
@@ -79,7 +82,14 @@ export function LNB({
}
}, [menuList, currentPath]);
function MenuItem({ item }: { item: MenuItem }) {
function MenuItem({
item,
isSubMenu,
}: {
item: MenuItem;
isSubMenu?: boolean;
}) {
const { open: isSidebarOpen } = useSidebar();
const isMatched = isPathMatched(item.urlPath);
const handleClick = () => {
@@ -91,24 +101,37 @@ export function LNB({
return <MenuGroup item={item} />;
}
const MenuItem = isSubMenu ? SidebarMenuSubItem : SidebarMenuItem;
const MenuButton = isSubMenu ? SidebarMenuSubButton : SidebarMenuButton;
return (
<SidebarMenuItem>
<SidebarMenuButton
<MenuItem>
<MenuButton
className={cn(
"h-auto py-4 text-[1.4rem] text-label transition-colors hover:bg-accent",
"h-auto cursor-pointer py-4 text-[1.4rem] text-label transition-colors hover:bg-accent",
isMatched && "bg-accent !text-primary",
!isSidebarOpen && "justify-center",
)}
data-matched={isMatched}
onClick={handleClick}
>
<i className={cn("diveicon ml-2", `di-${item.iconNm}`)} />
{item.menuKornNm}
</SidebarMenuButton>
</SidebarMenuItem>
<i
className={cn(
"diveicon",
`di-${item.iconNm}`,
isSidebarOpen && "ml-2",
)}
/>
<span className={cn("whitespace-nowrap", !isSidebarOpen && "hidden")}>
{item.menuKornNm}
</span>
</MenuButton>
</MenuItem>
);
}
function MenuGroup({ item }: { item: MenuItem }) {
const { open: isSidebarOpen } = useSidebar();
const isOpen = openMenuIds.includes(item.menuCd);
const handleOpenChange = (open: boolean) => {
@@ -127,30 +150,48 @@ export function LNB({
};
return (
<Collapsible open={isOpen} onOpenChange={handleOpenChange}>
<Collapsible
open={isOpen}
onOpenChange={handleOpenChange}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<div className="group">
<SidebarMenuButton className="group/menu-button flex h-auto items-center justify-between py-4 pr-2 text-[1.4rem] group-data-[state=open]:bg-transparent">
<div className="flex items-center gap-2">
<i
className={cn(
"diveicon ml-2 text-label",
`di-${item.iconNm}`,
)}
/>
<span className="text-label group-hover/menu-button:text-foreground">
{item.menuKornNm}
</span>
</div>
<ChevronRight className="!size-6 text-label group-data-[state=open]:rotate-90" />
</SidebarMenuButton>
</div>
<SidebarMenuButton
className={cn(
"flex h-auto items-center py-4 pr-2 text-[1.4rem] group-data-[state=open]/collapsible:bg-transparent",
!isSidebarOpen && "justify-center",
)}
>
<div className="flex items-center gap-2">
<i
className={cn(
"diveicon text-label",
`di-${item.iconNm}`,
isSidebarOpen && "ml-2",
)}
/>
<span
className={cn(
"whitespace-nowrap text-label group-hover/menu-button:text-foreground",
!isSidebarOpen && "hidden",
)}
>
{item.menuKornNm}
</span>
</div>
<ChevronRight
className={cn(
"ml-auto !size-6 text-label group-data-[state=open]/collapsible:rotate-90",
!isSidebarOpen && "hidden",
)}
/>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub className="mr-0">
<SidebarMenuSub>
{item.children?.map((child) => (
<MenuItem key={child.menuCd} item={child} />
<MenuItem key={child.menuCd} item={child} isSubMenu />
))}
</SidebarMenuSub>
</CollapsibleContent>
@@ -160,7 +201,10 @@ export function LNB({
}
return (
<Sidebar className="border-border bg-background px-4 py-[6rem]">
<Sidebar
collapsible="icon"
className="border-border bg-background px-4 py-[6rem]"
>
<SidebarContent className="bg-background">
<SidebarMenu className="gap-0">
{menuList?.map((item) => <MenuItem key={item.menuCd} item={item} />)}
@@ -28,7 +28,7 @@ const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "22rem";
const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_WIDTH_ICON = "6rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
type SidebarContextProps = {
@@ -530,7 +530,7 @@ const SidebarMenuItem = React.forwardRef<
SidebarMenuItem.displayName = "SidebarMenuItem";
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-16 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
@@ -23,12 +23,19 @@
background: none !important;
color: #fff !important;
}
[data-sidebar="menu-button"]:hover {
[data-sidebar="trigger"]:hover,
[data-sidebar="menu-button"]:hover,
[data-sidebar="menu-sub-button"]:hover {
background: rgba(0, 0, 0, 0.1) !important;
}
[data-sidebar="menu-button"][data-matched="true"] {
[data-sidebar="trigger"][data-matched="true"],
[data-sidebar="menu-button"][data-matched="true"],
[data-sidebar="menu-sub-button"][data-matched="true"] {
background: rgba(0, 0, 0, 0.2) !important;
}
[data-sidebar="trigger"] > svg {
color: #fff !important;
}
}
[data-theme="red"] {
@@ -58,11 +65,18 @@
background: none !important;
color: #fff !important;
}
[data-sidebar="menu-button"]:hover {
[data-sidebar="trigger"]:hover,
[data-sidebar="menu-button"]:hover,
[data-sidebar="menu-sub-button"]:hover {
background: rgba(0, 0, 0, 0.1) !important;
}
[data-sidebar="menu-button"][data-matched="true"] {
[data-sidebar="trigger"][data-matched="true"],
[data-sidebar="menu-button"][data-matched="true"],
[data-sidebar="menu-sub-button"][data-matched="true"] {
background: rgba(0, 0, 0, 0.2) !important;
}
[data-sidebar="trigger"] > svg {
color: #fff !important;
}
}
}