@j-solution/components 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +413 -415
  2. package/assets/jwms-portal-frontend-Ct2Tc7yj.css +1 -0
  3. package/assets/styles/j-components.css +1 -1
  4. package/assets/styles/themes.css +422 -422
  5. package/components/atoms/JButton.vue.cjs +1 -1
  6. package/components/atoms/JButton.vue.js +1 -1
  7. package/components/atoms/JButton.vue2.cjs.map +1 -1
  8. package/components/atoms/JButton.vue2.js.map +1 -1
  9. package/components/atoms/JLabel.vue.cjs.map +1 -1
  10. package/components/atoms/JLabel.vue.js.map +1 -1
  11. package/components/molecules/JTabs.vue.cjs +1 -1
  12. package/components/molecules/JTabs.vue.js +1 -1
  13. package/components/molecules/JTabs.vue2.cjs.map +1 -1
  14. package/components/molecules/JTabs.vue2.js.map +1 -1
  15. package/components/organisms/JDynamicTabs.vue.cjs.map +1 -1
  16. package/components/organisms/JDynamicTabs.vue.js.map +1 -1
  17. package/components/organisms/JFilterBar.vue.cjs +1 -1
  18. package/components/organisms/JFilterBar.vue.js +2 -2
  19. package/components/organisms/JFilterBar.vue2.cjs +1 -1
  20. package/components/organisms/JFilterBar.vue2.cjs.map +1 -1
  21. package/components/organisms/JFilterBar.vue2.js +14 -12
  22. package/components/organisms/JFilterBar.vue2.js.map +1 -1
  23. package/components/organisms/JPageContainer.vue.cjs.map +1 -1
  24. package/components/organisms/JPageContainer.vue.js.map +1 -1
  25. package/components/organisms/JSidebar/JSidebar.vue.cjs +2 -0
  26. package/components/organisms/JSidebar/JSidebar.vue.cjs.map +1 -0
  27. package/components/organisms/JSidebar/JSidebar.vue.js +189 -0
  28. package/components/organisms/JSidebar/JSidebar.vue.js.map +1 -0
  29. package/components/organisms/JSidebar/JSidebar.vue2.cjs +2 -0
  30. package/components/organisms/JSidebar/JSidebar.vue2.cjs.map +1 -0
  31. package/components/organisms/JSidebar/JSidebar.vue2.js +5 -0
  32. package/components/organisms/JSidebar/JSidebar.vue2.js.map +1 -0
  33. package/components/organisms/JSidebar/JSidebarGroup.vue.cjs +2 -0
  34. package/components/organisms/JSidebar/JSidebarGroup.vue.cjs.map +1 -0
  35. package/components/organisms/JSidebar/JSidebarGroup.vue.js +89 -0
  36. package/components/organisms/JSidebar/JSidebarGroup.vue.js.map +1 -0
  37. package/components/organisms/JSidebar/JSidebarGroup.vue2.cjs +2 -0
  38. package/components/organisms/JSidebar/JSidebarGroup.vue2.cjs.map +1 -0
  39. package/components/organisms/JSidebar/JSidebarGroup.vue2.js +5 -0
  40. package/components/organisms/JSidebar/JSidebarGroup.vue2.js.map +1 -0
  41. package/components/organisms/JSidebar/JSidebarItem.vue.cjs +2 -0
  42. package/components/organisms/JSidebar/JSidebarItem.vue.cjs.map +1 -0
  43. package/components/organisms/JSidebar/JSidebarItem.vue.js +79 -0
  44. package/components/organisms/JSidebar/JSidebarItem.vue.js.map +1 -0
  45. package/components/organisms/JSidebar/JSidebarItem.vue2.cjs +2 -0
  46. package/components/organisms/JSidebar/JSidebarItem.vue2.cjs.map +1 -0
  47. package/components/organisms/JSidebar/JSidebarItem.vue2.js +5 -0
  48. package/components/organisms/JSidebar/JSidebarItem.vue2.js.map +1 -0
  49. package/components/shadcn/Card.vue.cjs.map +1 -1
  50. package/components/shadcn/Card.vue.js.map +1 -1
  51. package/components/shadcn/CardContent.vue.cjs.map +1 -1
  52. package/components/shadcn/CardContent.vue.js.map +1 -1
  53. package/components/shadcn/CardHeader.vue.cjs.map +1 -1
  54. package/components/shadcn/CardHeader.vue.js.map +1 -1
  55. package/components/shadcn/Input.vue.cjs.map +1 -1
  56. package/components/shadcn/Input.vue.js.map +1 -1
  57. package/components/shadcn/SelectTrigger.vue.cjs.map +1 -1
  58. package/components/shadcn/SelectTrigger.vue.js.map +1 -1
  59. package/components/shadcn/TabsContent.vue.cjs.map +1 -1
  60. package/components/shadcn/TabsContent.vue.js.map +1 -1
  61. package/components/shadcn/Textarea.vue.cjs.map +1 -1
  62. package/components/shadcn/Textarea.vue.js.map +1 -1
  63. package/components/shadcn/index.cjs.map +1 -1
  64. package/components/shadcn/index.js.map +1 -1
  65. package/components/templates/JLayout.vue.cjs.map +1 -1
  66. package/components/templates/JLayout.vue.js.map +1 -1
  67. package/components/templates/JLayoutSimple.vue.cjs +1 -1
  68. package/components/templates/JLayoutSimple.vue.cjs.map +1 -1
  69. package/components/templates/JLayoutSimple.vue.js +36 -30
  70. package/components/templates/JLayoutSimple.vue.js.map +1 -1
  71. package/index.cjs +1 -1
  72. package/index.js +22 -20
  73. package/package.json +1 -1
  74. package/types/index.d.ts +119 -61
  75. package/types/sidebar.types.cjs +2 -0
  76. package/types/sidebar.types.cjs.map +1 -0
  77. package/types/sidebar.types.js +5 -0
  78. package/types/sidebar.types.js.map +1 -0
  79. package/assets/jwms-portal-frontend-BtHTA-UF.css +0 -1
@@ -1,4 +1,4 @@
1
- import { defineComponent as D, computed as x, createElementBlock as u, openBlock as o, normalizeClass as v, unref as p, createElementVNode as n, withDirectives as T, createCommentVNode as i, createBlock as f, createVNode as b, Fragment as $, renderList as z, withCtx as m, toDisplayString as d, withModifiers as E, renderSlot as V, createTextVNode as B, vShow as F } from "vue";
1
+ import { defineComponent as D, computed as v, createElementBlock as u, openBlock as o, normalizeClass as x, unref as p, createElementVNode as n, withDirectives as T, createCommentVNode as i, createBlock as f, createVNode as b, Fragment as $, renderList as z, withCtx as m, toDisplayString as d, withModifiers as E, renderSlot as V, createTextVNode as B, vShow as F } from "vue";
2
2
  import { ChevronDown as N, X as R } from "lucide-vue-next";
3
3
  import J from "../atoms/JBadge.vue.js";
4
4
  import k from "../atoms/JButton.vue.js";
@@ -7,7 +7,7 @@ import { cn as L } from "../../lib/utils.js";
7
7
  const M = { class: "flex items-center justify-between px-3 py-1.5" }, X = { class: "flex items-center gap-2" }, q = {
8
8
  key: 2,
9
9
  class: "flex items-center gap-1 flex-wrap"
10
- }, G = { class: "text-muted-foreground" }, H = ["onClick"], I = { class: "flex items-center gap-2" }, K = { class: "px-3 pb-3" }, ee = /* @__PURE__ */ D({
10
+ }, G = { class: "text-muted-foreground" }, H = ["onClick"], I = { class: "flex items-center gap-2" }, K = { class: "px-3 pb-3" }, P = { class: "filter-fields-grid" }, te = /* @__PURE__ */ D({
11
11
  __name: "JFilterBar",
12
12
  props: {
13
13
  class: {},
@@ -23,15 +23,15 @@ const M = { class: "flex items-center justify-between px-3 py-1.5" }, X = { clas
23
23
  },
24
24
  emits: ["update:collapsed", "update:filterValues", "search", "reset"],
25
25
  setup(r, { emit: w }) {
26
- const l = r, a = w, y = x(() => l.collapsible ? !l.collapsed : !0);
27
- function C(e) {
26
+ const l = r, a = w, y = v(() => l.collapsible ? !l.collapsed : !0);
27
+ function _(e) {
28
28
  return !!(e == null || typeof e == "string" && e.trim() === "" || Array.isArray(e) && e.length === 0);
29
29
  }
30
- const h = x(() => {
30
+ const h = v(() => {
31
31
  const e = [];
32
32
  for (const [t, s] of Object.entries(l.filterDisplay)) {
33
33
  const c = l.filterValues[t];
34
- if (C(c)) continue;
34
+ if (_(c)) continue;
35
35
  const g = s.displayValue ? s.displayValue(c) : String(c);
36
36
  g.trim() !== "" && e.push({
37
37
  key: t,
@@ -41,7 +41,7 @@ const M = { class: "flex items-center justify-between px-3 py-1.5" }, X = { clas
41
41
  }
42
42
  return e;
43
43
  });
44
- function _() {
44
+ function C() {
45
45
  a("update:collapsed", !l.collapsed);
46
46
  }
47
47
  function S() {
@@ -60,7 +60,7 @@ const M = { class: "flex items-center justify-between px-3 py-1.5" }, X = { clas
60
60
  typeof s == "string" ? t[e] = "" : Array.isArray(s) ? t[e] = [] : t[e] = null, a("update:filterValues", t);
61
61
  }
62
62
  return (e, t) => (o(), u("div", {
63
- class: v(p(L)("j-filter-bar w-full rounded-sm border bg-card text-card-foreground", l.class))
63
+ class: x(p(L)("j-filter-bar w-full rounded-sm border bg-card text-card-foreground", l.class))
64
64
  }, [
65
65
  n("div", M, [
66
66
  n("div", X, [
@@ -68,10 +68,10 @@ const M = { class: "flex items-center justify-between px-3 py-1.5" }, X = { clas
68
68
  key: 0,
69
69
  type: "button",
70
70
  class: "flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors",
71
- onClick: _
71
+ onClick: C
72
72
  }, [
73
73
  b(p(N), {
74
- class: v([
74
+ class: x([
75
75
  "h-3.5 w-3.5 transition-transform",
76
76
  y.value ? "rotate-0" : "-rotate-90"
77
77
  ])
@@ -131,7 +131,9 @@ const M = { class: "flex items-center justify-between px-3 py-1.5" }, X = { clas
131
131
  ])
132
132
  ]),
133
133
  T(n("div", K, [
134
- V(e.$slots, "filters", {}, void 0, !0)
134
+ n("div", P, [
135
+ V(e.$slots, "filters", {}, void 0, !0)
136
+ ])
135
137
  ], 512), [
136
138
  [F, y.value]
137
139
  ])
@@ -139,6 +141,6 @@ const M = { class: "flex items-center justify-between px-3 py-1.5" }, X = { clas
139
141
  }
140
142
  });
141
143
  export {
142
- ee as default
144
+ te as default
143
145
  };
144
146
  //# sourceMappingURL=JFilterBar.vue2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"JFilterBar.vue2.js","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\r\n <div :class=\"cn('j-filter-bar w-full rounded-sm border bg-card text-card-foreground', props.class)\">\r\n <!-- Row 1: toolbar -->\r\n <div class=\"flex items-center justify-between px-3 py-1.5\">\r\n <div class=\"flex items-center gap-2\">\r\n <button\r\n v-if=\"collapsible\"\r\n type=\"button\"\r\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\r\n @click=\"toggleCollapsed\"\r\n >\r\n <ChevronDown\r\n :class=\"[\r\n 'h-3.5 w-3.5 transition-transform',\r\n isExpanded ? 'rotate-0' : '-rotate-90',\r\n ]\"\r\n />\r\n </button>\r\n <!-- 타이틀 -->\r\n <JLabel\r\n v-if=\"title\"\r\n :text=\"title\"\r\n class=\"text-sm font-semibold text-foreground\"\r\n />\r\n <!-- 선택된 필터 뱃지 표시 -->\r\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\r\n <JBadge\r\n v-for=\"filter in activeFilters\"\r\n :key=\"filter.key\"\r\n variant=\"secondary\"\r\n size=\"sm\"\r\n class=\"flex items-center gap-1 cursor-default\"\r\n >\r\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\r\n <span>{{ filter.value }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\r\n @click.stop=\"removeFilter(filter.key)\"\r\n >\r\n <X class=\"h-3 w-3\" />\r\n </button>\r\n </JBadge>\r\n </div>\r\n </div>\r\n <div class=\"flex items-center gap-2\">\r\n <slot name=\"actions\" />\r\n <JButton\r\n v-if=\"showResetButton\"\r\n variant=\"secondary\"\r\n size=\"sm\"\r\n @click=\"handleReset\"\r\n >\r\n {{ resetButtonText }}\r\n </JButton>\r\n <JButton\r\n v-if=\"showSearchButton\"\r\n styletype=\"primary\"\r\n size=\"sm\"\r\n @click=\"handleSearch\"\r\n >\r\n {{ searchButtonText }}\r\n </JButton>\r\n </div>\r\n </div>\r\n\r\n <!-- Row 2: filters -->\r\n <div v-show=\"isExpanded\" class=\"px-3 pb-3\">\r\n <slot name=\"filters\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { ChevronDown, X } from 'lucide-vue-next'\r\nimport JBadge from '@/components/atoms/JBadge.vue'\r\nimport JButton from '@/components/atoms/JButton.vue'\r\nimport JLabel from '@/components/atoms/JLabel.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/** 활성 필터 아이템 타입 */\r\nexport interface ActiveFilterItem {\r\n /** 필터 식별 키 */\r\n key: string\r\n /** 표시할 라벨 (필터명) */\r\n label: string\r\n /** 표시할 값 */\r\n value: string\r\n}\r\n\r\n/** 필터 설정 타입 */\r\nexport interface FilterDisplayItem {\r\n /** 표시할 라벨 */\r\n label: string\r\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\r\n displayValue?: (value: unknown) => string\r\n}\r\n\r\nexport interface JFilterBarProps {\r\n /** 추가 클래스 (외부 커스터마이징용) */\r\n class?: string\r\n /** 필터바 타이틀 */\r\n title?: string\r\n /** 필터 접힘 상태 (v-model 지원) */\r\n collapsed?: boolean\r\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\r\n collapsible?: boolean\r\n /** 필터 값 객체 (v-model:filterValues 지원) */\r\n filterValues?: Record<string, unknown>\r\n /** 필터 표시 설정 (label, displayValue 등) */\r\n filterDisplay?: Record<string, FilterDisplayItem>\r\n /** 초기화 버튼 표시 여부 */\r\n showResetButton?: boolean\r\n /** 조회 버튼 표시 여부 */\r\n showSearchButton?: boolean\r\n /** 초기화 버튼 텍스트 */\r\n resetButtonText?: string\r\n /** 조회 버튼 텍스트 */\r\n searchButtonText?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\r\n collapsed: true,\r\n collapsible: true,\r\n filterValues: () => ({}),\r\n filterDisplay: () => ({}),\r\n showResetButton: false,\r\n showSearchButton: false,\r\n resetButtonText: '초기화',\r\n searchButtonText: '조회',\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n 'update:filterValues': [value: Record<string, unknown>]\r\n /** 조회 버튼 클릭 */\r\n search: []\r\n /** 초기화 버튼 클릭 */\r\n reset: []\r\n}>()\r\n\r\nconst isExpanded = computed(() => {\r\n if (!props.collapsible) return true\r\n return !props.collapsed\r\n})\r\n\r\n/** 값이 비어있는지 확인 */\r\nfunction isEmpty(value: unknown): boolean {\r\n if (value === null || value === undefined) return true\r\n if (typeof value === 'string' && value.trim() === '') return true\r\n if (Array.isArray(value) && value.length === 0) return true\r\n return false\r\n}\r\n\r\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\r\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\r\n const filters: ActiveFilterItem[] = []\r\n\r\n for (const [key, config] of Object.entries(props.filterDisplay)) {\r\n const value = props.filterValues[key]\r\n if (isEmpty(value)) continue\r\n\r\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\r\n if (displayValue.trim() === '') continue\r\n\r\n filters.push({\r\n key,\r\n label: config.label,\r\n value: displayValue,\r\n })\r\n }\r\n\r\n return filters\r\n})\r\n\r\nfunction toggleCollapsed() {\r\n emit('update:collapsed', !props.collapsed)\r\n}\r\n\r\nfunction handleReset() {\r\n // filterValues의 모든 값을 초기화\r\n const resetValues: Record<string, unknown> = {}\r\n for (const key of Object.keys(props.filterValues)) {\r\n const currentValue = props.filterValues[key]\r\n if (typeof currentValue === 'string') {\r\n resetValues[key] = ''\r\n } else if (Array.isArray(currentValue)) {\r\n resetValues[key] = []\r\n } else {\r\n resetValues[key] = null\r\n }\r\n }\r\n emit('update:filterValues', resetValues)\r\n emit('reset')\r\n}\r\n\r\nfunction handleSearch() {\r\n emit('search')\r\n}\r\n\r\nfunction removeFilter(key: string) {\r\n // filterValues 업데이트 (해당 키 값을 초기화)\r\n const newValues = { ...props.filterValues }\r\n const currentValue = newValues[key]\r\n\r\n // 타입에 따라 적절한 초기값으로 설정\r\n if (typeof currentValue === 'string') {\r\n newValues[key] = ''\r\n } else if (Array.isArray(currentValue)) {\r\n newValues[key] = []\r\n } else {\r\n newValues[key] = null\r\n }\r\n\r\n emit('update:filterValues', newValues)\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n/* ========================================\r\n 패턴 3: Tabs 아래 배치 시 연결 스타일\r\n ======================================== */\r\n\r\n:deep([data-state=\"active\"]) > .j-filter-bar {\r\n border-top: none;\r\n border-top-left-radius: 0;\r\n border-top-right-radius: 0;\r\n}\r\n\r\n:deep([role=\"tabpanel\"]) .j-filter-bar {\r\n border-top: none;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","ChevronDown","_createBlock","JLabel","_openBlock","_hoisted_3","_Fragment","_renderList","filter","JBadge","_hoisted_4","_toDisplayString","_withModifiers","$event","X","_hoisted_6","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_7"],"mappings":"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"}
1
+ {"version":3,"file":"JFilterBar.vue2.js","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\n <div :class=\"cn('j-filter-bar w-full rounded-sm border bg-card text-card-foreground', props.class)\">\n <!-- Row 1: toolbar -->\n <div class=\"flex items-center justify-between px-3 py-1.5\">\n <div class=\"flex items-center gap-2\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <ChevronDown\n :class=\"[\n 'h-3.5 w-3.5 transition-transform',\n isExpanded ? 'rotate-0' : '-rotate-90',\n ]\"\n />\n </button>\n <!-- 타이틀 -->\n <JLabel\n v-if=\"title\"\n :text=\"title\"\n class=\"text-sm font-semibold text-foreground\"\n />\n <!-- 선택된 필터 뱃지 표시 -->\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\n <JBadge\n v-for=\"filter in activeFilters\"\n :key=\"filter.key\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1 cursor-default\"\n >\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\n <span>{{ filter.value }}</span>\n <button\n type=\"button\"\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\n @click.stop=\"removeFilter(filter.key)\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <slot name=\"actions\" />\n <JButton\n v-if=\"showResetButton\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"handleReset\"\n >\n {{ resetButtonText }}\n </JButton>\n <JButton\n v-if=\"showSearchButton\"\n styletype=\"primary\"\n size=\"sm\"\n @click=\"handleSearch\"\n >\n {{ searchButtonText }}\n </JButton>\n </div>\n </div>\n\n <!-- Row 2: filters (반응형 그리드: max 4열, 자동 축소) -->\n <div v-show=\"isExpanded\" class=\"px-3 pb-3\">\n <div class=\"filter-fields-grid\">\n <slot name=\"filters\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport JBadge from '@/components/atoms/JBadge.vue'\nimport JButton from '@/components/atoms/JButton.vue'\nimport JLabel from '@/components/atoms/JLabel.vue'\nimport { cn } from '@/lib/utils'\n\n/** 활성 필터 아이템 타입 */\nexport interface ActiveFilterItem {\n /** 필터 식별 키 */\n key: string\n /** 표시할 라벨 (필터명) */\n label: string\n /** 표시할 값 */\n value: string\n}\n\n/** 필터 설정 타입 */\nexport interface FilterDisplayItem {\n /** 표시할 라벨 */\n label: string\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\n displayValue?: (value: unknown) => string\n}\n\nexport interface JFilterBarProps {\n /** 추가 클래스 (외부 커스터마이징용) */\n class?: string\n /** 필터바 타이틀 */\n title?: string\n /** 필터 접힘 상태 (v-model 지원) */\n collapsed?: boolean\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\n collapsible?: boolean\n /** 필터 값 객체 (v-model:filterValues 지원) */\n filterValues?: Record<string, unknown>\n /** 필터 표시 설정 (label, displayValue 등) */\n filterDisplay?: Record<string, FilterDisplayItem>\n /** 초기화 버튼 표시 여부 */\n showResetButton?: boolean\n /** 조회 버튼 표시 여부 */\n showSearchButton?: boolean\n /** 초기화 버튼 텍스트 */\n resetButtonText?: string\n /** 조회 버튼 텍스트 */\n searchButtonText?: string\n}\n\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\n collapsed: true,\n collapsible: true,\n filterValues: () => ({}),\n filterDisplay: () => ({}),\n showResetButton: false,\n showSearchButton: false,\n resetButtonText: '초기화',\n searchButtonText: '조회',\n})\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'update:filterValues': [value: Record<string, unknown>]\n /** 조회 버튼 클릭 */\n search: []\n /** 초기화 버튼 클릭 */\n reset: []\n}>()\n\nconst isExpanded = computed(() => {\n if (!props.collapsible) return true\n return !props.collapsed\n})\n\n/** 값이 비어있는지 확인 */\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string' && value.trim() === '') return true\n if (Array.isArray(value) && value.length === 0) return true\n return false\n}\n\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\n const filters: ActiveFilterItem[] = []\n\n for (const [key, config] of Object.entries(props.filterDisplay)) {\n const value = props.filterValues[key]\n if (isEmpty(value)) continue\n\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\n if (displayValue.trim() === '') continue\n\n filters.push({\n key,\n label: config.label,\n value: displayValue,\n })\n }\n\n return filters\n})\n\nfunction toggleCollapsed() {\n emit('update:collapsed', !props.collapsed)\n}\n\nfunction handleReset() {\n // filterValues의 모든 값을 초기화\n const resetValues: Record<string, unknown> = {}\n for (const key of Object.keys(props.filterValues)) {\n const currentValue = props.filterValues[key]\n if (typeof currentValue === 'string') {\n resetValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n resetValues[key] = []\n } else {\n resetValues[key] = null\n }\n }\n emit('update:filterValues', resetValues)\n emit('reset')\n}\n\nfunction handleSearch() {\n emit('search')\n}\n\nfunction removeFilter(key: string) {\n // filterValues 업데이트 (해당 키 값을 초기화)\n const newValues = { ...props.filterValues }\n const currentValue = newValues[key]\n\n // 타입에 따라 적절한 초기값으로 설정\n if (typeof currentValue === 'string') {\n newValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n newValues[key] = []\n } else {\n newValues[key] = null\n }\n\n emit('update:filterValues', newValues)\n}\n</script>\n\n<style scoped>\n/* 필터 필드 반응형 그리드: max 4열, 자동 축소 (4 → 3 → 2 → 1) */\n.filter-fields-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(max(25% - 0.75rem, 220px), 1fr));\n gap: 0.5rem 0.75rem;\n}\n\n/* ========================================\n 패턴 3: Tabs 아래 배치 시 연결 스타일\n ======================================== */\n\n:deep([data-state=\"active\"]) > .j-filter-bar {\n border-top: none;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n:deep([role=\"tabpanel\"]) .j-filter-bar {\n border-top: none;\n}\n</style>\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","ChevronDown","_createBlock","JLabel","_openBlock","_hoisted_3","_Fragment","_renderList","filter","JBadge","_hoisted_4","_toDisplayString","_withModifiers","$event","X","_hoisted_6","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_7","_hoisted_8"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"JPageContainer.vue.cjs","sources":["../../../../src/components/organisms/JPageContainer.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport JBreadcrumb from '@/components/molecules/JBreadcrumb.vue'\r\nimport JTitlebar from '@/components/molecules/JTitlebar.vue'\r\nimport { cn } from '@/lib/utils'\r\nimport type { BreadcrumbItem } from '@/components/molecules/JBreadcrumb.vue'\r\nimport type { TitlebarButton } from '@/components/molecules/JTitlebar.vue'\r\n\r\n/**\r\n * JPageContainer - 기본 페이지 컨테이너 컴포넌트 (organisms)\r\n * Page Container Component\r\n * \r\n * @description\r\n * 페이지의 기본 레이아웃을 담당하는 컨테이너 컴포넌트입니다.\r\n * 브레드크럼, 제목 영역(JTitlebar), 콘텐츠 영역을 포함합니다.\r\n * \r\n * @example\r\n * ```vue\r\n * <JPageContainer\r\n * :breadcrumb-items=\"breadcrumbItems\"\r\n * title=\"페이지 제목\"\r\n * :titlebar-buttons=\"buttons\"\r\n * >\r\n * <div>페이지 콘텐츠</div>\r\n * </JPageContainer>\r\n * ```\r\n */\r\n\r\ntype StyleType =\r\n | 'default' // 기본 스타일\r\n | 'minimal' // 최소 스타일\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 브레드크럼 아이템 목록 */\r\n breadcrumbItems?: BreadcrumbItem[]\r\n /** 브레드크럼 표시 여부 */\r\n showBreadcrumb?: boolean\r\n /** JTitlebar의 모든 props 전달 */\r\n title?: string\r\n icon?: string\r\n description?: string\r\n titlebarButtons?: TitlebarButton[]\r\n titlebarStyletype?: 'default' | 'primary' | 'accent' | 'neutral' | 'elevated'\r\n /** JTitlebar 표시 여부 */\r\n showTitlebar?: boolean\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 콘텐츠 영역 스크롤 가능 여부 */\r\n contentScroll?: boolean\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n breadcrumbItems: () => [],\r\n showBreadcrumb: true,\r\n showTitlebar: true,\r\n styletype: 'default',\r\n contentScroll: true,\r\n }\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 브레드크럼 아이템 클릭 이벤트 */\r\n breadcrumbClick: [item: BreadcrumbItem, index: number]\r\n /** 타이틀바 버튼 클릭 이벤트 */\r\n titlebarButtonClick: [button: TitlebarButton]\r\n}>()\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n containerClass: string\r\n contentClass: string\r\n}> = {\r\n default: {\r\n containerClass: 'flex flex-col h-full w-full bg-background',\r\n contentClass: 'flex-1 p-3 pt-2 pb-2 gap-2 bg-background text-foreground',\r\n },\r\n minimal: {\r\n containerClass: 'flex flex-col h-full w-full bg-background',\r\n contentClass: 'flex-1 p-1 gap-1 bg-background text-foreground',\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * 콘텐츠 클래스 (스크롤 포함)\r\n */\r\nconst contentClasses = computed(() => {\r\n return cn(\r\n preset.value.contentClass,\r\n props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\r\n )\r\n})\r\n\r\n/**\r\n * 브레드크럼 아이템 클릭 핸들러\r\n */\r\nconst handleBreadcrumbClick = (item: BreadcrumbItem, index: number) => {\r\n emit('breadcrumbClick', item, index)\r\n}\r\n\r\n/**\r\n * 타이틀바 버튼 클릭 핸들러\r\n */\r\nconst handleTitlebarButtonClick = (button: TitlebarButton) => {\r\n emit('titlebarButtonClick', button)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div :class=\"cn(preset.containerClass, props.class)\">\r\n <!-- 브레드크럼 -->\r\n <JBreadcrumb\r\n v-if=\"showBreadcrumb && breadcrumbItems && breadcrumbItems.length > 0\"\r\n :items=\"breadcrumbItems\"\r\n @item-click=\"handleBreadcrumbClick\"\r\n />\r\n\r\n <!-- 제목 영역 (JTitlebar) -->\r\n <JTitlebar\r\n v-if=\"showTitlebar\"\r\n :title=\"title\"\r\n :icon=\"icon\"\r\n :description=\"description\"\r\n :buttons=\"titlebarButtons\"\r\n :styletype=\"titlebarStyletype\"\r\n @button-click=\"handleTitlebarButtonClick\"\r\n >\r\n <template #buttons>\r\n <slot name=\"titlebar-buttons\" />\r\n </template>\r\n </JTitlebar>\r\n\r\n <!-- 콘텐츠 영역 -->\r\n <div :class=\"contentClasses\">\r\n <slot />\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","contentClasses","cn","handleBreadcrumbClick","item","index","handleTitlebarButtonClick","button","_createElementBlock","_normalizeClass","_unref","_createBlock","JBreadcrumb","JTitlebar","_renderSlot","_ctx","_createElementVNode"],"mappings":"6nBAgCA,MAAMA,EAAQC,EA8BRC,EAAOC,EAUPC,EAGD,CACH,QAAS,CACP,eAAgB,4CAChB,aAAc,0DAAA,EAEhB,QAAS,CACP,eAAgB,4CAChB,aAAc,gDAAA,CAChB,EAGIC,EAASC,EAAAA,SAAS,IACfF,EAAcJ,EAAM,SAAS,GAAKI,EAAc,OACxD,EAKKG,EAAiBD,EAAAA,SAAS,IACvBE,EAAAA,GACLH,EAAO,MAAM,aACbL,EAAM,cAAgB,gBAAkB,iBAAA,CAE3C,EAKKS,EAAwB,CAACC,EAAsBC,IAAkB,CACrET,EAAK,kBAAmBQ,EAAMC,CAAK,CACrC,EAKMC,EAA6BC,GAA2B,CAC5DX,EAAK,sBAAuBW,CAAM,CACpC,8BAIEC,EAAAA,mBA2BM,MAAA,CA3BA,MAAKC,EAAAA,eAAEC,EAAAA,YAAGX,EAAA,MAAO,eAAgBL,EAAM,KAAK,CAAA,CAAA,GAGxCC,EAAA,gBAAkBA,EAAA,iBAAmBA,EAAA,gBAAgB,OAAM,iBADnEgB,EAAAA,YAIEC,EAAAA,QAAA,OAFC,MAAOjB,EAAA,gBACP,YAAYQ,CAAA,iDAKPR,EAAA,4BADRgB,EAAAA,YAYYE,EAAAA,QAAA,OAVT,MAAOlB,EAAA,MACP,KAAMA,EAAA,KACN,YAAaA,EAAA,YACb,QAASA,EAAA,gBACT,UAAWA,EAAA,kBACX,cAAcW,CAAA,GAEJ,kBACT,IAAgC,CAAhCQ,aAAgCC,EAAA,OAAA,kBAAA,CAAA,6FAKpCC,EAAAA,mBAEM,MAAA,CAFA,uBAAOf,EAAA,KAAc,CAAA,GACzBa,aAAQC,EAAA,OAAA,SAAA,CAAA"}
1
+ {"version":3,"file":"JPageContainer.vue.cjs","sources":["../../../../src/components/organisms/JPageContainer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport JBreadcrumb from '@/components/molecules/JBreadcrumb.vue'\nimport JTitlebar from '@/components/molecules/JTitlebar.vue'\nimport { cn } from '@/lib/utils'\nimport type { BreadcrumbItem } from '@/components/molecules/JBreadcrumb.vue'\nimport type { TitlebarButton } from '@/components/molecules/JTitlebar.vue'\n\n/**\n * JPageContainer - 기본 페이지 컨테이너 컴포넌트 (organisms)\n * Page Container Component\n * \n * @description\n * 페이지의 기본 레이아웃을 담당하는 컨테이너 컴포넌트입니다.\n * 브레드크럼, 제목 영역(JTitlebar), 콘텐츠 영역을 포함합니다.\n * \n * @example\n * ```vue\n * <JPageContainer\n * :breadcrumb-items=\"breadcrumbItems\"\n * title=\"페이지 제목\"\n * :titlebar-buttons=\"buttons\"\n * >\n * <div>페이지 콘텐츠</div>\n * </JPageContainer>\n * ```\n */\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'minimal' // 최소 스타일\n\nconst props = withDefaults(\n defineProps<{\n /** 브레드크럼 아이템 목록 */\n breadcrumbItems?: BreadcrumbItem[]\n /** 브레드크럼 표시 여부 */\n showBreadcrumb?: boolean\n /** JTitlebar의 모든 props 전달 */\n title?: string\n icon?: string\n description?: string\n titlebarButtons?: TitlebarButton[]\n titlebarStyletype?: 'default' | 'primary' | 'accent' | 'neutral' | 'elevated'\n /** JTitlebar 표시 여부 */\n showTitlebar?: boolean\n /** 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n breadcrumbItems: () => [],\n showBreadcrumb: true,\n showTitlebar: true,\n styletype: 'default',\n contentScroll: true,\n }\n)\n\nconst emit = defineEmits<{\n /** 브레드크럼 아이템 클릭 이벤트 */\n breadcrumbClick: [item: BreadcrumbItem, index: number]\n /** 타이틀바 버튼 클릭 이벤트 */\n titlebarButtonClick: [button: TitlebarButton]\n}>()\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 p-3 pt-2 pb-2 gap-2 bg-background text-foreground',\n },\n minimal: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 p-1 gap-1 bg-background text-foreground',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 콘텐츠 클래스 (스크롤 포함)\n */\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\n )\n})\n\n/**\n * 브레드크럼 아이템 클릭 핸들러\n */\nconst handleBreadcrumbClick = (item: BreadcrumbItem, index: number) => {\n emit('breadcrumbClick', item, index)\n}\n\n/**\n * 타이틀바 버튼 클릭 핸들러\n */\nconst handleTitlebarButtonClick = (button: TitlebarButton) => {\n emit('titlebarButtonClick', button)\n}\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 브레드크럼 -->\n <JBreadcrumb\n v-if=\"showBreadcrumb && breadcrumbItems && breadcrumbItems.length > 0\"\n :items=\"breadcrumbItems\"\n @item-click=\"handleBreadcrumbClick\"\n />\n\n <!-- 제목 영역 (JTitlebar) -->\n <JTitlebar\n v-if=\"showTitlebar\"\n :title=\"title\"\n :icon=\"icon\"\n :description=\"description\"\n :buttons=\"titlebarButtons\"\n :styletype=\"titlebarStyletype\"\n @button-click=\"handleTitlebarButtonClick\"\n >\n <template #buttons>\n <slot name=\"titlebar-buttons\" />\n </template>\n </JTitlebar>\n\n <!-- 콘텐츠 영역 -->\n <div :class=\"contentClasses\">\n <slot />\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","contentClasses","cn","handleBreadcrumbClick","item","index","handleTitlebarButtonClick","button","_createElementBlock","_normalizeClass","_unref","_createBlock","JBreadcrumb","JTitlebar","_renderSlot","_ctx","_createElementVNode"],"mappings":"6nBAgCA,MAAMA,EAAQC,EA8BRC,EAAOC,EAUPC,EAGD,CACH,QAAS,CACP,eAAgB,4CAChB,aAAc,0DAAA,EAEhB,QAAS,CACP,eAAgB,4CAChB,aAAc,gDAAA,CAChB,EAGIC,EAASC,EAAAA,SAAS,IACfF,EAAcJ,EAAM,SAAS,GAAKI,EAAc,OACxD,EAKKG,EAAiBD,EAAAA,SAAS,IACvBE,EAAAA,GACLH,EAAO,MAAM,aACbL,EAAM,cAAgB,gBAAkB,iBAAA,CAE3C,EAKKS,EAAwB,CAACC,EAAsBC,IAAkB,CACrET,EAAK,kBAAmBQ,EAAMC,CAAK,CACrC,EAKMC,EAA6BC,GAA2B,CAC5DX,EAAK,sBAAuBW,CAAM,CACpC,8BAIEC,EAAAA,mBA2BM,MAAA,CA3BA,MAAKC,EAAAA,eAAEC,EAAAA,YAAGX,EAAA,MAAO,eAAgBL,EAAM,KAAK,CAAA,CAAA,GAGxCC,EAAA,gBAAkBA,EAAA,iBAAmBA,EAAA,gBAAgB,OAAM,iBADnEgB,EAAAA,YAIEC,EAAAA,QAAA,OAFC,MAAOjB,EAAA,gBACP,YAAYQ,CAAA,iDAKPR,EAAA,4BADRgB,EAAAA,YAYYE,EAAAA,QAAA,OAVT,MAAOlB,EAAA,MACP,KAAMA,EAAA,KACN,YAAaA,EAAA,YACb,QAASA,EAAA,gBACT,UAAWA,EAAA,kBACX,cAAcW,CAAA,GAEJ,kBACT,IAAgC,CAAhCQ,aAAgCC,EAAA,OAAA,kBAAA,CAAA,6FAKpCC,EAAAA,mBAEM,MAAA,CAFA,uBAAOf,EAAA,KAAc,CAAA,GACzBa,aAAQC,EAAA,OAAA,SAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"JPageContainer.vue.js","sources":["../../../../src/components/organisms/JPageContainer.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport JBreadcrumb from '@/components/molecules/JBreadcrumb.vue'\r\nimport JTitlebar from '@/components/molecules/JTitlebar.vue'\r\nimport { cn } from '@/lib/utils'\r\nimport type { BreadcrumbItem } from '@/components/molecules/JBreadcrumb.vue'\r\nimport type { TitlebarButton } from '@/components/molecules/JTitlebar.vue'\r\n\r\n/**\r\n * JPageContainer - 기본 페이지 컨테이너 컴포넌트 (organisms)\r\n * Page Container Component\r\n * \r\n * @description\r\n * 페이지의 기본 레이아웃을 담당하는 컨테이너 컴포넌트입니다.\r\n * 브레드크럼, 제목 영역(JTitlebar), 콘텐츠 영역을 포함합니다.\r\n * \r\n * @example\r\n * ```vue\r\n * <JPageContainer\r\n * :breadcrumb-items=\"breadcrumbItems\"\r\n * title=\"페이지 제목\"\r\n * :titlebar-buttons=\"buttons\"\r\n * >\r\n * <div>페이지 콘텐츠</div>\r\n * </JPageContainer>\r\n * ```\r\n */\r\n\r\ntype StyleType =\r\n | 'default' // 기본 스타일\r\n | 'minimal' // 최소 스타일\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 브레드크럼 아이템 목록 */\r\n breadcrumbItems?: BreadcrumbItem[]\r\n /** 브레드크럼 표시 여부 */\r\n showBreadcrumb?: boolean\r\n /** JTitlebar의 모든 props 전달 */\r\n title?: string\r\n icon?: string\r\n description?: string\r\n titlebarButtons?: TitlebarButton[]\r\n titlebarStyletype?: 'default' | 'primary' | 'accent' | 'neutral' | 'elevated'\r\n /** JTitlebar 표시 여부 */\r\n showTitlebar?: boolean\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 콘텐츠 영역 스크롤 가능 여부 */\r\n contentScroll?: boolean\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n breadcrumbItems: () => [],\r\n showBreadcrumb: true,\r\n showTitlebar: true,\r\n styletype: 'default',\r\n contentScroll: true,\r\n }\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 브레드크럼 아이템 클릭 이벤트 */\r\n breadcrumbClick: [item: BreadcrumbItem, index: number]\r\n /** 타이틀바 버튼 클릭 이벤트 */\r\n titlebarButtonClick: [button: TitlebarButton]\r\n}>()\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n containerClass: string\r\n contentClass: string\r\n}> = {\r\n default: {\r\n containerClass: 'flex flex-col h-full w-full bg-background',\r\n contentClass: 'flex-1 p-3 pt-2 pb-2 gap-2 bg-background text-foreground',\r\n },\r\n minimal: {\r\n containerClass: 'flex flex-col h-full w-full bg-background',\r\n contentClass: 'flex-1 p-1 gap-1 bg-background text-foreground',\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * 콘텐츠 클래스 (스크롤 포함)\r\n */\r\nconst contentClasses = computed(() => {\r\n return cn(\r\n preset.value.contentClass,\r\n props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\r\n )\r\n})\r\n\r\n/**\r\n * 브레드크럼 아이템 클릭 핸들러\r\n */\r\nconst handleBreadcrumbClick = (item: BreadcrumbItem, index: number) => {\r\n emit('breadcrumbClick', item, index)\r\n}\r\n\r\n/**\r\n * 타이틀바 버튼 클릭 핸들러\r\n */\r\nconst handleTitlebarButtonClick = (button: TitlebarButton) => {\r\n emit('titlebarButtonClick', button)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div :class=\"cn(preset.containerClass, props.class)\">\r\n <!-- 브레드크럼 -->\r\n <JBreadcrumb\r\n v-if=\"showBreadcrumb && breadcrumbItems && breadcrumbItems.length > 0\"\r\n :items=\"breadcrumbItems\"\r\n @item-click=\"handleBreadcrumbClick\"\r\n />\r\n\r\n <!-- 제목 영역 (JTitlebar) -->\r\n <JTitlebar\r\n v-if=\"showTitlebar\"\r\n :title=\"title\"\r\n :icon=\"icon\"\r\n :description=\"description\"\r\n :buttons=\"titlebarButtons\"\r\n :styletype=\"titlebarStyletype\"\r\n @button-click=\"handleTitlebarButtonClick\"\r\n >\r\n <template #buttons>\r\n <slot name=\"titlebar-buttons\" />\r\n </template>\r\n </JTitlebar>\r\n\r\n <!-- 콘텐츠 영역 -->\r\n <div :class=\"contentClasses\">\r\n <slot />\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","contentClasses","cn","handleBreadcrumbClick","item","index","handleTitlebarButtonClick","button","_createElementBlock","_normalizeClass","_unref","_createBlock","JBreadcrumb","JTitlebar","_renderSlot","_ctx","_createElementVNode"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgCA,UAAMA,IAAQC,GA8BRC,IAAOC,GAUPC,IAGD;AAAA,MACH,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,MAEhB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,IAChB,GAGIC,IAASC,EAAS,MACfF,EAAcJ,EAAM,SAAS,KAAKI,EAAc,OACxD,GAKKG,IAAiBD,EAAS,MACvBE;AAAA,MACLH,EAAO,MAAM;AAAA,MACbL,EAAM,gBAAgB,kBAAkB;AAAA,IAAA,CAE3C,GAKKS,IAAwB,CAACC,GAAsBC,MAAkB;AACrE,MAAAT,EAAK,mBAAmBQ,GAAMC,CAAK;AAAA,IACrC,GAKMC,IAA4B,CAACC,MAA2B;AAC5D,MAAAX,EAAK,uBAAuBW,CAAM;AAAA,IACpC;2BAIEC,EA2BM,OAAA;AAAA,MA3BA,OAAKC,EAAEC,KAAGX,EAAA,MAAO,gBAAgBL,EAAM,KAAK,CAAA;AAAA,IAAA;MAGxCC,EAAA,kBAAkBA,EAAA,mBAAmBA,EAAA,gBAAgB,SAAM,UADnEgB,EAIEC,GAAA;AAAA;QAFC,OAAOjB,EAAA;AAAA,QACP,aAAYQ;AAAA,MAAA;MAKPR,EAAA,qBADRgB,EAYYE,GAAA;AAAA;QAVT,OAAOlB,EAAA;AAAA,QACP,MAAMA,EAAA;AAAA,QACN,aAAaA,EAAA;AAAA,QACb,SAASA,EAAA;AAAA,QACT,WAAWA,EAAA;AAAA,QACX,eAAcW;AAAA,MAAA;QAEJ,WACT,MAAgC;AAAA,UAAhCQ,EAAgCC,EAAA,QAAA,kBAAA;AAAA,QAAA;;;MAKpCC,EAEM,OAAA;AAAA,QAFA,SAAOf,EAAA,KAAc;AAAA,MAAA;QACzBa,EAAQC,EAAA,QAAA,SAAA;AAAA,MAAA;;;;"}
1
+ {"version":3,"file":"JPageContainer.vue.js","sources":["../../../../src/components/organisms/JPageContainer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport JBreadcrumb from '@/components/molecules/JBreadcrumb.vue'\nimport JTitlebar from '@/components/molecules/JTitlebar.vue'\nimport { cn } from '@/lib/utils'\nimport type { BreadcrumbItem } from '@/components/molecules/JBreadcrumb.vue'\nimport type { TitlebarButton } from '@/components/molecules/JTitlebar.vue'\n\n/**\n * JPageContainer - 기본 페이지 컨테이너 컴포넌트 (organisms)\n * Page Container Component\n * \n * @description\n * 페이지의 기본 레이아웃을 담당하는 컨테이너 컴포넌트입니다.\n * 브레드크럼, 제목 영역(JTitlebar), 콘텐츠 영역을 포함합니다.\n * \n * @example\n * ```vue\n * <JPageContainer\n * :breadcrumb-items=\"breadcrumbItems\"\n * title=\"페이지 제목\"\n * :titlebar-buttons=\"buttons\"\n * >\n * <div>페이지 콘텐츠</div>\n * </JPageContainer>\n * ```\n */\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'minimal' // 최소 스타일\n\nconst props = withDefaults(\n defineProps<{\n /** 브레드크럼 아이템 목록 */\n breadcrumbItems?: BreadcrumbItem[]\n /** 브레드크럼 표시 여부 */\n showBreadcrumb?: boolean\n /** JTitlebar의 모든 props 전달 */\n title?: string\n icon?: string\n description?: string\n titlebarButtons?: TitlebarButton[]\n titlebarStyletype?: 'default' | 'primary' | 'accent' | 'neutral' | 'elevated'\n /** JTitlebar 표시 여부 */\n showTitlebar?: boolean\n /** 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n breadcrumbItems: () => [],\n showBreadcrumb: true,\n showTitlebar: true,\n styletype: 'default',\n contentScroll: true,\n }\n)\n\nconst emit = defineEmits<{\n /** 브레드크럼 아이템 클릭 이벤트 */\n breadcrumbClick: [item: BreadcrumbItem, index: number]\n /** 타이틀바 버튼 클릭 이벤트 */\n titlebarButtonClick: [button: TitlebarButton]\n}>()\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 p-3 pt-2 pb-2 gap-2 bg-background text-foreground',\n },\n minimal: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 p-1 gap-1 bg-background text-foreground',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 콘텐츠 클래스 (스크롤 포함)\n */\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\n )\n})\n\n/**\n * 브레드크럼 아이템 클릭 핸들러\n */\nconst handleBreadcrumbClick = (item: BreadcrumbItem, index: number) => {\n emit('breadcrumbClick', item, index)\n}\n\n/**\n * 타이틀바 버튼 클릭 핸들러\n */\nconst handleTitlebarButtonClick = (button: TitlebarButton) => {\n emit('titlebarButtonClick', button)\n}\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 브레드크럼 -->\n <JBreadcrumb\n v-if=\"showBreadcrumb && breadcrumbItems && breadcrumbItems.length > 0\"\n :items=\"breadcrumbItems\"\n @item-click=\"handleBreadcrumbClick\"\n />\n\n <!-- 제목 영역 (JTitlebar) -->\n <JTitlebar\n v-if=\"showTitlebar\"\n :title=\"title\"\n :icon=\"icon\"\n :description=\"description\"\n :buttons=\"titlebarButtons\"\n :styletype=\"titlebarStyletype\"\n @button-click=\"handleTitlebarButtonClick\"\n >\n <template #buttons>\n <slot name=\"titlebar-buttons\" />\n </template>\n </JTitlebar>\n\n <!-- 콘텐츠 영역 -->\n <div :class=\"contentClasses\">\n <slot />\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","contentClasses","cn","handleBreadcrumbClick","item","index","handleTitlebarButtonClick","button","_createElementBlock","_normalizeClass","_unref","_createBlock","JBreadcrumb","JTitlebar","_renderSlot","_ctx","_createElementVNode"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgCA,UAAMA,IAAQC,GA8BRC,IAAOC,GAUPC,IAGD;AAAA,MACH,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,MAEhB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,IAChB,GAGIC,IAASC,EAAS,MACfF,EAAcJ,EAAM,SAAS,KAAKI,EAAc,OACxD,GAKKG,IAAiBD,EAAS,MACvBE;AAAA,MACLH,EAAO,MAAM;AAAA,MACbL,EAAM,gBAAgB,kBAAkB;AAAA,IAAA,CAE3C,GAKKS,IAAwB,CAACC,GAAsBC,MAAkB;AACrE,MAAAT,EAAK,mBAAmBQ,GAAMC,CAAK;AAAA,IACrC,GAKMC,IAA4B,CAACC,MAA2B;AAC5D,MAAAX,EAAK,uBAAuBW,CAAM;AAAA,IACpC;2BAIEC,EA2BM,OAAA;AAAA,MA3BA,OAAKC,EAAEC,KAAGX,EAAA,MAAO,gBAAgBL,EAAM,KAAK,CAAA;AAAA,IAAA;MAGxCC,EAAA,kBAAkBA,EAAA,mBAAmBA,EAAA,gBAAgB,SAAM,UADnEgB,EAIEC,GAAA;AAAA;QAFC,OAAOjB,EAAA;AAAA,QACP,aAAYQ;AAAA,MAAA;MAKPR,EAAA,qBADRgB,EAYYE,GAAA;AAAA;QAVT,OAAOlB,EAAA;AAAA,QACP,MAAMA,EAAA;AAAA,QACN,aAAaA,EAAA;AAAA,QACb,SAASA,EAAA;AAAA,QACT,WAAWA,EAAA;AAAA,QACX,eAAcW;AAAA,MAAA;QAEJ,WACT,MAAgC;AAAA,UAAhCQ,EAAgCC,EAAA,QAAA,kBAAA;AAAA,QAAA;;;MAKpCC,EAEM,OAAA;AAAA,QAFA,SAAOf,EAAA,KAAc;AAAA,MAAA;QACzBa,EAAQC,EAAA,QAAA,SAAA;AAAA,MAAA;;;;"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),C=require("../../../types/sidebar.types.cjs"),u=require("../../atoms/JIcon.vue.cjs"),S=require("../../atoms/JInput.vue.cjs"),F=require("./JSidebarGroup.vue.cjs"),h=require("./JSidebarItem.vue.cjs"),x=require("../../../lib/utils.cjs"),L={key:0,class:"p-2 flex-shrink-0"},I={class:"relative"},q={class:"flex-1 overflow-y-auto overflow-x-hidden px-1 py-1"},z={class:"text-[10px] text-yellow-500/60"},J={class:"overflow-hidden"},K={class:"flex justify-center py-1"},M={key:1,class:"text-center py-6 text-muted-foreground text-xs"},P={class:"flex-shrink-0 border-t border-border p-1"},O={key:0},T=e.defineComponent({__name:"JSidebar",props:{items:{},collapsed:{type:Boolean,default:!1},activePath:{},width:{default:"220px"},collapsedWidth:{default:"56px"},storageKey:{},showSearch:{type:Boolean,default:!0},showFavorites:{type:Boolean,default:!0}},emits:["update:collapsed","menu-click"],setup(n,{emit:_}){const r=n,k=_,a=e.ref(new Set(B()));function B(){if(!r.storageKey)return[];try{const t=localStorage.getItem(r.storageKey);return t?JSON.parse(t):[]}catch{return[]}}function w(){r.storageKey&&localStorage.setItem(r.storageKey,JSON.stringify([...a.value]))}function b(t){a.value.has(t)?a.value.delete(t):a.value.add(t),a.value=new Set(a.value),w()}const i=e.reactive({collapsed:r.collapsed,activePath:r.activePath,favorites:a.value,toggleFavorite:b});e.watch(()=>r.collapsed,t=>{i.collapsed=t}),e.watch(()=>r.activePath,t=>{i.activePath=t}),e.watch(a,t=>{i.favorites=t}),e.provide(C.SIDEBAR_INJECTION_KEY,i);const c=e.ref(""),y=e.computed(()=>{if(!r.storageKey||a.value.size===0)return[];const t=[],l=o=>{for(const s of o)s.menuType==="L"&&a.value.has(s.id)&&t.push(s),s.children&&l(s.children)};return l(r.items),t}),g=e.computed(()=>{const t=c.value.trim().toLowerCase();if(!t)return r.items;const l=o=>{const s=[];for(const p of o){const V=p.label.toLowerCase().includes(t),v=p.children?l(p.children):void 0;(V||v&&v.length>0)&&s.push({...p,children:v})}return s};return l(r.items)}),d=e.computed(()=>{const t=c.value.trim().toLowerCase();return t?y.value.filter(l=>l.label.toLowerCase().includes(t)):y.value}),m=e.ref(!0),f=(t,l)=>{k("menu-click",t,l)},E=()=>{k("update:collapsed",!r.collapsed)},N=e.computed(()=>r.collapsed?r.collapsedWidth:r.width);return(t,l)=>(e.openBlock(),e.createElementBlock("aside",{class:"h-full bg-background border-r border-border flex flex-col flex-shrink-0 overflow-hidden transition-[width] duration-200 ease-out",style:e.normalizeStyle({width:N.value})},[!n.collapsed&&n.showSearch?(e.openBlock(),e.createElementBlock("div",L,[e.createElementVNode("div",I,[e.createVNode(u.default,{name:"search",size:"sm",class:"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground"}),e.createVNode(S.default,{modelValue:c.value,"onUpdate:modelValue":l[0]||(l[0]=o=>c.value=o),placeholder:"메뉴 검색...",class:"pl-8 h-7 text-xs"},null,8,["modelValue"])])])):e.createCommentVNode("",!0),e.createElementVNode("nav",q,[n.showFavorites&&n.storageKey&&d.value.length>0?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[n.collapsed?(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createElementVNode("div",K,[e.createVNode(u.default,{name:"star",size:"sm",class:"text-yellow-500"})]),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(d.value,o=>(e.openBlock(),e.createBlock(h.default,{key:"fav-c-"+o.id,item:o,onMenuClick:f},null,8,["item"]))),128))],64)):(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createElementVNode("button",{class:"flex items-center gap-1.5 w-full px-2 py-1.5 text-xs font-semibold text-yellow-600 cursor-pointer select-none transition-colors hover:text-yellow-700",onClick:l[1]||(l[1]=o=>m.value=!m.value)},[e.createVNode(u.default,{name:"chevronRight",size:"sm",class:e.normalizeClass(e.unref(x.cn)("flex-shrink-0 transition-transform duration-200",m.value&&"rotate-90"))},null,8,["class"]),e.createVNode(u.default,{name:"star",size:"sm",class:"flex-shrink-0 text-yellow-500"}),l[2]||(l[2]=e.createElementVNode("span",{class:"flex-1 text-left"},"즐겨찾기",-1)),e.createElementVNode("span",z,e.toDisplayString(d.value.length),1)]),e.createElementVNode("div",{class:e.normalizeClass(e.unref(x.cn)("grid transition-[grid-template-rows] duration-200 ease-out",m.value?"grid-rows-[1fr]":"grid-rows-[0fr]"))},[e.createElementVNode("div",J,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(d.value,o=>(e.openBlock(),e.createBlock(h.default,{key:"fav-"+o.id,item:o,onMenuClick:f},null,8,["item"]))),128))])],2)],64)),l[3]||(l[3]=e.createElementVNode("div",{class:"h-px bg-border mx-2 my-1"},null,-1))],64)):e.createCommentVNode("",!0),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(g.value,o=>(e.openBlock(),e.createElementBlock(e.Fragment,{key:o.id},[o.menuType==="F"?(e.openBlock(),e.createBlock(F.default,{key:0,item:o,onMenuClick:f},null,8,["item"])):o.menuType==="L"?(e.openBlock(),e.createBlock(h.default,{key:1,item:o,onMenuClick:f},null,8,["item"])):e.createCommentVNode("",!0)],64))),128)),g.value.length===0&&c.value.trim()?(e.openBlock(),e.createElementBlock("div",M," 검색 결과가 없습니다. ")):e.createCommentVNode("",!0)]),e.createElementVNode("div",P,[e.createElementVNode("button",{class:"flex items-center gap-2 w-full px-2 py-1 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors",onClick:E},[e.createVNode(u.default,{name:n.collapsed?"panelLeftOpen":"panelLeftClose",size:"sm"},null,8,["name"]),n.collapsed?e.createCommentVNode("",!0):(e.openBlock(),e.createElementBlock("span",O,"접기"))])])],4))}});exports.default=T;
2
+ //# sourceMappingURL=JSidebar.vue.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JSidebar.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, provide, watch, reactive } from 'vue'\nimport type { SidebarMenuItem, SidebarState } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JInput from '@/components/atoms/JInput.vue'\nimport JSidebarGroup from './JSidebarGroup.vue'\nimport JSidebarItem from './JSidebarItem.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebar - 통합 사이드바 컴포넌트\n *\n * DB 트리 데이터 기반, provide/inject로 상태 공유.\n * collapsed 모드, 검색, 즐겨찾기(localStorage) 지원.\n * 라우터를 모름 — @menu-click으로 아이템을 올려보냄.\n */\n\nconst props = withDefaults(\n defineProps<{\n /** 메뉴 트리 데이터 */\n items: SidebarMenuItem[]\n /** 접힘 상태 (v-model:collapsed) */\n collapsed?: boolean\n /** 현재 활성 경로 */\n activePath?: string\n /** 펼침 너비 */\n width?: string\n /** 접힘 너비 */\n collapsedWidth?: string\n /** 즐겨찾기 localStorage 키 (없으면 즐겨찾기 비활성) */\n storageKey?: string\n /** 검색 표시 여부 */\n showSearch?: boolean\n /** 즐겨찾기 섹션 표시 여부 */\n showFavorites?: boolean\n }>(),\n {\n collapsed: false,\n width: '220px',\n collapsedWidth: '56px',\n showSearch: true,\n showFavorites: true,\n },\n)\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\n// ── 즐겨찾기 (localStorage) ──\nconst favorites = ref<Set<string>>(new Set(loadFavorites()))\n\nfunction loadFavorites(): string[] {\n if (!props.storageKey) return []\n try {\n const raw = localStorage.getItem(props.storageKey)\n return raw ? JSON.parse(raw) : []\n } catch {\n return []\n }\n}\n\nfunction saveFavorites() {\n if (!props.storageKey) return\n localStorage.setItem(props.storageKey, JSON.stringify([...favorites.value]))\n}\n\nfunction toggleFavorite(id: string) {\n if (favorites.value.has(id)) {\n favorites.value.delete(id)\n } else {\n favorites.value.add(id)\n }\n favorites.value = new Set(favorites.value) // trigger reactivity\n saveFavorites()\n}\n\n// ── provide/inject 상태 ──\nconst sidebarState = reactive<SidebarState>({\n collapsed: props.collapsed,\n activePath: props.activePath,\n favorites: favorites.value,\n toggleFavorite,\n})\n\n// props 변경 시 state 동기화\nwatch(() => props.collapsed, (v) => { sidebarState.collapsed = v })\nwatch(() => props.activePath, (v) => { sidebarState.activePath = v })\nwatch(favorites, (v) => { sidebarState.favorites = v })\n\nprovide(SIDEBAR_INJECTION_KEY, sidebarState)\n\n// ── 검색 ──\nconst searchQuery = ref('')\n\n/** 즐겨찾기 아이템 (L 타입만, 트리 평탄화) */\nconst favoriteItems = computed(() => {\n if (!props.storageKey || favorites.value.size === 0) return []\n const result: SidebarMenuItem[] = []\n const flatten = (items: SidebarMenuItem[]) => {\n for (const item of items) {\n if (item.menuType === 'L' && favorites.value.has(item.id)) {\n result.push(item)\n }\n if (item.children) flatten(item.children)\n }\n }\n flatten(props.items)\n return result\n})\n\n/** 검색 필터링 (재귀) */\nconst filteredItems = computed(() => {\n const q = searchQuery.value.trim().toLowerCase()\n if (!q) return props.items\n\n const filter = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\n const result: SidebarMenuItem[] = []\n for (const item of items) {\n const matchLabel = item.label.toLowerCase().includes(q)\n const filteredChildren = item.children ? filter(item.children) : undefined\n if (matchLabel || (filteredChildren && filteredChildren.length > 0)) {\n result.push({ ...item, children: filteredChildren })\n }\n }\n return result\n }\n return filter(props.items)\n})\n\n/** 즐겨찾기 검색 필터링 */\nconst filteredFavorites = computed(() => {\n const q = searchQuery.value.trim().toLowerCase()\n if (!q) return favoriteItems.value\n return favoriteItems.value.filter(item => item.label.toLowerCase().includes(q))\n})\n\n// ── 즐겨찾기 그룹 펼침 ──\nconst favoritesExpanded = ref(true)\n\n// ── 이벤트 핸들러 ──\nconst handleMenuClick = (item: SidebarMenuItem, event: MouseEvent) => {\n emit('menu-click', item, event)\n}\n\nconst toggleCollapsed = () => {\n emit('update:collapsed', !props.collapsed)\n}\n\n// ── 사이드바 너비 ──\nconst sidebarWidth = computed(() => props.collapsed ? props.collapsedWidth : props.width)\n</script>\n\n<template>\n <aside\n class=\"h-full bg-background border-r border-border flex flex-col flex-shrink-0 overflow-hidden transition-[width] duration-200 ease-out\"\n :style=\"{ width: sidebarWidth }\"\n >\n <!-- 검색바 (펼침 + showSearch) -->\n <div v-if=\"!collapsed && showSearch\" class=\"p-2 flex-shrink-0\">\n <div class=\"relative\">\n <JIcon\n name=\"search\"\n size=\"sm\"\n class=\"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <JInput\n v-model=\"searchQuery\"\n placeholder=\"메뉴 검색...\"\n class=\"pl-8 h-7 text-xs\"\n />\n </div>\n </div>\n\n <!-- 메뉴 영역 -->\n <nav class=\"flex-1 overflow-y-auto overflow-x-hidden px-1 py-1\">\n <!-- 즐겨찾기 섹션 -->\n <template v-if=\"showFavorites && storageKey && filteredFavorites.length > 0\">\n <!-- 펼침: 즐겨찾기 그룹 -->\n <template v-if=\"!collapsed\">\n <button\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 text-xs font-semibold text-yellow-600 cursor-pointer select-none transition-colors hover:text-yellow-700\"\n @click=\"favoritesExpanded = !favoritesExpanded\"\n >\n <JIcon\n name=\"chevronRight\"\n size=\"sm\"\n :class=\"cn(\n 'flex-shrink-0 transition-transform duration-200',\n favoritesExpanded && 'rotate-90'\n )\"\n />\n <JIcon name=\"star\" size=\"sm\" class=\"flex-shrink-0 text-yellow-500\" />\n <span class=\"flex-1 text-left\">즐겨찾기</span>\n <span class=\"text-[10px] text-yellow-500/60\">{{ filteredFavorites.length }}</span>\n </button>\n <div\n :class=\"cn(\n 'grid transition-[grid-template-rows] duration-200 ease-out',\n favoritesExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n )\"\n >\n <div class=\"overflow-hidden\">\n <JSidebarItem\n v-for=\"fav in filteredFavorites\"\n :key=\"'fav-' + fav.id\"\n :item=\"fav\"\n @menu-click=\"handleMenuClick\"\n />\n </div>\n </div>\n </template>\n <!-- 접힘: 별 아이콘 + 구분선 -->\n <template v-else>\n <div class=\"flex justify-center py-1\">\n <JIcon name=\"star\" size=\"sm\" class=\"text-yellow-500\" />\n </div>\n <JSidebarItem\n v-for=\"fav in filteredFavorites\"\n :key=\"'fav-c-' + fav.id\"\n :item=\"fav\"\n @menu-click=\"handleMenuClick\"\n />\n </template>\n <div class=\"h-px bg-border mx-2 my-1\" />\n </template>\n\n <!-- 메인 메뉴 -->\n <template v-for=\"item in filteredItems\" :key=\"item.id\">\n <JSidebarGroup\n v-if=\"item.menuType === 'F'\"\n :item=\"item\"\n @menu-click=\"handleMenuClick\"\n />\n <JSidebarItem\n v-else-if=\"item.menuType === 'L'\"\n :item=\"item\"\n @menu-click=\"handleMenuClick\"\n />\n </template>\n\n <!-- 검색 결과 없음 -->\n <div\n v-if=\"filteredItems.length === 0 && searchQuery.trim()\"\n class=\"text-center py-6 text-muted-foreground text-xs\"\n >\n 검색 결과가 없습니다.\n </div>\n </nav>\n\n <!-- 하단: collapse 토글 -->\n <div class=\"flex-shrink-0 border-t border-border p-1\">\n <button\n class=\"flex items-center gap-2 w-full px-2 py-1 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <JIcon\n :name=\"collapsed ? 'panelLeftOpen' : 'panelLeftClose'\"\n size=\"sm\"\n />\n <span v-if=\"!collapsed\">접기</span>\n </button>\n </div>\n </aside>\n</template>\n"],"names":["props","__props","emit","__emit","favorites","ref","loadFavorites","raw","saveFavorites","toggleFavorite","id","sidebarState","reactive","watch","v","provide","SIDEBAR_INJECTION_KEY","searchQuery","favoriteItems","computed","result","flatten","items","item","filteredItems","q","filter","matchLabel","filteredChildren","filteredFavorites","favoritesExpanded","handleMenuClick","event","toggleCollapsed","sidebarWidth","_createElementBlock","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","JIcon","JInput","$event","_hoisted_3","_Fragment","_hoisted_6","_renderList","fav","_createBlock","JSidebarItem","_cache","_unref","cn","_hoisted_4","_toDisplayString","_hoisted_5","JSidebarGroup","_hoisted_7","_hoisted_8"],"mappings":"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"}
@@ -0,0 +1,189 @@
1
+ import { defineComponent as T, ref as C, reactive as W, watch as S, provide as q, computed as w, createElementBlock as r, openBlock as s, normalizeStyle as D, createCommentVNode as m, createElementVNode as a, createVNode as d, Fragment as u, normalizeClass as B, unref as I, toDisplayString as R, renderList as _, createBlock as k } from "vue";
2
+ import { SIDEBAR_INJECTION_KEY as j } from "../../../types/sidebar.types.js";
3
+ import v from "../../atoms/JIcon.vue.js";
4
+ import A from "../../atoms/JInput.vue.js";
5
+ import Q from "./JSidebarGroup.vue.js";
6
+ import L from "./JSidebarItem.vue.js";
7
+ import { cn as N } from "../../../lib/utils.js";
8
+ const U = {
9
+ key: 0,
10
+ class: "p-2 flex-shrink-0"
11
+ }, Y = { class: "relative" }, G = { class: "flex-1 overflow-y-auto overflow-x-hidden px-1 py-1" }, H = { class: "text-[10px] text-yellow-500/60" }, X = { class: "overflow-hidden" }, Z = { class: "flex justify-center py-1" }, ee = {
12
+ key: 1,
13
+ class: "text-center py-6 text-muted-foreground text-xs"
14
+ }, te = { class: "flex-shrink-0 border-t border-border p-1" }, le = { key: 0 }, ue = /* @__PURE__ */ T({
15
+ __name: "JSidebar",
16
+ props: {
17
+ items: {},
18
+ collapsed: { type: Boolean, default: !1 },
19
+ activePath: {},
20
+ width: { default: "220px" },
21
+ collapsedWidth: { default: "56px" },
22
+ storageKey: {},
23
+ showSearch: { type: Boolean, default: !0 },
24
+ showFavorites: { type: Boolean, default: !0 }
25
+ },
26
+ emits: ["update:collapsed", "menu-click"],
27
+ setup(i, { emit: E }) {
28
+ const o = i, z = E, n = C(new Set(V()));
29
+ function V() {
30
+ if (!o.storageKey) return [];
31
+ try {
32
+ const e = localStorage.getItem(o.storageKey);
33
+ return e ? JSON.parse(e) : [];
34
+ } catch {
35
+ return [];
36
+ }
37
+ }
38
+ function M() {
39
+ o.storageKey && localStorage.setItem(o.storageKey, JSON.stringify([...n.value]));
40
+ }
41
+ function P(e) {
42
+ n.value.has(e) ? n.value.delete(e) : n.value.add(e), n.value = new Set(n.value), M();
43
+ }
44
+ const p = W({
45
+ collapsed: o.collapsed,
46
+ activePath: o.activePath,
47
+ favorites: n.value,
48
+ toggleFavorite: P
49
+ });
50
+ S(() => o.collapsed, (e) => {
51
+ p.collapsed = e;
52
+ }), S(() => o.activePath, (e) => {
53
+ p.activePath = e;
54
+ }), S(n, (e) => {
55
+ p.favorites = e;
56
+ }), q(j, p);
57
+ const f = C(""), F = w(() => {
58
+ if (!o.storageKey || n.value.size === 0) return [];
59
+ const e = [], t = (l) => {
60
+ for (const c of l)
61
+ c.menuType === "L" && n.value.has(c.id) && e.push(c), c.children && t(c.children);
62
+ };
63
+ return t(o.items), e;
64
+ }), K = w(() => {
65
+ const e = f.value.trim().toLowerCase();
66
+ if (!e) return o.items;
67
+ const t = (l) => {
68
+ const c = [];
69
+ for (const g of l) {
70
+ const O = g.label.toLowerCase().includes(e), b = g.children ? t(g.children) : void 0;
71
+ (O || b && b.length > 0) && c.push({ ...g, children: b });
72
+ }
73
+ return c;
74
+ };
75
+ return t(o.items);
76
+ }), h = w(() => {
77
+ const e = f.value.trim().toLowerCase();
78
+ return e ? F.value.filter((t) => t.label.toLowerCase().includes(e)) : F.value;
79
+ }), x = C(!0), y = (e, t) => {
80
+ z("menu-click", e, t);
81
+ }, $ = () => {
82
+ z("update:collapsed", !o.collapsed);
83
+ }, J = w(() => o.collapsed ? o.collapsedWidth : o.width);
84
+ return (e, t) => (s(), r("aside", {
85
+ class: "h-full bg-background border-r border-border flex flex-col flex-shrink-0 overflow-hidden transition-[width] duration-200 ease-out",
86
+ style: D({ width: J.value })
87
+ }, [
88
+ !i.collapsed && i.showSearch ? (s(), r("div", U, [
89
+ a("div", Y, [
90
+ d(v, {
91
+ name: "search",
92
+ size: "sm",
93
+ class: "absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground"
94
+ }),
95
+ d(A, {
96
+ modelValue: f.value,
97
+ "onUpdate:modelValue": t[0] || (t[0] = (l) => f.value = l),
98
+ placeholder: "메뉴 검색...",
99
+ class: "pl-8 h-7 text-xs"
100
+ }, null, 8, ["modelValue"])
101
+ ])
102
+ ])) : m("", !0),
103
+ a("nav", G, [
104
+ i.showFavorites && i.storageKey && h.value.length > 0 ? (s(), r(u, { key: 0 }, [
105
+ i.collapsed ? (s(), r(u, { key: 1 }, [
106
+ a("div", Z, [
107
+ d(v, {
108
+ name: "star",
109
+ size: "sm",
110
+ class: "text-yellow-500"
111
+ })
112
+ ]),
113
+ (s(!0), r(u, null, _(h.value, (l) => (s(), k(L, {
114
+ key: "fav-c-" + l.id,
115
+ item: l,
116
+ onMenuClick: y
117
+ }, null, 8, ["item"]))), 128))
118
+ ], 64)) : (s(), r(u, { key: 0 }, [
119
+ a("button", {
120
+ class: "flex items-center gap-1.5 w-full px-2 py-1.5 text-xs font-semibold text-yellow-600 cursor-pointer select-none transition-colors hover:text-yellow-700",
121
+ onClick: t[1] || (t[1] = (l) => x.value = !x.value)
122
+ }, [
123
+ d(v, {
124
+ name: "chevronRight",
125
+ size: "sm",
126
+ class: B(I(N)(
127
+ "flex-shrink-0 transition-transform duration-200",
128
+ x.value && "rotate-90"
129
+ ))
130
+ }, null, 8, ["class"]),
131
+ d(v, {
132
+ name: "star",
133
+ size: "sm",
134
+ class: "flex-shrink-0 text-yellow-500"
135
+ }),
136
+ t[2] || (t[2] = a("span", { class: "flex-1 text-left" }, "즐겨찾기", -1)),
137
+ a("span", H, R(h.value.length), 1)
138
+ ]),
139
+ a("div", {
140
+ class: B(I(N)(
141
+ "grid transition-[grid-template-rows] duration-200 ease-out",
142
+ x.value ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
143
+ ))
144
+ }, [
145
+ a("div", X, [
146
+ (s(!0), r(u, null, _(h.value, (l) => (s(), k(L, {
147
+ key: "fav-" + l.id,
148
+ item: l,
149
+ onMenuClick: y
150
+ }, null, 8, ["item"]))), 128))
151
+ ])
152
+ ], 2)
153
+ ], 64)),
154
+ t[3] || (t[3] = a("div", { class: "h-px bg-border mx-2 my-1" }, null, -1))
155
+ ], 64)) : m("", !0),
156
+ (s(!0), r(u, null, _(K.value, (l) => (s(), r(u, {
157
+ key: l.id
158
+ }, [
159
+ l.menuType === "F" ? (s(), k(Q, {
160
+ key: 0,
161
+ item: l,
162
+ onMenuClick: y
163
+ }, null, 8, ["item"])) : l.menuType === "L" ? (s(), k(L, {
164
+ key: 1,
165
+ item: l,
166
+ onMenuClick: y
167
+ }, null, 8, ["item"])) : m("", !0)
168
+ ], 64))), 128)),
169
+ K.value.length === 0 && f.value.trim() ? (s(), r("div", ee, " 검색 결과가 없습니다. ")) : m("", !0)
170
+ ]),
171
+ a("div", te, [
172
+ a("button", {
173
+ class: "flex items-center gap-2 w-full px-2 py-1 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors",
174
+ onClick: $
175
+ }, [
176
+ d(v, {
177
+ name: i.collapsed ? "panelLeftOpen" : "panelLeftClose",
178
+ size: "sm"
179
+ }, null, 8, ["name"]),
180
+ i.collapsed ? m("", !0) : (s(), r("span", le, "접기"))
181
+ ])
182
+ ])
183
+ ], 4));
184
+ }
185
+ });
186
+ export {
187
+ ue as default
188
+ };
189
+ //# sourceMappingURL=JSidebar.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JSidebar.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, provide, watch, reactive } from 'vue'\nimport type { SidebarMenuItem, SidebarState } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JInput from '@/components/atoms/JInput.vue'\nimport JSidebarGroup from './JSidebarGroup.vue'\nimport JSidebarItem from './JSidebarItem.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebar - 통합 사이드바 컴포넌트\n *\n * DB 트리 데이터 기반, provide/inject로 상태 공유.\n * collapsed 모드, 검색, 즐겨찾기(localStorage) 지원.\n * 라우터를 모름 — @menu-click으로 아이템을 올려보냄.\n */\n\nconst props = withDefaults(\n defineProps<{\n /** 메뉴 트리 데이터 */\n items: SidebarMenuItem[]\n /** 접힘 상태 (v-model:collapsed) */\n collapsed?: boolean\n /** 현재 활성 경로 */\n activePath?: string\n /** 펼침 너비 */\n width?: string\n /** 접힘 너비 */\n collapsedWidth?: string\n /** 즐겨찾기 localStorage 키 (없으면 즐겨찾기 비활성) */\n storageKey?: string\n /** 검색 표시 여부 */\n showSearch?: boolean\n /** 즐겨찾기 섹션 표시 여부 */\n showFavorites?: boolean\n }>(),\n {\n collapsed: false,\n width: '220px',\n collapsedWidth: '56px',\n showSearch: true,\n showFavorites: true,\n },\n)\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\n// ── 즐겨찾기 (localStorage) ──\nconst favorites = ref<Set<string>>(new Set(loadFavorites()))\n\nfunction loadFavorites(): string[] {\n if (!props.storageKey) return []\n try {\n const raw = localStorage.getItem(props.storageKey)\n return raw ? JSON.parse(raw) : []\n } catch {\n return []\n }\n}\n\nfunction saveFavorites() {\n if (!props.storageKey) return\n localStorage.setItem(props.storageKey, JSON.stringify([...favorites.value]))\n}\n\nfunction toggleFavorite(id: string) {\n if (favorites.value.has(id)) {\n favorites.value.delete(id)\n } else {\n favorites.value.add(id)\n }\n favorites.value = new Set(favorites.value) // trigger reactivity\n saveFavorites()\n}\n\n// ── provide/inject 상태 ──\nconst sidebarState = reactive<SidebarState>({\n collapsed: props.collapsed,\n activePath: props.activePath,\n favorites: favorites.value,\n toggleFavorite,\n})\n\n// props 변경 시 state 동기화\nwatch(() => props.collapsed, (v) => { sidebarState.collapsed = v })\nwatch(() => props.activePath, (v) => { sidebarState.activePath = v })\nwatch(favorites, (v) => { sidebarState.favorites = v })\n\nprovide(SIDEBAR_INJECTION_KEY, sidebarState)\n\n// ── 검색 ──\nconst searchQuery = ref('')\n\n/** 즐겨찾기 아이템 (L 타입만, 트리 평탄화) */\nconst favoriteItems = computed(() => {\n if (!props.storageKey || favorites.value.size === 0) return []\n const result: SidebarMenuItem[] = []\n const flatten = (items: SidebarMenuItem[]) => {\n for (const item of items) {\n if (item.menuType === 'L' && favorites.value.has(item.id)) {\n result.push(item)\n }\n if (item.children) flatten(item.children)\n }\n }\n flatten(props.items)\n return result\n})\n\n/** 검색 필터링 (재귀) */\nconst filteredItems = computed(() => {\n const q = searchQuery.value.trim().toLowerCase()\n if (!q) return props.items\n\n const filter = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\n const result: SidebarMenuItem[] = []\n for (const item of items) {\n const matchLabel = item.label.toLowerCase().includes(q)\n const filteredChildren = item.children ? filter(item.children) : undefined\n if (matchLabel || (filteredChildren && filteredChildren.length > 0)) {\n result.push({ ...item, children: filteredChildren })\n }\n }\n return result\n }\n return filter(props.items)\n})\n\n/** 즐겨찾기 검색 필터링 */\nconst filteredFavorites = computed(() => {\n const q = searchQuery.value.trim().toLowerCase()\n if (!q) return favoriteItems.value\n return favoriteItems.value.filter(item => item.label.toLowerCase().includes(q))\n})\n\n// ── 즐겨찾기 그룹 펼침 ──\nconst favoritesExpanded = ref(true)\n\n// ── 이벤트 핸들러 ──\nconst handleMenuClick = (item: SidebarMenuItem, event: MouseEvent) => {\n emit('menu-click', item, event)\n}\n\nconst toggleCollapsed = () => {\n emit('update:collapsed', !props.collapsed)\n}\n\n// ── 사이드바 너비 ──\nconst sidebarWidth = computed(() => props.collapsed ? props.collapsedWidth : props.width)\n</script>\n\n<template>\n <aside\n class=\"h-full bg-background border-r border-border flex flex-col flex-shrink-0 overflow-hidden transition-[width] duration-200 ease-out\"\n :style=\"{ width: sidebarWidth }\"\n >\n <!-- 검색바 (펼침 + showSearch) -->\n <div v-if=\"!collapsed && showSearch\" class=\"p-2 flex-shrink-0\">\n <div class=\"relative\">\n <JIcon\n name=\"search\"\n size=\"sm\"\n class=\"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <JInput\n v-model=\"searchQuery\"\n placeholder=\"메뉴 검색...\"\n class=\"pl-8 h-7 text-xs\"\n />\n </div>\n </div>\n\n <!-- 메뉴 영역 -->\n <nav class=\"flex-1 overflow-y-auto overflow-x-hidden px-1 py-1\">\n <!-- 즐겨찾기 섹션 -->\n <template v-if=\"showFavorites && storageKey && filteredFavorites.length > 0\">\n <!-- 펼침: 즐겨찾기 그룹 -->\n <template v-if=\"!collapsed\">\n <button\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 text-xs font-semibold text-yellow-600 cursor-pointer select-none transition-colors hover:text-yellow-700\"\n @click=\"favoritesExpanded = !favoritesExpanded\"\n >\n <JIcon\n name=\"chevronRight\"\n size=\"sm\"\n :class=\"cn(\n 'flex-shrink-0 transition-transform duration-200',\n favoritesExpanded && 'rotate-90'\n )\"\n />\n <JIcon name=\"star\" size=\"sm\" class=\"flex-shrink-0 text-yellow-500\" />\n <span class=\"flex-1 text-left\">즐겨찾기</span>\n <span class=\"text-[10px] text-yellow-500/60\">{{ filteredFavorites.length }}</span>\n </button>\n <div\n :class=\"cn(\n 'grid transition-[grid-template-rows] duration-200 ease-out',\n favoritesExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n )\"\n >\n <div class=\"overflow-hidden\">\n <JSidebarItem\n v-for=\"fav in filteredFavorites\"\n :key=\"'fav-' + fav.id\"\n :item=\"fav\"\n @menu-click=\"handleMenuClick\"\n />\n </div>\n </div>\n </template>\n <!-- 접힘: 별 아이콘 + 구분선 -->\n <template v-else>\n <div class=\"flex justify-center py-1\">\n <JIcon name=\"star\" size=\"sm\" class=\"text-yellow-500\" />\n </div>\n <JSidebarItem\n v-for=\"fav in filteredFavorites\"\n :key=\"'fav-c-' + fav.id\"\n :item=\"fav\"\n @menu-click=\"handleMenuClick\"\n />\n </template>\n <div class=\"h-px bg-border mx-2 my-1\" />\n </template>\n\n <!-- 메인 메뉴 -->\n <template v-for=\"item in filteredItems\" :key=\"item.id\">\n <JSidebarGroup\n v-if=\"item.menuType === 'F'\"\n :item=\"item\"\n @menu-click=\"handleMenuClick\"\n />\n <JSidebarItem\n v-else-if=\"item.menuType === 'L'\"\n :item=\"item\"\n @menu-click=\"handleMenuClick\"\n />\n </template>\n\n <!-- 검색 결과 없음 -->\n <div\n v-if=\"filteredItems.length === 0 && searchQuery.trim()\"\n class=\"text-center py-6 text-muted-foreground text-xs\"\n >\n 검색 결과가 없습니다.\n </div>\n </nav>\n\n <!-- 하단: collapse 토글 -->\n <div class=\"flex-shrink-0 border-t border-border p-1\">\n <button\n class=\"flex items-center gap-2 w-full px-2 py-1 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <JIcon\n :name=\"collapsed ? 'panelLeftOpen' : 'panelLeftClose'\"\n size=\"sm\"\n />\n <span v-if=\"!collapsed\">접기</span>\n </button>\n </div>\n </aside>\n</template>\n"],"names":["props","__props","emit","__emit","favorites","ref","loadFavorites","raw","saveFavorites","toggleFavorite","id","sidebarState","reactive","watch","v","provide","SIDEBAR_INJECTION_KEY","searchQuery","favoriteItems","computed","result","flatten","items","item","filteredItems","q","filter","matchLabel","filteredChildren","filteredFavorites","favoritesExpanded","handleMenuClick","event","toggleCollapsed","sidebarWidth","_createElementBlock","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","JIcon","JInput","$event","_hoisted_3","_Fragment","_hoisted_6","_renderList","fav","_createBlock","JSidebarItem","_cache","_unref","cn","_hoisted_4","_toDisplayString","_hoisted_5","JSidebarGroup","_hoisted_7","_hoisted_8"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./JSidebar.vue.cjs");exports.default=e.default;
2
+ //# sourceMappingURL=JSidebar.vue2.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JSidebar.vue2.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import f from "./JSidebar.vue.js";
2
+ export {
3
+ f as default
4
+ };
5
+ //# sourceMappingURL=JSidebar.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JSidebar.vue2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +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.5 mt-1 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
+ //# sourceMappingURL=JSidebarGroup.vue.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JSidebarGroup.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebarGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, inject, watch } from 'vue'\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JSidebarItem from './JSidebarItem.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebarGroup - 그룹 헤더 (menuType='F' 전용)\n * 폴더 타입 메뉴를 렌더링. 펼침/접힘 토글 + 하위 메뉴 재귀 렌더링.\n * 링크(L)는 JSidebarItem에서 처리.\n */\n\nconst props = defineProps<{\n item: SidebarMenuItem\n}>()\n\nconst emit = defineEmits<{\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\nconst state = inject(SIDEBAR_INJECTION_KEY)!\n\nconst isExpanded = ref(false)\n\n/** 직계 자식 중 L 타입 개수 (재귀하지 않음) */\nconst childCount = computed(() => {\n if (!props.item.children) return 0\n return props.item.children.filter(c => c.menuType === 'L').length\n})\n\nconst toggleExpand = () => {\n isExpanded.value = !isExpanded.value\n}\n\n/** 자식의 menu-click을 상위로 전파 */\nconst handleChildClick = (item: SidebarMenuItem, event: MouseEvent) => {\n emit('menu-click', item, event)\n}\n\n/** 현재 경로가 이 그룹의 자식에 포함되면 자동 펼침 */\nconst hasActiveChild = computed(() => {\n if (!state.activePath || !props.item.children) return false\n const checkActive = (items: SidebarMenuItem[]): boolean => {\n for (const child of items) {\n if (child.menuType === 'L' && child.path === state.activePath) return true\n if (child.children && checkActive(child.children)) return true\n }\n return false\n }\n return checkActive(props.item.children)\n})\n\nwatch(hasActiveChild, (active) => {\n if (active) isExpanded.value = true\n}, { immediate: true })\n</script>\n\n<template>\n <!-- Collapsed: 구분선만 -->\n <template v-if=\"state.collapsed\">\n <div class=\"h-px bg-border mx-2 my-1\" />\n <!-- collapsed 상태에서 자식 아이템 직접 렌더링 -->\n <template v-for=\"child in item.children\" :key=\"child.id\">\n <JSidebarItem\n v-if=\"child.menuType === 'L'\"\n :item=\"child\"\n @menu-click=\"handleChildClick\"\n />\n <!-- 중첩 폴더는 collapsed에서 무시 (1단계만) -->\n </template>\n </template>\n\n <!-- Expanded: 그룹 헤더 + 하위 메뉴 -->\n <div v-else>\n <!-- 그룹 헤더 -->\n <button\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 mt-1 text-xs font-semibold text-muted-foreground cursor-pointer select-none transition-colors hover:text-foreground\"\n @click=\"toggleExpand\"\n >\n <JIcon\n name=\"chevronRight\"\n size=\"sm\"\n :class=\"cn(\n 'flex-shrink-0 transition-transform duration-200',\n isExpanded && 'rotate-90'\n )\"\n />\n <span class=\"flex-1 truncate text-left\">{{ item.label }}</span>\n <span\n v-if=\"childCount > 0\"\n class=\"text-[10px] text-muted-foreground/60 flex-shrink-0\"\n >\n {{ childCount }}\n </span>\n </button>\n\n <!-- 하위 메뉴 (grid-template-rows 애니메이션) -->\n <div\n :class=\"cn(\n 'grid transition-[grid-template-rows] duration-200 ease-out',\n isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n )\"\n >\n <div class=\"overflow-hidden ml-3 pl-2 border-l border-border/50\">\n <template v-for=\"child in item.children\" :key=\"child.id\">\n <JSidebarItem\n v-if=\"child.menuType === 'L'\"\n :item=\"child\"\n @menu-click=\"handleChildClick\"\n />\n <!-- 중첩 폴더: 재귀 -->\n <JSidebarGroup\n v-else-if=\"child.menuType === 'F'\"\n :item=\"child\"\n @menu-click=\"handleChildClick\"\n />\n </template>\n </div>\n </div>\n </div>\n</template>\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,oDAA2E1B,EAAA,OAAU,WAAA,sBAK/FkB,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,+DAAkF1B,EAAA,MAAU,kBAAA,iBAAA,KAKpGkB,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"}