@j-solution/components 1.9.6 → 1.9.8
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.
- package/README.md +8 -7
- package/assets/jwms-portal-frontend-Cv2tSqVg.css +1 -0
- package/assets/styles/j-components.css +1 -1
- package/assets/styles/main.css +8 -7
- package/assets/styles/themes.css +3 -3
- package/components/atoms/JGrid.vue.cjs +1 -1
- package/components/atoms/JGrid.vue.js +2 -2
- package/components/atoms/JGrid.vue2.cjs +1 -1
- package/components/atoms/JGrid.vue2.cjs.map +1 -1
- package/components/atoms/JGrid.vue2.js +34 -34
- package/components/atoms/JGrid.vue2.js.map +1 -1
- package/components/atoms/JSplitter.vue.cjs +1 -1
- package/components/atoms/JSplitter.vue.js +2 -2
- package/components/atoms/JSplitter.vue2.cjs.map +1 -1
- package/components/atoms/JSplitter.vue2.js.map +1 -1
- package/components/organisms/JSidebar/JSidebarGroup.vue.cjs +1 -1
- package/components/organisms/JSidebar/JSidebarGroup.vue.cjs.map +1 -1
- package/components/organisms/JSidebar/JSidebarGroup.vue.js +1 -1
- package/components/organisms/JSidebar/JSidebarGroup.vue.js.map +1 -1
- package/components/organisms/JSidebar/JSidebarItem.vue.cjs +1 -1
- package/components/organisms/JSidebar/JSidebarItem.vue.cjs.map +1 -1
- package/components/organisms/JSidebar/JSidebarItem.vue.js +1 -1
- package/components/organisms/JSidebar/JSidebarItem.vue.js.map +1 -1
- package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs +1 -1
- package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs.map +1 -1
- package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js +2 -2
- package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js.map +1 -1
- package/package.json +1 -1
- package/assets/jwms-portal-frontend-DLwBRhhZ.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSplitter.vue2.cjs","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n },\n)\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - props.defaultSize)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"direction\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"defaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/*
|
|
1
|
+
{"version":3,"file":"JSplitter.vue2.cjs","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n },\n)\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - props.defaultSize)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"direction\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"defaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/* ── overflow 보호 ──────────────────────────────────────────────────── */\n:deep(.jsplitter-group [data-panel]) {\n min-width: 0;\n min-height: 0;\n}\n\n/* ── horizontal 핸들: 8px 히트영역 ─────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"horizontal\"]) {\n width: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── vertical 핸들: 8px 히트영역 ───────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]) {\n height: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── 시각선: 1px (after:w-1 override) ──────────────────────────────── */\n:deep(.jsplitter-handle::after) {\n width: 1px !important;\n background: hsl(var(--border)) !important;\n transition: background 0.15s ease, width 0.12s ease !important;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::after) {\n height: 1px !important;\n width: 100% !important;\n}\n\n/* ── 그립 도트 인디케이터 (::before) ───────────────────────────────── */\n:deep(.jsplitter-handle::before) {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 3px;\n height: 20px;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n background-size: 3px 6px;\n background-repeat: repeat-y;\n border-radius: 2px;\n opacity: 0;\n transition: opacity 0.15s ease;\n pointer-events: none;\n z-index: 4;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::before) {\n width: 20px;\n height: 3px;\n background-size: 6px 3px;\n background-repeat: repeat-x;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n}\n\n/* ── hover ──────────────────────────────────────────────────────────── */\n:deep(.jsplitter-handle:hover) {\n background: hsl(var(--primary) / 0.04) !important;\n}\n\n:deep(.jsplitter-handle:hover::after) {\n width: 2px !important;\n background: hsl(var(--primary) / 0.55) !important;\n}\n\n:deep(.jsplitter-handle:hover::before) {\n opacity: 1;\n}\n\n/* ── drag / active ──────────────────────────────────────────────────── */\n:deep(.jsplitter-handle[data-state=\"drag\"]),\n:deep(.jsplitter-handle:active) {\n background: hsl(var(--primary) / 0.06) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::after),\n:deep(.jsplitter-handle:active::after) {\n width: 2px !important;\n background: hsl(var(--primary)) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::before),\n:deep(.jsplitter-handle:active::before) {\n opacity: 1;\n background-image: radial-gradient(circle, hsl(var(--primary) / 0.65) 1px, transparent 1px);\n}\n</style>\n"],"names":["props","__props","secondDefaultSize","computed","_createBlock","_unref","ResizablePanelGroup","_normalizeClass","cn","$props","_createVNode","ResizablePanel","_normalizeStyle","_renderSlot","_ctx","ResizableHandle"],"mappings":"kiBAWA,MAAMA,EAAQC,EA+BRC,EAAoBC,EAAAA,SAAS,IAAM,IAAMH,EAAM,WAAW,8BAI9DI,EAAAA,YA+BsBC,EAAAA,MAAAC,EAAAA,OAAA,EAAA,CA/BA,UAAWL,EAAA,UAAY,MAAKM,EAAAA,eAAEF,EAAAA,MAAAG,EAAAA,EAAA,EAAE,kCAAoCC,EAAAA,OAAO,KAAK,CAAA,CAAA,qBAEpG,IAWiB,CAXjBC,cAWiBL,EAAAA,MAAAM,EAAAA,aAAA,EAAA,CAVd,eAAcV,EAAA,YACd,WAAUA,EAAA,QACV,WAAUA,EAAA,QACV,MAAKM,EAAAA,eAAEN,EAAA,IAAG,EAAQA,EAAA,YAAS,aAAA,0BAAA,0BAAA,EAAA,EAC3B,MAAKW,EAAAA,eAAEX,EAAA,IAAG,EAAA,CAAA,QAAA,GAAqBA,EAAA,GAAG,IAAA,EAAA,CAAA,CAAA,CAAA,qBAEnC,IAAqB,CAArBY,EAAAA,WAAqBC,EAAA,OAAA,QAAA,CAAA,EAAA,OAAA,EAAA,EAErBD,EAAAA,WAAoBC,EAAA,OAAA,OAAA,CAAA,EAAA,OAAA,EAAA,EACpBD,EAAAA,WAAmBC,EAAA,OAAA,MAAA,CAAA,EAAA,OAAA,EAAA,CAAA,kEAIrBJ,cAAsEL,EAAAA,MAAAU,EAAAA,OAAA,EAAA,CAApD,cAAad,EAAA,WAAY,MAAM,kBAAA,0BAGjDS,cAWiBL,EAAAA,MAAAM,EAAAA,aAAA,EAAA,CAVd,eAAcT,EAAA,MACd,WAAUD,EAAA,cACV,WAAUA,EAAA,cACV,MAAKM,EAAAA,eAAEN,EAAA,IAAG,EAAQA,EAAA,YAAS,aAAA,0BAAA,0BAAA,EAAA,EAC3B,MAAKW,EAAAA,eAAEX,EAAA,IAAG,EAAA,CAAA,QAAA,GAAqBA,EAAA,GAAG,IAAA,EAAA,CAAA,CAAA,CAAA,qBAEnC,IAAsB,CAAtBY,EAAAA,WAAsBC,EAAA,OAAA,SAAA,CAAA,EAAA,OAAA,EAAA,EAEtBD,EAAAA,WAAqBC,EAAA,OAAA,QAAA,CAAA,EAAA,OAAA,EAAA,EACrBD,EAAAA,WAAsBC,EAAA,OAAA,SAAA,CAAA,EAAA,OAAA,EAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSplitter.vue2.js","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n },\n)\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - props.defaultSize)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"direction\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"defaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/*
|
|
1
|
+
{"version":3,"file":"JSplitter.vue2.js","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n },\n)\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - props.defaultSize)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"direction\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"defaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/* ── overflow 보호 ──────────────────────────────────────────────────── */\n:deep(.jsplitter-group [data-panel]) {\n min-width: 0;\n min-height: 0;\n}\n\n/* ── horizontal 핸들: 8px 히트영역 ─────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"horizontal\"]) {\n width: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── vertical 핸들: 8px 히트영역 ───────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]) {\n height: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── 시각선: 1px (after:w-1 override) ──────────────────────────────── */\n:deep(.jsplitter-handle::after) {\n width: 1px !important;\n background: hsl(var(--border)) !important;\n transition: background 0.15s ease, width 0.12s ease !important;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::after) {\n height: 1px !important;\n width: 100% !important;\n}\n\n/* ── 그립 도트 인디케이터 (::before) ───────────────────────────────── */\n:deep(.jsplitter-handle::before) {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 3px;\n height: 20px;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n background-size: 3px 6px;\n background-repeat: repeat-y;\n border-radius: 2px;\n opacity: 0;\n transition: opacity 0.15s ease;\n pointer-events: none;\n z-index: 4;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::before) {\n width: 20px;\n height: 3px;\n background-size: 6px 3px;\n background-repeat: repeat-x;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n}\n\n/* ── hover ──────────────────────────────────────────────────────────── */\n:deep(.jsplitter-handle:hover) {\n background: hsl(var(--primary) / 0.04) !important;\n}\n\n:deep(.jsplitter-handle:hover::after) {\n width: 2px !important;\n background: hsl(var(--primary) / 0.55) !important;\n}\n\n:deep(.jsplitter-handle:hover::before) {\n opacity: 1;\n}\n\n/* ── drag / active ──────────────────────────────────────────────────── */\n:deep(.jsplitter-handle[data-state=\"drag\"]),\n:deep(.jsplitter-handle:active) {\n background: hsl(var(--primary) / 0.06) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::after),\n:deep(.jsplitter-handle:active::after) {\n width: 2px !important;\n background: hsl(var(--primary)) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::before),\n:deep(.jsplitter-handle:active::before) {\n opacity: 1;\n background-image: radial-gradient(circle, hsl(var(--primary) / 0.65) 1px, transparent 1px);\n}\n</style>\n"],"names":["props","__props","secondDefaultSize","computed","_createBlock","_unref","ResizablePanelGroup","_normalizeClass","cn","$props","_createVNode","ResizablePanel","_normalizeStyle","_renderSlot","_ctx","ResizableHandle"],"mappings":";;;;;;;;;;;;;;;;;;;AAWA,UAAMA,IAAQC,GA+BRC,IAAoBC,EAAS,MAAM,MAAMH,EAAM,WAAW;2BAI9DI,EA+BsBC,EAAAC,CAAA,GAAA;AAAA,MA/BA,WAAWL,EAAA;AAAA,MAAY,OAAKM,EAAEF,EAAAG,CAAA,EAAE,mCAAoCC,EAAAA,OAAO,KAAK,CAAA;AAAA,IAAA;iBAEpG,MAWiB;AAAA,QAXjBC,EAWiBL,EAAAM,CAAA,GAAA;AAAA,UAVd,gBAAcV,EAAA;AAAA,UACd,YAAUA,EAAA;AAAA,UACV,YAAUA,EAAA;AAAA,UACV,OAAKM,EAAEN,EAAA,MAAG,IAAQA,EAAA,cAAS,eAAA,4BAAA,4BAAA,EAAA;AAAA,UAC3B,OAAKW,EAAEX,EAAA,MAAG,IAAA,EAAA,SAAA,GAAqBA,EAAA,GAAG,KAAA,IAAA,CAAA,CAAA;AAAA,QAAA;qBAEnC,MAAqB;AAAA,YAArBY,EAAqBC,EAAA,QAAA,SAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YAErBD,EAAoBC,EAAA,QAAA,QAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YACpBD,EAAmBC,EAAA,QAAA,OAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAAA;;;QAIrBJ,EAAsEL,EAAAU,CAAA,GAAA;AAAA,UAApD,eAAad,EAAA;AAAA,UAAY,OAAM;AAAA,QAAA;QAGjDS,EAWiBL,EAAAM,CAAA,GAAA;AAAA,UAVd,gBAAcT,EAAA;AAAA,UACd,YAAUD,EAAA;AAAA,UACV,YAAUA,EAAA;AAAA,UACV,OAAKM,EAAEN,EAAA,MAAG,IAAQA,EAAA,cAAS,eAAA,4BAAA,4BAAA,EAAA;AAAA,UAC3B,OAAKW,EAAEX,EAAA,MAAG,IAAA,EAAA,SAAA,GAAqBA,EAAA,GAAG,KAAA,IAAA,CAAA,CAAA;AAAA,QAAA;qBAEnC,MAAsB;AAAA,YAAtBY,EAAsBC,EAAA,QAAA,UAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YAEtBD,EAAqBC,EAAA,QAAA,SAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YACrBD,EAAsBC,EAAA,QAAA,UAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAAA;;;;;;;;"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),v=require("../../../types/sidebar.types.cjs"),g=require("../../atoms/JIcon.vue.cjs"),m=require("./JSidebarItem.vue.cjs"),d=require("../../../lib/utils.cjs"),h={key:1},y={class:"flex-1 truncate text-left"},B={key:0,class:"text-[10px] text-muted-foreground/60 flex-shrink-0"},x={class:"overflow-hidden ml-3 pl-2 border-l border-border/50"},E=e.defineComponent({__name:"JSidebarGroup",props:{item:{}},emits:["menu-click"],setup(c,{emit:p}){const i=c,k=p,u=e.inject(v.SIDEBAR_INJECTION_KEY),l=e.ref(!1),s=e.computed(()=>i.item.children?i.item.children.filter(t=>t.menuType==="L").length:0),f=()=>{l.value=!l.value},a=(t,r)=>{k("menu-click",t,r)},_=e.computed(()=>{if(!u.activePath||!i.item.children)return!1;const t=r=>{for(const o of r)if(o.menuType==="L"&&o.path===u.activePath||o.children&&t(o.children))return!0;return!1};return t(i.item.children)});return e.watch(_,t=>{t&&(l.value=!0)},{immediate:!0}),(t,r)=>{const o=e.resolveComponent("JSidebarGroup",!0);return e.unref(u).collapsed?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[r[0]||(r[0]=e.createElementVNode("div",{class:"h-px bg-border mx-2 my-1"},null,-1)),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(c.item.children,n=>(e.openBlock(),e.createElementBlock(e.Fragment,{key:n.id},[n.menuType==="L"?(e.openBlock(),e.createBlock(m.default,{key:0,item:n,onMenuClick:a},null,8,["item"])):e.createCommentVNode("",!0)],64))),128))],64)):(e.openBlock(),e.createElementBlock("div",h,[e.createElementVNode("button",{class:"flex items-center gap-1.5 w-full px-2 py-1
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),v=require("../../../types/sidebar.types.cjs"),g=require("../../atoms/JIcon.vue.cjs"),m=require("./JSidebarItem.vue.cjs"),d=require("../../../lib/utils.cjs"),h={key:1},y={class:"flex-1 truncate text-left"},B={key:0,class:"text-[10px] text-muted-foreground/60 flex-shrink-0"},x={class:"overflow-hidden ml-3 pl-2 border-l border-border/50"},E=e.defineComponent({__name:"JSidebarGroup",props:{item:{}},emits:["menu-click"],setup(c,{emit:p}){const i=c,k=p,u=e.inject(v.SIDEBAR_INJECTION_KEY),l=e.ref(!1),s=e.computed(()=>i.item.children?i.item.children.filter(t=>t.menuType==="L").length:0),f=()=>{l.value=!l.value},a=(t,r)=>{k("menu-click",t,r)},_=e.computed(()=>{if(!u.activePath||!i.item.children)return!1;const t=r=>{for(const o of r)if(o.menuType==="L"&&o.path===u.activePath||o.children&&t(o.children))return!0;return!1};return t(i.item.children)});return e.watch(_,t=>{t&&(l.value=!0)},{immediate:!0}),(t,r)=>{const o=e.resolveComponent("JSidebarGroup",!0);return e.unref(u).collapsed?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[r[0]||(r[0]=e.createElementVNode("div",{class:"h-px bg-border mx-2 my-1"},null,-1)),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(c.item.children,n=>(e.openBlock(),e.createElementBlock(e.Fragment,{key:n.id},[n.menuType==="L"?(e.openBlock(),e.createBlock(m.default,{key:0,item:n,onMenuClick:a},null,8,["item"])):e.createCommentVNode("",!0)],64))),128))],64)):(e.openBlock(),e.createElementBlock("div",h,[e.createElementVNode("button",{class:"flex items-center gap-1.5 w-full px-2 py-1 mt-0.5 text-xs font-semibold text-muted-foreground cursor-pointer select-none transition-colors hover:text-foreground",onClick:f},[e.createVNode(g.default,{name:"chevronRight",size:"sm",class:e.normalizeClass(e.unref(d.cn)("flex-shrink-0 transition-transform duration-200",l.value&&"rotate-90"))},null,8,["class"]),e.createElementVNode("span",y,e.toDisplayString(c.item.label),1),s.value>0?(e.openBlock(),e.createElementBlock("span",B,e.toDisplayString(s.value),1)):e.createCommentVNode("",!0)]),e.createElementVNode("div",{class:e.normalizeClass(e.unref(d.cn)("grid transition-[grid-template-rows] duration-200 ease-out",l.value?"grid-rows-[1fr]":"grid-rows-[0fr]"))},[e.createElementVNode("div",x,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(c.item.children,n=>(e.openBlock(),e.createElementBlock(e.Fragment,{key:n.id},[n.menuType==="L"?(e.openBlock(),e.createBlock(m.default,{key:0,item:n,onMenuClick:a},null,8,["item"])):n.menuType==="F"?(e.openBlock(),e.createBlock(o,{key:1,item:n,onMenuClick:a},null,8,["item"])):e.createCommentVNode("",!0)],64))),128))])],2)]))}}});exports.default=E;
|
|
2
2
|
//# sourceMappingURL=JSidebarGroup.vue.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebarGroup.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebarGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, inject, watch } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JSidebarItem from './JSidebarItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarGroup - 그룹 헤더 (menuType='F' 전용)\r\n * 폴더 타입 메뉴를 렌더링. 펼침/접힘 토글 + 하위 메뉴 재귀 렌더링.\r\n * 링크(L)는 JSidebarItem에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isExpanded = ref(false)\r\n\r\n/** 직계 자식 중 L 타입 개수 (재귀하지 않음) */\r\nconst childCount = computed(() => {\r\n if (!props.item.children) return 0\r\n return props.item.children.filter(c => c.menuType === 'L').length\r\n})\r\n\r\nconst toggleExpand = () => {\r\n isExpanded.value = !isExpanded.value\r\n}\r\n\r\n/** 자식의 menu-click을 상위로 전파 */\r\nconst handleChildClick = (item: SidebarMenuItem, event: MouseEvent) => {\r\n emit('menu-click', item, event)\r\n}\r\n\r\n/** 현재 경로가 이 그룹의 자식에 포함되면 자동 펼침 */\r\nconst hasActiveChild = computed(() => {\r\n if (!state.activePath || !props.item.children) return false\r\n const checkActive = (items: SidebarMenuItem[]): boolean => {\r\n for (const child of items) {\r\n if (child.menuType === 'L' && child.path === state.activePath) return true\r\n if (child.children && checkActive(child.children)) return true\r\n }\r\n return false\r\n }\r\n return checkActive(props.item.children)\r\n})\r\n\r\nwatch(hasActiveChild, (active) => {\r\n if (active) isExpanded.value = true\r\n}, { immediate: true })\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 구분선만 -->\r\n <template v-if=\"state.collapsed\">\r\n <div class=\"h-px bg-border mx-2 my-1\" />\r\n <!-- collapsed 상태에서 자식 아이템 직접 렌더링 -->\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더는 collapsed에서 무시 (1단계만) -->\r\n </template>\r\n </template>\r\n\r\n <!-- Expanded: 그룹 헤더 + 하위 메뉴 -->\r\n <div v-else>\r\n <!-- 그룹 헤더 -->\r\n <button\r\n class=\"flex items-center gap-1.5 w-full px-2 py-1
|
|
1
|
+
{"version":3,"file":"JSidebarGroup.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebarGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, inject, watch } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JSidebarItem from './JSidebarItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarGroup - 그룹 헤더 (menuType='F' 전용)\r\n * 폴더 타입 메뉴를 렌더링. 펼침/접힘 토글 + 하위 메뉴 재귀 렌더링.\r\n * 링크(L)는 JSidebarItem에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isExpanded = ref(false)\r\n\r\n/** 직계 자식 중 L 타입 개수 (재귀하지 않음) */\r\nconst childCount = computed(() => {\r\n if (!props.item.children) return 0\r\n return props.item.children.filter(c => c.menuType === 'L').length\r\n})\r\n\r\nconst toggleExpand = () => {\r\n isExpanded.value = !isExpanded.value\r\n}\r\n\r\n/** 자식의 menu-click을 상위로 전파 */\r\nconst handleChildClick = (item: SidebarMenuItem, event: MouseEvent) => {\r\n emit('menu-click', item, event)\r\n}\r\n\r\n/** 현재 경로가 이 그룹의 자식에 포함되면 자동 펼침 */\r\nconst hasActiveChild = computed(() => {\r\n if (!state.activePath || !props.item.children) return false\r\n const checkActive = (items: SidebarMenuItem[]): boolean => {\r\n for (const child of items) {\r\n if (child.menuType === 'L' && child.path === state.activePath) return true\r\n if (child.children && checkActive(child.children)) return true\r\n }\r\n return false\r\n }\r\n return checkActive(props.item.children)\r\n})\r\n\r\nwatch(hasActiveChild, (active) => {\r\n if (active) isExpanded.value = true\r\n}, { immediate: true })\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 구분선만 -->\r\n <template v-if=\"state.collapsed\">\r\n <div class=\"h-px bg-border mx-2 my-1\" />\r\n <!-- collapsed 상태에서 자식 아이템 직접 렌더링 -->\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더는 collapsed에서 무시 (1단계만) -->\r\n </template>\r\n </template>\r\n\r\n <!-- Expanded: 그룹 헤더 + 하위 메뉴 -->\r\n <div v-else>\r\n <!-- 그룹 헤더 -->\r\n <button\r\n class=\"flex items-center gap-1.5 w-full px-2 py-1 mt-0.5 text-xs font-semibold text-muted-foreground cursor-pointer select-none transition-colors hover:text-foreground\"\r\n @click=\"toggleExpand\"\r\n >\r\n <JIcon\r\n name=\"chevronRight\"\r\n size=\"sm\"\r\n :class=\"cn(\r\n 'flex-shrink-0 transition-transform duration-200',\r\n isExpanded && 'rotate-90'\r\n )\"\r\n />\r\n <span class=\"flex-1 truncate text-left\">{{ item.label }}</span>\r\n <span\r\n v-if=\"childCount > 0\"\r\n class=\"text-[10px] text-muted-foreground/60 flex-shrink-0\"\r\n >\r\n {{ childCount }}\r\n </span>\r\n </button>\r\n\r\n <!-- 하위 메뉴 (grid-template-rows 애니메이션) -->\r\n <div\r\n :class=\"cn(\r\n 'grid transition-[grid-template-rows] duration-200 ease-out',\r\n isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\r\n )\"\r\n >\r\n <div class=\"overflow-hidden ml-3 pl-2 border-l border-border/50\">\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더: 재귀 -->\r\n <JSidebarGroup\r\n v-else-if=\"child.menuType === 'F'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isExpanded","ref","childCount","computed","c","toggleExpand","handleChildClick","item","event","hasActiveChild","checkActive","items","child","watch","active","_unref","_createElementBlock","_Fragment","_createElementVNode","_openBlock","_renderList","_createBlock","JSidebarItem","_hoisted_1","_createVNode","JIcon","cn","_hoisted_2","_toDisplayString","_hoisted_3","_hoisted_4","_component_JSidebarGroup"],"mappings":"wjBAcA,MAAMA,EAAQC,EAIRC,EAAOC,EAIPC,EAAQC,EAAAA,OAAOC,uBAAqB,EAEpCC,EAAaC,EAAAA,IAAI,EAAK,EAGtBC,EAAaC,EAAAA,SAAS,IACrBV,EAAM,KAAK,SACTA,EAAM,KAAK,SAAS,UAAYW,EAAE,WAAa,GAAG,EAAE,OAD1B,CAElC,EAEKC,EAAe,IAAM,CACzBL,EAAW,MAAQ,CAACA,EAAW,KACjC,EAGMM,EAAmB,CAACC,EAAuBC,IAAsB,CACrEb,EAAK,aAAcY,EAAMC,CAAK,CAChC,EAGMC,EAAiBN,EAAAA,SAAS,IAAM,CACpC,GAAI,CAACN,EAAM,YAAc,CAACJ,EAAM,KAAK,SAAU,MAAO,GACtD,MAAMiB,EAAeC,GAAsC,CACzD,UAAWC,KAASD,EAElB,GADIC,EAAM,WAAa,KAAOA,EAAM,OAASf,EAAM,YAC/Ce,EAAM,UAAYF,EAAYE,EAAM,QAAQ,EAAG,MAAO,GAE5D,MAAO,EACT,EACA,OAAOF,EAAYjB,EAAM,KAAK,QAAQ,CACxC,CAAC,EAEDoB,OAAAA,QAAMJ,EAAiBK,GAAW,CAC5BA,MAAmB,MAAQ,GACjC,EAAG,CAAE,UAAW,GAAM,yDAKJ,OAAAC,QAAAlB,CAAA,EAAM,yBAAtBmB,EAAAA,mBAWWC,WAAA,CAAA,IAAA,GAAA,aAVTC,EAAAA,mBAAwC,MAAA,CAAnC,MAAM,0BAAA,EAA0B,KAAA,EAAA,IAErCC,EAAAA,UAAA,EAAA,EAAAH,EAAAA,mBAOWC,WAAA,KAAAG,EAAAA,WAPe1B,EAAA,KAAK,SAAdkB,mDAA8B,IAAAA,EAAM,EAAA,GAE3CA,EAAM,WAAQ,mBADtBS,EAAAA,YAIEC,EAAAA,QAAA,OAFC,KAAMV,EACN,YAAYN,CAAA,kFAOnBU,EAAAA,mBA8CM,MAAAO,EAAA,CA5CJL,EAAAA,mBAmBS,SAAA,CAlBP,MAAM,mKACL,QAAOb,CAAA,GAERmB,EAAAA,YAOEC,EAAAA,QAAA,CANA,KAAK,eACL,KAAK,KACJ,uBAAOV,EAAAA,MAAAW,IAAA,oDAA6E1B,EAAA,OAAU,WAAA,sBAKjGkB,qBAA+D,OAA/DS,EAA+DC,EAAAA,gBAApBlC,EAAA,KAAK,KAAK,EAAA,CAAA,EAE7CQ,EAAA,MAAU,iBADlBc,EAAAA,mBAKO,OALPa,EAKOD,EAAAA,gBADF1B,EAAA,KAAU,EAAA,CAAA,iCAKjBgB,EAAAA,mBAqBM,MAAA,CApBH,uBAAOH,EAAAA,MAAAW,IAAA,+DAAoF1B,EAAA,MAAU,kBAAA,iBAAA,KAKtGkB,EAAAA,mBAcM,MAdNY,EAcM,EAbJX,EAAAA,UAAA,EAAA,EAAAH,EAAAA,mBAYWC,WAAA,KAAAG,EAAAA,WAZe1B,EAAA,KAAK,SAAdkB,mDAA8B,IAAAA,EAAM,EAAA,GAE3CA,EAAM,WAAQ,mBADtBS,EAAAA,YAIEC,EAAAA,QAAA,OAFC,KAAMV,EACN,YAAYN,CAAA,oBAIFM,EAAM,WAAQ,mBAD3BS,EAAAA,YAIEU,EAAA,OAFC,KAAMnB,EACN,YAAYN,CAAA"}
|
|
@@ -43,7 +43,7 @@ const M = { key: 1 }, V = { class: "flex-1 truncate text-left" }, z = {
|
|
|
43
43
|
], 64))), 128))
|
|
44
44
|
], 64)) : (e(), n("div", M, [
|
|
45
45
|
c("button", {
|
|
46
|
-
class: "flex items-center gap-1.5 w-full px-2 py-1
|
|
46
|
+
class: "flex items-center gap-1.5 w-full px-2 py-1 mt-0.5 text-xs font-semibold text-muted-foreground cursor-pointer select-none transition-colors hover:text-foreground",
|
|
47
47
|
onClick: w
|
|
48
48
|
}, [
|
|
49
49
|
A(I, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebarGroup.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebarGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, inject, watch } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JSidebarItem from './JSidebarItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarGroup - 그룹 헤더 (menuType='F' 전용)\r\n * 폴더 타입 메뉴를 렌더링. 펼침/접힘 토글 + 하위 메뉴 재귀 렌더링.\r\n * 링크(L)는 JSidebarItem에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isExpanded = ref(false)\r\n\r\n/** 직계 자식 중 L 타입 개수 (재귀하지 않음) */\r\nconst childCount = computed(() => {\r\n if (!props.item.children) return 0\r\n return props.item.children.filter(c => c.menuType === 'L').length\r\n})\r\n\r\nconst toggleExpand = () => {\r\n isExpanded.value = !isExpanded.value\r\n}\r\n\r\n/** 자식의 menu-click을 상위로 전파 */\r\nconst handleChildClick = (item: SidebarMenuItem, event: MouseEvent) => {\r\n emit('menu-click', item, event)\r\n}\r\n\r\n/** 현재 경로가 이 그룹의 자식에 포함되면 자동 펼침 */\r\nconst hasActiveChild = computed(() => {\r\n if (!state.activePath || !props.item.children) return false\r\n const checkActive = (items: SidebarMenuItem[]): boolean => {\r\n for (const child of items) {\r\n if (child.menuType === 'L' && child.path === state.activePath) return true\r\n if (child.children && checkActive(child.children)) return true\r\n }\r\n return false\r\n }\r\n return checkActive(props.item.children)\r\n})\r\n\r\nwatch(hasActiveChild, (active) => {\r\n if (active) isExpanded.value = true\r\n}, { immediate: true })\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 구분선만 -->\r\n <template v-if=\"state.collapsed\">\r\n <div class=\"h-px bg-border mx-2 my-1\" />\r\n <!-- collapsed 상태에서 자식 아이템 직접 렌더링 -->\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더는 collapsed에서 무시 (1단계만) -->\r\n </template>\r\n </template>\r\n\r\n <!-- Expanded: 그룹 헤더 + 하위 메뉴 -->\r\n <div v-else>\r\n <!-- 그룹 헤더 -->\r\n <button\r\n class=\"flex items-center gap-1.5 w-full px-2 py-1
|
|
1
|
+
{"version":3,"file":"JSidebarGroup.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebarGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, inject, watch } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JSidebarItem from './JSidebarItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarGroup - 그룹 헤더 (menuType='F' 전용)\r\n * 폴더 타입 메뉴를 렌더링. 펼침/접힘 토글 + 하위 메뉴 재귀 렌더링.\r\n * 링크(L)는 JSidebarItem에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isExpanded = ref(false)\r\n\r\n/** 직계 자식 중 L 타입 개수 (재귀하지 않음) */\r\nconst childCount = computed(() => {\r\n if (!props.item.children) return 0\r\n return props.item.children.filter(c => c.menuType === 'L').length\r\n})\r\n\r\nconst toggleExpand = () => {\r\n isExpanded.value = !isExpanded.value\r\n}\r\n\r\n/** 자식의 menu-click을 상위로 전파 */\r\nconst handleChildClick = (item: SidebarMenuItem, event: MouseEvent) => {\r\n emit('menu-click', item, event)\r\n}\r\n\r\n/** 현재 경로가 이 그룹의 자식에 포함되면 자동 펼침 */\r\nconst hasActiveChild = computed(() => {\r\n if (!state.activePath || !props.item.children) return false\r\n const checkActive = (items: SidebarMenuItem[]): boolean => {\r\n for (const child of items) {\r\n if (child.menuType === 'L' && child.path === state.activePath) return true\r\n if (child.children && checkActive(child.children)) return true\r\n }\r\n return false\r\n }\r\n return checkActive(props.item.children)\r\n})\r\n\r\nwatch(hasActiveChild, (active) => {\r\n if (active) isExpanded.value = true\r\n}, { immediate: true })\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 구분선만 -->\r\n <template v-if=\"state.collapsed\">\r\n <div class=\"h-px bg-border mx-2 my-1\" />\r\n <!-- collapsed 상태에서 자식 아이템 직접 렌더링 -->\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더는 collapsed에서 무시 (1단계만) -->\r\n </template>\r\n </template>\r\n\r\n <!-- Expanded: 그룹 헤더 + 하위 메뉴 -->\r\n <div v-else>\r\n <!-- 그룹 헤더 -->\r\n <button\r\n class=\"flex items-center gap-1.5 w-full px-2 py-1 mt-0.5 text-xs font-semibold text-muted-foreground cursor-pointer select-none transition-colors hover:text-foreground\"\r\n @click=\"toggleExpand\"\r\n >\r\n <JIcon\r\n name=\"chevronRight\"\r\n size=\"sm\"\r\n :class=\"cn(\r\n 'flex-shrink-0 transition-transform duration-200',\r\n isExpanded && 'rotate-90'\r\n )\"\r\n />\r\n <span class=\"flex-1 truncate text-left\">{{ item.label }}</span>\r\n <span\r\n v-if=\"childCount > 0\"\r\n class=\"text-[10px] text-muted-foreground/60 flex-shrink-0\"\r\n >\r\n {{ childCount }}\r\n </span>\r\n </button>\r\n\r\n <!-- 하위 메뉴 (grid-template-rows 애니메이션) -->\r\n <div\r\n :class=\"cn(\r\n 'grid transition-[grid-template-rows] duration-200 ease-out',\r\n isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\r\n )\"\r\n >\r\n <div class=\"overflow-hidden ml-3 pl-2 border-l border-border/50\">\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더: 재귀 -->\r\n <JSidebarGroup\r\n v-else-if=\"child.menuType === 'F'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isExpanded","ref","childCount","computed","c","toggleExpand","handleChildClick","item","event","hasActiveChild","checkActive","items","child","watch","active","_unref","_createElementBlock","_Fragment","_createElementVNode","_openBlock","_renderList","_createBlock","JSidebarItem","_hoisted_1","_createVNode","JIcon","cn","_hoisted_2","_toDisplayString","_hoisted_3","_hoisted_4","_component_JSidebarGroup"],"mappings":";;;;;;;;;;;;;;;AAcA,UAAMA,IAAQC,GAIRC,IAAOC,GAIPC,IAAQC,EAAOC,CAAqB,GAEpCC,IAAaC,EAAI,EAAK,GAGtBC,IAAaC,EAAS,MACrBV,EAAM,KAAK,WACTA,EAAM,KAAK,SAAS,OAAO,OAAKW,EAAE,aAAa,GAAG,EAAE,SAD1B,CAElC,GAEKC,IAAe,MAAM;AACzB,MAAAL,EAAW,QAAQ,CAACA,EAAW;AAAA,IACjC,GAGMM,IAAmB,CAACC,GAAuBC,MAAsB;AACrE,MAAAb,EAAK,cAAcY,GAAMC,CAAK;AAAA,IAChC,GAGMC,IAAiBN,EAAS,MAAM;AACpC,UAAI,CAACN,EAAM,cAAc,CAACJ,EAAM,KAAK,SAAU,QAAO;AACtD,YAAMiB,IAAc,CAACC,MAAsC;AACzD,mBAAWC,KAASD;AAElB,cADIC,EAAM,aAAa,OAAOA,EAAM,SAASf,EAAM,cAC/Ce,EAAM,YAAYF,EAAYE,EAAM,QAAQ,EAAG,QAAO;AAE5D,eAAO;AAAA,MACT;AACA,aAAOF,EAAYjB,EAAM,KAAK,QAAQ;AAAA,IACxC,CAAC;AAED,WAAAoB,EAAMJ,GAAgB,CAACK,MAAW;AAChC,MAAIA,QAAmB,QAAQ;AAAA,IACjC,GAAG,EAAE,WAAW,IAAM;;AAKJ,aAAAC,EAAAlB,CAAA,EAAM,kBAAtBmB,EAWWC,GAAA,EAAA,KAAA,KAAA;AAAA,wBAVTC,EAAwC,OAAA,EAAnC,OAAM,2BAAA,GAA0B,MAAA,EAAA;AAAA,SAErCC,EAAA,EAAA,GAAAH,EAOWC,GAAA,MAAAG,EAPe1B,EAAA,KAAK,WAAdkB;UAA8B,KAAAA,EAAM;AAAA,QAAA;UAE3CA,EAAM,aAAQ,YADtBS,EAIEC,GAAA;AAAA;YAFC,MAAMV;AAAA,YACN,aAAYN;AAAA,UAAA;;sBAOnBU,EA8CM,OAAAO,GAAA;AAAA,QA5CJL,EAmBS,UAAA;AAAA,UAlBP,OAAM;AAAA,UACL,SAAOb;AAAA,QAAA;UAERmB,EAOEC,GAAA;AAAA,YANA,MAAK;AAAA,YACL,MAAK;AAAA,YACJ,SAAOV,EAAAW,CAAA;AAAA;cAA6E1B,EAAA,SAAU;AAAA,YAAA;;UAKjGkB,EAA+D,QAA/DS,GAA+DC,EAApBlC,EAAA,KAAK,KAAK,GAAA,CAAA;AAAA,UAE7CQ,EAAA,QAAU,UADlBc,EAKO,QALPa,GAKOD,EADF1B,EAAA,KAAU,GAAA,CAAA;;QAKjBgB,EAqBM,OAAA;AAAA,UApBH,SAAOH,EAAAW,CAAA;AAAA;YAAoF1B,EAAA,QAAU,oBAAA;AAAA,UAAA;;UAKtGkB,EAcM,OAdNY,GAcM;AAAA,aAbJX,EAAA,EAAA,GAAAH,EAYWC,GAAA,MAAAG,EAZe1B,EAAA,KAAK,WAAdkB;cAA8B,KAAAA,EAAM;AAAA,YAAA;cAE3CA,EAAM,aAAQ,YADtBS,EAIEC,GAAA;AAAA;gBAFC,MAAMV;AAAA,gBACN,aAAYN;AAAA,cAAA,yBAIFM,EAAM,aAAQ,YAD3BS,EAIEU,GAAA;AAAA;gBAFC,MAAMnB;AAAA,gBACN,aAAYN;AAAA,cAAA;;;;;;;;"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),p=require("../../../types/sidebar.types.cjs"),a=require("../../atoms/JIcon.vue.cjs"),f=require("../../atoms/JTooltip.vue.cjs"),l=require("../../../lib/utils.cjs"),v={key:0,class:"absolute left-0 top-1 bottom-1 w-[3px] rounded-r bg-primary"},y={class:"flex-1 truncate text-xs"},g=e.defineComponent({__name:"JSidebarItem",props:{item:{}},emits:["menu-click"],setup(t,{emit:u}){const o=t,m=u,i=e.inject(p.SIDEBAR_INJECTION_KEY),r=e.computed(()=>!o.item.path||!i.activePath?!1:i.activePath===o.item.path),c=e.computed(()=>i.favorites.has(o.item.id)),s=n=>{o.item.disabled||m("menu-click",o.item,n)},d=n=>{n.stopPropagation(),i.toggleFavorite(o.item.id)};return(n,k)=>e.unref(i).collapsed?(e.openBlock(),e.createBlock(f.default,{key:0,content:t.item.label,side:"right",size:"sm"},{trigger:e.withCtx(()=>[e.createElementVNode("div",{class:e.normalizeClass(e.unref(l.cn)("flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors",r.value?"bg-primary/10 text-primary":"text-foreground hover:bg-accent/50",t.item.disabled&&"opacity-40 cursor-not-allowed")),onClick:s},[e.createVNode(a.default,{name:t.item.icon||"file",size:"sm"},null,8,["name"])],2)]),_:1},8,["content"])):(e.openBlock(),e.createElementBlock("div",{key:1,class:e.normalizeClass(e.unref(l.cn)("group flex items-center gap-
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),p=require("../../../types/sidebar.types.cjs"),a=require("../../atoms/JIcon.vue.cjs"),f=require("../../atoms/JTooltip.vue.cjs"),l=require("../../../lib/utils.cjs"),v={key:0,class:"absolute left-0 top-1 bottom-1 w-[3px] rounded-r bg-primary"},y={class:"flex-1 truncate text-xs"},g=e.defineComponent({__name:"JSidebarItem",props:{item:{}},emits:["menu-click"],setup(t,{emit:u}){const o=t,m=u,i=e.inject(p.SIDEBAR_INJECTION_KEY),r=e.computed(()=>!o.item.path||!i.activePath?!1:i.activePath===o.item.path),c=e.computed(()=>i.favorites.has(o.item.id)),s=n=>{o.item.disabled||m("menu-click",o.item,n)},d=n=>{n.stopPropagation(),i.toggleFavorite(o.item.id)};return(n,k)=>e.unref(i).collapsed?(e.openBlock(),e.createBlock(f.default,{key:0,content:t.item.label,side:"right",size:"sm"},{trigger:e.withCtx(()=>[e.createElementVNode("div",{class:e.normalizeClass(e.unref(l.cn)("flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors",r.value?"bg-primary/10 text-primary":"text-foreground hover:bg-accent/50",t.item.disabled&&"opacity-40 cursor-not-allowed")),onClick:s},[e.createVNode(a.default,{name:t.item.icon||"file",size:"sm"},null,8,["name"])],2)]),_:1},8,["content"])):(e.openBlock(),e.createElementBlock("div",{key:1,class:e.normalizeClass(e.unref(l.cn)("group flex items-center gap-1.5 py-0.5 px-2 rounded-md cursor-pointer transition-colors relative",r.value?"bg-primary/10 text-primary font-medium":"text-foreground hover:bg-accent/50",t.item.disabled&&"opacity-40 cursor-not-allowed")),onClick:s},[r.value?(e.openBlock(),e.createElementBlock("div",v)):e.createCommentVNode("",!0),t.item.icon?(e.openBlock(),e.createBlock(a.default,{key:1,name:t.item.icon,size:"sm",class:"flex-shrink-0"},null,8,["name"])):e.createCommentVNode("",!0),e.createElementVNode("span",y,e.toDisplayString(t.item.label),1),e.createElementVNode("button",{class:e.normalizeClass(e.unref(l.cn)("flex-shrink-0 p-0.5 rounded transition-opacity",c.value?"opacity-100 text-yellow-500":"opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-yellow-500")),onClick:d},[e.createVNode(a.default,{name:"star",size:"sm",class:e.normalizeClass(c.value?"fill-yellow-500":void 0)},null,8,["class"])],2)],2))}});exports.default=g;
|
|
2
2
|
//# sourceMappingURL=JSidebarItem.vue.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebarItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebarItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed, inject } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JTooltip from '@/components/atoms/JTooltip.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarItem - 메뉴 아이템 (menuType='L' 전용)\r\n * 링크 타입 메뉴만 렌더링. 폴더(F)는 JSidebarGroup에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isActive = computed(() => {\r\n if (!props.item.path || !state.activePath) return false\r\n return state.activePath === props.item.path\r\n})\r\n\r\nconst isFav = computed(() => state.favorites.has(props.item.id))\r\n\r\nconst handleClick = (event: MouseEvent) => {\r\n if (props.item.disabled) return\r\n emit('menu-click', props.item, event)\r\n}\r\n\r\nconst handleFavoriteClick = (event: MouseEvent) => {\r\n event.stopPropagation()\r\n state.toggleFavorite(props.item.id)\r\n}\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 아이콘 + Tooltip -->\r\n <JTooltip\r\n v-if=\"state.collapsed\"\r\n :content=\"item.label\"\r\n side=\"right\"\r\n size=\"sm\"\r\n >\r\n <template #trigger>\r\n <div\r\n :class=\"cn(\r\n 'flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors',\r\n isActive\r\n ? 'bg-primary/10 text-primary'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <JIcon :name=\"item.icon || 'file'\" size=\"sm\" />\r\n </div>\r\n </template>\r\n </JTooltip>\r\n\r\n <!-- Expanded: 아이콘 + 라벨 + 즐겨찾기 -->\r\n <div\r\n v-else\r\n :class=\"cn(\r\n 'group flex items-center gap-
|
|
1
|
+
{"version":3,"file":"JSidebarItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebarItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed, inject } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JTooltip from '@/components/atoms/JTooltip.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarItem - 메뉴 아이템 (menuType='L' 전용)\r\n * 링크 타입 메뉴만 렌더링. 폴더(F)는 JSidebarGroup에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isActive = computed(() => {\r\n if (!props.item.path || !state.activePath) return false\r\n return state.activePath === props.item.path\r\n})\r\n\r\nconst isFav = computed(() => state.favorites.has(props.item.id))\r\n\r\nconst handleClick = (event: MouseEvent) => {\r\n if (props.item.disabled) return\r\n emit('menu-click', props.item, event)\r\n}\r\n\r\nconst handleFavoriteClick = (event: MouseEvent) => {\r\n event.stopPropagation()\r\n state.toggleFavorite(props.item.id)\r\n}\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 아이콘 + Tooltip -->\r\n <JTooltip\r\n v-if=\"state.collapsed\"\r\n :content=\"item.label\"\r\n side=\"right\"\r\n size=\"sm\"\r\n >\r\n <template #trigger>\r\n <div\r\n :class=\"cn(\r\n 'flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors',\r\n isActive\r\n ? 'bg-primary/10 text-primary'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <JIcon :name=\"item.icon || 'file'\" size=\"sm\" />\r\n </div>\r\n </template>\r\n </JTooltip>\r\n\r\n <!-- Expanded: 아이콘 + 라벨 + 즐겨찾기 -->\r\n <div\r\n v-else\r\n :class=\"cn(\r\n 'group flex items-center gap-1.5 py-0.5 px-2 rounded-md cursor-pointer transition-colors relative',\r\n isActive\r\n ? 'bg-primary/10 text-primary font-medium'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <!-- 활성 좌측 바 -->\r\n <div\r\n v-if=\"isActive\"\r\n class=\"absolute left-0 top-1 bottom-1 w-[3px] rounded-r bg-primary\"\r\n />\r\n\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n size=\"sm\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span class=\"flex-1 truncate text-xs\">{{ item.label }}</span>\r\n\r\n <!-- 즐겨찾기 별 -->\r\n <button\r\n :class=\"cn(\r\n 'flex-shrink-0 p-0.5 rounded transition-opacity',\r\n isFav\r\n ? 'opacity-100 text-yellow-500'\r\n : 'opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-yellow-500'\r\n )\"\r\n @click=\"handleFavoriteClick\"\r\n >\r\n <JIcon\r\n name=\"star\"\r\n size=\"sm\"\r\n :class=\"isFav ? 'fill-yellow-500' : undefined\"\r\n />\r\n </button>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isActive","computed","isFav","handleClick","event","handleFavoriteClick","_unref","_createBlock","JTooltip","_createElementVNode","cn","_createVNode","JIcon","_createElementBlock","_openBlock","_hoisted_1","_hoisted_2","_toDisplayString","_normalizeClass"],"mappings":"0fAaA,MAAMA,EAAQC,EAIRC,EAAOC,EAIPC,EAAQC,EAAAA,OAAOC,uBAAqB,EAEpCC,EAAWC,EAAAA,SAAS,IACpB,CAACR,EAAM,KAAK,MAAQ,CAACI,EAAM,WAAmB,GAC3CA,EAAM,aAAeJ,EAAM,KAAK,IACxC,EAEKS,EAAQD,WAAS,IAAMJ,EAAM,UAAU,IAAIJ,EAAM,KAAK,EAAE,CAAC,EAEzDU,EAAeC,GAAsB,CACrCX,EAAM,KAAK,UACfE,EAAK,aAAcF,EAAM,KAAMW,CAAK,CACtC,EAEMC,EAAuBD,GAAsB,CACjDA,EAAM,gBAAA,EACNP,EAAM,eAAeJ,EAAM,KAAK,EAAE,CACpC,eAMUa,EAAAA,MAAAT,CAAA,EAAM,yBADdU,EAAAA,YAoBWC,UAAA,OAlBR,QAASd,EAAA,KAAK,MACf,KAAK,QACL,KAAK,IAAA,GAEM,kBACT,IAWM,CAXNe,EAAAA,mBAWM,MAAA,CAVH,uBAAOH,EAAAA,MAAAI,IAAA,6FAAsHV,EAAA,wEAAqHN,EAAA,KAAK,UAAQ,+BAAA,GAO/P,QAAOS,CAAA,GAERQ,EAAAA,YAA+CC,EAAAA,QAAA,CAAvC,KAAMlB,EAAA,KAAK,MAAI,OAAY,KAAK,IAAA,6DAM9CmB,EAAAA,mBAyCM,MAAA,OAvCH,uBAAOP,EAAAA,MAAAI,IAAA,qGAAsHV,EAAA,oFAAqHN,EAAA,KAAK,UAAQ,+BAAA,GAO/P,QAAOS,CAAA,GAIAH,EAAA,OADRc,EAAAA,UAAA,EAAAD,EAAAA,mBAGE,MAHFE,CAGE,+BAGMrB,EAAA,KAAK,oBADba,EAAAA,YAKEK,EAAAA,QAAA,OAHC,KAAMlB,EAAA,KAAK,KACZ,KAAK,KACL,MAAM,eAAA,gDAERe,qBAA6D,OAA7DO,EAA6DC,EAAAA,gBAApBvB,EAAA,KAAK,KAAK,EAAA,CAAA,EAGnDe,EAAAA,mBAcS,SAAA,CAbN,uBAAOH,EAAAA,MAAAI,IAAA,mDAAwER,EAAA,sHAM/E,QAAOG,CAAA,GAERM,EAAAA,YAIEC,EAAAA,QAAA,CAHA,KAAK,OACL,KAAK,KACJ,MAAKM,EAAAA,eAAEhB,EAAA,MAAK,kBAAuB,MAAS,CAAA"}
|
|
@@ -43,7 +43,7 @@ const B = {
|
|
|
43
43
|
}, 8, ["content"])) : (n(), y("div", {
|
|
44
44
|
key: 1,
|
|
45
45
|
class: a(r(m)(
|
|
46
|
-
"group flex items-center gap-
|
|
46
|
+
"group flex items-center gap-1.5 py-0.5 px-2 rounded-md cursor-pointer transition-colors relative",
|
|
47
47
|
c.value ? "bg-primary/10 text-primary font-medium" : "text-foreground hover:bg-accent/50",
|
|
48
48
|
e.item.disabled && "opacity-40 cursor-not-allowed"
|
|
49
49
|
)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebarItem.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebarItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed, inject } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JTooltip from '@/components/atoms/JTooltip.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarItem - 메뉴 아이템 (menuType='L' 전용)\r\n * 링크 타입 메뉴만 렌더링. 폴더(F)는 JSidebarGroup에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isActive = computed(() => {\r\n if (!props.item.path || !state.activePath) return false\r\n return state.activePath === props.item.path\r\n})\r\n\r\nconst isFav = computed(() => state.favorites.has(props.item.id))\r\n\r\nconst handleClick = (event: MouseEvent) => {\r\n if (props.item.disabled) return\r\n emit('menu-click', props.item, event)\r\n}\r\n\r\nconst handleFavoriteClick = (event: MouseEvent) => {\r\n event.stopPropagation()\r\n state.toggleFavorite(props.item.id)\r\n}\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 아이콘 + Tooltip -->\r\n <JTooltip\r\n v-if=\"state.collapsed\"\r\n :content=\"item.label\"\r\n side=\"right\"\r\n size=\"sm\"\r\n >\r\n <template #trigger>\r\n <div\r\n :class=\"cn(\r\n 'flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors',\r\n isActive\r\n ? 'bg-primary/10 text-primary'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <JIcon :name=\"item.icon || 'file'\" size=\"sm\" />\r\n </div>\r\n </template>\r\n </JTooltip>\r\n\r\n <!-- Expanded: 아이콘 + 라벨 + 즐겨찾기 -->\r\n <div\r\n v-else\r\n :class=\"cn(\r\n 'group flex items-center gap-
|
|
1
|
+
{"version":3,"file":"JSidebarItem.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebarItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed, inject } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JTooltip from '@/components/atoms/JTooltip.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarItem - 메뉴 아이템 (menuType='L' 전용)\r\n * 링크 타입 메뉴만 렌더링. 폴더(F)는 JSidebarGroup에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isActive = computed(() => {\r\n if (!props.item.path || !state.activePath) return false\r\n return state.activePath === props.item.path\r\n})\r\n\r\nconst isFav = computed(() => state.favorites.has(props.item.id))\r\n\r\nconst handleClick = (event: MouseEvent) => {\r\n if (props.item.disabled) return\r\n emit('menu-click', props.item, event)\r\n}\r\n\r\nconst handleFavoriteClick = (event: MouseEvent) => {\r\n event.stopPropagation()\r\n state.toggleFavorite(props.item.id)\r\n}\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 아이콘 + Tooltip -->\r\n <JTooltip\r\n v-if=\"state.collapsed\"\r\n :content=\"item.label\"\r\n side=\"right\"\r\n size=\"sm\"\r\n >\r\n <template #trigger>\r\n <div\r\n :class=\"cn(\r\n 'flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors',\r\n isActive\r\n ? 'bg-primary/10 text-primary'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <JIcon :name=\"item.icon || 'file'\" size=\"sm\" />\r\n </div>\r\n </template>\r\n </JTooltip>\r\n\r\n <!-- Expanded: 아이콘 + 라벨 + 즐겨찾기 -->\r\n <div\r\n v-else\r\n :class=\"cn(\r\n 'group flex items-center gap-1.5 py-0.5 px-2 rounded-md cursor-pointer transition-colors relative',\r\n isActive\r\n ? 'bg-primary/10 text-primary font-medium'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <!-- 활성 좌측 바 -->\r\n <div\r\n v-if=\"isActive\"\r\n class=\"absolute left-0 top-1 bottom-1 w-[3px] rounded-r bg-primary\"\r\n />\r\n\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n size=\"sm\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span class=\"flex-1 truncate text-xs\">{{ item.label }}</span>\r\n\r\n <!-- 즐겨찾기 별 -->\r\n <button\r\n :class=\"cn(\r\n 'flex-shrink-0 p-0.5 rounded transition-opacity',\r\n isFav\r\n ? 'opacity-100 text-yellow-500'\r\n : 'opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-yellow-500'\r\n )\"\r\n @click=\"handleFavoriteClick\"\r\n >\r\n <JIcon\r\n name=\"star\"\r\n size=\"sm\"\r\n :class=\"isFav ? 'fill-yellow-500' : undefined\"\r\n />\r\n </button>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isActive","computed","isFav","handleClick","event","handleFavoriteClick","_unref","_createBlock","JTooltip","_createElementVNode","cn","_createVNode","JIcon","_createElementBlock","_openBlock","_hoisted_1","_hoisted_2","_toDisplayString","_normalizeClass"],"mappings":";;;;;;;;;;;;;;;AAaA,UAAMA,IAAQC,GAIRC,IAAOC,GAIPC,IAAQC,EAAOC,CAAqB,GAEpCC,IAAWC,EAAS,MACpB,CAACR,EAAM,KAAK,QAAQ,CAACI,EAAM,aAAmB,KAC3CA,EAAM,eAAeJ,EAAM,KAAK,IACxC,GAEKS,IAAQD,EAAS,MAAMJ,EAAM,UAAU,IAAIJ,EAAM,KAAK,EAAE,CAAC,GAEzDU,IAAc,CAACC,MAAsB;AACzC,MAAIX,EAAM,KAAK,YACfE,EAAK,cAAcF,EAAM,MAAMW,CAAK;AAAA,IACtC,GAEMC,IAAsB,CAACD,MAAsB;AACjD,MAAAA,EAAM,gBAAA,GACNP,EAAM,eAAeJ,EAAM,KAAK,EAAE;AAAA,IACpC;qBAMUa,EAAAT,CAAA,EAAM,kBADdU,EAoBWC,GAAA;AAAA;MAlBR,SAASd,EAAA,KAAK;AAAA,MACf,MAAK;AAAA,MACL,MAAK;AAAA,IAAA;MAEM,WACT,MAWM;AAAA,QAXNe,EAWM,OAAA;AAAA,UAVH,SAAOH,EAAAI,CAAA;AAAA;YAAsHV,EAAA;YAAqHN,EAAA,KAAK,YAAQ;AAAA,UAAA;UAO/P,SAAOS;AAAA,QAAA;UAERQ,EAA+CC,GAAA;AAAA,YAAvC,MAAMlB,EAAA,KAAK,QAAI;AAAA,YAAY,MAAK;AAAA,UAAA;;;;gCAM9CmB,EAyCM,OAAA;AAAA;MAvCH,SAAOP,EAAAI,CAAA;AAAA;QAAsHV,EAAA;QAAqHN,EAAA,KAAK,YAAQ;AAAA,MAAA;MAO/P,SAAOS;AAAA,IAAA;MAIAH,EAAA,SADRc,EAAA,GAAAD,EAGE,OAHFE,CAGE;MAGMrB,EAAA,KAAK,aADba,EAKEK,GAAA;AAAA;QAHC,MAAMlB,EAAA,KAAK;AAAA,QACZ,MAAK;AAAA,QACL,OAAM;AAAA,MAAA;MAERe,EAA6D,QAA7DO,GAA6DC,EAApBvB,EAAA,KAAK,KAAK,GAAA,CAAA;AAAA,MAGnDe,EAcS,UAAA;AAAA,QAbN,SAAOH,EAAAI,CAAA;AAAA;UAAwER,EAAA;;QAM/E,SAAOG;AAAA,MAAA;QAERM,EAIEC,GAAA;AAAA,UAHA,MAAK;AAAA,UACL,MAAK;AAAA,UACJ,OAAKM,EAAEhB,EAAA,QAAK,oBAAuB,MAAS;AAAA,QAAA;;;;;"}
|
|
@@ -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"),c=require("../../../lib/utils.cjs"),S={key:1,class:"w-4 flex-shrink-0"},E={key:0,class:"border-l border-border/60 ml-[14px] pl-[6px]"},N=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:g}){const n=t,s=g,x=F.useRouter(),p=e.computed(()=>c.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),l=e.computed(()=>n.item.menuType==="F"||Array.isArray(n.item.children)&&n.item.children.length>0),d=e.computed(()=>{if(!l.value)return!1;const a=n.item.menuKey||n.item.label;return n.expandedKeys?.has(a)??!1}),v=e.computed(()=>n.item.disabled||!p.value),C=e.computed(()=>({paddingLeft:"8px",paddingRight:"8px"})),b=a=>{if(v.value){a.preventDefault();return}if(l.value){const i=n.item.menuKey||n.item.label,r=!d.value;s("expandChange",i,r),s("menuClick",{menuItem:n.item,path:[n.item],event:a})}else!n.disableNavigation&&n.item.path&&x.push(n.item.path),s("menuClick",{menuItem:n.item,path:[n.item],event:a})},h={default:{itemClass:"flex items-center gap-1.5 py-
|
|
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"),c=require("../../../lib/utils.cjs"),S={key:1,class:"w-4 flex-shrink-0"},E={key:0,class:"border-l border-border/60 ml-[14px] pl-[6px]"},N=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:g}){const n=t,s=g,x=F.useRouter(),p=e.computed(()=>c.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),l=e.computed(()=>n.item.menuType==="F"||Array.isArray(n.item.children)&&n.item.children.length>0),d=e.computed(()=>{if(!l.value)return!1;const a=n.item.menuKey||n.item.label;return n.expandedKeys?.has(a)??!1}),v=e.computed(()=>n.item.disabled||!p.value),C=e.computed(()=>({paddingLeft:"8px",paddingRight:"8px"})),b=a=>{if(v.value){a.preventDefault();return}if(l.value){const i=n.item.menuKey||n.item.label,r=!d.value;s("expandChange",i,r),s("menuClick",{menuItem:n.item,path:[n.item],event:a})}else!n.disableNavigation&&n.item.path&&x.push(n.item.path),s("menuClick",{menuItem:n.item,path:[n.item],event:a})},h={default:{itemClass:"flex items-center gap-1.5 py-0.5 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate text-xs",iconSize:"sm"},minimal:{itemClass:"flex items-center gap-1 py-0.5 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate text-xs",iconSize:"sm"}},u=e.computed(()=>h[n.styletype]??h.default),B=e.computed(()=>d.value?"chevronDown":"chevronRight"),k=e.computed(()=>{if(!l.value||!Array.isArray(n.item.children))return 0;const a=i=>{let r=0;for(const o of i)r++,Array.isArray(o.children)&&o.children.length>0&&(r+=a(o.children));return r};return a(n.item.children)});return(a,i)=>{const r=e.resolveComponent("JDynamicMenuItem",!0);return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(c.cn)("w-full",t.className))},[e.createElementVNode("div",{class:e.normalizeClass(e.unref(c.cn)(u.value.itemClass,{"bg-primary/5 text-primary border-l-2 border-primary shadow-sm":m.value,"hover:bg-accent/50":!v.value&&!m.value,"opacity-50 cursor-not-allowed":v.value,"font-medium":m.value,"font-semibold":l.value})),style:e.normalizeStyle(C.value),onClick:b},[l.value?(e.openBlock(),e.createBlock(f.default,{key:0,name:B.value,size:u.value.iconSize,class:"flex-shrink-0 opacity-60",style:{"stroke-width":"1.5"}},null,8,["name","size"])):(e.openBlock(),e.createElementBlock("span",S)),t.item.icon&&!l.value?(e.openBlock(),e.createBlock(f.default,{key:2,name:t.item.icon,size:u.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):e.createCommentVNode("",!0),e.createElementVNode("span",{class:e.normalizeClass(u.value.labelClass)},e.toDisplayString(t.item.label),3),l.value&&k.value>0?(e.openBlock(),e.createElementBlock("span",{key:3,class:e.normalizeClass(e.unref(c.cn)("text-muted-foreground ml-1 flex-shrink-0",n.styletype==="minimal"?"text-[10px]":"text-xs"))}," ("+e.toDisplayString(k.value)+") ",3)):e.createCommentVNode("",!0),t.item.menuKey&&t.item.menuType==="L"&&t.onFavoriteToggle?(e.openBlock(),e.createElementBlock("button",{key:4,class:e.normalizeClass(e.unref(c.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:i[0]||(i[0]=e.withModifiers(o=>t.onFavoriteToggle(t.item.menuKey),["stop"]))},[e.createVNode(f.default,{name:(t.isFavorite&&t.isFavorite(t.item.menuKey),"star"),size:u.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),l.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",E,[(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,"disable-navigation":t.disableNavigation,"active-key":t.activeKey,onMenuClick:i[1]||(i[1]=y=>s("menuClick",y)),onExpandChange:i[2]||(i[2]=(y,K)=>s("expandChange",y,K))},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=N;
|
|
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\">\nimport { computed } from 'vue'\nimport { useRouter } from 'vue-router'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport { cn, hasMenuPermission } from '@/lib/utils'\n\n/**\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\n * Recursive Menu Item Component\n * \n * @description\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\n */\n\ntype StyleType = 'default' | 'minimal'\n\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\nconst emit = defineEmits<{\n /** 메뉴 클릭 이벤트 */\n menuClick: [event: MenuClickEvent]\n /** 확장 상태 변경 이벤트 */\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\n}>()\n\nconst router = useRouter()\n\n/**\n * 권한 체크 함수\n * Permission check function\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\n */\nconst checkPermission = computed(() => {\n return hasMenuPermission(props.item.menuKey, props.permissions)\n})\n\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\n/**\n * 메뉴 타입이 폴더인지 여부\n * 순환 참조 방지: children이 유효한 배열인지 확인\n */\nconst isFolder = computed(() => {\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\n})\n\n/**\n * 메뉴가 확장되어 있는지 여부\n */\nconst isExpanded = computed(() => {\n if (!isFolder.value) return false\n const key = props.item.menuKey || props.item.label\n return props.expandedKeys?.has(key) ?? false\n})\n\n/**\n * 메뉴가 비활성화되어 있는지 여부\n */\nconst isDisabled = computed(() => {\n return props.item.disabled || !checkPermission.value\n})\n\n/**\n * 레벨별 들여쓰기 스타일\n * 모든 레벨에서 동일한 padding 사용 (들여쓰기는 컨테이너의 ml로 조절)\n */\nconst indentStyle = computed(() => {\n return { paddingLeft: '8px', paddingRight: '8px' }\n})\n\n\n\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 // 폴더도 메뉴 클릭 이벤트 발생\n emit('menuClick', {\n menuItem: props.item,\n path: [props.item],\n event,\n })\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\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n itemClass: string\n labelClass: string\n iconSize: 'sm' | 'md'\n}> = {\n default: {\n itemClass: 'flex items-center gap-1.5 py-1 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm',\n },\n minimal: {\n itemClass: 'flex items-center gap-1 py-1 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * Chevron 아이콘 컴포넌트\n */\nconst ChevronIcon = computed(() => {\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\n})\n\n/**\n * 하위 메뉴 갯수 계산 (재귀적으로 모든 하위 메뉴 포함)\n * 폴더 타입인 경우에만 표시\n */\nconst childrenCount = computed(() => {\n if (!isFolder.value || !Array.isArray(props.item.children)) {\n return 0\n }\n \n const countChildren = (items: SidebarMenuItem[]): number => {\n let count = 0\n for (const item of items) {\n count++ // 현재 아이템 카운트\n if (Array.isArray(item.children) && item.children.length > 0) {\n count += countChildren(item.children) // 재귀적으로 하위 메뉴 카운트\n }\n }\n return count\n }\n \n return countChildren(props.item.children)\n})\n</script>\n\n<template>\n <div :class=\"cn('w-full', className)\">\n <!-- 메뉴 아이템 -->\n <div\n :class=\"cn(\n preset.itemClass,\n {\n 'bg-primary/5 text-primary border-l-2 border-primary shadow-sm': isActive,\n 'hover:bg-accent/50': !isDisabled && !isActive,\n 'opacity-50 cursor-not-allowed': isDisabled,\n 'font-medium': isActive,\n 'font-semibold': isFolder, // 폴더인 경우 볼드체\n }\n )\"\n :style=\"indentStyle\"\n @click=\"handleMenuClick\"\n >\n <!-- Chevron 아이콘 (폴더 타입만, 덜 굵게) -->\n <JIcon\n v-if=\"isFolder\"\n :name=\"ChevronIcon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0 opacity-60\"\n style=\"stroke-width: 1.5;\"\n />\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\n\n <!-- 메뉴 아이콘 (폴더가 아닌 경우만 표시) -->\n <JIcon\n v-if=\"item.icon && !isFolder\"\n :name=\"item.icon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0\"\n />\n\n <!-- 메뉴 라벨 -->\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\n \n <!-- 하위 메뉴 갯수 (폴더인 경우만) -->\n <span\n v-if=\"isFolder && childrenCount > 0\"\n :class=\"cn(\n 'text-muted-foreground ml-1 flex-shrink-0',\n props.styletype === 'minimal' ? 'text-[10px]' : 'text-xs'\n )\"\n >\n ({{ childrenCount }})\n </span>\n \n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\n <button\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\n :class=\"cn(\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\n )\"\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\n >\n <JIcon\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\n :size=\"preset.iconSize\"\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\n />\n </button>\n </div>\n\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\n <div\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\n class=\"border-l border-border/60 ml-[14px] pl-[6px]\"\n >\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>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","childrenCount","countChildren","items","count","item","_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.cjs","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRouter } from 'vue-router'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport { cn, hasMenuPermission } from '@/lib/utils'\n\n/**\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\n * Recursive Menu Item Component\n * \n * @description\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\n */\n\ntype StyleType = 'default' | 'minimal'\n\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\nconst emit = defineEmits<{\n /** 메뉴 클릭 이벤트 */\n menuClick: [event: MenuClickEvent]\n /** 확장 상태 변경 이벤트 */\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\n}>()\n\nconst router = useRouter()\n\n/**\n * 권한 체크 함수\n * Permission check function\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\n */\nconst checkPermission = computed(() => {\n return hasMenuPermission(props.item.menuKey, props.permissions)\n})\n\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\n/**\n * 메뉴 타입이 폴더인지 여부\n * 순환 참조 방지: children이 유효한 배열인지 확인\n */\nconst isFolder = computed(() => {\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\n})\n\n/**\n * 메뉴가 확장되어 있는지 여부\n */\nconst isExpanded = computed(() => {\n if (!isFolder.value) return false\n const key = props.item.menuKey || props.item.label\n return props.expandedKeys?.has(key) ?? false\n})\n\n/**\n * 메뉴가 비활성화되어 있는지 여부\n */\nconst isDisabled = computed(() => {\n return props.item.disabled || !checkPermission.value\n})\n\n/**\n * 레벨별 들여쓰기 스타일\n * 모든 레벨에서 동일한 padding 사용 (들여쓰기는 컨테이너의 ml로 조절)\n */\nconst indentStyle = computed(() => {\n return { paddingLeft: '8px', paddingRight: '8px' }\n})\n\n\n\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 // 폴더도 메뉴 클릭 이벤트 발생\n emit('menuClick', {\n menuItem: props.item,\n path: [props.item],\n event,\n })\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\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n itemClass: string\n labelClass: string\n iconSize: 'sm' | 'md'\n}> = {\n default: {\n itemClass: 'flex items-center gap-1.5 py-0.5 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm',\n },\n minimal: {\n itemClass: 'flex items-center gap-1 py-0.5 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * Chevron 아이콘 컴포넌트\n */\nconst ChevronIcon = computed(() => {\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\n})\n\n/**\n * 하위 메뉴 갯수 계산 (재귀적으로 모든 하위 메뉴 포함)\n * 폴더 타입인 경우에만 표시\n */\nconst childrenCount = computed(() => {\n if (!isFolder.value || !Array.isArray(props.item.children)) {\n return 0\n }\n \n const countChildren = (items: SidebarMenuItem[]): number => {\n let count = 0\n for (const item of items) {\n count++ // 현재 아이템 카운트\n if (Array.isArray(item.children) && item.children.length > 0) {\n count += countChildren(item.children) // 재귀적으로 하위 메뉴 카운트\n }\n }\n return count\n }\n \n return countChildren(props.item.children)\n})\n</script>\n\n<template>\n <div :class=\"cn('w-full', className)\">\n <!-- 메뉴 아이템 -->\n <div\n :class=\"cn(\n preset.itemClass,\n {\n 'bg-primary/5 text-primary border-l-2 border-primary shadow-sm': isActive,\n 'hover:bg-accent/50': !isDisabled && !isActive,\n 'opacity-50 cursor-not-allowed': isDisabled,\n 'font-medium': isActive,\n 'font-semibold': isFolder, // 폴더인 경우 볼드체\n }\n )\"\n :style=\"indentStyle\"\n @click=\"handleMenuClick\"\n >\n <!-- Chevron 아이콘 (폴더 타입만, 덜 굵게) -->\n <JIcon\n v-if=\"isFolder\"\n :name=\"ChevronIcon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0 opacity-60\"\n style=\"stroke-width: 1.5;\"\n />\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\n\n <!-- 메뉴 아이콘 (폴더가 아닌 경우만 표시) -->\n <JIcon\n v-if=\"item.icon && !isFolder\"\n :name=\"item.icon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0\"\n />\n\n <!-- 메뉴 라벨 -->\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\n \n <!-- 하위 메뉴 갯수 (폴더인 경우만) -->\n <span\n v-if=\"isFolder && childrenCount > 0\"\n :class=\"cn(\n 'text-muted-foreground ml-1 flex-shrink-0',\n props.styletype === 'minimal' ? 'text-[10px]' : 'text-xs'\n )\"\n >\n ({{ childrenCount }})\n </span>\n \n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\n <button\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\n :class=\"cn(\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\n )\"\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\n >\n <JIcon\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\n :size=\"preset.iconSize\"\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\n />\n </button>\n </div>\n\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\n <div\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\n class=\"border-l border-border/60 ml-[14px] pl-[6px]\"\n >\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>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","childrenCount","countChildren","items","count","item","_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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"}
|
|
@@ -52,12 +52,12 @@ const j = {
|
|
|
52
52
|
});
|
|
53
53
|
}, p = {
|
|
54
54
|
default: {
|
|
55
|
-
itemClass: "flex items-center gap-1.5 py-
|
|
55
|
+
itemClass: "flex items-center gap-1.5 py-0.5 rounded-md cursor-pointer transition-colors group",
|
|
56
56
|
labelClass: "flex-1 truncate text-xs",
|
|
57
57
|
iconSize: "sm"
|
|
58
58
|
},
|
|
59
59
|
minimal: {
|
|
60
|
-
itemClass: "flex items-center gap-1 py-
|
|
60
|
+
itemClass: "flex items-center gap-1 py-0.5 rounded-md cursor-pointer transition-colors group",
|
|
61
61
|
labelClass: "flex-1 truncate text-xs",
|
|
62
62
|
iconSize: "sm"
|
|
63
63
|
// JIcon은 'xs'를 지원하지 않으므로 'sm' 사용
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JDynamicMenuItem.vue.js","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRouter } from 'vue-router'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport { cn, hasMenuPermission } from '@/lib/utils'\n\n/**\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\n * Recursive Menu Item Component\n * \n * @description\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\n */\n\ntype StyleType = 'default' | 'minimal'\n\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\nconst emit = defineEmits<{\n /** 메뉴 클릭 이벤트 */\n menuClick: [event: MenuClickEvent]\n /** 확장 상태 변경 이벤트 */\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\n}>()\n\nconst router = useRouter()\n\n/**\n * 권한 체크 함수\n * Permission check function\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\n */\nconst checkPermission = computed(() => {\n return hasMenuPermission(props.item.menuKey, props.permissions)\n})\n\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\n/**\n * 메뉴 타입이 폴더인지 여부\n * 순환 참조 방지: children이 유효한 배열인지 확인\n */\nconst isFolder = computed(() => {\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\n})\n\n/**\n * 메뉴가 확장되어 있는지 여부\n */\nconst isExpanded = computed(() => {\n if (!isFolder.value) return false\n const key = props.item.menuKey || props.item.label\n return props.expandedKeys?.has(key) ?? false\n})\n\n/**\n * 메뉴가 비활성화되어 있는지 여부\n */\nconst isDisabled = computed(() => {\n return props.item.disabled || !checkPermission.value\n})\n\n/**\n * 레벨별 들여쓰기 스타일\n * 모든 레벨에서 동일한 padding 사용 (들여쓰기는 컨테이너의 ml로 조절)\n */\nconst indentStyle = computed(() => {\n return { paddingLeft: '8px', paddingRight: '8px' }\n})\n\n\n\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 // 폴더도 메뉴 클릭 이벤트 발생\n emit('menuClick', {\n menuItem: props.item,\n path: [props.item],\n event,\n })\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\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n itemClass: string\n labelClass: string\n iconSize: 'sm' | 'md'\n}> = {\n default: {\n itemClass: 'flex items-center gap-1.5 py-1 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm',\n },\n minimal: {\n itemClass: 'flex items-center gap-1 py-1 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * Chevron 아이콘 컴포넌트\n */\nconst ChevronIcon = computed(() => {\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\n})\n\n/**\n * 하위 메뉴 갯수 계산 (재귀적으로 모든 하위 메뉴 포함)\n * 폴더 타입인 경우에만 표시\n */\nconst childrenCount = computed(() => {\n if (!isFolder.value || !Array.isArray(props.item.children)) {\n return 0\n }\n \n const countChildren = (items: SidebarMenuItem[]): number => {\n let count = 0\n for (const item of items) {\n count++ // 현재 아이템 카운트\n if (Array.isArray(item.children) && item.children.length > 0) {\n count += countChildren(item.children) // 재귀적으로 하위 메뉴 카운트\n }\n }\n return count\n }\n \n return countChildren(props.item.children)\n})\n</script>\n\n<template>\n <div :class=\"cn('w-full', className)\">\n <!-- 메뉴 아이템 -->\n <div\n :class=\"cn(\n preset.itemClass,\n {\n 'bg-primary/5 text-primary border-l-2 border-primary shadow-sm': isActive,\n 'hover:bg-accent/50': !isDisabled && !isActive,\n 'opacity-50 cursor-not-allowed': isDisabled,\n 'font-medium': isActive,\n 'font-semibold': isFolder, // 폴더인 경우 볼드체\n }\n )\"\n :style=\"indentStyle\"\n @click=\"handleMenuClick\"\n >\n <!-- Chevron 아이콘 (폴더 타입만, 덜 굵게) -->\n <JIcon\n v-if=\"isFolder\"\n :name=\"ChevronIcon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0 opacity-60\"\n style=\"stroke-width: 1.5;\"\n />\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\n\n <!-- 메뉴 아이콘 (폴더가 아닌 경우만 표시) -->\n <JIcon\n v-if=\"item.icon && !isFolder\"\n :name=\"item.icon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0\"\n />\n\n <!-- 메뉴 라벨 -->\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\n \n <!-- 하위 메뉴 갯수 (폴더인 경우만) -->\n <span\n v-if=\"isFolder && childrenCount > 0\"\n :class=\"cn(\n 'text-muted-foreground ml-1 flex-shrink-0',\n props.styletype === 'minimal' ? 'text-[10px]' : 'text-xs'\n )\"\n >\n ({{ childrenCount }})\n </span>\n \n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\n <button\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\n :class=\"cn(\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\n )\"\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\n >\n <JIcon\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\n :size=\"preset.iconSize\"\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\n />\n </button>\n </div>\n\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\n <div\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\n class=\"border-l border-border/60 ml-[14px] pl-[6px]\"\n >\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>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","childrenCount","countChildren","items","count","item","_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\">\nimport { computed } from 'vue'\nimport { useRouter } from 'vue-router'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport { cn, hasMenuPermission } from '@/lib/utils'\n\n/**\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\n * Recursive Menu Item Component\n * \n * @description\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\n */\n\ntype StyleType = 'default' | 'minimal'\n\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\nconst emit = defineEmits<{\n /** 메뉴 클릭 이벤트 */\n menuClick: [event: MenuClickEvent]\n /** 확장 상태 변경 이벤트 */\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\n}>()\n\nconst router = useRouter()\n\n/**\n * 권한 체크 함수\n * Permission check function\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\n */\nconst checkPermission = computed(() => {\n return hasMenuPermission(props.item.menuKey, props.permissions)\n})\n\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\n/**\n * 메뉴 타입이 폴더인지 여부\n * 순환 참조 방지: children이 유효한 배열인지 확인\n */\nconst isFolder = computed(() => {\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\n})\n\n/**\n * 메뉴가 확장되어 있는지 여부\n */\nconst isExpanded = computed(() => {\n if (!isFolder.value) return false\n const key = props.item.menuKey || props.item.label\n return props.expandedKeys?.has(key) ?? false\n})\n\n/**\n * 메뉴가 비활성화되어 있는지 여부\n */\nconst isDisabled = computed(() => {\n return props.item.disabled || !checkPermission.value\n})\n\n/**\n * 레벨별 들여쓰기 스타일\n * 모든 레벨에서 동일한 padding 사용 (들여쓰기는 컨테이너의 ml로 조절)\n */\nconst indentStyle = computed(() => {\n return { paddingLeft: '8px', paddingRight: '8px' }\n})\n\n\n\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 // 폴더도 메뉴 클릭 이벤트 발생\n emit('menuClick', {\n menuItem: props.item,\n path: [props.item],\n event,\n })\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\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n itemClass: string\n labelClass: string\n iconSize: 'sm' | 'md'\n}> = {\n default: {\n itemClass: 'flex items-center gap-1.5 py-0.5 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm',\n },\n minimal: {\n itemClass: 'flex items-center gap-1 py-0.5 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * Chevron 아이콘 컴포넌트\n */\nconst ChevronIcon = computed(() => {\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\n})\n\n/**\n * 하위 메뉴 갯수 계산 (재귀적으로 모든 하위 메뉴 포함)\n * 폴더 타입인 경우에만 표시\n */\nconst childrenCount = computed(() => {\n if (!isFolder.value || !Array.isArray(props.item.children)) {\n return 0\n }\n \n const countChildren = (items: SidebarMenuItem[]): number => {\n let count = 0\n for (const item of items) {\n count++ // 현재 아이템 카운트\n if (Array.isArray(item.children) && item.children.length > 0) {\n count += countChildren(item.children) // 재귀적으로 하위 메뉴 카운트\n }\n }\n return count\n }\n \n return countChildren(props.item.children)\n})\n</script>\n\n<template>\n <div :class=\"cn('w-full', className)\">\n <!-- 메뉴 아이템 -->\n <div\n :class=\"cn(\n preset.itemClass,\n {\n 'bg-primary/5 text-primary border-l-2 border-primary shadow-sm': isActive,\n 'hover:bg-accent/50': !isDisabled && !isActive,\n 'opacity-50 cursor-not-allowed': isDisabled,\n 'font-medium': isActive,\n 'font-semibold': isFolder, // 폴더인 경우 볼드체\n }\n )\"\n :style=\"indentStyle\"\n @click=\"handleMenuClick\"\n >\n <!-- Chevron 아이콘 (폴더 타입만, 덜 굵게) -->\n <JIcon\n v-if=\"isFolder\"\n :name=\"ChevronIcon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0 opacity-60\"\n style=\"stroke-width: 1.5;\"\n />\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\n\n <!-- 메뉴 아이콘 (폴더가 아닌 경우만 표시) -->\n <JIcon\n v-if=\"item.icon && !isFolder\"\n :name=\"item.icon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0\"\n />\n\n <!-- 메뉴 라벨 -->\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\n \n <!-- 하위 메뉴 갯수 (폴더인 경우만) -->\n <span\n v-if=\"isFolder && childrenCount > 0\"\n :class=\"cn(\n 'text-muted-foreground ml-1 flex-shrink-0',\n props.styletype === 'minimal' ? 'text-[10px]' : 'text-xs'\n )\"\n >\n ({{ childrenCount }})\n </span>\n \n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\n <button\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\n :class=\"cn(\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\n )\"\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\n >\n <JIcon\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\n :size=\"preset.iconSize\"\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\n />\n </button>\n </div>\n\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\n <div\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\n class=\"border-l border-border/60 ml-[14px] pl-[6px]\"\n >\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>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","childrenCount","countChildren","items","count","item","_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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"}
|