@j-solution/components 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +10 -11
  2. package/USAGE_GUIDE.md +1 -1
  3. package/assets/jwms-portal-frontend-BEvCuV-9.css +1 -0
  4. package/assets/styles/j-components.css +1 -1
  5. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs +1 -1
  6. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs.map +1 -1
  7. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js +45 -40
  8. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js.map +1 -1
  9. package/components/organisms/JSidebarSimple.vue.cjs +1 -1
  10. package/components/organisms/JSidebarSimple.vue.js +2 -2
  11. package/components/organisms/JSidebarSimple.vue2.cjs +1 -1
  12. package/components/organisms/JSidebarSimple.vue2.cjs.map +1 -1
  13. package/components/organisms/JSidebarSimple.vue2.js +40 -70
  14. package/components/organisms/JSidebarSimple.vue2.js.map +1 -1
  15. package/components/organisms/JTree.vue.cjs +2 -0
  16. package/components/organisms/JTree.vue.cjs.map +1 -0
  17. package/components/organisms/JTree.vue.js +83 -0
  18. package/components/organisms/JTree.vue.js.map +1 -0
  19. package/components/organisms/JTree.vue2.cjs +2 -0
  20. package/components/organisms/JTree.vue2.cjs.map +1 -0
  21. package/components/organisms/JTree.vue2.js +5 -0
  22. package/components/organisms/JTree.vue2.js.map +1 -0
  23. package/index.cjs +1 -1
  24. package/index.js +15 -13
  25. package/lib/menu-utils.cjs +2 -0
  26. package/lib/menu-utils.cjs.map +1 -0
  27. package/lib/menu-utils.js +33 -0
  28. package/lib/menu-utils.js.map +1 -0
  29. package/package.json +1 -1
  30. package/types/index.d.ts +89 -6
  31. package/assets/jwms-portal-frontend-D8DdrheA.css +0 -1
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),F=require("vue-router"),f=require("../../atoms/JIcon.vue.cjs"),u=require("../../../lib/utils.cjs"),P={key:1,class:"w-4 flex-shrink-0"},S={key:0,class:"w-full"},E=e.defineComponent({__name:"JDynamicMenuItem",props:{item:{},level:{default:0},permissions:{default:()=>[]},activePath:{},expandedKeys:{default:()=>new Set},favorites:{default:()=>[]},onFavoriteToggle:{},isFavorite:{},styletype:{default:"default"},className:{},maxDepth:{default:10}},emits:["menuClick","expandChange"],setup(t,{emit:h}){const n=t,s=h,k=F.useRouter(),x=e.computed(()=>u.hasMenuPermission(n.item.menuKey,n.permissions)),m=e.computed(()=>!n.item.path||!n.activePath?!1:n.activePath===n.item.path),c=e.computed(()=>n.item.menuType==="F"||Array.isArray(n.item.children)&&n.item.children.length>0),d=e.computed(()=>{if(!c.value)return!1;const l=n.item.menuKey||n.item.label;return n.expandedKeys?.has(l)??!1}),v=e.computed(()=>n.item.disabled||!x.value),C=e.computed(()=>({paddingLeft:`${12+(n.level||0)*16}px`})),p=l=>{if(v.value){l.preventDefault();return}if(c.value){const a=n.item.menuKey||n.item.label,r=!d.value;s("expandChange",a,r)}else n.item.path&&(k.push(n.item.path),s("menuClick",{menuItem:n.item,path:[n.item],event:l}))},g={default:{itemClass:"flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate",iconSize:"sm"},minimal:{itemClass:"flex items-center gap-1.5 py-1.5 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate text-xs",iconSize:"sm"}},i=e.computed(()=>g[n.styletype]??g.default),b=e.computed(()=>d.value?"chevronDown":"chevronRight");return(l,a)=>{const r=e.resolveComponent("JDynamicMenuItem",!0);return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(u.cn)("w-full",t.className))},[e.createElementVNode("div",{class:e.normalizeClass(e.unref(u.cn)(i.value.itemClass,{"bg-accent text-accent-foreground":m.value,"hover:bg-accent/50":!v.value&&!m.value,"opacity-50 cursor-not-allowed":v.value,"font-medium":m.value})),style:e.normalizeStyle(C.value),onClick:p},[c.value?(e.openBlock(),e.createBlock(f.default,{key:0,name:b.value,size:i.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):(e.openBlock(),e.createElementBlock("span",P)),t.item.icon?(e.openBlock(),e.createBlock(f.default,{key:2,name:t.item.icon,size:i.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):e.createCommentVNode("",!0),e.createElementVNode("span",{class:e.normalizeClass(i.value.labelClass)},e.toDisplayString(t.item.label),3),t.item.menuKey&&t.item.menuType==="L"&&t.onFavoriteToggle?(e.openBlock(),e.createElementBlock("button",{key:3,class:e.normalizeClass(e.unref(u.cn)("opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0",n.styletype==="minimal"?"p-0.5":"p-1",t.isFavorite&&t.isFavorite(t.item.menuKey)&&"opacity-100")),onClick:a[0]||(a[0]=e.withModifiers(o=>t.onFavoriteToggle(t.item.menuKey),["stop"]))},[e.createVNode(f.default,{name:(t.isFavorite&&t.isFavorite(t.item.menuKey),"star"),size:i.value.iconSize,class:e.normalizeClass(t.isFavorite&&t.isFavorite(t.item.menuKey)?"text-yellow-500 fill-yellow-500":"text-muted-foreground")},null,8,["name","size","class"])],2)):e.createCommentVNode("",!0)],6),c.value&&d.value&&t.item.children&&Array.isArray(t.item.children)&&t.item.children.length>0&&t.level+1<t.maxDepth?(e.openBlock(),e.createElementBlock("div",S,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.item.children,(o,z)=>(e.openBlock(),e.createBlock(r,{key:o.menuKey||o.label||z,item:o,level:t.level+1,"max-depth":t.maxDepth,permissions:t.permissions,"active-path":t.activePath,"expanded-keys":t.expandedKeys,favorites:t.favorites,"on-favorite-toggle":t.onFavoriteToggle,"is-favorite":t.isFavorite,styletype:t.styletype,onMenuClick:a[1]||(a[1]=y=>s("menuClick",y)),onExpandChange:a[2]||(a[2]=(y,B)=>s("expandChange",y,B))},null,8,["item","level","max-depth","permissions","active-path","expanded-keys","favorites","on-favorite-toggle","is-favorite","styletype"]))),128))])):e.createCommentVNode("",!0)],2)}}});exports.default=E;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),B=require("vue-router"),f=require("../../atoms/JIcon.vue.cjs"),r=require("../../../lib/utils.cjs"),F={key:1,class:"w-4 flex-shrink-0"},P={key:0,class:"w-full"},S=e.defineComponent({__name:"JDynamicMenuItem",props:{item:{},level:{default:0},permissions:{default:()=>[]},activePath:{},expandedKeys:{default:()=>new Set},favorites:{default:()=>[]},onFavoriteToggle:{},isFavorite:{},styletype:{default:"default"},className:{},maxDepth:{default:10},disableNavigation:{type:Boolean,default:!1},activeKey:{default:null}},emits:["menuClick","expandChange"],setup(t,{emit:h}){const n=t,s=h,k=B.useRouter(),x=e.computed(()=>r.hasMenuPermission(n.item.menuKey,n.permissions)),m=e.computed(()=>n.activeKey!==void 0&&n.activeKey!==null?n.item.menuKey===n.activeKey:!n.item.path||!n.activePath?!1:n.activePath===n.item.path),c=e.computed(()=>n.item.menuType==="F"||Array.isArray(n.item.children)&&n.item.children.length>0),d=e.computed(()=>{if(!c.value)return!1;const i=n.item.menuKey||n.item.label;return n.expandedKeys?.has(i)??!1}),v=e.computed(()=>n.item.disabled||!x.value),C=e.computed(()=>({paddingLeft:`${12+(n.level||0)*16}px`})),b=i=>{if(v.value){i.preventDefault();return}if(c.value){const a=n.item.menuKey||n.item.label,u=!d.value;s("expandChange",a,u)}else!n.disableNavigation&&n.item.path&&k.push(n.item.path),s("menuClick",{menuItem:n.item,path:[n.item],event:i})},g={default:{itemClass:"flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate",iconSize:"sm"},minimal:{itemClass:"flex items-center gap-1.5 py-1.5 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate text-xs",iconSize:"sm"}},l=e.computed(()=>g[n.styletype]??g.default),p=e.computed(()=>d.value?"chevronDown":"chevronRight");return(i,a)=>{const u=e.resolveComponent("JDynamicMenuItem",!0);return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(r.cn)("w-full",t.className))},[e.createElementVNode("div",{class:e.normalizeClass(e.unref(r.cn)(l.value.itemClass,{"bg-accent text-accent-foreground":m.value,"hover:bg-accent/50":!v.value&&!m.value,"opacity-50 cursor-not-allowed":v.value,"font-medium":m.value})),style:e.normalizeStyle(C.value),onClick:b},[c.value?(e.openBlock(),e.createBlock(f.default,{key:0,name:p.value,size:l.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):(e.openBlock(),e.createElementBlock("span",F)),t.item.icon?(e.openBlock(),e.createBlock(f.default,{key:2,name:t.item.icon,size:l.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):e.createCommentVNode("",!0),e.createElementVNode("span",{class:e.normalizeClass(l.value.labelClass)},e.toDisplayString(t.item.label),3),t.item.menuKey&&t.item.menuType==="L"&&t.onFavoriteToggle?(e.openBlock(),e.createElementBlock("button",{key:3,class:e.normalizeClass(e.unref(r.cn)("opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0",n.styletype==="minimal"?"p-0.5":"p-1",t.isFavorite&&t.isFavorite(t.item.menuKey)&&"opacity-100")),onClick:a[0]||(a[0]=e.withModifiers(o=>t.onFavoriteToggle(t.item.menuKey),["stop"]))},[e.createVNode(f.default,{name:(t.isFavorite&&t.isFavorite(t.item.menuKey),"star"),size:l.value.iconSize,class:e.normalizeClass(t.isFavorite&&t.isFavorite(t.item.menuKey)?"text-yellow-500 fill-yellow-500":"text-muted-foreground")},null,8,["name","size","class"])],2)):e.createCommentVNode("",!0)],6),c.value&&d.value&&t.item.children&&Array.isArray(t.item.children)&&t.item.children.length>0&&t.level+1<t.maxDepth?(e.openBlock(),e.createElementBlock("div",P,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.item.children,(o,K)=>(e.openBlock(),e.createBlock(u,{key:o.menuKey||o.label||K,item:o,level:t.level+1,"max-depth":t.maxDepth,permissions:t.permissions,"active-path":t.activePath,"expanded-keys":t.expandedKeys,favorites:t.favorites,"on-favorite-toggle":t.onFavoriteToggle,"is-favorite":t.isFavorite,styletype:t.styletype,"disable-navigation":t.disableNavigation,"active-key":t.activeKey,onMenuClick:a[1]||(a[1]=y=>s("menuClick",y)),onExpandChange:a[2]||(a[2]=(y,z)=>s("expandChange",y,z))},null,8,["item","level","max-depth","permissions","active-path","expanded-keys","favorites","on-favorite-toggle","is-favorite","styletype","disable-navigation","active-key"]))),128))])):e.createCommentVNode("",!0)],2)}}});exports.default=S;
2
2
  //# sourceMappingURL=JDynamicMenuItem.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JDynamicMenuItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { useRouter } from 'vue-router'\r\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport { cn, hasMenuPermission } from '@/lib/utils'\r\n\r\n/**\r\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\r\n * Recursive Menu Item Component\r\n * \r\n * @description\r\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\r\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\r\n */\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 아이템 */\r\n item: SidebarMenuItem\r\n /** 메뉴 레벨 (들여쓰기용, 0부터 시작) */\r\n level?: number\r\n /** 권한 목록 */\r\n permissions?: MenuPermission[]\r\n /** 활성화된 메뉴 경로 */\r\n activePath?: string\r\n /** 확장된 메뉴 키 목록 */\r\n expandedKeys?: Set<number | string>\r\n /** 즐겨찾기 메뉴 키 목록 */\r\n favorites?: (number | string)[]\r\n /** 즐겨찾기 변경 핸들러 */\r\n onFavoriteToggle?: (menuKey: number | string | undefined) => void\r\n /** 즐겨찾기 확인 함수 */\r\n isFavorite?: (menuKey: number | string | undefined) => boolean\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 추가 CSS 클래스 */\r\n className?: string\r\n /** 최대 깊이 제한 (무한 루프 방지, 기본값: 10) */\r\n maxDepth?: number\r\n }>(),\r\n {\r\n level: 0,\r\n permissions: () => [],\r\n expandedKeys: () => new Set(),\r\n favorites: () => [],\r\n styletype: 'default',\r\n maxDepth: 10,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n /** 확장 상태 변경 이벤트 */\r\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\r\n}>()\r\n\r\nconst router = useRouter()\r\n\r\n/**\r\n * 권한 체크 함수\r\n * Permission check function\r\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\r\n */\r\nconst checkPermission = computed(() => {\r\n return hasMenuPermission(props.item.menuKey, props.permissions)\r\n})\r\n\r\n/**\r\n * 메뉴가 활성화되어 있는지 여부\r\n */\r\nconst isActive = computed(() => {\r\n if (!props.item.path || !props.activePath) return false\r\n return props.activePath === props.item.path\r\n})\r\n\r\n/**\r\n * 메뉴 타입이 폴더인지 여부\r\n * 순환 참조 방지: children이 유효한 배열인지 확인\r\n */\r\nconst isFolder = computed(() => {\r\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\r\n})\r\n\r\n/**\r\n * 메뉴가 확장되어 있는지 여부\r\n */\r\nconst isExpanded = computed(() => {\r\n if (!isFolder.value) return false\r\n const key = props.item.menuKey || props.item.label\r\n return props.expandedKeys?.has(key) ?? false\r\n})\r\n\r\n/**\r\n * 메뉴가 비활성화되어 있는지 여부\r\n */\r\nconst isDisabled = computed(() => {\r\n return props.item.disabled || !checkPermission.value\r\n})\r\n\r\n/**\r\n * 레벨별 들여쓰기 스타일\r\n * Tailwind의 표준 클래스는 제한적이므로 인라인 스타일 사용\r\n */\r\nconst indentStyle = computed(() => {\r\n const basePadding = 12 // 기본 패딩 (px)\r\n const level = props.level || 0\r\n const levelPadding = level * 16 // 레벨당 16px\r\n const totalPadding = basePadding + levelPadding\r\n return { paddingLeft: `${totalPadding}px` }\r\n})\r\n\r\n/**\r\n * 메뉴 클릭 핸들러\r\n */\r\nconst handleMenuClick = (event: MouseEvent) => {\r\n if (isDisabled.value) {\r\n event.preventDefault()\r\n return\r\n }\r\n\r\n if (isFolder.value) {\r\n // 폴더 타입: 확장/축소 토글\r\n const key = props.item.menuKey || props.item.label\r\n const newExpanded = !isExpanded.value\r\n emit('expandChange', key, newExpanded)\r\n } else if (props.item.path) {\r\n // 링크 타입: 라우팅\r\n router.push(props.item.path)\r\n \r\n // 메뉴 클릭 이벤트 발생\r\n emit('menuClick', {\r\n menuItem: props.item,\r\n path: [props.item], // 단순화된 경로 (필요시 부모 경로 포함하도록 확장 가능)\r\n event,\r\n })\r\n }\r\n}\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n itemClass: string\r\n labelClass: string\r\n iconSize: 'sm' | 'md'\r\n}> = {\r\n default: {\r\n itemClass: 'flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate',\r\n iconSize: 'sm',\r\n },\r\n minimal: {\r\n itemClass: 'flex items-center gap-1.5 py-1.5 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate text-xs',\r\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * Chevron 아이콘 컴포넌트\r\n */\r\nconst ChevronIcon = computed(() => {\r\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"cn('w-full', className)\">\r\n <!-- 메뉴 아이템 -->\r\n <div\r\n :class=\"cn(\r\n preset.itemClass,\r\n {\r\n 'bg-accent text-accent-foreground': isActive,\r\n 'hover:bg-accent/50': !isDisabled && !isActive,\r\n 'opacity-50 cursor-not-allowed': isDisabled,\r\n 'font-medium': isActive,\r\n }\r\n )\"\r\n :style=\"indentStyle\"\r\n @click=\"handleMenuClick\"\r\n >\r\n <!-- Chevron 아이콘 (폴더 타입만) -->\r\n <JIcon\r\n v-if=\"isFolder\"\r\n :name=\"ChevronIcon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\r\n\r\n <!-- 메뉴 아이콘 -->\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n\r\n <!-- 메뉴 라벨 -->\r\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\r\n \r\n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\r\n <button\r\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\r\n :class=\"cn(\r\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\r\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\r\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\r\n )\"\r\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\r\n >\r\n <JIcon\r\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\r\n :size=\"preset.iconSize\"\r\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\r\n />\r\n </button>\r\n </div>\r\n\r\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\r\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\r\n <div\r\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\r\n class=\"w-full\"\r\n >\r\n <JDynamicMenuItem\r\n v-for=\"(child, index) in item.children\"\r\n :key=\"child.menuKey || child.label || index\"\r\n :item=\"child\"\r\n :level=\"level + 1\"\r\n :max-depth=\"maxDepth\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :favorites=\"favorites\"\r\n :on-favorite-toggle=\"onFavoriteToggle\"\r\n :is-favorite=\"isFavorite\"\r\n :styletype=\"styletype\"\r\n @menu-click=\"emit('menuClick', $event)\"\r\n @expand-change=\"(menuKey, expanded) => emit('expandChange', menuKey, expanded)\"\r\n />\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_createBlock","JIcon","_openBlock","_hoisted_1","_toDisplayString","_cache","_withModifiers","$event","_createVNode","_hoisted_2","_Fragment","child","index","_component_JDynamicMenuItem","menuKey","expanded"],"mappings":"qnBAkBA,MAAMA,EAAQC,EAmCRC,EAAOC,EAOPC,EAASC,EAAAA,UAAA,EAOTC,EAAkBC,EAAAA,SAAS,IACxBC,EAAAA,kBAAkBR,EAAM,KAAK,QAASA,EAAM,WAAW,CAC/D,EAKKS,EAAWF,EAAAA,SAAS,IACpB,CAACP,EAAM,KAAK,MAAQ,CAACA,EAAM,WAAmB,GAC3CA,EAAM,aAAeA,EAAM,KAAK,IACxC,EAMKU,EAAWH,EAAAA,SAAS,IACjBP,EAAM,KAAK,WAAa,KAAQ,MAAM,QAAQA,EAAM,KAAK,QAAQ,GAAKA,EAAM,KAAK,SAAS,OAAS,CAC3G,EAKKW,EAAaJ,EAAAA,SAAS,IAAM,CAChC,GAAI,CAACG,EAAS,MAAO,MAAO,GAC5B,MAAME,EAAMZ,EAAM,KAAK,SAAWA,EAAM,KAAK,MAC7C,OAAOA,EAAM,cAAc,IAAIY,CAAG,GAAK,EACzC,CAAC,EAKKC,EAAaN,EAAAA,SAAS,IACnBP,EAAM,KAAK,UAAY,CAACM,EAAgB,KAChD,EAMKQ,EAAcP,EAAAA,SAAS,KAKpB,CAAE,YAAa,GADD,IAFPP,EAAM,OAAS,GACA,EAEQ,IAAA,EACtC,EAKKe,EAAmBC,GAAsB,CAC7C,GAAIH,EAAW,MAAO,CACpBG,EAAM,eAAA,EACN,MACF,CAEA,GAAIN,EAAS,MAAO,CAElB,MAAME,EAAMZ,EAAM,KAAK,SAAWA,EAAM,KAAK,MACvCiB,EAAc,CAACN,EAAW,MAChCT,EAAK,eAAgBU,EAAKK,CAAW,CACvC,MAAWjB,EAAM,KAAK,OAEpBI,EAAO,KAAKJ,EAAM,KAAK,IAAI,EAG3BE,EAAK,YAAa,CAChB,SAAUF,EAAM,KAChB,KAAM,CAACA,EAAM,IAAI,EACjB,MAAAgB,CAAA,CACD,EAEL,EAKME,EAID,CACH,QAAS,CACP,UAAW,iFACX,WAAY,kBACZ,SAAU,IAAA,EAEZ,QAAS,CACP,UAAW,qFACX,WAAY,0BACZ,SAAU,IAAA,CACZ,EAGIC,EAASZ,EAAAA,SAAS,IACfW,EAAclB,EAAM,SAAS,GAAKkB,EAAc,OACxD,EAKKE,EAAcb,EAAAA,SAAS,IACpBI,EAAW,MAAQ,cAAgB,cAC3C,uFAICU,EAAAA,mBA4EM,MAAA,CA5EA,MAAKC,EAAAA,eAAEC,QAAAC,EAAAA,EAAA,EAAE,SAAWvB,EAAA,SAAS,CAAA,CAAA,GAEjCwB,EAAAA,mBAiDM,MAAA,CAhDH,uBAAOF,EAAAA,MAAAC,IAAA,EAAaL,EAAA,MAAO,8CAAqEV,EAAA,MAA4C,qBAAA,CAAAI,EAAA,QAAeJ,EAAA,sCAAsDI,EAAA,oBAAsCJ,EAAA,KAAA,IASvP,uBAAOK,EAAA,KAAW,EAClB,QAAOC,CAAA,GAIAL,EAAA,qBADRgB,EAAAA,YAKEC,EAAAA,QAAA,OAHC,KAAMP,EAAA,MACN,KAAMD,EAAA,MAAO,SACd,MAAM,eAAA,4BAERS,EAAAA,YAAAP,EAAAA,mBAAyC,OAAzCQ,CAAyC,GAIjC5B,EAAA,KAAK,oBADbyB,EAAAA,YAKEC,EAAAA,QAAA,OAHC,KAAM1B,EAAA,KAAK,KACX,KAAMkB,EAAA,MAAO,SACd,MAAM,eAAA,uDAIRM,EAAAA,mBAAwD,OAAA,CAAjD,MAAKH,EAAAA,eAAEH,EAAA,MAAO,UAAU,CAAA,EAAKW,EAAAA,gBAAA7B,EAAA,KAAK,KAAK,EAAA,CAAA,EAItCA,EAAA,KAAK,SAAWA,OAAK,gBAAoBA,EAAA,gCADjDoB,EAAAA,mBAcS,SAAA,OAZN,uBAAOE,EAAAA,MAAAC,IAAA,+FAAwHxB,EAAM,YAAS,UAAA,QAAA,MAA6CC,EAAA,YAAcA,EAAA,WAAWA,EAAA,KAAK,OAAO,GAAA,aAAA,GAKhO,QAAK8B,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAC,gBAAAC,GAAOhC,EAAA,iBAAiBA,EAAA,KAAK,OAAO,EAAA,CAAA,MAAA,CAAA,EAAA,GAE1CiC,EAAAA,YAIEP,EAAAA,QAAA,CAHC,MAAM1B,EAAA,YAAcA,aAAWA,EAAA,KAAK,OAAO,EAAA,QAC3C,KAAMkB,EAAA,MAAO,SACb,uBAAOlB,EAAA,YAAcA,aAAWA,EAAA,KAAK,OAAO,EAAA,kCAAA,uBAAA,CAAA,uEAQ3CS,EAAA,OAAYC,SAAcV,EAAA,KAAK,UAAY,MAAM,QAAQA,OAAK,QAAQ,GAAKA,EAAA,KAAK,SAAS,OAAM,GAASA,EAAA,MAAK,EAAQA,EAAA,UAD7H2B,EAAAA,UAAA,EAAAP,EAAAA,mBAoBM,MApBNc,EAoBM,EAhBJP,EAAAA,UAAA,EAAA,EAAAP,EAAAA,mBAeEe,EAAAA,2BAdyBnC,EAAA,KAAK,SAAQ,CAA9BoC,EAAOC,mBADjBZ,EAAAA,YAeEa,EAAA,CAbC,IAAKF,EAAM,SAAWA,EAAM,OAASC,EACrC,KAAMD,EACN,MAAOpC,EAAA,MAAK,EACZ,YAAWA,EAAA,SACX,YAAaA,EAAA,YACb,cAAaA,EAAA,WACb,gBAAeA,EAAA,aACf,UAAWA,EAAA,UACX,qBAAoBA,EAAA,iBACpB,cAAaA,EAAA,WACb,UAAWA,EAAA,UACX,YAAU8B,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAE,GAAE/B,EAAI,YAAc+B,CAAM,GACpC,eAAaF,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,CAAGS,EAASC,IAAavC,EAAI,eAAiBsC,EAASC,CAAQ,EAAA"}
1
+ {"version":3,"file":"JDynamicMenuItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { useRouter } from 'vue-router'\r\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport { cn, hasMenuPermission } from '@/lib/utils'\r\n\r\n/**\r\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\r\n * Recursive Menu Item Component\r\n * \r\n * @description\r\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\r\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\r\n */\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\n defineProps<{\n /** 메뉴 아이템 */\n item: SidebarMenuItem\n /** 메뉴 레벨 (들여쓰기용, 0부터 시작) */\n level?: number\n /** 권한 목록 */\n permissions?: MenuPermission[]\n /** 활성화된 메뉴 경로 */\n activePath?: string\n /** 확장된 메뉴 키 목록 */\n expandedKeys?: Set<number | string>\n /** 즐겨찾기 메뉴 키 목록 */\n favorites?: (number | string)[]\n /** 즐겨찾기 변경 핸들러 */\n onFavoriteToggle?: (menuKey: number | string | undefined) => void\n /** 즐겨찾기 확인 함수 */\n isFavorite?: (menuKey: number | string | undefined) => boolean\n /** 스타일 타입 */\n styletype?: StyleType\n /** 추가 CSS 클래스 */\n className?: string\n /** 최대 깊이 제한 (무한 루프 방지, 기본값: 10) */\n maxDepth?: number\n /** 네비게이션 비활성화 (true일 때 router.push 건너뛰고 emit만 수행) */\n disableNavigation?: boolean\n /** 활성화된 메뉴 키 (menuKey 기반 활성화, activePath보다 우선) */\n activeKey?: number | string | null\n }>(),\n {\n level: 0,\n permissions: () => [],\n expandedKeys: () => new Set(),\n favorites: () => [],\n styletype: 'default',\n maxDepth: 10,\n disableNavigation: false,\n activeKey: null,\n },\n)\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n /** 확장 상태 변경 이벤트 */\r\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\r\n}>()\r\n\r\nconst router = useRouter()\r\n\r\n/**\r\n * 권한 체크 함수\r\n * Permission check function\r\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\r\n */\r\nconst checkPermission = computed(() => {\r\n return hasMenuPermission(props.item.menuKey, props.permissions)\r\n})\r\n\r\n/**\n * 메뉴가 활성화되어 있는지 여부\n * activeKey가 제공되면 menuKey 매칭, 아니면 경로 매칭\n */\nconst isActive = computed(() => {\n // activeKey가 제공되면 menuKey 기반 매칭 (우선순위 높음)\n if (props.activeKey !== undefined && props.activeKey !== null) {\n return props.item.menuKey === props.activeKey\n }\n // 경로 기반 매칭 (기본 동작)\n if (!props.item.path || !props.activePath) return false\n return props.activePath === props.item.path\n})\n\r\n/**\r\n * 메뉴 타입이 폴더인지 여부\r\n * 순환 참조 방지: children이 유효한 배열인지 확인\r\n */\r\nconst isFolder = computed(() => {\r\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\r\n})\r\n\r\n/**\r\n * 메뉴가 확장되어 있는지 여부\r\n */\r\nconst isExpanded = computed(() => {\r\n if (!isFolder.value) return false\r\n const key = props.item.menuKey || props.item.label\r\n return props.expandedKeys?.has(key) ?? false\r\n})\r\n\r\n/**\r\n * 메뉴가 비활성화되어 있는지 여부\r\n */\r\nconst isDisabled = computed(() => {\r\n return props.item.disabled || !checkPermission.value\r\n})\r\n\r\n/**\r\n * 레벨별 들여쓰기 스타일\r\n * Tailwind의 표준 클래스는 제한적이므로 인라인 스타일 사용\r\n */\r\nconst indentStyle = computed(() => {\r\n const basePadding = 12 // 기본 패딩 (px)\r\n const level = props.level || 0\r\n const levelPadding = level * 16 // 레벨당 16px\r\n const totalPadding = basePadding + levelPadding\r\n return { paddingLeft: `${totalPadding}px` }\r\n})\r\n\r\n/**\n * 메뉴 클릭 핸들러\n */\nconst handleMenuClick = (event: MouseEvent) => {\n if (isDisabled.value) {\n event.preventDefault()\n return\n }\n\n if (isFolder.value) {\n // 폴더 타입: 확장/축소 토글\n const key = props.item.menuKey || props.item.label\n const newExpanded = !isExpanded.value\n emit('expandChange', key, newExpanded)\n } else {\n // 링크 타입: 라우팅 (disableNavigation이 false일 때만)\n if (!props.disableNavigation && props.item.path) {\n router.push(props.item.path)\n }\n \n // 메뉴 클릭 이벤트 발생\n emit('menuClick', {\n menuItem: props.item,\n path: [props.item], // 단순화된 경로 (필요시 부모 경로 포함하도록 확장 가능)\n event,\n })\n }\n}\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n itemClass: string\r\n labelClass: string\r\n iconSize: 'sm' | 'md'\r\n}> = {\r\n default: {\r\n itemClass: 'flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate',\r\n iconSize: 'sm',\r\n },\r\n minimal: {\r\n itemClass: 'flex items-center gap-1.5 py-1.5 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate text-xs',\r\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * Chevron 아이콘 컴포넌트\r\n */\r\nconst ChevronIcon = computed(() => {\r\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"cn('w-full', className)\">\r\n <!-- 메뉴 아이템 -->\r\n <div\r\n :class=\"cn(\r\n preset.itemClass,\r\n {\r\n 'bg-accent text-accent-foreground': isActive,\r\n 'hover:bg-accent/50': !isDisabled && !isActive,\r\n 'opacity-50 cursor-not-allowed': isDisabled,\r\n 'font-medium': isActive,\r\n }\r\n )\"\r\n :style=\"indentStyle\"\r\n @click=\"handleMenuClick\"\r\n >\r\n <!-- Chevron 아이콘 (폴더 타입만) -->\r\n <JIcon\r\n v-if=\"isFolder\"\r\n :name=\"ChevronIcon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\r\n\r\n <!-- 메뉴 아이콘 -->\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n\r\n <!-- 메뉴 라벨 -->\r\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\r\n \r\n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\r\n <button\r\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\r\n :class=\"cn(\r\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\r\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\r\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\r\n )\"\r\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\r\n >\r\n <JIcon\r\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\r\n :size=\"preset.iconSize\"\r\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\r\n />\r\n </button>\r\n </div>\r\n\r\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\r\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\r\n <div\r\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\r\n class=\"w-full\"\r\n >\r\n <JDynamicMenuItem\n v-for=\"(child, index) in item.children\"\n :key=\"child.menuKey || child.label || index\"\n :item=\"child\"\n :level=\"level + 1\"\n :max-depth=\"maxDepth\"\n :permissions=\"permissions\"\n :active-path=\"activePath\"\n :expanded-keys=\"expandedKeys\"\n :favorites=\"favorites\"\n :on-favorite-toggle=\"onFavoriteToggle\"\n :is-favorite=\"isFavorite\"\n :styletype=\"styletype\"\n :disable-navigation=\"disableNavigation\"\n :active-key=\"activeKey\"\n @menu-click=\"emit('menuClick', $event)\"\n @expand-change=\"(menuKey, expanded) => emit('expandChange', menuKey, expanded)\"\n />\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_createBlock","JIcon","_openBlock","_hoisted_1","_toDisplayString","_cache","_withModifiers","$event","_createVNode","_hoisted_2","_Fragment","child","index","_component_JDynamicMenuItem","menuKey","expanded"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { defineComponent as M, computed as i, resolveComponent as A, createElementBlock as m, openBlock as a, normalizeClass as r, unref as g, createElementVNode as z, createCommentVNode as x, normalizeStyle as I, createBlock as k, toDisplayString as N, withModifiers as L, createVNode as B, Fragment as J, renderList as R } from "vue";
1
+ import { defineComponent as p, computed as a, resolveComponent as M, createElementBlock as m, openBlock as n, normalizeClass as r, unref as h, createElementVNode as F, createCommentVNode as x, normalizeStyle as A, createBlock as k, toDisplayString as I, withModifiers as B, createVNode as L, Fragment as J, renderList as R } from "vue";
2
2
  import { useRouter as V } from "vue-router";
3
3
  import C from "../../atoms/JIcon.vue.js";
4
4
  import { hasMenuPermission as $, cn as b } from "../../../lib/utils.js";
@@ -8,7 +8,7 @@ const Y = {
8
8
  }, j = {
9
9
  key: 0,
10
10
  class: "w-full"
11
- }, Q = /* @__PURE__ */ M({
11
+ }, Q = /* @__PURE__ */ p({
12
12
  __name: "JDynamicMenuItem",
13
13
  props: {
14
14
  item: {},
@@ -21,29 +21,32 @@ const Y = {
21
21
  isFavorite: {},
22
22
  styletype: { default: "default" },
23
23
  className: {},
24
- maxDepth: { default: 10 }
24
+ maxDepth: { default: 10 },
25
+ disableNavigation: { type: Boolean, default: !1 },
26
+ activeKey: { default: null }
25
27
  },
26
28
  emits: ["menuClick", "expandChange"],
27
- setup(e, { emit: P }) {
28
- const t = e, u = P, K = V(), S = i(() => $(t.item.menuKey, t.permissions)), v = i(() => !t.item.path || !t.activePath ? !1 : t.activePath === t.item.path), c = i(() => t.item.menuType === "F" || Array.isArray(t.item.children) && t.item.children.length > 0), y = i(() => {
29
+ setup(e, { emit: z }) {
30
+ const t = e, u = z, P = V(), S = a(() => $(t.item.menuKey, t.permissions)), v = a(() => t.activeKey !== void 0 && t.activeKey !== null ? t.item.menuKey === t.activeKey : !t.item.path || !t.activePath ? !1 : t.activePath === t.item.path), c = a(() => t.item.menuType === "F" || Array.isArray(t.item.children) && t.item.children.length > 0), y = a(() => {
29
31
  if (!c.value) return !1;
30
32
  const l = t.item.menuKey || t.item.label;
31
33
  return t.expandedKeys?.has(l) ?? !1;
32
- }), f = i(() => t.item.disabled || !S.value), p = i(() => ({ paddingLeft: `${12 + (t.level || 0) * 16}px` })), w = (l) => {
34
+ }), f = a(() => t.item.disabled || !S.value), w = a(() => ({ paddingLeft: `${12 + (t.level || 0) * 16}px` })), D = (l) => {
33
35
  if (f.value) {
34
36
  l.preventDefault();
35
37
  return;
36
38
  }
37
39
  if (c.value) {
38
- const n = t.item.menuKey || t.item.label, d = !y.value;
39
- u("expandChange", n, d);
40
- } else t.item.path && (K.push(t.item.path), u("menuClick", {
41
- menuItem: t.item,
42
- path: [t.item],
43
- // 단순화된 경로 (필요시 부모 경로 포함하도록 확장 가능)
44
- event: l
45
- }));
46
- }, F = {
40
+ const i = t.item.menuKey || t.item.label, d = !y.value;
41
+ u("expandChange", i, d);
42
+ } else
43
+ !t.disableNavigation && t.item.path && P.push(t.item.path), u("menuClick", {
44
+ menuItem: t.item,
45
+ path: [t.item],
46
+ // 단순화된 경로 (필요시 부모 경로 포함하도록 확장 가능)
47
+ event: l
48
+ });
49
+ }, K = {
47
50
  default: {
48
51
  itemClass: "flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group",
49
52
  labelClass: "flex-1 truncate",
@@ -55,14 +58,14 @@ const Y = {
55
58
  iconSize: "sm"
56
59
  // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용
57
60
  }
58
- }, s = i(() => F[t.styletype] ?? F.default), D = i(() => y.value ? "chevronDown" : "chevronRight");
59
- return (l, n) => {
60
- const d = A("JDynamicMenuItem", !0);
61
- return a(), m("div", {
62
- class: r(g(b)("w-full", e.className))
61
+ }, s = a(() => K[t.styletype] ?? K.default), E = a(() => y.value ? "chevronDown" : "chevronRight");
62
+ return (l, i) => {
63
+ const d = M("JDynamicMenuItem", !0);
64
+ return n(), m("div", {
65
+ class: r(h(b)("w-full", e.className))
63
66
  }, [
64
- z("div", {
65
- class: r(g(b)(
67
+ F("div", {
68
+ class: r(h(b)(
66
69
  s.value.itemClass,
67
70
  {
68
71
  "bg-accent text-accent-foreground": v.value,
@@ -71,43 +74,43 @@ const Y = {
71
74
  "font-medium": v.value
72
75
  }
73
76
  )),
74
- style: I(p.value),
75
- onClick: w
77
+ style: A(w.value),
78
+ onClick: D
76
79
  }, [
77
- c.value ? (a(), k(C, {
80
+ c.value ? (n(), k(C, {
78
81
  key: 0,
79
- name: D.value,
82
+ name: E.value,
80
83
  size: s.value.iconSize,
81
84
  class: "flex-shrink-0"
82
- }, null, 8, ["name", "size"])) : (a(), m("span", Y)),
83
- e.item.icon ? (a(), k(C, {
85
+ }, null, 8, ["name", "size"])) : (n(), m("span", Y)),
86
+ e.item.icon ? (n(), k(C, {
84
87
  key: 2,
85
88
  name: e.item.icon,
86
89
  size: s.value.iconSize,
87
90
  class: "flex-shrink-0"
88
91
  }, null, 8, ["name", "size"])) : x("", !0),
89
- z("span", {
92
+ F("span", {
90
93
  class: r(s.value.labelClass)
91
- }, N(e.item.label), 3),
92
- e.item.menuKey && e.item.menuType === "L" && e.onFavoriteToggle ? (a(), m("button", {
94
+ }, I(e.item.label), 3),
95
+ e.item.menuKey && e.item.menuType === "L" && e.onFavoriteToggle ? (n(), m("button", {
93
96
  key: 3,
94
- class: r(g(b)(
97
+ class: r(h(b)(
95
98
  "opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0",
96
99
  t.styletype === "minimal" ? "p-0.5" : "p-1",
97
100
  e.isFavorite && e.isFavorite(e.item.menuKey) && "opacity-100"
98
101
  )),
99
- onClick: n[0] || (n[0] = L((o) => e.onFavoriteToggle(e.item.menuKey), ["stop"]))
102
+ onClick: i[0] || (i[0] = B((o) => e.onFavoriteToggle(e.item.menuKey), ["stop"]))
100
103
  }, [
101
- B(C, {
104
+ L(C, {
102
105
  name: (e.isFavorite && e.isFavorite(e.item.menuKey), "star"),
103
106
  size: s.value.iconSize,
104
107
  class: r(e.isFavorite && e.isFavorite(e.item.menuKey) ? "text-yellow-500 fill-yellow-500" : "text-muted-foreground")
105
108
  }, null, 8, ["name", "size", "class"])
106
109
  ], 2)) : x("", !0)
107
110
  ], 6),
108
- c.value && y.value && e.item.children && Array.isArray(e.item.children) && e.item.children.length > 0 && e.level + 1 < e.maxDepth ? (a(), m("div", j, [
109
- (a(!0), m(J, null, R(e.item.children, (o, E) => (a(), k(d, {
110
- key: o.menuKey || o.label || E,
111
+ c.value && y.value && e.item.children && Array.isArray(e.item.children) && e.item.children.length > 0 && e.level + 1 < e.maxDepth ? (n(), m("div", j, [
112
+ (n(!0), m(J, null, R(e.item.children, (o, N) => (n(), k(d, {
113
+ key: o.menuKey || o.label || N,
111
114
  item: o,
112
115
  level: e.level + 1,
113
116
  "max-depth": e.maxDepth,
@@ -118,9 +121,11 @@ const Y = {
118
121
  "on-favorite-toggle": e.onFavoriteToggle,
119
122
  "is-favorite": e.isFavorite,
120
123
  styletype: e.styletype,
121
- onMenuClick: n[1] || (n[1] = (h) => u("menuClick", h)),
122
- onExpandChange: n[2] || (n[2] = (h, T) => u("expandChange", h, T))
123
- }, null, 8, ["item", "level", "max-depth", "permissions", "active-path", "expanded-keys", "favorites", "on-favorite-toggle", "is-favorite", "styletype"]))), 128))
124
+ "disable-navigation": e.disableNavigation,
125
+ "active-key": e.activeKey,
126
+ onMenuClick: i[1] || (i[1] = (g) => u("menuClick", g)),
127
+ onExpandChange: i[2] || (i[2] = (g, T) => u("expandChange", g, T))
128
+ }, null, 8, ["item", "level", "max-depth", "permissions", "active-path", "expanded-keys", "favorites", "on-favorite-toggle", "is-favorite", "styletype", "disable-navigation", "active-key"]))), 128))
124
129
  ])) : x("", !0)
125
130
  ], 2);
126
131
  };
@@ -1 +1 @@
1
- {"version":3,"file":"JDynamicMenuItem.vue.js","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { useRouter } from 'vue-router'\r\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport { cn, hasMenuPermission } from '@/lib/utils'\r\n\r\n/**\r\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\r\n * Recursive Menu Item Component\r\n * \r\n * @description\r\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\r\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\r\n */\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 아이템 */\r\n item: SidebarMenuItem\r\n /** 메뉴 레벨 (들여쓰기용, 0부터 시작) */\r\n level?: number\r\n /** 권한 목록 */\r\n permissions?: MenuPermission[]\r\n /** 활성화된 메뉴 경로 */\r\n activePath?: string\r\n /** 확장된 메뉴 키 목록 */\r\n expandedKeys?: Set<number | string>\r\n /** 즐겨찾기 메뉴 키 목록 */\r\n favorites?: (number | string)[]\r\n /** 즐겨찾기 변경 핸들러 */\r\n onFavoriteToggle?: (menuKey: number | string | undefined) => void\r\n /** 즐겨찾기 확인 함수 */\r\n isFavorite?: (menuKey: number | string | undefined) => boolean\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 추가 CSS 클래스 */\r\n className?: string\r\n /** 최대 깊이 제한 (무한 루프 방지, 기본값: 10) */\r\n maxDepth?: number\r\n }>(),\r\n {\r\n level: 0,\r\n permissions: () => [],\r\n expandedKeys: () => new Set(),\r\n favorites: () => [],\r\n styletype: 'default',\r\n maxDepth: 10,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n /** 확장 상태 변경 이벤트 */\r\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\r\n}>()\r\n\r\nconst router = useRouter()\r\n\r\n/**\r\n * 권한 체크 함수\r\n * Permission check function\r\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\r\n */\r\nconst checkPermission = computed(() => {\r\n return hasMenuPermission(props.item.menuKey, props.permissions)\r\n})\r\n\r\n/**\r\n * 메뉴가 활성화되어 있는지 여부\r\n */\r\nconst isActive = computed(() => {\r\n if (!props.item.path || !props.activePath) return false\r\n return props.activePath === props.item.path\r\n})\r\n\r\n/**\r\n * 메뉴 타입이 폴더인지 여부\r\n * 순환 참조 방지: children이 유효한 배열인지 확인\r\n */\r\nconst isFolder = computed(() => {\r\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\r\n})\r\n\r\n/**\r\n * 메뉴가 확장되어 있는지 여부\r\n */\r\nconst isExpanded = computed(() => {\r\n if (!isFolder.value) return false\r\n const key = props.item.menuKey || props.item.label\r\n return props.expandedKeys?.has(key) ?? false\r\n})\r\n\r\n/**\r\n * 메뉴가 비활성화되어 있는지 여부\r\n */\r\nconst isDisabled = computed(() => {\r\n return props.item.disabled || !checkPermission.value\r\n})\r\n\r\n/**\r\n * 레벨별 들여쓰기 스타일\r\n * Tailwind의 표준 클래스는 제한적이므로 인라인 스타일 사용\r\n */\r\nconst indentStyle = computed(() => {\r\n const basePadding = 12 // 기본 패딩 (px)\r\n const level = props.level || 0\r\n const levelPadding = level * 16 // 레벨당 16px\r\n const totalPadding = basePadding + levelPadding\r\n return { paddingLeft: `${totalPadding}px` }\r\n})\r\n\r\n/**\r\n * 메뉴 클릭 핸들러\r\n */\r\nconst handleMenuClick = (event: MouseEvent) => {\r\n if (isDisabled.value) {\r\n event.preventDefault()\r\n return\r\n }\r\n\r\n if (isFolder.value) {\r\n // 폴더 타입: 확장/축소 토글\r\n const key = props.item.menuKey || props.item.label\r\n const newExpanded = !isExpanded.value\r\n emit('expandChange', key, newExpanded)\r\n } else if (props.item.path) {\r\n // 링크 타입: 라우팅\r\n router.push(props.item.path)\r\n \r\n // 메뉴 클릭 이벤트 발생\r\n emit('menuClick', {\r\n menuItem: props.item,\r\n path: [props.item], // 단순화된 경로 (필요시 부모 경로 포함하도록 확장 가능)\r\n event,\r\n })\r\n }\r\n}\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n itemClass: string\r\n labelClass: string\r\n iconSize: 'sm' | 'md'\r\n}> = {\r\n default: {\r\n itemClass: 'flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate',\r\n iconSize: 'sm',\r\n },\r\n minimal: {\r\n itemClass: 'flex items-center gap-1.5 py-1.5 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate text-xs',\r\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * Chevron 아이콘 컴포넌트\r\n */\r\nconst ChevronIcon = computed(() => {\r\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"cn('w-full', className)\">\r\n <!-- 메뉴 아이템 -->\r\n <div\r\n :class=\"cn(\r\n preset.itemClass,\r\n {\r\n 'bg-accent text-accent-foreground': isActive,\r\n 'hover:bg-accent/50': !isDisabled && !isActive,\r\n 'opacity-50 cursor-not-allowed': isDisabled,\r\n 'font-medium': isActive,\r\n }\r\n )\"\r\n :style=\"indentStyle\"\r\n @click=\"handleMenuClick\"\r\n >\r\n <!-- Chevron 아이콘 (폴더 타입만) -->\r\n <JIcon\r\n v-if=\"isFolder\"\r\n :name=\"ChevronIcon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\r\n\r\n <!-- 메뉴 아이콘 -->\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n\r\n <!-- 메뉴 라벨 -->\r\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\r\n \r\n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\r\n <button\r\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\r\n :class=\"cn(\r\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\r\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\r\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\r\n )\"\r\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\r\n >\r\n <JIcon\r\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\r\n :size=\"preset.iconSize\"\r\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\r\n />\r\n </button>\r\n </div>\r\n\r\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\r\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\r\n <div\r\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\r\n class=\"w-full\"\r\n >\r\n <JDynamicMenuItem\r\n v-for=\"(child, index) in item.children\"\r\n :key=\"child.menuKey || child.label || index\"\r\n :item=\"child\"\r\n :level=\"level + 1\"\r\n :max-depth=\"maxDepth\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :favorites=\"favorites\"\r\n :on-favorite-toggle=\"onFavoriteToggle\"\r\n :is-favorite=\"isFavorite\"\r\n :styletype=\"styletype\"\r\n @menu-click=\"emit('menuClick', $event)\"\r\n @expand-change=\"(menuKey, expanded) => emit('expandChange', menuKey, expanded)\"\r\n />\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_createBlock","JIcon","_openBlock","_hoisted_1","_toDisplayString","_cache","_withModifiers","$event","_createVNode","_hoisted_2","_Fragment","child","index","_component_JDynamicMenuItem","menuKey","expanded"],"mappings":"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"}
1
+ {"version":3,"file":"JDynamicMenuItem.vue.js","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { useRouter } from 'vue-router'\r\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport { cn, hasMenuPermission } from '@/lib/utils'\r\n\r\n/**\r\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\r\n * Recursive Menu Item Component\r\n * \r\n * @description\r\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\r\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\r\n */\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\n defineProps<{\n /** 메뉴 아이템 */\n item: SidebarMenuItem\n /** 메뉴 레벨 (들여쓰기용, 0부터 시작) */\n level?: number\n /** 권한 목록 */\n permissions?: MenuPermission[]\n /** 활성화된 메뉴 경로 */\n activePath?: string\n /** 확장된 메뉴 키 목록 */\n expandedKeys?: Set<number | string>\n /** 즐겨찾기 메뉴 키 목록 */\n favorites?: (number | string)[]\n /** 즐겨찾기 변경 핸들러 */\n onFavoriteToggle?: (menuKey: number | string | undefined) => void\n /** 즐겨찾기 확인 함수 */\n isFavorite?: (menuKey: number | string | undefined) => boolean\n /** 스타일 타입 */\n styletype?: StyleType\n /** 추가 CSS 클래스 */\n className?: string\n /** 최대 깊이 제한 (무한 루프 방지, 기본값: 10) */\n maxDepth?: number\n /** 네비게이션 비활성화 (true일 때 router.push 건너뛰고 emit만 수행) */\n disableNavigation?: boolean\n /** 활성화된 메뉴 키 (menuKey 기반 활성화, activePath보다 우선) */\n activeKey?: number | string | null\n }>(),\n {\n level: 0,\n permissions: () => [],\n expandedKeys: () => new Set(),\n favorites: () => [],\n styletype: 'default',\n maxDepth: 10,\n disableNavigation: false,\n activeKey: null,\n },\n)\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n /** 확장 상태 변경 이벤트 */\r\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\r\n}>()\r\n\r\nconst router = useRouter()\r\n\r\n/**\r\n * 권한 체크 함수\r\n * Permission check function\r\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\r\n */\r\nconst checkPermission = computed(() => {\r\n return hasMenuPermission(props.item.menuKey, props.permissions)\r\n})\r\n\r\n/**\n * 메뉴가 활성화되어 있는지 여부\n * activeKey가 제공되면 menuKey 매칭, 아니면 경로 매칭\n */\nconst isActive = computed(() => {\n // activeKey가 제공되면 menuKey 기반 매칭 (우선순위 높음)\n if (props.activeKey !== undefined && props.activeKey !== null) {\n return props.item.menuKey === props.activeKey\n }\n // 경로 기반 매칭 (기본 동작)\n if (!props.item.path || !props.activePath) return false\n return props.activePath === props.item.path\n})\n\r\n/**\r\n * 메뉴 타입이 폴더인지 여부\r\n * 순환 참조 방지: children이 유효한 배열인지 확인\r\n */\r\nconst isFolder = computed(() => {\r\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\r\n})\r\n\r\n/**\r\n * 메뉴가 확장되어 있는지 여부\r\n */\r\nconst isExpanded = computed(() => {\r\n if (!isFolder.value) return false\r\n const key = props.item.menuKey || props.item.label\r\n return props.expandedKeys?.has(key) ?? false\r\n})\r\n\r\n/**\r\n * 메뉴가 비활성화되어 있는지 여부\r\n */\r\nconst isDisabled = computed(() => {\r\n return props.item.disabled || !checkPermission.value\r\n})\r\n\r\n/**\r\n * 레벨별 들여쓰기 스타일\r\n * Tailwind의 표준 클래스는 제한적이므로 인라인 스타일 사용\r\n */\r\nconst indentStyle = computed(() => {\r\n const basePadding = 12 // 기본 패딩 (px)\r\n const level = props.level || 0\r\n const levelPadding = level * 16 // 레벨당 16px\r\n const totalPadding = basePadding + levelPadding\r\n return { paddingLeft: `${totalPadding}px` }\r\n})\r\n\r\n/**\n * 메뉴 클릭 핸들러\n */\nconst handleMenuClick = (event: MouseEvent) => {\n if (isDisabled.value) {\n event.preventDefault()\n return\n }\n\n if (isFolder.value) {\n // 폴더 타입: 확장/축소 토글\n const key = props.item.menuKey || props.item.label\n const newExpanded = !isExpanded.value\n emit('expandChange', key, newExpanded)\n } else {\n // 링크 타입: 라우팅 (disableNavigation이 false일 때만)\n if (!props.disableNavigation && props.item.path) {\n router.push(props.item.path)\n }\n \n // 메뉴 클릭 이벤트 발생\n emit('menuClick', {\n menuItem: props.item,\n path: [props.item], // 단순화된 경로 (필요시 부모 경로 포함하도록 확장 가능)\n event,\n })\n }\n}\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n itemClass: string\r\n labelClass: string\r\n iconSize: 'sm' | 'md'\r\n}> = {\r\n default: {\r\n itemClass: 'flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate',\r\n iconSize: 'sm',\r\n },\r\n minimal: {\r\n itemClass: 'flex items-center gap-1.5 py-1.5 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate text-xs',\r\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * Chevron 아이콘 컴포넌트\r\n */\r\nconst ChevronIcon = computed(() => {\r\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"cn('w-full', className)\">\r\n <!-- 메뉴 아이템 -->\r\n <div\r\n :class=\"cn(\r\n preset.itemClass,\r\n {\r\n 'bg-accent text-accent-foreground': isActive,\r\n 'hover:bg-accent/50': !isDisabled && !isActive,\r\n 'opacity-50 cursor-not-allowed': isDisabled,\r\n 'font-medium': isActive,\r\n }\r\n )\"\r\n :style=\"indentStyle\"\r\n @click=\"handleMenuClick\"\r\n >\r\n <!-- Chevron 아이콘 (폴더 타입만) -->\r\n <JIcon\r\n v-if=\"isFolder\"\r\n :name=\"ChevronIcon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\r\n\r\n <!-- 메뉴 아이콘 -->\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n\r\n <!-- 메뉴 라벨 -->\r\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\r\n \r\n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\r\n <button\r\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\r\n :class=\"cn(\r\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\r\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\r\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\r\n )\"\r\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\r\n >\r\n <JIcon\r\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\r\n :size=\"preset.iconSize\"\r\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\r\n />\r\n </button>\r\n </div>\r\n\r\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\r\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\r\n <div\r\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\r\n class=\"w-full\"\r\n >\r\n <JDynamicMenuItem\n v-for=\"(child, index) in item.children\"\n :key=\"child.menuKey || child.label || index\"\n :item=\"child\"\n :level=\"level + 1\"\n :max-depth=\"maxDepth\"\n :permissions=\"permissions\"\n :active-path=\"activePath\"\n :expanded-keys=\"expandedKeys\"\n :favorites=\"favorites\"\n :on-favorite-toggle=\"onFavoriteToggle\"\n :is-favorite=\"isFavorite\"\n :styletype=\"styletype\"\n :disable-navigation=\"disableNavigation\"\n :active-key=\"activeKey\"\n @menu-click=\"emit('menuClick', $event)\"\n @expand-change=\"(menuKey, expanded) => emit('expandChange', menuKey, expanded)\"\n />\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_createBlock","JIcon","_openBlock","_hoisted_1","_toDisplayString","_cache","_withModifiers","$event","_createVNode","_hoisted_2","_Fragment","child","index","_component_JDynamicMenuItem","menuKey","expanded"],"mappings":"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"}
@@ -3,5 +3,5 @@
3
3
  for (const [t_key, t_val] of t_opts)
4
4
  t_merged[t_key] = t_val;
5
5
  return t_merged;
6
- };,u=t(e.default,[["__scopeId","data-v-f4f33c97"]]);exports.default=u;
6
+ };,u=t(e.default,[["__scopeId","data-v-68150655"]]);exports.default=u;
7
7
  //# sourceMappingURL=JSidebarSimple.vue.cjs.map
@@ -6,8 +6,8 @@ const r = (r_comp, r_opts) => {
6
6
  r_merged[r_key] = r_val;
7
7
  return r_merged;
8
8
  };
9
- const p = /* @__PURE__ */ r(o, [["__scopeId", "data-v-f4f33c97"]]);
9
+ const a = /* @__PURE__ */ r(o, [["__scopeId", "data-v-68150655"]]);
10
10
  export {
11
- p as default
11
+ a as default
12
12
  };
13
13
  //# sourceMappingURL=JSidebarSimple.vue.js.map
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),E=require("vue-router"),x=require("./JSidebarSimple/JDynamicMenuItem.vue.cjs"),f=require("../../lib/utils.cjs"),S=e.defineComponent({__name:"JSidebarSimple",props:{menuItems:{},permissions:{default:()=>[]},searchQuery:{default:""},styletype:{default:"default"},class:{},width:{default:"250px"},isVisible:{type:Boolean,default:!0}},emits:["menuClick"],setup(u,{emit:p}){const t=u,v=p,C=E.useRoute(),b=e.computed(()=>C.path),c=e.ref(new Set),m=e.computed(()=>{if(!Array.isArray(t.menuItems)||t.menuItems.length===0)return[];if(!t.searchQuery||t.searchQuery.trim()==="")return t.menuItems;const r=t.searchQuery.toLowerCase().trim(),i=a=>{const n=[];if(!Array.isArray(a))return n;for(const s of a){const d=s.label?.toLowerCase().includes(r)??!1;let l;s.children&&Array.isArray(s.children)&&s.children.length>0&&(l=i(s.children)),(d||Array.isArray(l)&&l.length>0)&&n.push({...s,children:l})}return n};return i(t.menuItems)});e.watch(()=>m.value,r=>{if(!t.searchQuery||t.searchQuery.trim()==="")return;(n=>{const s=new Set,d=l=>{if(Array.isArray(l)){for(const o of l)if(o.children&&Array.isArray(o.children)&&o.children.length>0){const A=o.menuKey||o.label;s.add(A),d(o.children)}}};return d(n),s})(r).forEach(n=>{c.value.add(n)})},{immediate:!1});const k=(r,i)=>{r&&(i?c.value.add(r):c.value.delete(r))},g=r=>{v("menuClick",r)},h={default:{containerClass:"h-full bg-background border-r border-border overflow-y-auto",menuPaddingClass:"p-2"},minimal:{containerClass:"h-full bg-background border-r border-border overflow-y-auto",menuPaddingClass:"p-1"}},y=e.computed(()=>h[t.styletype]??h.default),w=e.computed(()=>f.cn(y.value.containerClass,t.class));return(r,i)=>(e.openBlock(),e.createBlock(e.Transition,{name:"slide"},{default:e.withCtx(()=>[e.withDirectives(e.createElementVNode("aside",{class:e.normalizeClass(w.value),style:e.normalizeStyle({width:u.width})},[e.createElementVNode("div",{class:e.normalizeClass(e.unref(f.cn)(y.value.menuPaddingClass,"space-y-1"))},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(m.value,(a,n)=>(e.openBlock(),e.createBlock(x.default,{key:a.menuKey||a.label||n,item:a,level:0,permissions:u.permissions,"active-path":b.value,"expanded-keys":c.value,styletype:u.styletype,onMenuClick:g,onExpandChange:k},null,8,["item","permissions","active-path","expanded-keys","styletype"]))),128))],2)],6),[[e.vShow,t.isVisible]])]),_:1}))}});exports.default=S;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),b=require("vue-router"),g=require("./JSidebarSimple/JDynamicMenuItem.vue.cjs"),c=require("../../lib/utils.cjs"),d=require("../../lib/menu-utils.cjs"),E=e.defineComponent({__name:"JSidebarSimple",props:{menuItems:{},permissions:{default:()=>[]},searchQuery:{default:""},styletype:{default:"default"},class:{},width:{default:"250px"},isVisible:{type:Boolean,default:!0}},emits:["menuClick"],setup(n,{emit:m}){const s=n,p=m,y=b.useRoute(),f=e.computed(()=>y.path),r=e.ref(new Set),u=e.computed(()=>d.filterMenuItems(s.menuItems,s.searchQuery||""));e.watch(()=>u.value,t=>{if(!s.searchQuery||s.searchQuery.trim()==="")return;d.getExpandedKeysForSearch(t).forEach(a=>{r.value.add(a)})},{immediate:!1});const v=(t,l)=>{t&&(l?r.value.add(t):r.value.delete(t))},h=t=>{p("menuClick",t)},o={default:{containerClass:"h-full bg-background border-r border-border overflow-y-auto",menuPaddingClass:"p-2"},minimal:{containerClass:"h-full bg-background border-r border-border overflow-y-auto",menuPaddingClass:"p-1"}},i=e.computed(()=>o[s.styletype]??o.default),k=e.computed(()=>c.cn(i.value.containerClass,s.class));return(t,l)=>(e.openBlock(),e.createBlock(e.Transition,{name:"slide"},{default:e.withCtx(()=>[e.withDirectives(e.createElementVNode("aside",{class:e.normalizeClass(k.value),style:e.normalizeStyle({width:n.width})},[e.createElementVNode("div",{class:e.normalizeClass(e.unref(c.cn)(i.value.menuPaddingClass,"space-y-1"))},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(u.value,(a,C)=>(e.openBlock(),e.createBlock(g.default,{key:a.menuKey||a.label||C,item:a,level:0,permissions:n.permissions,"active-path":f.value,"expanded-keys":r.value,styletype:n.styletype,onMenuClick:h,onExpandChange:v},null,8,["item","permissions","active-path","expanded-keys","styletype"]))),128))],2)],6),[[e.vShow,s.isVisible]])]),_:1}))}});exports.default=E;
2
2
  //# sourceMappingURL=JSidebarSimple.vue2.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JSidebarSimple.vue2.cjs","sources":["../../../../src/components/organisms/JSidebarSimple.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from 'vue'\r\nimport { useRoute } from 'vue-router'\r\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\r\nimport JDynamicMenuItem from './JSidebarSimple/JDynamicMenuItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarSimple - 간단한 사이드바 컴포넌트\r\n * Simple Sidebar Component\r\n * \r\n * @description\r\n * 다단계 메뉴 구조를 지원하는 기본 사이드바 컴포넌트입니다.\r\n * 권한 체크, 메뉴 검색 등의 기능을 제공합니다.\r\n * \r\n * @example\r\n * ```vue\r\n * <JSidebarSimple\r\n * :menu-items=\"menuItems\"\r\n * :permissions=\"userPermissions\"\r\n * @menu-click=\"handleMenuClick\"\r\n * />\r\n * ```\r\n * \r\n * @example JSON 메뉴 데이터 예시\r\n * ```json\r\n * [\r\n * {\r\n * \"label\": \"대시보드\",\r\n * \"icon\": \"house\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 1,\r\n * \"path\": \"/dashboard\"\r\n * },\r\n * {\r\n * \"label\": \"재고 관리\",\r\n * \"icon\": \"package\",\r\n * \"menuType\": \"F\",\r\n * \"menuKey\": 2,\r\n * \"children\": [\r\n * {\r\n * \"label\": \"재고 현황\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 21,\r\n * \"path\": \"/inventory/status\"\r\n * },\r\n * {\r\n * \"label\": \"입고 관리\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 22,\r\n * \"path\": \"/inventory/receiving\"\r\n * }\r\n * ]\r\n * }\r\n * ]\r\n * ```\r\n */\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 아이템 목록 */\r\n menuItems: SidebarMenuItem[]\r\n /** 권한 목록 */\r\n permissions?: MenuPermission[]\r\n /** 검색어 */\r\n searchQuery?: string\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n /** 너비 */\r\n width?: string\r\n /** 표시 여부 */\r\n isVisible?: boolean\r\n }>(),\r\n {\r\n permissions: () => [],\r\n searchQuery: '',\r\n styletype: 'default',\r\n width: '250px',\r\n isVisible: true,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n}>()\r\n\r\n// vue-router가 설정되지 않은 경우를 대비 (Storybook에서 router가 제공됨)\r\nconst route = useRoute()\r\n\r\n/**\r\n * 현재 활성화된 경로\r\n */\r\nconst activePath = computed(() => route.path)\r\n\r\n/**\r\n * 확장된 메뉴 키 목록 (Set)\r\n */\r\nconst expandedKeys = ref<Set<number | string>>(new Set())\r\n\r\n/**\r\n * 검색어로 필터링된 메뉴 아이템\r\n * 재귀적으로 children까지 검색\r\n */\r\nconst filteredMenuItems = computed(() => {\r\n if (!Array.isArray(props.menuItems) || props.menuItems.length === 0) {\r\n return []\r\n }\r\n\r\n if (!props.searchQuery || props.searchQuery.trim() === '') {\r\n return props.menuItems\r\n }\r\n\r\n const query = props.searchQuery.toLowerCase().trim()\r\n\r\n /**\r\n * 메뉴 아이템을 재귀적으로 검색\r\n */\r\n const searchInMenu = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\r\n const result: SidebarMenuItem[] = []\r\n\r\n if (!Array.isArray(items)) {\r\n return result\r\n }\r\n\r\n for (const item of items) {\r\n const matchesLabel = item.label?.toLowerCase().includes(query) ?? false\r\n\r\n // 하위 메뉴 검색\r\n let filteredChildren: SidebarMenuItem[] | undefined = undefined\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n filteredChildren = searchInMenu(item.children)\r\n }\r\n\r\n // 현재 메뉴나 하위 메뉴 중 하나라도 매칭되면 포함\r\n if (matchesLabel || (Array.isArray(filteredChildren) && filteredChildren.length > 0)) {\r\n result.push({\r\n ...item,\r\n children: filteredChildren,\r\n })\r\n }\r\n }\r\n\r\n return result\r\n }\r\n\r\n return searchInMenu(props.menuItems)\r\n})\r\n\r\n/**\r\n * 검색 결과에 따라 부모 메뉴 자동 확장\r\n * computed 외부에서 watch를 통해 처리\r\n */\r\nwatch(\r\n () => filteredMenuItems.value,\r\n (filtered) => {\r\n if (!props.searchQuery || props.searchQuery.trim() === '') {\r\n return\r\n }\r\n\r\n // 검색 결과에서 매칭된 하위 메뉴가 있는 부모를 찾아 확장\r\n const findParentsWithMatches = (items: SidebarMenuItem[]): Set<number | string> => {\r\n const keysToExpand = new Set<number | string>()\r\n\r\n const traverse = (menuItems: SidebarMenuItem[]): void => {\r\n if (!Array.isArray(menuItems)) {\r\n return\r\n }\r\n\r\n for (const item of menuItems) {\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n const key = item.menuKey || item.label\r\n keysToExpand.add(key)\r\n traverse(item.children)\r\n }\r\n }\r\n }\r\n\r\n traverse(items)\r\n return keysToExpand\r\n }\r\n\r\n const keysToExpand = findParentsWithMatches(filtered)\r\n keysToExpand.forEach(key => {\r\n expandedKeys.value.add(key)\r\n })\r\n },\r\n { immediate: false }\r\n)\r\n\r\n/**\r\n * 확장 상태 변경 핸들러\r\n */\r\nconst handleExpandChange = (menuKey: number | string | undefined, expanded: boolean) => {\r\n if (!menuKey) return\r\n\r\n if (expanded) {\r\n expandedKeys.value.add(menuKey)\r\n } else {\r\n expandedKeys.value.delete(menuKey)\r\n }\r\n}\r\n\r\n/**\r\n * 메뉴 클릭 핸들러\r\n */\r\nconst handleMenuClick = (event: MenuClickEvent) => {\r\n emit('menuClick', event)\r\n}\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n containerClass: string\r\n menuPaddingClass: string\r\n}> = {\r\n default: {\r\n containerClass: 'h-full bg-background border-r border-border overflow-y-auto',\r\n menuPaddingClass: 'p-2',\r\n },\r\n minimal: {\r\n containerClass: 'h-full bg-background border-r border-border overflow-y-auto',\r\n menuPaddingClass: 'p-1',\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * 루트 클래스\r\n */\r\nconst rootClasses = computed(() => {\r\n return cn(\r\n preset.value.containerClass,\r\n props.class\r\n )\r\n})\r\n</script>\r\n\r\n<template>\r\n <Transition name=\"slide\">\r\n <aside v-show=\"props.isVisible\" :class=\"rootClasses\" :style=\"{ width }\">\r\n <div :class=\"cn(preset.menuPaddingClass, 'space-y-1')\">\r\n <JDynamicMenuItem\r\n v-for=\"(item, index) in filteredMenuItems\"\r\n :key=\"item.menuKey || item.label || index\"\r\n :item=\"item\"\r\n :level=\"0\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :styletype=\"styletype\"\r\n @menu-click=\"handleMenuClick\"\r\n @expand-change=\"handleExpandChange\"\r\n />\r\n </div>\r\n </aside>\r\n </Transition>\r\n</template>\r\n\r\n<style scoped>\r\n.slide-enter-active,\r\n.slide-leave-active {\r\n transition: transform 0.3s ease, opacity 0.3s ease;\r\n}\r\n\r\n.slide-enter-from,\r\n.slide-leave-to {\r\n transform: translateX(-100%);\r\n opacity: 0;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","route","useRoute","activePath","computed","expandedKeys","ref","filteredMenuItems","query","searchInMenu","items","result","item","matchesLabel","filteredChildren","watch","filtered","keysToExpand","traverse","menuItems","key","handleExpandChange","menuKey","expanded","handleMenuClick","event","STYLE_PRESETS","preset","rootClasses","cn","_createBlock","_Transition","_createElementVNode","_normalizeClass","_unref","_openBlock","_createElementBlock","_Fragment","_renderList","index","JDynamicMenuItem","_vShow"],"mappings":"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"}
1
+ {"version":3,"file":"JSidebarSimple.vue2.cjs","sources":["../../../../src/components/organisms/JSidebarSimple.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JDynamicMenuItem from './JSidebarSimple/JDynamicMenuItem.vue'\nimport { cn } from '@/lib/utils'\nimport { filterMenuItems, getExpandedKeysForSearch } from '@/lib/menu-utils'\n\r\n/**\r\n * JSidebarSimple - 간단한 사이드바 컴포넌트\r\n * Simple Sidebar Component\r\n * \r\n * @description\r\n * 다단계 메뉴 구조를 지원하는 기본 사이드바 컴포넌트입니다.\r\n * 권한 체크, 메뉴 검색 등의 기능을 제공합니다.\r\n * \r\n * @example\r\n * ```vue\r\n * <JSidebarSimple\r\n * :menu-items=\"menuItems\"\r\n * :permissions=\"userPermissions\"\r\n * @menu-click=\"handleMenuClick\"\r\n * />\r\n * ```\r\n * \r\n * @example JSON 메뉴 데이터 예시\r\n * ```json\r\n * [\r\n * {\r\n * \"label\": \"대시보드\",\r\n * \"icon\": \"house\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 1,\r\n * \"path\": \"/dashboard\"\r\n * },\r\n * {\r\n * \"label\": \"재고 관리\",\r\n * \"icon\": \"package\",\r\n * \"menuType\": \"F\",\r\n * \"menuKey\": 2,\r\n * \"children\": [\r\n * {\r\n * \"label\": \"재고 현황\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 21,\r\n * \"path\": \"/inventory/status\"\r\n * },\r\n * {\r\n * \"label\": \"입고 관리\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 22,\r\n * \"path\": \"/inventory/receiving\"\r\n * }\r\n * ]\r\n * }\r\n * ]\r\n * ```\r\n */\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 아이템 목록 */\r\n menuItems: SidebarMenuItem[]\r\n /** 권한 목록 */\r\n permissions?: MenuPermission[]\r\n /** 검색어 */\r\n searchQuery?: string\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n /** 너비 */\r\n width?: string\r\n /** 표시 여부 */\r\n isVisible?: boolean\r\n }>(),\r\n {\r\n permissions: () => [],\r\n searchQuery: '',\r\n styletype: 'default',\r\n width: '250px',\r\n isVisible: true,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n}>()\r\n\r\n// vue-router가 설정되지 않은 경우를 대비 (Storybook에서 router가 제공됨)\r\nconst route = useRoute()\r\n\r\n/**\r\n * 현재 활성화된 경로\r\n */\r\nconst activePath = computed(() => route.path)\r\n\r\n/**\r\n * 확장된 메뉴 키 목록 (Set)\r\n */\r\nconst expandedKeys = ref<Set<number | string>>(new Set())\r\n\r\n/**\n * 검색어로 필터링된 메뉴 아이템\n * 재귀적으로 children까지 검색\n */\nconst filteredMenuItems = computed(() => {\n return filterMenuItems(props.menuItems, props.searchQuery || '')\n})\n\r\n/**\n * 검색 결과에 따라 부모 메뉴 자동 확장\n * computed 외부에서 watch를 통해 처리\n */\nwatch(\n () => filteredMenuItems.value,\n (filtered) => {\n if (!props.searchQuery || props.searchQuery.trim() === '') {\n return\n }\n\n // 검색 결과에서 매칭된 하위 메뉴가 있는 부모를 찾아 확장\n const keysToExpand = getExpandedKeysForSearch(filtered)\n keysToExpand.forEach(key => {\n expandedKeys.value.add(key)\n })\n },\n { immediate: false }\n)\n\r\n/**\r\n * 확장 상태 변경 핸들러\r\n */\r\nconst handleExpandChange = (menuKey: number | string | undefined, expanded: boolean) => {\r\n if (!menuKey) return\r\n\r\n if (expanded) {\r\n expandedKeys.value.add(menuKey)\r\n } else {\r\n expandedKeys.value.delete(menuKey)\r\n }\r\n}\r\n\r\n/**\r\n * 메뉴 클릭 핸들러\r\n */\r\nconst handleMenuClick = (event: MenuClickEvent) => {\r\n emit('menuClick', event)\r\n}\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n containerClass: string\r\n menuPaddingClass: string\r\n}> = {\r\n default: {\r\n containerClass: 'h-full bg-background border-r border-border overflow-y-auto',\r\n menuPaddingClass: 'p-2',\r\n },\r\n minimal: {\r\n containerClass: 'h-full bg-background border-r border-border overflow-y-auto',\r\n menuPaddingClass: 'p-1',\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * 루트 클래스\r\n */\r\nconst rootClasses = computed(() => {\r\n return cn(\r\n preset.value.containerClass,\r\n props.class\r\n )\r\n})\r\n</script>\r\n\r\n<template>\r\n <Transition name=\"slide\">\r\n <aside v-show=\"props.isVisible\" :class=\"rootClasses\" :style=\"{ width }\">\r\n <div :class=\"cn(preset.menuPaddingClass, 'space-y-1')\">\r\n <JDynamicMenuItem\r\n v-for=\"(item, index) in filteredMenuItems\"\r\n :key=\"item.menuKey || item.label || index\"\r\n :item=\"item\"\r\n :level=\"0\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :styletype=\"styletype\"\r\n @menu-click=\"handleMenuClick\"\r\n @expand-change=\"handleExpandChange\"\r\n />\r\n </div>\r\n </aside>\r\n </Transition>\r\n</template>\r\n\r\n<style scoped>\r\n.slide-enter-active,\r\n.slide-leave-active {\r\n transition: transform 0.3s ease, opacity 0.3s ease;\r\n}\r\n\r\n.slide-enter-from,\r\n.slide-leave-to {\r\n transform: translateX(-100%);\r\n opacity: 0;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","route","useRoute","activePath","computed","expandedKeys","ref","filteredMenuItems","filterMenuItems","watch","filtered","getExpandedKeysForSearch","key","handleExpandChange","menuKey","expanded","handleMenuClick","event","STYLE_PRESETS","preset","rootClasses","cn","_createBlock","_Transition","_createElementVNode","_normalizeClass","_unref","_openBlock","_createElementBlock","_Fragment","_renderList","item","index","JDynamicMenuItem","_vShow"],"mappings":"0hBA6DA,MAAMA,EAAQC,EA0BRC,EAAOC,EAMPC,EAAQC,EAAAA,SAAA,EAKRC,EAAaC,EAAAA,SAAS,IAAMH,EAAM,IAAI,EAKtCI,EAAeC,EAAAA,IAA0B,IAAI,GAAK,EAMlDC,EAAoBH,EAAAA,SAAS,IAC1BI,EAAAA,gBAAgBX,EAAM,UAAWA,EAAM,aAAe,EAAE,CAChE,EAMDY,EAAAA,MACE,IAAMF,EAAkB,MACvBG,GAAa,CACZ,GAAI,CAACb,EAAM,aAAeA,EAAM,YAAY,KAAA,IAAW,GACrD,OAImBc,EAAAA,yBAAyBD,CAAQ,EACzC,QAAQE,GAAO,CAC1BP,EAAa,MAAM,IAAIO,CAAG,CAC5B,CAAC,CACH,EACA,CAAE,UAAW,EAAA,CAAM,EAMrB,MAAMC,EAAqB,CAACC,EAAsCC,IAAsB,CACjFD,IAEDC,EACFV,EAAa,MAAM,IAAIS,CAAO,EAE9BT,EAAa,MAAM,OAAOS,CAAO,EAErC,EAKME,EAAmBC,GAA0B,CACjDlB,EAAK,YAAakB,CAAK,CACzB,EAKMC,EAGD,CACH,QAAS,CACP,eAAgB,8DAChB,iBAAkB,KAAA,EAEpB,QAAS,CACP,eAAgB,8DAChB,iBAAkB,KAAA,CACpB,EAGIC,EAASf,EAAAA,SAAS,IACfc,EAAcrB,EAAM,SAAS,GAAKqB,EAAc,OACxD,EAKKE,EAAchB,EAAAA,SAAS,IACpBiB,EAAAA,GACLF,EAAO,MAAM,eACbtB,EAAM,KAAA,CAET,8BAICyB,EAAAA,YAiBaC,EAAAA,WAAA,CAjBD,KAAK,SAAO,mBACtB,IAeQ,kBAfRC,EAAAA,mBAeQ,QAAA,CAfyB,uBAAOJ,EAAA,KAAW,EAAG,8BAAStB,EAAA,MAAK,CAAA,GAClE0B,EAAAA,mBAaM,MAAA,CAbA,MAAKC,EAAAA,eAAEC,EAAAA,MAAAL,EAAAA,EAAA,EAAGF,EAAA,MAAO,iBAAgB,WAAA,CAAA,CAAA,IACrCQ,EAAAA,UAAA,EAAA,EAAAC,EAAAA,mBAWEC,WAAA,KAAAC,EAAAA,WAVwBvB,EAAA,MAAiB,CAAjCwB,EAAMC,mBADhBV,EAAAA,YAWEW,UAAA,CATC,IAAKF,EAAK,SAAWA,EAAK,OAASC,EACnC,KAAAD,EACA,MAAO,EACP,YAAajC,EAAA,YACb,cAAaK,EAAA,MACb,gBAAeE,EAAA,MACf,UAAWP,EAAA,UACX,YAAYkB,EACZ,eAAeH,CAAA,4FAZP,CAAAqB,EAAAA,MAAArC,EAAM,SAAS,CAAA"}
@@ -1,8 +1,9 @@
1
- import { defineComponent as I, computed as d, ref as M, watch as Q, createBlock as p, openBlock as m, Transition as T, withCtx as L, withDirectives as B, createElementVNode as v, normalizeStyle as _, normalizeClass as C, unref as V, createElementBlock as z, Fragment as R, renderList as W, vShow as q } from "vue";
2
- import { useRoute as D } from "vue-router";
3
- import F from "./JSidebarSimple/JDynamicMenuItem.vue.js";
4
- import { cn as b } from "../../lib/utils.js";
5
- const $ = /* @__PURE__ */ I({
1
+ import { defineComponent as E, computed as r, ref as w, watch as S, createBlock as c, openBlock as o, Transition as P, withCtx as T, withDirectives as B, createElementVNode as m, normalizeStyle as I, normalizeClass as p, unref as M, createElementBlock as Q, Fragment as _, renderList as V, vShow as z } from "vue";
2
+ import { useRoute as F } from "vue-router";
3
+ import L from "./JSidebarSimple/JDynamicMenuItem.vue.js";
4
+ import { cn as f } from "../../lib/utils.js";
5
+ import { filterMenuItems as R, getExpandedKeysForSearch as D } from "../../lib/menu-utils.js";
6
+ const j = /* @__PURE__ */ E({
6
7
  __name: "JSidebarSimple",
7
8
  props: {
8
9
  menuItems: {},
@@ -14,55 +15,24 @@ const $ = /* @__PURE__ */ I({
14
15
  isVisible: { type: Boolean, default: !0 }
15
16
  },
16
17
  emits: ["menuClick"],
17
- setup(o, { emit: k }) {
18
- const e = o, g = k, w = D(), x = d(() => w.path), c = M(/* @__PURE__ */ new Set()), h = d(() => {
19
- if (!Array.isArray(e.menuItems) || e.menuItems.length === 0)
20
- return [];
21
- if (!e.searchQuery || e.searchQuery.trim() === "")
22
- return e.menuItems;
23
- const r = e.searchQuery.toLowerCase().trim(), l = (s) => {
24
- const t = [];
25
- if (!Array.isArray(s))
26
- return t;
27
- for (const n of s) {
28
- const u = n.label?.toLowerCase().includes(r) ?? !1;
29
- let a;
30
- n.children && Array.isArray(n.children) && n.children.length > 0 && (a = l(n.children)), (u || Array.isArray(a) && a.length > 0) && t.push({
31
- ...n,
32
- children: a
33
- });
34
- }
35
- return t;
36
- };
37
- return l(e.menuItems);
38
- });
39
- Q(
40
- () => h.value,
41
- (r) => {
42
- if (!e.searchQuery || e.searchQuery.trim() === "")
18
+ setup(s, { emit: y }) {
19
+ const t = s, h = y, v = F(), C = r(() => v.path), n = w(/* @__PURE__ */ new Set()), i = r(() => R(t.menuItems, t.searchQuery || ""));
20
+ S(
21
+ () => i.value,
22
+ (e) => {
23
+ if (!t.searchQuery || t.searchQuery.trim() === "")
43
24
  return;
44
- ((t) => {
45
- const n = /* @__PURE__ */ new Set(), u = (a) => {
46
- if (Array.isArray(a)) {
47
- for (const i of a)
48
- if (i.children && Array.isArray(i.children) && i.children.length > 0) {
49
- const P = i.menuKey || i.label;
50
- n.add(P), u(i.children);
51
- }
52
- }
53
- };
54
- return u(t), n;
55
- })(r).forEach((t) => {
56
- c.value.add(t);
25
+ D(e).forEach((a) => {
26
+ n.value.add(a);
57
27
  });
58
28
  },
59
29
  { immediate: !1 }
60
30
  );
61
- const A = (r, l) => {
62
- r && (l ? c.value.add(r) : c.value.delete(r));
63
- }, E = (r) => {
64
- g("menuClick", r);
65
- }, f = {
31
+ const b = (e, l) => {
32
+ e && (l ? n.value.add(e) : n.value.delete(e));
33
+ }, k = (e) => {
34
+ h("menuClick", e);
35
+ }, u = {
66
36
  default: {
67
37
  containerClass: "h-full bg-background border-r border-border overflow-y-auto",
68
38
  menuPaddingClass: "p-2"
@@ -71,33 +41,33 @@ const $ = /* @__PURE__ */ I({
71
41
  containerClass: "h-full bg-background border-r border-border overflow-y-auto",
72
42
  menuPaddingClass: "p-1"
73
43
  }
74
- }, y = d(() => f[e.styletype] ?? f.default), S = d(() => b(
75
- y.value.containerClass,
76
- e.class
44
+ }, d = r(() => u[t.styletype] ?? u.default), x = r(() => f(
45
+ d.value.containerClass,
46
+ t.class
77
47
  ));
78
- return (r, l) => (m(), p(T, { name: "slide" }, {
79
- default: L(() => [
80
- B(v("aside", {
81
- class: C(S.value),
82
- style: _({ width: o.width })
48
+ return (e, l) => (o(), c(P, { name: "slide" }, {
49
+ default: T(() => [
50
+ B(m("aside", {
51
+ class: p(x.value),
52
+ style: I({ width: s.width })
83
53
  }, [
84
- v("div", {
85
- class: C(V(b)(y.value.menuPaddingClass, "space-y-1"))
54
+ m("div", {
55
+ class: p(M(f)(d.value.menuPaddingClass, "space-y-1"))
86
56
  }, [
87
- (m(!0), z(R, null, W(h.value, (s, t) => (m(), p(F, {
88
- key: s.menuKey || s.label || t,
89
- item: s,
57
+ (o(!0), Q(_, null, V(i.value, (a, g) => (o(), c(L, {
58
+ key: a.menuKey || a.label || g,
59
+ item: a,
90
60
  level: 0,
91
- permissions: o.permissions,
92
- "active-path": x.value,
93
- "expanded-keys": c.value,
94
- styletype: o.styletype,
95
- onMenuClick: E,
96
- onExpandChange: A
61
+ permissions: s.permissions,
62
+ "active-path": C.value,
63
+ "expanded-keys": n.value,
64
+ styletype: s.styletype,
65
+ onMenuClick: k,
66
+ onExpandChange: b
97
67
  }, null, 8, ["item", "permissions", "active-path", "expanded-keys", "styletype"]))), 128))
98
68
  ], 2)
99
69
  ], 6), [
100
- [q, e.isVisible]
70
+ [z, t.isVisible]
101
71
  ])
102
72
  ]),
103
73
  _: 1
@@ -105,6 +75,6 @@ const $ = /* @__PURE__ */ I({
105
75
  }
106
76
  });
107
77
  export {
108
- $ as default
78
+ j as default
109
79
  };
110
80
  //# sourceMappingURL=JSidebarSimple.vue2.js.map