Skip to Content
PatternsMenu Store (useMenu)

Menu Store (useMenu)

iCignal 레이아웃의 탭 네비게이션, 브레드크럼, 북마크를 관리하는 Zustand 스토어입니다.


목차

  1. 개요
  2. 기본 사용법
  3. 탭 관리
  4. 메뉴 검색
  5. 브레드크럼
  6. 북마크
  7. CachedOutlet 연동
  8. API Reference
  9. Best Practices

개요

useMenu@vortex/ui-icignal에서 제공하는 Zustand 스토어로, 다음 기능을 통합 관리합니다.

기능설명
탭 관리열린 페이지를 브라우저 탭처럼 관리
메뉴 검색계층적 메뉴 트리에서 URL로 항목 검색
브레드크럼pathname 기반 브레드크럼 자동 생성
북마크즐겨찾기 페이지 추가/제거/토글
영속성localStorage에 탭과 북마크 상태를 저장

설치

@vortex/ui-icignal 패키지에 포함되어 있습니다.

import { useMenu } from "@vortex/ui-icignal"

기본 사용법

Store 구독 (Selector 패턴)

// 필요한 값만 구독 (권장) const tabs = useMenu((state) => state.tabs) const push = useMenu((state) => state.push) // 여러 값 동시 구독 const { tabs, push, pop } = useMenu((state) => ({ tabs: state.tabs, push: state.push, pop: state.pop, }))

메뉴 아이템 초기화

앱 진입 시 사이드바 메뉴 데이터를 스토어에 등록합니다.

import { useMenu, type MenuItem } from "@vortex/ui-icignal" const menuItems: MenuItem[] = [ { type: "link", title: "대시보드", url: "/dashboard", items: [ { title: "개요", url: "/dashboard/overview" }, { title: "분석", url: "/dashboard/analytics" }, ], }, { type: "page", title: "사용자 관리", url: "/users", }, ] function AppLayout() { const setMenuItems = useMenu((state) => state.setMenuItems) useEffect(() => { setMenuItems(menuItems) }, [setMenuItems]) return <>{/* ... */}</> }

탭 관리

열린 페이지를 브라우저 탭처럼 관리합니다. 탭 상태는 localStorage에 자동 저장됩니다.

탭 추가 (push)

const push = useMenu((state) => state.push) // 탭 추가 (이미 열린 URL이면 무시) push({ title: "대시보드", url: "/dashboard" })
  • 동일 URL의 탭이 이미 있으면 중복 추가하지 않습니다.
  • maxTabs를 초과하면 가장 오래된 탭이 자동 제거됩니다.

탭 닫기 (pop)

const pop = useMenu((state) => state.pop) // 특정 인덱스의 탭 닫기 pop(2) // 마지막 탭 닫기 (인덱스 생략) pop()

다른 탭 모두 닫기 (closeOthers)

const closeOthers = useMenu((state) => state.closeOthers) // 인덱스 1번 탭만 남기고 나머지 닫기 closeOthers(1)

전체 탭 닫기 (closeAll)

const closeAll = useMenu((state) => state.closeAll) closeAll()

최대 탭 수 설정 (setMaxTabs)

const setMaxTabs = useMenu((state) => state.setMaxTabs) // 최대 15개 탭까지 허용 (기본값: 10) setMaxTabs(15)

메뉴 검색

URL로 메뉴 아이템 찾기 (findByUrl)

계층적 메뉴 트리를 재귀 탐색하여 URL이 일치하는 항목을 반환합니다.

const findByUrl = useMenu((state) => state.findByUrl) const menuItem = findByUrl("/dashboard/analytics") // → { title: "분석", url: "/dashboard/analytics" } 또는 undefined

검색 쿼리 (setSearchQuery)

메뉴 검색 UI에 연결할 수 있는 검색 쿼리 상태입니다.

const searchQuery = useMenu((state) => state.searchQuery) const setSearchQuery = useMenu((state) => state.setSearchQuery) <Input value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="메뉴 검색..." />

브레드크럼

자동 브레드크럼 생성 (getBreadcrumbItems)

현재 pathname을 세그먼트별로 분해하고, 각 경로에 매칭되는 메뉴 항목의 제목을 가져와 브레드크럼을 자동 생성합니다.

import { useMenu } from "@vortex/ui-icignal" import { Breadcrumb } from "@vortex/ui-icignal" function AppBreadcrumb() { const getBreadcrumbItems = useMenu((state) => state.getBreadcrumbItems) const pathname = usePathname() const items = getBreadcrumbItems(pathname) // 예: pathname="/dashboard/analytics" // → [ // { label: "대시보드", href: "/dashboard", type: "link" }, // { label: "분석", href: "/dashboard/analytics", type: "page" }, // ] return <Breadcrumb items={items} /> }

getBreadcrumbItemssetMenuItems로 등록된 메뉴 트리를 기반으로 동작합니다. 메뉴에 등록되지 않은 경로 세그먼트는 브레드크럼에서 건너뜁니다.


북마크

즐겨찾기 페이지를 관리합니다. 북마크 상태는 localStorage에 자동 저장됩니다.

북마크 추가/제거

const addBookmark = useMenu((state) => state.addBookmark) const removeBookmark = useMenu((state) => state.removeBookmark) // 북마크 추가 addBookmark({ title: "대시보드", url: "/dashboard" }) // 북마크 제거 removeBookmark("/dashboard")

북마크 토글

const toggleBookmark = useMenu((state) => state.toggleBookmark) // 있으면 제거, 없으면 추가 toggleBookmark({ title: "대시보드", url: "/dashboard" })

URL로 북마크 토글 (toggleBookmarkByUrl)

메뉴 트리에서 URL에 해당하는 항목을 찾아 자동으로 title을 채워줍니다.

const toggleBookmarkByUrl = useMenu((state) => state.toggleBookmarkByUrl) // 메뉴에서 자동으로 title을 찾아서 토글 toggleBookmarkByUrl("/dashboard")

북마크 여부 확인 (isBookmarked)

const isBookmarked = useMenu((state) => state.isBookmarked) <Button variant={isBookmarked("/dashboard") ? "filled" : "outlined"} onClick={() => toggleBookmarkByUrl("/dashboard")} > {isBookmarked("/dashboard") ? "★" : "☆"} </Button>

CachedOutlet 연동

useMenuCachedOutlet 컴포넌트와 함께 사용되어 페이지 캐싱 + 탭 자동 관리를 제공합니다.

동작 흐름

  1. 사용자가 페이지를 이동하면 CachedOutletfindByUrl로 메뉴 항목을 찾음
  2. 찾은 메뉴 항목을 push로 탭에 자동 추가
  3. 방문한 페이지 컴포넌트를 메모리에 캐싱 (display:none으로 숨김)
  4. 탭을 닫으면 해당 페이지의 캐시도 함께 제거
import { CachedOutlet } from "@vortex/ui-icignal" // 라우트 맵 기반 (Next.js) const routes = { "/dashboard": DashboardPage, "/users": UsersPage, "/settings": SettingsPage, } function MainContent() { return <CachedOutlet routes={routes} fallback={NotFoundPage} /> }

TabNavigation과의 조합

useMenu의 탭 상태를 TabNavigation 컴포넌트와 연결하면 완전한 탭 기반 네비게이션을 구현할 수 있습니다.

import { useMenu, TabNavigation } from "@vortex/ui-icignal" import { useRouter, usePathname } from "next/navigation" function AppTabs() { const tabs = useMenu((state) => state.tabs) const pop = useMenu((state) => state.pop) const router = useRouter() const pathname = usePathname() const tabItems = tabs.map((tab) => ({ value: tab.url, label: tab.title, })) return ( <TabNavigation items={tabItems} value={[pathname]} onValueChange={([url]) => router.push(url)} closable onClose={(url) => { const index = tabs.findIndex((t) => t.url === url) if (index !== -1) pop(index) }} /> ) }

API Reference

State

속성타입기본값설명
itemsMenuItem[][]전체 메뉴 트리
tabsMenuItem[][]열린 탭 목록
maxTabsnumber10최대 탭 수
searchQuerystring""메뉴 검색 쿼리
bookmarksBookmarkItem[][]북마크 목록

Actions

메서드시그니처설명
push(item: MenuItem) => void탭 추가 (중복 무시)
pop(index?: number) => void탭 닫기 (인덱스 또는 마지막)
closeOthers(index: number) => void지정 탭 외 모두 닫기
closeAll() => void전체 탭 닫기
findByUrl(url: string) => MenuItem | undefinedURL로 메뉴 아이템 검색
setMaxTabs(maxTabs: number) => void최대 탭 수 설정
setMenuItems(items: MenuItem[]) => void메뉴 트리 설정
setSearchQuery(query: string) => void검색 쿼리 설정
getBreadcrumbItems(pathname: string) => BreadcrumbItemProps[]브레드크럼 아이템 생성
addBookmark(item: BookmarkItem) => void북마크 추가
removeBookmark(url: string) => void북마크 제거
toggleBookmark(item: BookmarkItem) => void북마크 토글
toggleBookmarkByUrl(url: string) => voidURL로 북마크 토글
isBookmarked(url: string) => boolean북마크 여부 확인
속성타입설명
type"link" | "page" | "ellipsis"메뉴 타입 (선택)
titlestring메뉴 제목 (필수)
urlstring메뉴 URL (필수)
iconReactNode아이콘 (선택)
itemsMenuItem[]하위 메뉴 (선택)

BookmarkItem

속성타입설명
titlestring페이지 제목
urlstring페이지 URL

영속성 (persist)

useMenu는 Zustand persist 미들웨어를 사용하며, 다음 상태만 localStorage에 저장합니다.

저장 항목localStorage 키
tabsicignal:menu
bookmarksicignal:menu

items, maxTabs, searchQuery는 저장되지 않습니다. 앱 시작 시 setMenuItems로 메뉴를 다시 설정해야 합니다.


Best Practices

✅ 권장 사항

영역권장 사항
SelectoruseMenu((s) => s.tabs) 형태로 필요한 값만 구독
초기화레이아웃 최상위에서 setMenuItems 1회 호출
CachedOutlet탭 + 페이지 캐싱이 필요하면 CachedOutlet과 함께 사용
북마크 토글가능하면 toggleBookmarkByUrl로 title 자동 매칭
maxTabs성능을 위해 적절한 상한 설정 (기본 10)

⚠️ 피해야 할 것

  • setMenuItems를 매 렌더링마다 호출 → useEffect 내에서 1회만 호출
  • 전체 store 구독 → Selector 패턴으로 필요한 값만 구독
  • findByUrl 결과를 캐싱 없이 렌더링마다 호출 → 변수에 저장 후 사용

관련 문서

Last updated on