{"version":3,"file":"JSidebar.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, provide, watch, reactive } from 'vue'\nimport type { SidebarMenuItem, SidebarState } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JInput from '@/components/atoms/JInput.vue'\nimport JSidebarGroup from './JSidebarGroup.vue'\nimport JSidebarItem from './JSidebarItem.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebar - 통합 사이드바 컴포넌트\n *\n * DB 트리 데이터 기반, provide/inject로 상태 공유.\n * collapsed 모드, 검색, 즐겨찾기(localStorage) 지원.\n * 라우터를 모름 — @menu-click으로 아이템을 올려보냄.\n */\n\nconst props = withDefaults(\n  defineProps<{\n    /** 메뉴 트리 데이터 */\n    items: SidebarMenuItem[]\n    /** 접힘 상태 (v-model:collapsed) */\n    collapsed?: boolean\n    /** 현재 활성 경로 */\n    activePath?: string\n    /** 펼침 너비 */\n    width?: string\n    /** 접힘 너비 */\n    collapsedWidth?: string\n    /** 즐겨찾기 localStorage 키 (없으면 즐겨찾기 비활성) */\n    storageKey?: string\n    /** 검색 표시 여부 */\n    showSearch?: boolean\n    /** 즐겨찾기 섹션 표시 여부 */\n    showFavorites?: boolean\n  }>(),\n  {\n    collapsed: false,\n    width: '220px',\n    collapsedWidth: '56px',\n    showSearch: true,\n    showFavorites: true,\n  },\n)\n\nconst emit = defineEmits<{\n  'update:collapsed': [value: boolean]\n  'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\n// ── 즐겨찾기 (localStorage) ──\nconst favorites = ref<Set<string>>(new Set(loadFavorites()))\n\nfunction loadFavorites(): string[] {\n  if (!props.storageKey) return []\n  try {\n    const raw = localStorage.getItem(props.storageKey)\n    return raw ? JSON.parse(raw) : []\n  } catch {\n    return []\n  }\n}\n\nfunction saveFavorites() {\n  if (!props.storageKey) return\n  localStorage.setItem(props.storageKey, JSON.stringify([...favorites.value]))\n}\n\nfunction toggleFavorite(id: string) {\n  if (favorites.value.has(id)) {\n    favorites.value.delete(id)\n  } else {\n    favorites.value.add(id)\n  }\n  favorites.value = new Set(favorites.value) // trigger reactivity\n  saveFavorites()\n}\n\n// ── provide/inject 상태 ──\nconst sidebarState = reactive<SidebarState>({\n  collapsed: props.collapsed,\n  activePath: props.activePath,\n  favorites: favorites.value,\n  toggleFavorite,\n})\n\n// props 변경 시 state 동기화\nwatch(() => props.collapsed, (v) => { sidebarState.collapsed = v })\nwatch(() => props.activePath, (v) => { sidebarState.activePath = v })\nwatch(favorites, (v) => { sidebarState.favorites = v })\n\nprovide(SIDEBAR_INJECTION_KEY, sidebarState)\n\n// ── 검색 ──\nconst searchQuery = ref('')\n\n/** 즐겨찾기 아이템 (L 타입만, 트리 평탄화) */\nconst favoriteItems = computed(() => {\n  if (!props.storageKey || favorites.value.size === 0) return []\n  const result: SidebarMenuItem[] = []\n  const flatten = (items: SidebarMenuItem[]) => {\n    for (const item of items) {\n      if (item.menuType === 'L' && favorites.value.has(item.id)) {\n        result.push(item)\n      }\n      if (item.children) flatten(item.children)\n    }\n  }\n  flatten(props.items)\n  return result\n})\n\n/** 검색 필터링 (재귀) */\nconst filteredItems = computed(() => {\n  const q = searchQuery.value.trim().toLowerCase()\n  if (!q) return props.items\n\n  const filter = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\n    const result: SidebarMenuItem[] = []\n    for (const item of items) {\n      const matchLabel = item.label.toLowerCase().includes(q)\n      const filteredChildren = item.children ? filter(item.children) : undefined\n      if (matchLabel || (filteredChildren && filteredChildren.length > 0)) {\n        result.push({ ...item, children: filteredChildren })\n      }\n    }\n    return result\n  }\n  return filter(props.items)\n})\n\n/** 즐겨찾기 검색 필터링 */\nconst filteredFavorites = computed(() => {\n  const q = searchQuery.value.trim().toLowerCase()\n  if (!q) return favoriteItems.value\n  return favoriteItems.value.filter(item => item.label.toLowerCase().includes(q))\n})\n\n// ── 즐겨찾기 그룹 펼침 ──\nconst favoritesExpanded = ref(true)\n\n// ── 이벤트 핸들러 ──\nconst handleMenuClick = (item: SidebarMenuItem, event: MouseEvent) => {\n  emit('menu-click', item, event)\n}\n\nconst toggleCollapsed = () => {\n  emit('update:collapsed', !props.collapsed)\n}\n\n// ── 사이드바 너비 ──\nconst sidebarWidth = computed(() => props.collapsed ? props.collapsedWidth : props.width)\n</script>\n\n<template>\n  <aside\n    class=\"h-full bg-background border-r border-border flex flex-col flex-shrink-0 overflow-hidden transition-[width] duration-200 ease-out\"\n    :style=\"{ width: sidebarWidth }\"\n  >\n    <!-- 검색바 (펼침 + showSearch) -->\n    <div v-if=\"!collapsed && showSearch\" class=\"p-2 flex-shrink-0\">\n      <div class=\"relative\">\n        <JIcon\n          name=\"search\"\n          size=\"sm\"\n          class=\"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground\"\n        />\n        <JInput\n          v-model=\"searchQuery\"\n          placeholder=\"메뉴 검색...\"\n          class=\"pl-8 h-7 text-xs\"\n        />\n      </div>\n    </div>\n\n    <!-- 메뉴 영역 -->\n    <nav class=\"flex-1 overflow-y-auto overflow-x-hidden px-1 py-1\">\n      <!-- 즐겨찾기 섹션 -->\n      <template v-if=\"showFavorites && storageKey && filteredFavorites.length > 0\">\n        <!-- 펼침: 즐겨찾기 그룹 -->\n        <template v-if=\"!collapsed\">\n          <button\n            class=\"flex items-center gap-1.5 w-full px-2 py-1.5 text-xs font-semibold text-yellow-600 cursor-pointer select-none transition-colors hover:text-yellow-700\"\n            @click=\"favoritesExpanded = !favoritesExpanded\"\n          >\n            <JIcon\n              name=\"chevronRight\"\n              size=\"sm\"\n              :class=\"cn(\n                'flex-shrink-0 transition-transform duration-200',\n                favoritesExpanded && 'rotate-90'\n              )\"\n            />\n            <JIcon name=\"star\" size=\"sm\" class=\"flex-shrink-0 text-yellow-500\" />\n            <span class=\"flex-1 text-left\">즐겨찾기</span>\n            <span class=\"text-[10px] text-yellow-500/60\">{{ filteredFavorites.length }}</span>\n          </button>\n          <div\n            :class=\"cn(\n              'grid transition-[grid-template-rows] duration-200 ease-out',\n              favoritesExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n            )\"\n          >\n            <div class=\"overflow-hidden\">\n              <JSidebarItem\n                v-for=\"fav in filteredFavorites\"\n                :key=\"'fav-' + fav.id\"\n                :item=\"fav\"\n                @menu-click=\"handleMenuClick\"\n              />\n            </div>\n          </div>\n        </template>\n        <!-- 접힘: 별 아이콘 + 구분선 -->\n        <template v-else>\n          <div class=\"flex justify-center py-1\">\n            <JIcon name=\"star\" size=\"sm\" class=\"text-yellow-500\" />\n          </div>\n          <JSidebarItem\n            v-for=\"fav in filteredFavorites\"\n            :key=\"'fav-c-' + fav.id\"\n            :item=\"fav\"\n            @menu-click=\"handleMenuClick\"\n          />\n        </template>\n        <div class=\"h-px bg-border mx-2 my-1\" />\n      </template>\n\n      <!-- 메인 메뉴 -->\n      <template v-for=\"item in filteredItems\" :key=\"item.id\">\n        <JSidebarGroup\n          v-if=\"item.menuType === 'F'\"\n          :item=\"item\"\n          @menu-click=\"handleMenuClick\"\n        />\n        <JSidebarItem\n          v-else-if=\"item.menuType === 'L'\"\n          :item=\"item\"\n          @menu-click=\"handleMenuClick\"\n        />\n      </template>\n\n      <!-- 검색 결과 없음 -->\n      <div\n        v-if=\"filteredItems.length === 0 && searchQuery.trim()\"\n        class=\"text-center py-6 text-muted-foreground text-xs\"\n      >\n        검색 결과가 없습니다.\n      </div>\n    </nav>\n\n    <!-- 하단: collapse 토글 -->\n    <div class=\"flex-shrink-0 border-t border-border p-1\">\n      <button\n        class=\"flex items-center gap-2 w-full px-2 py-1 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors\"\n        @click=\"toggleCollapsed\"\n      >\n        <JIcon\n          :name=\"collapsed ? 'panelLeftOpen' : 'panelLeftClose'\"\n          size=\"sm\"\n        />\n        <span v-if=\"!collapsed\">접기</span>\n      </button>\n    </div>\n  </aside>\n</template>\n"],"names":["props","__props","emit","__emit","favorites","ref","loadFavorites","raw","saveFavorites","toggleFavorite","id","sidebarState","reactive","watch","v","provide","SIDEBAR_INJECTION_KEY","searchQuery","favoriteItems","computed","result","flatten","items","item","filteredItems","q","filter","matchLabel","filteredChildren","filteredFavorites","favoritesExpanded","handleMenuClick","event","toggleCollapsed","sidebarWidth","_createElementBlock","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","JIcon","JInput","$event","_hoisted_3","_Fragment","_hoisted_6","_renderList","fav","_createBlock","JSidebarItem","_cache","_unref","cn","_hoisted_4","_toDisplayString","_hoisted_5","JSidebarGroup","_hoisted_7","_hoisted_8"],"mappings":"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"}