@j-solution/components 1.6.1 → 1.7.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.
- package/README.md +8 -6
- package/assets/jwms-portal-frontend-CwxPfHfa.css +1 -0
- package/assets/styles/j-components.css +1 -1
- package/assets/styles/themes.css +107 -0
- package/components/atoms/JAvatar.vue.cjs +1 -1
- package/components/atoms/JAvatar.vue.cjs.map +1 -1
- package/components/atoms/JAvatar.vue.js +10 -7
- package/components/atoms/JAvatar.vue.js.map +1 -1
- package/components/atoms/JBadge.vue.cjs +1 -1
- package/components/atoms/JBadge.vue.cjs.map +1 -1
- package/components/atoms/JBadge.vue.js +7 -6
- package/components/atoms/JBadge.vue.js.map +1 -1
- package/components/atoms/JButton.vue.cjs +1 -1
- package/components/atoms/JButton.vue.cjs.map +1 -1
- package/components/atoms/JButton.vue.js +5 -5
- package/components/atoms/JButton.vue.js.map +1 -1
- package/components/atoms/JDatepicker.vue.cjs +1 -1
- package/components/atoms/JDatepicker.vue.cjs.map +1 -1
- package/components/atoms/JDatepicker.vue.js +10 -10
- package/components/atoms/JDatepicker.vue.js.map +1 -1
- package/components/atoms/JEditor.vue.cjs +1 -1
- package/components/atoms/JEditor.vue.js +1 -1
- package/components/atoms/JEditor.vue2.cjs +1 -1
- package/components/atoms/JEditor.vue2.cjs.map +1 -1
- package/components/atoms/JEditor.vue2.js +31 -17
- package/components/atoms/JEditor.vue2.js.map +1 -1
- 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 +45 -33
- package/components/atoms/JGrid.vue2.js.map +1 -1
- package/components/atoms/JIcon.vue.cjs +1 -1
- package/components/atoms/JIcon.vue.cjs.map +1 -1
- package/components/atoms/JIcon.vue.js +14 -13
- package/components/atoms/JIcon.vue.js.map +1 -1
- package/components/atoms/JKbd.vue.cjs +1 -1
- package/components/atoms/JKbd.vue.cjs.map +1 -1
- package/components/atoms/JKbd.vue.js +13 -10
- package/components/atoms/JKbd.vue.js.map +1 -1
- package/components/atoms/JLabel.vue.cjs +1 -1
- package/components/atoms/JLabel.vue.cjs.map +1 -1
- package/components/atoms/JLabel.vue.js +4 -4
- package/components/atoms/JLabel.vue.js.map +1 -1
- package/components/atoms/JLink.vue.cjs +1 -1
- package/components/atoms/JLink.vue.cjs.map +1 -1
- package/components/atoms/JLink.vue.js +5 -5
- package/components/atoms/JLink.vue.js.map +1 -1
- package/components/atoms/JPreview.vue.cjs +1 -1
- package/components/atoms/JPreview.vue.js +2 -2
- package/components/atoms/JPreview.vue2.cjs +1 -1
- package/components/atoms/JPreview.vue2.cjs.map +1 -1
- package/components/atoms/JPreview.vue2.js +33 -20
- package/components/atoms/JPreview.vue2.js.map +1 -1
- package/components/atoms/JProgress.vue.cjs +1 -1
- package/components/atoms/JProgress.vue.cjs.map +1 -1
- package/components/atoms/JProgress.vue.js +15 -9
- package/components/atoms/JProgress.vue.js.map +1 -1
- package/components/atoms/JRadio.vue.cjs +1 -1
- package/components/atoms/JRadio.vue.cjs.map +1 -1
- package/components/atoms/JRadio.vue.js +1 -1
- package/components/atoms/JRadio.vue.js.map +1 -1
- package/components/atoms/JSearchCombo.vue.cjs +1 -1
- package/components/atoms/JSearchCombo.vue.cjs.map +1 -1
- package/components/atoms/JSearchCombo.vue.js +38 -37
- package/components/atoms/JSearchCombo.vue.js.map +1 -1
- package/components/atoms/JSpinner.vue.cjs +1 -1
- package/components/atoms/JSpinner.vue.cjs.map +1 -1
- package/components/atoms/JSpinner.vue.js +8 -7
- package/components/atoms/JSpinner.vue.js.map +1 -1
- package/components/atoms/JSplitter.vue.cjs +1 -1
- package/components/atoms/JSplitter.vue.cjs.map +1 -1
- package/components/atoms/JSplitter.vue.js +32 -27
- package/components/atoms/JSplitter.vue.js.map +1 -1
- package/components/atoms/JTooltip.vue.cjs +1 -1
- package/components/atoms/JTooltip.vue.cjs.map +1 -1
- package/components/atoms/JTooltip.vue.js +18 -15
- package/components/atoms/JTooltip.vue.js.map +1 -1
- package/components/examples/ExampleCrudPage.vue.cjs +1 -1
- package/components/examples/ExampleCrudPage.vue.cjs.map +1 -1
- package/components/examples/ExampleCrudPage.vue.js +162 -108
- package/components/examples/ExampleCrudPage.vue.js.map +1 -1
- package/components/examples/ExampleTabMappingPage.vue.cjs +1 -1
- package/components/examples/ExampleTabMappingPage.vue.cjs.map +1 -1
- package/components/examples/ExampleTabMappingPage.vue.js +162 -119
- package/components/examples/ExampleTabMappingPage.vue.js.map +1 -1
- package/components/molecules/JBreadcrumb.vue.cjs +1 -1
- package/components/molecules/JBreadcrumb.vue.cjs.map +1 -1
- package/components/molecules/JBreadcrumb.vue.js +3 -3
- package/components/molecules/JBreadcrumb.vue.js.map +1 -1
- package/components/molecules/JFormField.vue.cjs +1 -1
- package/components/molecules/JFormField.vue.cjs.map +1 -1
- package/components/molecules/JFormField.vue.js +26 -24
- package/components/molecules/JFormField.vue.js.map +1 -1
- package/components/molecules/JTabs.vue.cjs +1 -1
- package/components/molecules/JTabs.vue.js +1 -1
- package/components/molecules/JTabs.vue2.cjs +1 -1
- package/components/molecules/JTabs.vue2.cjs.map +1 -1
- package/components/molecules/JTabs.vue2.js +7 -7
- package/components/molecules/JTabs.vue2.js.map +1 -1
- package/components/molecules/JTitlebar.vue.cjs +1 -1
- package/components/molecules/JTitlebar.vue.cjs.map +1 -1
- package/components/molecules/JTitlebar.vue.js +35 -36
- package/components/molecules/JTitlebar.vue.js.map +1 -1
- package/components/organisms/JFilterBar.vue.cjs +1 -1
- package/components/organisms/JFilterBar.vue.cjs.map +1 -1
- package/components/organisms/JFilterBar.vue.js +5 -5
- package/components/organisms/JFilterBar.vue.js.map +1 -1
- package/components/organisms/JHeader.vue.cjs +1 -1
- package/components/organisms/JHeader.vue.cjs.map +1 -1
- package/components/organisms/JHeader.vue.js +25 -23
- package/components/organisms/JHeader.vue.js.map +1 -1
- package/components/organisms/JModal.vue.cjs +1 -1
- package/components/organisms/JModal.vue.cjs.map +1 -1
- package/components/organisms/JModal.vue.js +30 -27
- package/components/organisms/JModal.vue.js.map +1 -1
- package/components/organisms/JSidebarAdvanced.vue.cjs +1 -1
- package/components/organisms/JSidebarAdvanced.vue.js +7 -7
- package/components/organisms/JSidebarAdvanced.vue2.cjs +1 -1
- package/components/organisms/JSidebarAdvanced.vue2.cjs.map +1 -1
- package/components/organisms/JSidebarAdvanced.vue2.js +40 -40
- package/components/organisms/JSidebarAdvanced.vue2.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 +83 -63
- package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js.map +1 -1
- package/components/organisms/JSidebarSimple.vue.cjs +1 -1
- package/components/organisms/JSidebarSimple.vue.js +2 -2
- package/components/organisms/JSidebarSimple.vue2.cjs +1 -1
- package/components/organisms/JSidebarSimple.vue2.cjs.map +1 -1
- package/components/organisms/JSidebarSimple.vue2.js +2 -2
- package/components/organisms/JSidebarSimple.vue2.js.map +1 -1
- package/components/shadcn/AccordionTrigger.vue.cjs +1 -1
- package/components/shadcn/AccordionTrigger.vue.cjs.map +1 -1
- package/components/shadcn/AccordionTrigger.vue.js +3 -3
- package/components/shadcn/AccordionTrigger.vue.js.map +1 -1
- package/components/shadcn/CardContent.vue.cjs +1 -1
- package/components/shadcn/CardContent.vue.cjs.map +1 -1
- package/components/shadcn/CardContent.vue.js +1 -1
- package/components/shadcn/CardContent.vue.js.map +1 -1
- package/components/shadcn/CardDescription.vue.cjs +1 -1
- package/components/shadcn/CardDescription.vue.cjs.map +1 -1
- package/components/shadcn/CardDescription.vue.js +1 -1
- package/components/shadcn/CardDescription.vue.js.map +1 -1
- package/components/shadcn/CardFooter.vue.cjs +1 -1
- package/components/shadcn/CardFooter.vue.cjs.map +1 -1
- package/components/shadcn/CardFooter.vue.js +7 -7
- package/components/shadcn/CardFooter.vue.js.map +1 -1
- package/components/shadcn/CardHeader.vue.cjs +1 -1
- package/components/shadcn/CardHeader.vue.cjs.map +1 -1
- package/components/shadcn/CardHeader.vue.js +8 -8
- package/components/shadcn/CardHeader.vue.js.map +1 -1
- package/components/shadcn/CardTitle.vue.cjs +1 -1
- package/components/shadcn/CardTitle.vue.cjs.map +1 -1
- package/components/shadcn/CardTitle.vue.js +5 -5
- package/components/shadcn/CardTitle.vue.js.map +1 -1
- package/components/shadcn/Input.vue.cjs +1 -1
- package/components/shadcn/Input.vue.cjs.map +1 -1
- package/components/shadcn/Input.vue.js +1 -1
- package/components/shadcn/Input.vue.js.map +1 -1
- package/components/shadcn/SelectTrigger.vue.cjs +1 -1
- package/components/shadcn/SelectTrigger.vue.cjs.map +1 -1
- package/components/shadcn/SelectTrigger.vue.js +2 -2
- package/components/shadcn/SelectTrigger.vue.js.map +1 -1
- package/components/shadcn/Switch.vue.cjs +1 -1
- package/components/shadcn/Switch.vue.cjs.map +1 -1
- package/components/shadcn/Switch.vue.js +2 -2
- package/components/shadcn/Switch.vue.js.map +1 -1
- package/components/shadcn/TabsList.vue.cjs +1 -1
- package/components/shadcn/TabsList.vue.cjs.map +1 -1
- package/components/shadcn/TabsList.vue.js +1 -1
- package/components/shadcn/TabsList.vue.js.map +1 -1
- package/components/shadcn/TabsTrigger.vue.cjs +1 -1
- package/components/shadcn/TabsTrigger.vue.cjs.map +1 -1
- package/components/shadcn/TabsTrigger.vue.js +4 -4
- package/components/shadcn/TabsTrigger.vue.js.map +1 -1
- package/components/shadcn/Textarea.vue.cjs +1 -1
- package/components/shadcn/Textarea.vue.cjs.map +1 -1
- package/components/shadcn/Textarea.vue.js +2 -2
- package/components/shadcn/Textarea.vue.js.map +1 -1
- package/components/shadcn/index.cjs +1 -1
- package/components/shadcn/index.cjs.map +1 -1
- package/components/shadcn/index.js +8 -7
- package/components/shadcn/index.js.map +1 -1
- package/package.json +1 -1
- package/types/index.d.ts +131 -15
- package/assets/jwms-portal-frontend-DntSIcYt.css +0 -1
|
@@ -23,18 +23,18 @@ const _ = { class: "relative" }, ee = {
|
|
|
23
23
|
},
|
|
24
24
|
emits: ["menuClick", "favoriteChange"],
|
|
25
25
|
setup(d, { emit: q }) {
|
|
26
|
-
const
|
|
27
|
-
if (!Array.isArray(
|
|
26
|
+
const s = d, E = q, N = O(), h = A("menu"), l = A(""), T = y(() => N.path), p = A(/* @__PURE__ */ new Set()), x = y(() => {
|
|
27
|
+
if (!Array.isArray(s.favorites) || s.favorites.length === 0)
|
|
28
28
|
return [];
|
|
29
|
-
if (!Array.isArray(
|
|
29
|
+
if (!Array.isArray(s.menuItems) || s.menuItems.length === 0)
|
|
30
30
|
return [];
|
|
31
31
|
const e = (t) => {
|
|
32
32
|
const r = [];
|
|
33
33
|
if (!Array.isArray(t))
|
|
34
34
|
return r;
|
|
35
35
|
for (const n of t) {
|
|
36
|
-
const
|
|
37
|
-
if (Array.isArray(
|
|
36
|
+
const a = n.menuKey || n.label;
|
|
37
|
+
if (Array.isArray(s.favorites) && s.favorites.includes(a) && n.menuType === "L" && r.push({
|
|
38
38
|
...n,
|
|
39
39
|
children: void 0
|
|
40
40
|
// children 제거하여 1단계로만 표시
|
|
@@ -45,27 +45,27 @@ const _ = { class: "relative" }, ee = {
|
|
|
45
45
|
}
|
|
46
46
|
return r;
|
|
47
47
|
};
|
|
48
|
-
return e(
|
|
48
|
+
return e(s.menuItems);
|
|
49
49
|
}), k = y(() => {
|
|
50
|
-
if (!Array.isArray(
|
|
50
|
+
if (!Array.isArray(s.menuItems) || s.menuItems.length === 0)
|
|
51
51
|
return [];
|
|
52
52
|
if (!l.value || l.value.trim() === "")
|
|
53
|
-
return
|
|
53
|
+
return s.menuItems;
|
|
54
54
|
const e = l.value.toLowerCase().trim(), t = (r) => {
|
|
55
55
|
const n = [];
|
|
56
56
|
if (!Array.isArray(r))
|
|
57
57
|
return n;
|
|
58
|
-
for (const
|
|
59
|
-
const b =
|
|
58
|
+
for (const a of r) {
|
|
59
|
+
const b = a.label?.toLowerCase().includes(e) ?? !1;
|
|
60
60
|
let o;
|
|
61
|
-
|
|
62
|
-
...
|
|
61
|
+
a.children && Array.isArray(a.children) && a.children.length > 0 && (o = t(a.children)), (b || Array.isArray(o) && o.length > 0) && n.push({
|
|
62
|
+
...a,
|
|
63
63
|
children: o
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
return n;
|
|
67
67
|
};
|
|
68
|
-
return t(
|
|
68
|
+
return t(s.menuItems);
|
|
69
69
|
});
|
|
70
70
|
U(
|
|
71
71
|
() => k.value,
|
|
@@ -73,18 +73,18 @@ const _ = { class: "relative" }, ee = {
|
|
|
73
73
|
if (!l.value || l.value.trim() === "")
|
|
74
74
|
return;
|
|
75
75
|
((n) => {
|
|
76
|
-
const
|
|
76
|
+
const a = /* @__PURE__ */ new Set(), b = (o) => {
|
|
77
77
|
if (Array.isArray(o)) {
|
|
78
78
|
for (const v of o)
|
|
79
79
|
if (v.children && Array.isArray(v.children) && v.children.length > 0) {
|
|
80
80
|
const J = v.menuKey || v.label;
|
|
81
|
-
|
|
81
|
+
a.add(J), b(v.children);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
|
-
return b(n),
|
|
85
|
+
return b(n), a;
|
|
86
86
|
})(e).forEach((n) => {
|
|
87
|
-
|
|
87
|
+
p.value.add(n);
|
|
88
88
|
});
|
|
89
89
|
},
|
|
90
90
|
{ immediate: !1 }
|
|
@@ -99,43 +99,43 @@ const _ = { class: "relative" }, ee = {
|
|
|
99
99
|
(t) => t.label?.toLowerCase().includes(e) ?? !1
|
|
100
100
|
);
|
|
101
101
|
}), F = (e) => {
|
|
102
|
-
|
|
102
|
+
h.value !== e && (h.value = e, l.value = "");
|
|
103
103
|
}, L = (e, t) => {
|
|
104
|
-
e && (t ?
|
|
104
|
+
e && (t ? p.value.add(e) : p.value.delete(e));
|
|
105
105
|
}, M = (e) => {
|
|
106
106
|
E("menuClick", e);
|
|
107
107
|
}, R = (e) => {
|
|
108
108
|
if (!e) return;
|
|
109
|
-
const t =
|
|
109
|
+
const t = s.favorites?.includes(e) ?? !1;
|
|
110
110
|
E("favoriteChange", e, !t);
|
|
111
111
|
}, S = (e, t) => {
|
|
112
112
|
for (const r of e) {
|
|
113
113
|
if ((r.menuKey || r.label) === t)
|
|
114
114
|
return r;
|
|
115
115
|
if (r.children && r.children.length > 0) {
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
116
|
+
const a = S(r.children, t);
|
|
117
|
+
if (a) return a;
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
return null;
|
|
121
|
-
}, W = (e) => !e || !
|
|
121
|
+
}, W = (e) => !e || !s.favorites?.includes(e) ? !1 : S(s.menuItems, e)?.menuType === "L", V = {
|
|
122
122
|
default: {
|
|
123
123
|
containerClass: "h-full bg-background border-r border-border flex flex-col",
|
|
124
124
|
tabContainerClass: "flex border-b border-border",
|
|
125
|
-
tabButtonClass: "flex-1 px-
|
|
126
|
-
searchContainerClass: "p-
|
|
127
|
-
menuContainerClass: "flex-1 overflow-y-auto p-
|
|
125
|
+
tabButtonClass: "flex-1 px-3 py-1.5 text-xs font-medium transition-colors border-b-2 hover:bg-accent/50",
|
|
126
|
+
searchContainerClass: "p-1.5 border-b border-border",
|
|
127
|
+
menuContainerClass: "flex-1 overflow-y-auto p-1.5 space-y-0.5"
|
|
128
128
|
},
|
|
129
129
|
minimal: {
|
|
130
130
|
containerClass: "h-full bg-background border-r border-border flex flex-col",
|
|
131
|
-
tabContainerClass: "flex border-b border-border
|
|
132
|
-
tabButtonClass: "flex-1 px-2 py-
|
|
131
|
+
tabContainerClass: "flex border-b border-border",
|
|
132
|
+
tabButtonClass: "flex-1 px-2 py-1 text-xs font-medium transition-colors border-b-2",
|
|
133
133
|
searchContainerClass: "p-1 border-b border-border",
|
|
134
|
-
menuContainerClass: "flex-1 overflow-y-auto p-1 space-y-
|
|
134
|
+
menuContainerClass: "flex-1 overflow-y-auto p-1 space-y-0.5"
|
|
135
135
|
}
|
|
136
|
-
}, m = y(() => V[
|
|
136
|
+
}, m = y(() => V[s.styletype] ?? V.default), D = y(() => g(
|
|
137
137
|
m.value.containerClass,
|
|
138
|
-
|
|
138
|
+
s.class
|
|
139
139
|
));
|
|
140
140
|
return (e, t) => (i(), $(Y, { name: "slide" }, {
|
|
141
141
|
default: j(() => [
|
|
@@ -149,14 +149,14 @@ const _ = { class: "relative" }, ee = {
|
|
|
149
149
|
u("button", {
|
|
150
150
|
class: c(w(g)(
|
|
151
151
|
m.value.tabButtonClass,
|
|
152
|
-
|
|
152
|
+
h.value === "menu" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"
|
|
153
153
|
)),
|
|
154
154
|
onClick: t[0] || (t[0] = (r) => F("menu"))
|
|
155
155
|
}, " 기본메뉴 ", 2),
|
|
156
156
|
u("button", {
|
|
157
157
|
class: c(w(g)(
|
|
158
158
|
m.value.tabButtonClass,
|
|
159
|
-
|
|
159
|
+
h.value === "favorites" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"
|
|
160
160
|
)),
|
|
161
161
|
onClick: t[1] || (t[1] = (r) => F("favorites"))
|
|
162
162
|
}, " 즐겨찾기 ", 2)
|
|
@@ -176,7 +176,7 @@ const _ = { class: "relative" }, ee = {
|
|
|
176
176
|
placeholder: "메뉴 검색...",
|
|
177
177
|
class: c(w(g)(
|
|
178
178
|
"pl-8",
|
|
179
|
-
|
|
179
|
+
s.styletype === "minimal" && "h-8 text-xs"
|
|
180
180
|
))
|
|
181
181
|
}, null, 8, ["modelValue", "class"])
|
|
182
182
|
])
|
|
@@ -184,7 +184,7 @@ const _ = { class: "relative" }, ee = {
|
|
|
184
184
|
u("div", {
|
|
185
185
|
class: c(m.value.menuContainerClass)
|
|
186
186
|
}, [
|
|
187
|
-
|
|
187
|
+
h.value === "menu" ? (i(), f(C, { key: 0 }, [
|
|
188
188
|
k.value.length > 0 ? (i(!0), f(C, { key: 0 }, P(k.value, (r, n) => (i(), f("div", {
|
|
189
189
|
key: r.menuKey || r.label || n,
|
|
190
190
|
class: "flex items-center group"
|
|
@@ -194,7 +194,7 @@ const _ = { class: "relative" }, ee = {
|
|
|
194
194
|
level: 0,
|
|
195
195
|
permissions: d.permissions,
|
|
196
196
|
"active-path": T.value,
|
|
197
|
-
"expanded-keys":
|
|
197
|
+
"expanded-keys": p.value,
|
|
198
198
|
favorites: d.favorites,
|
|
199
199
|
"on-favorite-toggle": R,
|
|
200
200
|
"is-favorite": W,
|
|
@@ -204,7 +204,7 @@ const _ = { class: "relative" }, ee = {
|
|
|
204
204
|
onExpandChange: L
|
|
205
205
|
}, null, 8, ["item", "permissions", "active-path", "expanded-keys", "favorites", "styletype"])
|
|
206
206
|
]))), 128)) : (i(), f("div", ee, [...t[3] || (t[3] = [
|
|
207
|
-
u("p",
|
|
207
|
+
u("p", { class: "text-xs" }, "검색 결과가 없습니다.", -1)
|
|
208
208
|
])]))
|
|
209
209
|
], 64)) : (i(), f(C, { key: 1 }, [
|
|
210
210
|
B.value.length > 0 ? (i(!0), f(C, { key: 0 }, P(B.value, (r, n) => (i(), $(z, {
|
|
@@ -213,17 +213,17 @@ const _ = { class: "relative" }, ee = {
|
|
|
213
213
|
level: 0,
|
|
214
214
|
permissions: d.permissions,
|
|
215
215
|
"active-path": T.value,
|
|
216
|
-
"expanded-keys":
|
|
216
|
+
"expanded-keys": p.value,
|
|
217
217
|
styletype: d.styletype,
|
|
218
218
|
onMenuClick: M,
|
|
219
219
|
onExpandChange: L
|
|
220
220
|
}, null, 8, ["item", "permissions", "active-path", "expanded-keys", "styletype"]))), 128)) : (i(), f("div", te, [...t[4] || (t[4] = [
|
|
221
|
-
u("p",
|
|
221
|
+
u("p", { class: "text-xs" }, "즐겨찾기가 없습니다.", -1)
|
|
222
222
|
])]))
|
|
223
223
|
], 64))
|
|
224
224
|
], 2)
|
|
225
225
|
], 6), [
|
|
226
|
-
[K,
|
|
226
|
+
[K, s.isVisible]
|
|
227
227
|
])
|
|
228
228
|
]),
|
|
229
229
|
_: 1
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebarAdvanced.vue2.js","sources":["../../../../src/components/organisms/JSidebarAdvanced.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from 'vue'\r\nimport { useRoute } from 'vue-router'\r\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\r\nimport JDynamicMenuItem from './JSidebarSimple/JDynamicMenuItem.vue'\r\nimport JInput from '@/components/atoms/JInput.vue'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarAdvanced - 고급 사이드바 컴포넌트\r\n * Advanced Sidebar Component\r\n * \r\n * @description\r\n * 검색, 즐겨찾기, 다단계 메뉴를 지원하는 고급 사이드바 컴포넌트입니다.\r\n * 기본 메뉴와 즐겨찾기 탭을 제공합니다.\r\n * \r\n * @example\r\n * ```vue\r\n * <JSidebarAdvanced\r\n * :menu-items=\"menuItems\"\r\n * :permissions=\"userPermissions\"\r\n * :favorites=\"favoriteMenuKeys\"\r\n * @menu-click=\"handleMenuClick\"\r\n * @favorite-change=\"handleFavoriteChange\"\r\n * />\r\n * ```\r\n * \r\n * @example JSON 메뉴 데이터 예시\r\n * ```json\r\n * [\r\n * {\r\n * \"label\": \"대시보드\",\r\n * \"icon\": \"house\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 1,\r\n * \"path\": \"/dashboard\"\r\n * },\r\n * {\r\n * \"label\": \"재고 관리\",\r\n * \"icon\": \"package\",\r\n * \"menuType\": \"F\",\r\n * \"menuKey\": 2,\r\n * \"children\": [\r\n * {\r\n * \"label\": \"재고 현황\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 21,\r\n * \"path\": \"/inventory/status\"\r\n * },\r\n * {\r\n * \"label\": \"입고 관리\",\r\n * \"menuType\": \"F\",\r\n * \"menuKey\": 22,\r\n * \"children\": [\r\n * {\r\n * \"label\": \"입고 등록\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 221,\r\n * \"path\": \"/inventory/receiving/register\"\r\n * }\r\n * ]\r\n * }\r\n * ]\r\n * }\r\n * ]\r\n * ```\r\n */\r\n\r\ntype TabType = 'menu' | 'favorites'\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 아이템 목록 */\r\n menuItems: SidebarMenuItem[]\r\n /** 권한 목록 */\r\n permissions?: MenuPermission[]\r\n /** 즐겨찾기 메뉴 키 목록 */\r\n favorites?: (number | string)[]\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n /** 너비 */\r\n width?: string\r\n /** 표시 여부 */\r\n isVisible?: boolean\r\n }>(),\r\n {\r\n permissions: () => [],\r\n favorites: () => [],\r\n styletype: 'minimal',\r\n width: '280px',\r\n isVisible: true,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n /** 즐겨찾기 변경 이벤트 */\r\n favoriteChange: [menuKey: number | string | undefined, isFavorite: boolean]\r\n}>()\r\n\r\n// vue-router가 설정되지 않은 경우를 대비 (Storybook에서 router가 제공됨)\r\nconst route = useRoute()\r\n\r\n/**\r\n * 현재 활성 탭\r\n */\r\nconst activeTab = ref<TabType>('menu')\r\n\r\n/**\r\n * 검색어\r\n */\r\nconst searchQuery = ref('')\r\n\r\n/**\r\n * 현재 활성화된 경로\r\n */\r\nconst activePath = computed(() => route.path)\r\n\r\n/**\r\n * 확장된 메뉴 키 목록\r\n */\r\nconst expandedKeys = ref<Set<number | string>>(new Set())\r\n\r\n/**\r\n * 즐겨찾기 메뉴 아이템 목록\r\n * 즐겨찾기는 dept 없이 1단계로만 평탄화하여 표시\r\n */\r\nconst favoriteMenuItems = computed(() => {\r\n if (!Array.isArray(props.favorites) || props.favorites.length === 0) {\r\n return []\r\n }\r\n\r\n if (!Array.isArray(props.menuItems) || props.menuItems.length === 0) {\r\n return []\r\n }\r\n\r\n /**\r\n * 메뉴 아이템을 재귀적으로 순회하며 즐겨찾기만 추출\r\n * 즐겨찾기에서는 dept 없이 1단계로만 평탄화\r\n * menuType L(Link)만 포함하고 F(Folder)는 제외\r\n */\r\n const flattenFavorites = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\r\n const result: SidebarMenuItem[] = []\r\n\r\n if (!Array.isArray(items)) {\r\n return result\r\n }\r\n\r\n for (const item of items) {\r\n const key = item.menuKey || item.label\r\n const isFavorite = Array.isArray(props.favorites) && props.favorites.includes(key)\r\n\r\n // 즐겨찾기이고 menuType이 L(Link)인 경우만 추가 (F는 제외)\r\n if (isFavorite && item.menuType === 'L') {\r\n result.push({\r\n ...item,\r\n children: undefined, // children 제거하여 1단계로만 표시\r\n })\r\n }\r\n\r\n // 하위 메뉴도 재귀적으로 탐색\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n const childFavorites = flattenFavorites(item.children)\r\n result.push(...childFavorites)\r\n }\r\n }\r\n\r\n return result\r\n }\r\n\r\n return flattenFavorites(props.menuItems)\r\n})\r\n\r\n/**\r\n * 검색어로 필터링된 메뉴 아이템\r\n * 재귀적으로 children까지 검색\r\n */\r\nconst filteredMenuItems = computed(() => {\r\n if (!Array.isArray(props.menuItems) || props.menuItems.length === 0) {\r\n return []\r\n }\r\n\r\n if (!searchQuery.value || searchQuery.value.trim() === '') {\r\n return props.menuItems\r\n }\r\n\r\n const query = searchQuery.value.toLowerCase().trim()\r\n\r\n /**\r\n * 메뉴 아이템을 재귀적으로 검색\r\n */\r\n const searchInMenu = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\r\n const result: SidebarMenuItem[] = []\r\n\r\n if (!Array.isArray(items)) {\r\n return result\r\n }\r\n\r\n for (const item of items) {\r\n const matchesLabel = item.label?.toLowerCase().includes(query) ?? false\r\n\r\n // 하위 메뉴 검색\r\n let filteredChildren: SidebarMenuItem[] | undefined = undefined\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n filteredChildren = searchInMenu(item.children)\r\n }\r\n\r\n // 현재 메뉴나 하위 메뉴 중 하나라도 매칭되면 포함\r\n if (matchesLabel || (Array.isArray(filteredChildren) && filteredChildren.length > 0)) {\r\n result.push({\r\n ...item,\r\n children: filteredChildren,\r\n })\r\n }\r\n }\r\n\r\n return result\r\n }\r\n\r\n return searchInMenu(props.menuItems)\r\n})\r\n\r\n/**\r\n * 검색 결과에 따라 부모 메뉴 자동 확장\r\n * computed 외부에서 watch를 통해 처리\r\n */\r\nwatch(\r\n () => filteredMenuItems.value,\r\n (filtered) => {\r\n if (!searchQuery.value || searchQuery.value.trim() === '') {\r\n return\r\n }\r\n\r\n // 검색 결과에서 매칭된 하위 메뉴가 있는 부모를 찾아 확장\r\n const findParentsWithMatches = (items: SidebarMenuItem[]): Set<number | string> => {\r\n const keysToExpand = new Set<number | string>()\r\n\r\n const traverse = (menuItems: SidebarMenuItem[]): void => {\r\n if (!Array.isArray(menuItems)) {\r\n return\r\n }\r\n\r\n for (const item of menuItems) {\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n const key = item.menuKey || item.label\r\n keysToExpand.add(key)\r\n traverse(item.children)\r\n }\r\n }\r\n }\r\n\r\n traverse(items)\r\n return keysToExpand\r\n }\r\n\r\n const keysToExpand = findParentsWithMatches(filtered)\r\n keysToExpand.forEach(key => {\r\n expandedKeys.value.add(key)\r\n })\r\n },\r\n { immediate: false }\r\n)\r\n\r\n/**\r\n * 검색어로 필터링된 즐겨찾기 메뉴 아이템\r\n * 즐겨찾기는 이미 평탄화되어 있으므로 단순 필터링만 수행\r\n */\r\nconst filteredFavoriteItems = computed(() => {\r\n if (!Array.isArray(favoriteMenuItems.value) || favoriteMenuItems.value.length === 0) {\r\n return []\r\n }\r\n\r\n if (!searchQuery.value || searchQuery.value.trim() === '') {\r\n return favoriteMenuItems.value\r\n }\r\n\r\n const query = searchQuery.value.toLowerCase().trim()\r\n\r\n // 즐겨찾기는 이미 평탄화되어 1단계이므로 단순 필터링만 수행\r\n return favoriteMenuItems.value.filter((item) =>\r\n item.label?.toLowerCase().includes(query) ?? false\r\n )\r\n})\r\n\r\n\r\n/**\r\n * 탭 변경 핸들러\r\n * 탭 전환 시 검색 쿼리를 초기화하여 각 탭의 독립적인 검색 상태 유지\r\n */\r\nconst handleTabChange = (tab: TabType) => {\r\n if (activeTab.value !== tab) {\r\n activeTab.value = tab\r\n // 탭 전환 시 검색 쿼리 초기화 (선택적 - UX 고려)\r\n // 검색 쿼리를 유지하려면 아래 라인을 제거하세요\r\n searchQuery.value = ''\r\n }\r\n}\r\n\r\n/**\r\n * 확장 상태 변경 핸들러\r\n */\r\nconst handleExpandChange = (menuKey: number | string | undefined, expanded: boolean) => {\r\n if (!menuKey) return\r\n\r\n if (expanded) {\r\n expandedKeys.value.add(menuKey)\r\n } else {\r\n expandedKeys.value.delete(menuKey)\r\n }\r\n}\r\n\r\n/**\r\n * 메뉴 클릭 핸들러\r\n */\r\nconst handleMenuClick = (event: MenuClickEvent) => {\r\n emit('menuClick', event)\r\n}\r\n\r\n/**\r\n * 즐겨찾기 토글 핸들러\r\n */\r\nconst handleFavoriteToggle = (menuKey: number | string | undefined) => {\r\n if (!menuKey) return\r\n\r\n const isFavorite = props.favorites?.includes(menuKey) ?? false\r\n emit('favoriteChange', menuKey, !isFavorite)\r\n}\r\n\r\n/**\r\n * 메뉴 아이템에서 특정 menuKey를 찾는 헬퍼 함수\r\n */\r\nconst findMenuItemByKey = (items: SidebarMenuItem[], targetKey: number | string): SidebarMenuItem | null => {\r\n for (const item of items) {\r\n const key = item.menuKey || item.label\r\n if (key === targetKey) {\r\n return item\r\n }\r\n if (item.children && item.children.length > 0) {\r\n const found = findMenuItemByKey(item.children, targetKey)\r\n if (found) return found\r\n }\r\n }\r\n return null\r\n}\r\n\r\n/**\r\n * 메뉴가 즐겨찾기인지 확인 (L 타입만 즐겨찾기 가능)\r\n */\r\nconst isFavorite = (menuKey: number | string | undefined): boolean => {\r\n if (!menuKey) return false\r\n if (!props.favorites?.includes(menuKey)) return false\r\n \r\n // menuType이 L인 경우만 즐겨찾기로 인정\r\n const menuItem = findMenuItemByKey(props.menuItems, menuKey)\r\n return menuItem?.menuType === 'L'\r\n}\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n containerClass: string\r\n tabContainerClass: string\r\n tabButtonClass: string\r\n searchContainerClass: string\r\n menuContainerClass: string\r\n}> = {\r\n default: {\r\n containerClass: 'h-full bg-background border-r border-border flex flex-col',\r\n tabContainerClass: 'flex border-b border-border',\r\n tabButtonClass: 'flex-1 px-4 py-2 text-sm font-medium transition-colors border-b-2 hover:bg-accent/50',\r\n searchContainerClass: 'p-2 border-b border-border',\r\n menuContainerClass: 'flex-1 overflow-y-auto p-2 space-y-1',\r\n },\r\n minimal: {\r\n containerClass: 'h-full bg-background border-r border-border flex flex-col',\r\n tabContainerClass: 'flex border-b border-border pt-[8px]',\r\n tabButtonClass: 'flex-1 px-2 py-[6.5px] text-xs font-medium transition-colors border-b-2',\r\n searchContainerClass: 'p-1 border-b border-border',\r\n menuContainerClass: 'flex-1 overflow-y-auto p-1 space-y-1',\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * 루트 클래스\r\n */\r\nconst rootClasses = computed(() => {\r\n return cn(\r\n preset.value.containerClass,\r\n props.class\r\n )\r\n})\r\n</script>\r\n\r\n<template>\r\n <Transition name=\"slide\">\r\n <aside v-show=\"props.isVisible\" :class=\"rootClasses\" :style=\"{ width }\">\r\n <!-- 탭 헤더 -->\r\n <div :class=\"preset.tabContainerClass\">\r\n <button\r\n :class=\"cn(\r\n preset.tabButtonClass,\r\n activeTab === 'menu'\r\n ? 'border-primary text-primary'\r\n : 'border-transparent text-muted-foreground hover:text-foreground'\r\n )\"\r\n @click=\"handleTabChange('menu')\"\r\n >\r\n 기본메뉴\r\n </button>\r\n <button\r\n :class=\"cn(\r\n preset.tabButtonClass,\r\n activeTab === 'favorites'\r\n ? 'border-primary text-primary'\r\n : 'border-transparent text-muted-foreground hover:text-foreground'\r\n )\"\r\n @click=\"handleTabChange('favorites')\"\r\n >\r\n 즐겨찾기\r\n </button>\r\n </div>\r\n\r\n <!-- 검색 영역 -->\r\n <div :class=\"preset.searchContainerClass\">\r\n <div class=\"relative\">\r\n <JIcon\r\n name=\"search\"\r\n size=\"sm\"\r\n class=\"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground\"\r\n />\r\n <JInput\r\n v-model=\"searchQuery\"\r\n placeholder=\"메뉴 검색...\"\r\n :class=\"cn(\r\n 'pl-8',\r\n props.styletype === 'minimal' && 'h-8 text-xs'\r\n )\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <!-- 메뉴 목록 -->\r\n <div :class=\"preset.menuContainerClass\">\r\n <template v-if=\"activeTab === 'menu'\">\r\n <template v-if=\"filteredMenuItems.length > 0\">\r\n <div\r\n v-for=\"(item, index) in filteredMenuItems\"\r\n :key=\"item.menuKey || item.label || index\"\r\n class=\"flex items-center group\"\r\n >\r\n <JDynamicMenuItem\r\n :item=\"item\"\r\n :level=\"0\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :favorites=\"favorites\"\r\n :on-favorite-toggle=\"handleFavoriteToggle\"\r\n :is-favorite=\"isFavorite\"\r\n :styletype=\"styletype\"\r\n class=\"flex-1\"\r\n @menu-click=\"handleMenuClick\"\r\n @expand-change=\"handleExpandChange\"\r\n />\r\n </div>\r\n </template>\r\n <div v-else class=\"text-center py-8 text-muted-foreground\">\r\n <p>검색 결과가 없습니다.</p>\r\n </div>\r\n </template>\r\n\r\n <template v-else>\r\n <template v-if=\"filteredFavoriteItems.length > 0\">\r\n <JDynamicMenuItem\r\n v-for=\"(item, index) in filteredFavoriteItems\"\r\n :key=\"item.menuKey || item.label || index\"\r\n :item=\"item\"\r\n :level=\"0\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :styletype=\"styletype\"\r\n @menu-click=\"handleMenuClick\"\r\n @expand-change=\"handleExpandChange\"\r\n />\r\n </template>\r\n <div v-else class=\"text-center py-8 text-muted-foreground\">\r\n <p>즐겨찾기가 없습니다.</p>\r\n </div>\r\n </template>\r\n </div>\r\n </aside>\r\n </Transition>\r\n</template>\r\n\r\n<style scoped>\r\n.slide-enter-active,\r\n.slide-leave-active {\r\n transition: transform 0.3s ease, opacity 0.3s ease;\r\n}\r\n\r\n.slide-enter-from,\r\n.slide-leave-to {\r\n transform: translateX(-100%);\r\n opacity: 0;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","route","useRoute","activeTab","ref","searchQuery","activePath","computed","expandedKeys","favoriteMenuItems","flattenFavorites","items","result","item","key","childFavorites","filteredMenuItems","query","searchInMenu","matchesLabel","filteredChildren","watch","filtered","keysToExpand","traverse","menuItems","filteredFavoriteItems","handleTabChange","tab","handleExpandChange","menuKey","expanded","handleMenuClick","event","handleFavoriteToggle","isFavorite","findMenuItemByKey","targetKey","found","STYLE_PRESETS","preset","rootClasses","cn","_createBlock","_Transition","_createElementVNode","_normalizeClass","_unref","_hoisted_1","_createVNode","JIcon","JInput","$event","_createElementBlock","_Fragment","_openBlock","_renderList","index","JDynamicMenuItem","_hoisted_2","_cache","_hoisted_3","_vShow"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"JSidebarAdvanced.vue2.js","sources":["../../../../src/components/organisms/JSidebarAdvanced.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from 'vue'\r\nimport { useRoute } from 'vue-router'\r\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\r\nimport JDynamicMenuItem from './JSidebarSimple/JDynamicMenuItem.vue'\r\nimport JInput from '@/components/atoms/JInput.vue'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarAdvanced - 고급 사이드바 컴포넌트\r\n * Advanced Sidebar Component\r\n * \r\n * @description\r\n * 검색, 즐겨찾기, 다단계 메뉴를 지원하는 고급 사이드바 컴포넌트입니다.\r\n * 기본 메뉴와 즐겨찾기 탭을 제공합니다.\r\n * \r\n * @example\r\n * ```vue\r\n * <JSidebarAdvanced\r\n * :menu-items=\"menuItems\"\r\n * :permissions=\"userPermissions\"\r\n * :favorites=\"favoriteMenuKeys\"\r\n * @menu-click=\"handleMenuClick\"\r\n * @favorite-change=\"handleFavoriteChange\"\r\n * />\r\n * ```\r\n * \r\n * @example JSON 메뉴 데이터 예시\r\n * ```json\r\n * [\r\n * {\r\n * \"label\": \"대시보드\",\r\n * \"icon\": \"house\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 1,\r\n * \"path\": \"/dashboard\"\r\n * },\r\n * {\r\n * \"label\": \"재고 관리\",\r\n * \"icon\": \"package\",\r\n * \"menuType\": \"F\",\r\n * \"menuKey\": 2,\r\n * \"children\": [\r\n * {\r\n * \"label\": \"재고 현황\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 21,\r\n * \"path\": \"/inventory/status\"\r\n * },\r\n * {\r\n * \"label\": \"입고 관리\",\r\n * \"menuType\": \"F\",\r\n * \"menuKey\": 22,\r\n * \"children\": [\r\n * {\r\n * \"label\": \"입고 등록\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 221,\r\n * \"path\": \"/inventory/receiving/register\"\r\n * }\r\n * ]\r\n * }\r\n * ]\r\n * }\r\n * ]\r\n * ```\r\n */\r\n\r\ntype TabType = 'menu' | 'favorites'\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 아이템 목록 */\r\n menuItems: SidebarMenuItem[]\r\n /** 권한 목록 */\r\n permissions?: MenuPermission[]\r\n /** 즐겨찾기 메뉴 키 목록 */\r\n favorites?: (number | string)[]\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n /** 너비 */\r\n width?: string\r\n /** 표시 여부 */\r\n isVisible?: boolean\r\n }>(),\r\n {\r\n permissions: () => [],\r\n favorites: () => [],\r\n styletype: 'minimal',\r\n width: '280px',\r\n isVisible: true,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n /** 즐겨찾기 변경 이벤트 */\r\n favoriteChange: [menuKey: number | string | undefined, isFavorite: boolean]\r\n}>()\r\n\r\n// vue-router가 설정되지 않은 경우를 대비 (Storybook에서 router가 제공됨)\r\nconst route = useRoute()\r\n\r\n/**\r\n * 현재 활성 탭\r\n */\r\nconst activeTab = ref<TabType>('menu')\r\n\r\n/**\r\n * 검색어\r\n */\r\nconst searchQuery = ref('')\r\n\r\n/**\r\n * 현재 활성화된 경로\r\n */\r\nconst activePath = computed(() => route.path)\r\n\r\n/**\r\n * 확장된 메뉴 키 목록\r\n */\r\nconst expandedKeys = ref<Set<number | string>>(new Set())\r\n\r\n/**\r\n * 즐겨찾기 메뉴 아이템 목록\r\n * 즐겨찾기는 dept 없이 1단계로만 평탄화하여 표시\r\n */\r\nconst favoriteMenuItems = computed(() => {\r\n if (!Array.isArray(props.favorites) || props.favorites.length === 0) {\r\n return []\r\n }\r\n\r\n if (!Array.isArray(props.menuItems) || props.menuItems.length === 0) {\r\n return []\r\n }\r\n\r\n /**\r\n * 메뉴 아이템을 재귀적으로 순회하며 즐겨찾기만 추출\r\n * 즐겨찾기에서는 dept 없이 1단계로만 평탄화\r\n * menuType L(Link)만 포함하고 F(Folder)는 제외\r\n */\r\n const flattenFavorites = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\r\n const result: SidebarMenuItem[] = []\r\n\r\n if (!Array.isArray(items)) {\r\n return result\r\n }\r\n\r\n for (const item of items) {\r\n const key = item.menuKey || item.label\r\n const isFavorite = Array.isArray(props.favorites) && props.favorites.includes(key)\r\n\r\n // 즐겨찾기이고 menuType이 L(Link)인 경우만 추가 (F는 제외)\r\n if (isFavorite && item.menuType === 'L') {\r\n result.push({\r\n ...item,\r\n children: undefined, // children 제거하여 1단계로만 표시\r\n })\r\n }\r\n\r\n // 하위 메뉴도 재귀적으로 탐색\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n const childFavorites = flattenFavorites(item.children)\r\n result.push(...childFavorites)\r\n }\r\n }\r\n\r\n return result\r\n }\r\n\r\n return flattenFavorites(props.menuItems)\r\n})\r\n\r\n/**\r\n * 검색어로 필터링된 메뉴 아이템\r\n * 재귀적으로 children까지 검색\r\n */\r\nconst filteredMenuItems = computed(() => {\r\n if (!Array.isArray(props.menuItems) || props.menuItems.length === 0) {\r\n return []\r\n }\r\n\r\n if (!searchQuery.value || searchQuery.value.trim() === '') {\r\n return props.menuItems\r\n }\r\n\r\n const query = searchQuery.value.toLowerCase().trim()\r\n\r\n /**\r\n * 메뉴 아이템을 재귀적으로 검색\r\n */\r\n const searchInMenu = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\r\n const result: SidebarMenuItem[] = []\r\n\r\n if (!Array.isArray(items)) {\r\n return result\r\n }\r\n\r\n for (const item of items) {\r\n const matchesLabel = item.label?.toLowerCase().includes(query) ?? false\r\n\r\n // 하위 메뉴 검색\r\n let filteredChildren: SidebarMenuItem[] | undefined = undefined\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n filteredChildren = searchInMenu(item.children)\r\n }\r\n\r\n // 현재 메뉴나 하위 메뉴 중 하나라도 매칭되면 포함\r\n if (matchesLabel || (Array.isArray(filteredChildren) && filteredChildren.length > 0)) {\r\n result.push({\r\n ...item,\r\n children: filteredChildren,\r\n })\r\n }\r\n }\r\n\r\n return result\r\n }\r\n\r\n return searchInMenu(props.menuItems)\r\n})\r\n\r\n/**\r\n * 검색 결과에 따라 부모 메뉴 자동 확장\r\n * computed 외부에서 watch를 통해 처리\r\n */\r\nwatch(\r\n () => filteredMenuItems.value,\r\n (filtered) => {\r\n if (!searchQuery.value || searchQuery.value.trim() === '') {\r\n return\r\n }\r\n\r\n // 검색 결과에서 매칭된 하위 메뉴가 있는 부모를 찾아 확장\r\n const findParentsWithMatches = (items: SidebarMenuItem[]): Set<number | string> => {\r\n const keysToExpand = new Set<number | string>()\r\n\r\n const traverse = (menuItems: SidebarMenuItem[]): void => {\r\n if (!Array.isArray(menuItems)) {\r\n return\r\n }\r\n\r\n for (const item of menuItems) {\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n const key = item.menuKey || item.label\r\n keysToExpand.add(key)\r\n traverse(item.children)\r\n }\r\n }\r\n }\r\n\r\n traverse(items)\r\n return keysToExpand\r\n }\r\n\r\n const keysToExpand = findParentsWithMatches(filtered)\r\n keysToExpand.forEach(key => {\r\n expandedKeys.value.add(key)\r\n })\r\n },\r\n { immediate: false }\r\n)\r\n\r\n/**\r\n * 검색어로 필터링된 즐겨찾기 메뉴 아이템\r\n * 즐겨찾기는 이미 평탄화되어 있으므로 단순 필터링만 수행\r\n */\r\nconst filteredFavoriteItems = computed(() => {\r\n if (!Array.isArray(favoriteMenuItems.value) || favoriteMenuItems.value.length === 0) {\r\n return []\r\n }\r\n\r\n if (!searchQuery.value || searchQuery.value.trim() === '') {\r\n return favoriteMenuItems.value\r\n }\r\n\r\n const query = searchQuery.value.toLowerCase().trim()\r\n\r\n // 즐겨찾기는 이미 평탄화되어 1단계이므로 단순 필터링만 수행\r\n return favoriteMenuItems.value.filter((item) =>\r\n item.label?.toLowerCase().includes(query) ?? false\r\n )\r\n})\r\n\r\n\r\n/**\r\n * 탭 변경 핸들러\r\n * 탭 전환 시 검색 쿼리를 초기화하여 각 탭의 독립적인 검색 상태 유지\r\n */\r\nconst handleTabChange = (tab: TabType) => {\r\n if (activeTab.value !== tab) {\r\n activeTab.value = tab\r\n // 탭 전환 시 검색 쿼리 초기화 (선택적 - UX 고려)\r\n // 검색 쿼리를 유지하려면 아래 라인을 제거하세요\r\n searchQuery.value = ''\r\n }\r\n}\r\n\r\n/**\r\n * 확장 상태 변경 핸들러\r\n */\r\nconst handleExpandChange = (menuKey: number | string | undefined, expanded: boolean) => {\r\n if (!menuKey) return\r\n\r\n if (expanded) {\r\n expandedKeys.value.add(menuKey)\r\n } else {\r\n expandedKeys.value.delete(menuKey)\r\n }\r\n}\r\n\r\n/**\r\n * 메뉴 클릭 핸들러\r\n */\r\nconst handleMenuClick = (event: MenuClickEvent) => {\r\n emit('menuClick', event)\r\n}\r\n\r\n/**\r\n * 즐겨찾기 토글 핸들러\r\n */\r\nconst handleFavoriteToggle = (menuKey: number | string | undefined) => {\r\n if (!menuKey) return\r\n\r\n const isFavorite = props.favorites?.includes(menuKey) ?? false\r\n emit('favoriteChange', menuKey, !isFavorite)\r\n}\r\n\r\n/**\r\n * 메뉴 아이템에서 특정 menuKey를 찾는 헬퍼 함수\r\n */\r\nconst findMenuItemByKey = (items: SidebarMenuItem[], targetKey: number | string): SidebarMenuItem | null => {\r\n for (const item of items) {\r\n const key = item.menuKey || item.label\r\n if (key === targetKey) {\r\n return item\r\n }\r\n if (item.children && item.children.length > 0) {\r\n const found = findMenuItemByKey(item.children, targetKey)\r\n if (found) return found\r\n }\r\n }\r\n return null\r\n}\r\n\r\n/**\r\n * 메뉴가 즐겨찾기인지 확인 (L 타입만 즐겨찾기 가능)\r\n */\r\nconst isFavorite = (menuKey: number | string | undefined): boolean => {\r\n if (!menuKey) return false\r\n if (!props.favorites?.includes(menuKey)) return false\r\n \r\n // menuType이 L인 경우만 즐겨찾기로 인정\r\n const menuItem = findMenuItemByKey(props.menuItems, menuKey)\r\n return menuItem?.menuType === 'L'\r\n}\r\n\r\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n tabContainerClass: string\n tabButtonClass: string\n searchContainerClass: string\n menuContainerClass: string\n}> = {\n default: {\n containerClass: 'h-full bg-background border-r border-border flex flex-col',\n tabContainerClass: 'flex border-b border-border',\n tabButtonClass: 'flex-1 px-3 py-1.5 text-xs font-medium transition-colors border-b-2 hover:bg-accent/50',\n searchContainerClass: 'p-1.5 border-b border-border',\n menuContainerClass: 'flex-1 overflow-y-auto p-1.5 space-y-0.5',\n },\n minimal: {\n containerClass: 'h-full bg-background border-r border-border flex flex-col',\n tabContainerClass: 'flex border-b border-border',\n tabButtonClass: 'flex-1 px-2 py-1 text-xs font-medium transition-colors border-b-2',\n searchContainerClass: 'p-1 border-b border-border',\n menuContainerClass: 'flex-1 overflow-y-auto p-1 space-y-0.5',\n },\n}\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * 루트 클래스\r\n */\r\nconst rootClasses = computed(() => {\r\n return cn(\r\n preset.value.containerClass,\r\n props.class\r\n )\r\n})\r\n</script>\r\n\r\n<template>\r\n <Transition name=\"slide\">\r\n <aside v-show=\"props.isVisible\" :class=\"rootClasses\" :style=\"{ width }\">\r\n <!-- 탭 헤더 -->\r\n <div :class=\"preset.tabContainerClass\">\r\n <button\r\n :class=\"cn(\r\n preset.tabButtonClass,\r\n activeTab === 'menu'\r\n ? 'border-primary text-primary'\r\n : 'border-transparent text-muted-foreground hover:text-foreground'\r\n )\"\r\n @click=\"handleTabChange('menu')\"\r\n >\r\n 기본메뉴\r\n </button>\r\n <button\r\n :class=\"cn(\r\n preset.tabButtonClass,\r\n activeTab === 'favorites'\r\n ? 'border-primary text-primary'\r\n : 'border-transparent text-muted-foreground hover:text-foreground'\r\n )\"\r\n @click=\"handleTabChange('favorites')\"\r\n >\r\n 즐겨찾기\r\n </button>\r\n </div>\r\n\r\n <!-- 검색 영역 -->\r\n <div :class=\"preset.searchContainerClass\">\r\n <div class=\"relative\">\r\n <JIcon\r\n name=\"search\"\r\n size=\"sm\"\r\n class=\"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground\"\r\n />\r\n <JInput\r\n v-model=\"searchQuery\"\r\n placeholder=\"메뉴 검색...\"\r\n :class=\"cn(\r\n 'pl-8',\r\n props.styletype === 'minimal' && 'h-8 text-xs'\r\n )\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <!-- 메뉴 목록 -->\r\n <div :class=\"preset.menuContainerClass\">\r\n <template v-if=\"activeTab === 'menu'\">\r\n <template v-if=\"filteredMenuItems.length > 0\">\r\n <div\r\n v-for=\"(item, index) in filteredMenuItems\"\r\n :key=\"item.menuKey || item.label || index\"\r\n class=\"flex items-center group\"\r\n >\r\n <JDynamicMenuItem\r\n :item=\"item\"\r\n :level=\"0\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :favorites=\"favorites\"\r\n :on-favorite-toggle=\"handleFavoriteToggle\"\r\n :is-favorite=\"isFavorite\"\r\n :styletype=\"styletype\"\r\n class=\"flex-1\"\r\n @menu-click=\"handleMenuClick\"\r\n @expand-change=\"handleExpandChange\"\r\n />\r\n </div>\r\n </template>\n <div v-else class=\"text-center py-8 text-muted-foreground\">\n <p class=\"text-xs\">검색 결과가 없습니다.</p>\n </div>\n </template>\n\n <template v-else>\n <template v-if=\"filteredFavoriteItems.length > 0\">\n <JDynamicMenuItem\n v-for=\"(item, index) in filteredFavoriteItems\"\n :key=\"item.menuKey || item.label || index\"\n :item=\"item\"\n :level=\"0\"\n :permissions=\"permissions\"\n :active-path=\"activePath\"\n :expanded-keys=\"expandedKeys\"\n :styletype=\"styletype\"\n @menu-click=\"handleMenuClick\"\n @expand-change=\"handleExpandChange\"\n />\n </template>\n <div v-else class=\"text-center py-8 text-muted-foreground\">\n <p class=\"text-xs\">즐겨찾기가 없습니다.</p>\n </div>\n </template>\r\n </div>\r\n </aside>\r\n </Transition>\r\n</template>\r\n\r\n<style scoped>\r\n.slide-enter-active,\r\n.slide-leave-active {\r\n transition: transform 0.3s ease, opacity 0.3s ease;\r\n}\r\n\r\n.slide-enter-from,\r\n.slide-leave-to {\r\n transform: translateX(-100%);\r\n opacity: 0;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","route","useRoute","activeTab","ref","searchQuery","activePath","computed","expandedKeys","favoriteMenuItems","flattenFavorites","items","result","item","key","childFavorites","filteredMenuItems","query","searchInMenu","matchesLabel","filteredChildren","watch","filtered","keysToExpand","traverse","menuItems","filteredFavoriteItems","handleTabChange","tab","handleExpandChange","menuKey","expanded","handleMenuClick","event","handleFavoriteToggle","isFavorite","findMenuItemByKey","targetKey","found","STYLE_PRESETS","preset","rootClasses","cn","_createBlock","_Transition","_createElementVNode","_normalizeClass","_unref","_hoisted_1","_createVNode","JIcon","JInput","$event","_createElementBlock","_Fragment","_openBlock","_renderList","index","JDynamicMenuItem","_hoisted_2","_cache","_hoisted_3","_vShow"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),F=require("vue-router"),f=require("../../atoms/JIcon.vue.cjs"),u=require("../../../lib/utils.cjs"),S={key:1,class:"w-4 flex-shrink-0"},E={key:0,class:"w-full"},P=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:k}){const n=t,r=k,x=F.useRouter(),C=e.computed(()=>u.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),o=e.computed(()=>n.item.menuType==="F"||Array.isArray(n.item.children)&&n.item.children.length>0),d=e.computed(()=>{if(!o.value)return!1;const i=n.item.menuKey||n.item.label;return n.expandedKeys?.has(i)??!1}),v=e.computed(()=>n.item.disabled||!C.value),p=e.computed(()=>({paddingLeft:`${8+(n.level||0)*12}px`})),b=i=>{if(v.value){i.preventDefault();return}if(o.value){const a=n.item.menuKey||n.item.label,s=!d.value;r("expandChange",a,s),r("menuClick",{menuItem:n.item,path:[n.item],event:i})}else!n.disableNavigation&&n.item.path&&x.push(n.item.path),r("menuClick",{menuItem:n.item,path:[n.item],event:i})},h={default:{itemClass:"flex items-center gap-1.5 py-1 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate text-xs",iconSize:"sm"},minimal:{itemClass:"flex items-center gap-1 py-1 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate text-xs",iconSize:"sm"}},c=e.computed(()=>h[n.styletype]??h.default),B=e.computed(()=>d.value?"chevronDown":"chevronRight"),g=e.computed(()=>{if(!o.value||!Array.isArray(n.item.children))return 0;const i=a=>{let s=0;for(const l of a)s++,Array.isArray(l.children)&&l.children.length>0&&(s+=i(l.children));return s};return i(n.item.children)});return(i,a)=>{const s=e.resolveComponent("JDynamicMenuItem",!0);return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(u.cn)("w-full",t.className))},[e.createElementVNode("div",{class:e.normalizeClass(e.unref(u.cn)(c.value.itemClass,{"bg-accent text-accent-foreground":m.value,"hover:bg-accent/50":!v.value&&!m.value,"opacity-50 cursor-not-allowed":v.value,"font-medium":m.value,"font-semibold":o.value})),style:e.normalizeStyle(p.value),onClick:b},[o.value?(e.openBlock(),e.createBlock(f.default,{key:0,name:B.value,size:c.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&&!o.value?(e.openBlock(),e.createBlock(f.default,{key:2,name:t.item.icon,size:c.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):e.createCommentVNode("",!0),e.createElementVNode("span",{class:e.normalizeClass(c.value.labelClass)},e.toDisplayString(t.item.label),3),o.value&&g.value>0?(e.openBlock(),e.createElementBlock("span",{key:3,class:e.normalizeClass(e.unref(u.cn)("text-muted-foreground ml-1 flex-shrink-0",n.styletype==="minimal"?"text-[10px]":"text-xs"))}," ("+e.toDisplayString(g.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(u.cn)("opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0",n.styletype==="minimal"?"p-0.5":"p-1",t.isFavorite&&t.isFavorite(t.item.menuKey)&&"opacity-100")),onClick:a[0]||(a[0]=e.withModifiers(l=>t.onFavoriteToggle(t.item.menuKey),["stop"]))},[e.createVNode(f.default,{name:(t.isFavorite&&t.isFavorite(t.item.menuKey),"star"),size:c.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),o.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,(l,z)=>(e.openBlock(),e.createBlock(s,{key:l.menuKey||l.label||z,item:l,level:t.level+1,"max-depth":t.maxDepth,permissions:t.permissions,"active-path":t.activePath,"expanded-keys":t.expandedKeys,favorites:t.favorites,"on-favorite-toggle":t.onFavoriteToggle,"is-favorite":t.isFavorite,styletype:t.styletype,"disable-navigation":t.disableNavigation,"active-key":t.activeKey,onMenuClick:a[1]||(a[1]=y=>r("menuClick",y)),onExpandChange:a[2]||(a[2]=(y,K)=>r("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=P;
|
|
2
2
|
//# sourceMappingURL=JDynamicMenuItem.vue.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JDynamicMenuItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { useRouter } from 'vue-router'\r\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport { cn, hasMenuPermission } from '@/lib/utils'\r\n\r\n/**\r\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\r\n * Recursive Menu Item Component\r\n * \r\n * @description\r\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\r\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\r\n */\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 아이템 */\r\n item: SidebarMenuItem\r\n /** 메뉴 레벨 (들여쓰기용, 0부터 시작) */\r\n level?: number\r\n /** 권한 목록 */\r\n permissions?: MenuPermission[]\r\n /** 활성화된 메뉴 경로 */\r\n activePath?: string\r\n /** 확장된 메뉴 키 목록 */\r\n expandedKeys?: Set<number | string>\r\n /** 즐겨찾기 메뉴 키 목록 */\r\n favorites?: (number | string)[]\r\n /** 즐겨찾기 변경 핸들러 */\r\n onFavoriteToggle?: (menuKey: number | string | undefined) => void\r\n /** 즐겨찾기 확인 함수 */\r\n isFavorite?: (menuKey: number | string | undefined) => boolean\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 추가 CSS 클래스 */\r\n className?: string\r\n /** 최대 깊이 제한 (무한 루프 방지, 기본값: 10) */\r\n maxDepth?: number\r\n /** 네비게이션 비활성화 (true일 때 router.push 건너뛰고 emit만 수행) */\r\n disableNavigation?: boolean\r\n /** 활성화된 메뉴 키 (menuKey 기반 활성화, activePath보다 우선) */\r\n activeKey?: number | string | null\r\n }>(),\r\n {\r\n level: 0,\r\n permissions: () => [],\r\n expandedKeys: () => new Set(),\r\n favorites: () => [],\r\n styletype: 'default',\r\n maxDepth: 10,\r\n disableNavigation: false,\r\n activeKey: null,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n /** 확장 상태 변경 이벤트 */\r\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\r\n}>()\r\n\r\nconst router = useRouter()\r\n\r\n/**\r\n * 권한 체크 함수\r\n * Permission check function\r\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\r\n */\r\nconst checkPermission = computed(() => {\r\n return hasMenuPermission(props.item.menuKey, props.permissions)\r\n})\r\n\r\n/**\r\n * 메뉴가 활성화되어 있는지 여부\r\n * activeKey가 제공되면 menuKey 매칭, 아니면 경로 매칭\r\n */\r\nconst isActive = computed(() => {\r\n // activeKey가 제공되면 menuKey 기반 매칭 (우선순위 높음)\r\n if (props.activeKey !== undefined && props.activeKey !== null) {\r\n return props.item.menuKey === props.activeKey\r\n }\r\n // 경로 기반 매칭 (기본 동작)\r\n if (!props.item.path || !props.activePath) return false\r\n return props.activePath === props.item.path\r\n})\r\n\r\n/**\r\n * 메뉴 타입이 폴더인지 여부\r\n * 순환 참조 방지: children이 유효한 배열인지 확인\r\n */\r\nconst isFolder = computed(() => {\r\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\r\n})\r\n\r\n/**\r\n * 메뉴가 확장되어 있는지 여부\r\n */\r\nconst isExpanded = computed(() => {\r\n if (!isFolder.value) return false\r\n const key = props.item.menuKey || props.item.label\r\n return props.expandedKeys?.has(key) ?? false\r\n})\r\n\r\n/**\r\n * 메뉴가 비활성화되어 있는지 여부\r\n */\r\nconst isDisabled = computed(() => {\r\n return props.item.disabled || !checkPermission.value\r\n})\r\n\r\n/**\r\n * 레벨별 들여쓰기 스타일\r\n * Tailwind의 표준 클래스는 제한적이므로 인라인 스타일 사용\r\n */\r\nconst indentStyle = computed(() => {\r\n const basePadding = 12 // 기본 패딩 (px)\r\n const level = props.level || 0\r\n const levelPadding = level * 16 // 레벨당 16px\r\n const totalPadding = basePadding + levelPadding\r\n return { paddingLeft: `${totalPadding}px` }\r\n})\r\n\r\n/**\r\n * 메뉴 클릭 핸들러\r\n */\r\nconst handleMenuClick = (event: MouseEvent) => {\r\n if (isDisabled.value) {\r\n event.preventDefault()\r\n return\r\n }\r\n\r\n if (isFolder.value) {\r\n // 폴더 타입: 확장/축소 토글\r\n const key = props.item.menuKey || props.item.label\r\n const newExpanded = !isExpanded.value\r\n emit('expandChange', key, newExpanded)\r\n // 폴더도 메뉴 클릭 이벤트 발생\r\n emit('menuClick', {\r\n menuItem: props.item,\r\n path: [props.item],\r\n event,\r\n })\r\n } else {\r\n // 링크 타입: 라우팅 (disableNavigation이 false일 때만)\r\n if (!props.disableNavigation && props.item.path) {\r\n router.push(props.item.path)\r\n }\r\n \r\n // 메뉴 클릭 이벤트 발생\r\n emit('menuClick', {\r\n menuItem: props.item,\r\n path: [props.item], // 단순화된 경로 (필요시 부모 경로 포함하도록 확장 가능)\r\n event,\r\n })\r\n }\r\n}\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n itemClass: string\r\n labelClass: string\r\n iconSize: 'sm' | 'md'\r\n}> = {\r\n default: {\r\n itemClass: 'flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate',\r\n iconSize: 'sm',\r\n },\r\n minimal: {\r\n itemClass: 'flex items-center gap-1.5 py-1.5 rounded-md cursor-pointer transition-colors group',\r\n labelClass: 'flex-1 truncate text-xs',\r\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * Chevron 아이콘 컴포넌트\r\n */\r\nconst ChevronIcon = computed(() => {\r\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"cn('w-full', className)\">\r\n <!-- 메뉴 아이템 -->\r\n <div\r\n :class=\"cn(\r\n preset.itemClass,\r\n {\r\n 'bg-accent text-accent-foreground': isActive,\r\n 'hover:bg-accent/50': !isDisabled && !isActive,\r\n 'opacity-50 cursor-not-allowed': isDisabled,\r\n 'font-medium': isActive,\r\n }\r\n )\"\r\n :style=\"indentStyle\"\r\n @click=\"handleMenuClick\"\r\n >\r\n <!-- Chevron 아이콘 (폴더 타입만) -->\r\n <JIcon\r\n v-if=\"isFolder\"\r\n :name=\"ChevronIcon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\r\n\r\n <!-- 메뉴 아이콘 -->\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n :size=\"preset.iconSize\"\r\n class=\"flex-shrink-0\"\r\n />\r\n\r\n <!-- 메뉴 라벨 -->\r\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\r\n \r\n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\r\n <button\r\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\r\n :class=\"cn(\r\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\r\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\r\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\r\n )\"\r\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\r\n >\r\n <JIcon\r\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\r\n :size=\"preset.iconSize\"\r\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\r\n />\r\n </button>\r\n </div>\r\n\r\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\r\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\r\n <div\r\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\r\n class=\"w-full\"\r\n >\r\n <JDynamicMenuItem\r\n v-for=\"(child, index) in item.children\"\r\n :key=\"child.menuKey || child.label || index\"\r\n :item=\"child\"\r\n :level=\"level + 1\"\r\n :max-depth=\"maxDepth\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :favorites=\"favorites\"\r\n :on-favorite-toggle=\"onFavoriteToggle\"\r\n :is-favorite=\"isFavorite\"\r\n :styletype=\"styletype\"\r\n :disable-navigation=\"disableNavigation\"\r\n :active-key=\"activeKey\"\r\n @menu-click=\"emit('menuClick', $event)\"\r\n @expand-change=\"(menuKey, expanded) => emit('expandChange', menuKey, expanded)\"\r\n />\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_createBlock","JIcon","_openBlock","_hoisted_1","_toDisplayString","_cache","_withModifiers","$event","_createVNode","_hoisted_2","_Fragment","child","index","_component_JDynamicMenuItem","menuKey","expanded"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"JDynamicMenuItem.vue.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 * Tailwind의 표준 클래스는 제한적이므로 인라인 스타일 사용\n */\nconst indentStyle = computed(() => {\n const basePadding = 8 // 기본 패딩 (px) - 12에서 8로 축소\n const level = props.level || 0\n const levelPadding = level * 12 // 레벨당 12px (16에서 12로 축소)\n const totalPadding = basePadding + levelPadding\n return { paddingLeft: `${totalPadding}px` }\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-accent text-accent-foreground': 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=\"w-full\"\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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"}
|