@j-solution/components 1.6.1 → 1.8.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-BtHTA-UF.css +1 -0
- package/assets/styles/global-utilities.css +34 -0
- package/assets/styles/j-components.css +1 -1
- package/assets/styles/themes.css +128 -21
- 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 +6 -1
- package/components/atoms/JButton.vue.cjs.map +1 -1
- package/components/atoms/JButton.vue.js +10 -85
- package/components/atoms/JButton.vue.js.map +1 -1
- package/components/atoms/JButton.vue2.cjs +1 -1
- package/components/atoms/JButton.vue2.cjs.map +1 -1
- package/components/atoms/JButton.vue2.js +85 -2
- package/components/atoms/JButton.vue2.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 +59 -43
- 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 +26 -22
- 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/JSectionTitle.vue.cjs +7 -0
- package/components/atoms/JSectionTitle.vue.cjs.map +1 -0
- package/components/atoms/JSectionTitle.vue.js +13 -0
- package/components/atoms/JSectionTitle.vue.js.map +1 -0
- package/components/atoms/JSectionTitle.vue2.cjs +2 -0
- package/components/atoms/JSectionTitle.vue2.cjs.map +1 -0
- package/components/atoms/JSectionTitle.vue2.js +67 -0
- package/components/atoms/JSectionTitle.vue2.js.map +1 -0
- 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 +6 -1
- package/components/atoms/JSplitter.vue.cjs.map +1 -1
- package/components/atoms/JSplitter.vue.js +10 -54
- package/components/atoms/JSplitter.vue.js.map +1 -1
- package/components/atoms/JSplitter.vue2.cjs +1 -1
- package/components/atoms/JSplitter.vue2.cjs.map +1 -1
- package/components/atoms/JSplitter.vue2.js +59 -2
- package/components/atoms/JSplitter.vue2.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 +265 -191
- 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 +349 -333
- package/components/examples/ExampleTabMappingPage.vue.js.map +1 -1
- package/components/molecules/JAlert.vue.cjs +1 -1
- package/components/molecules/JAlert.vue.cjs.map +1 -1
- package/components/molecules/JAlert.vue.js +18 -16
- package/components/molecules/JAlert.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/JCard.vue.cjs +1 -1
- package/components/molecules/JCard.vue.cjs.map +1 -1
- package/components/molecules/JCard.vue.js +55 -39
- package/components/molecules/JCard.vue.js.map +1 -1
- package/components/molecules/JEmptyState.vue.cjs +7 -0
- package/components/molecules/JEmptyState.vue.cjs.map +1 -0
- package/components/molecules/JEmptyState.vue.js +13 -0
- package/components/molecules/JEmptyState.vue.js.map +1 -0
- package/components/molecules/JEmptyState.vue2.cjs +2 -0
- package/components/molecules/JEmptyState.vue2.cjs.map +1 -0
- package/components/molecules/JEmptyState.vue2.js +127 -0
- package/components/molecules/JEmptyState.vue2.js.map +1 -0
- package/components/molecules/JFormField.vue.cjs +6 -1
- package/components/molecules/JFormField.vue.cjs.map +1 -1
- package/components/molecules/JFormField.vue.js +10 -262
- package/components/molecules/JFormField.vue.js.map +1 -1
- package/components/molecules/JFormField.vue2.cjs +2 -0
- package/components/molecules/JFormField.vue2.cjs.map +1 -0
- package/components/molecules/JFormField.vue2.js +271 -0
- package/components/molecules/JFormField.vue2.js.map +1 -0
- 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 +50 -56
- 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 +49 -47
- package/components/molecules/JTitlebar.vue.js.map +1 -1
- package/components/organisms/JDynamicForm.vue2.cjs +1 -1
- package/components/organisms/JDynamicForm.vue2.cjs.map +1 -1
- package/components/organisms/JDynamicForm.vue2.js +35 -32
- package/components/organisms/JDynamicForm.vue2.js.map +1 -1
- package/components/organisms/JDynamicTabs.vue.cjs +1 -1
- package/components/organisms/JDynamicTabs.vue.cjs.map +1 -1
- package/components/organisms/JDynamicTabs.vue.js +47 -52
- package/components/organisms/JDynamicTabs.vue.js.map +1 -1
- package/components/organisms/JFilterBar.vue.cjs +6 -1
- package/components/organisms/JFilterBar.vue.cjs.map +1 -1
- package/components/organisms/JFilterBar.vue.js +10 -137
- package/components/organisms/JFilterBar.vue.js.map +1 -1
- package/components/organisms/JFilterBar.vue2.cjs +1 -1
- package/components/organisms/JFilterBar.vue2.cjs.map +1 -1
- package/components/organisms/JFilterBar.vue2.js +141 -2
- package/components/organisms/JFilterBar.vue2.js.map +1 -1
- package/components/organisms/JFormModal.vue.cjs +1 -1
- package/components/organisms/JFormModal.vue.cjs.map +1 -1
- package/components/organisms/JFormModal.vue.js +54 -49
- package/components/organisms/JFormModal.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 +211 -208
- 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 +31 -26
- package/components/organisms/JModal.vue.js.map +1 -1
- package/components/organisms/JPageContainer.vue.cjs +1 -1
- package/components/organisms/JPageContainer.vue.cjs.map +1 -1
- package/components/organisms/JPageContainer.vue.js +22 -22
- package/components/organisms/JPageContainer.vue.js.map +1 -1
- package/components/organisms/JSearchPanel.vue2.cjs +1 -1
- package/components/organisms/JSearchPanel.vue2.cjs.map +1 -1
- package/components/organisms/JSearchPanel.vue2.js +34 -32
- package/components/organisms/JSearchPanel.vue2.js.map +1 -1
- package/components/organisms/JShuttle.vue.cjs +7 -0
- package/components/organisms/JShuttle.vue.cjs.map +1 -0
- package/components/organisms/JShuttle.vue.js +13 -0
- package/components/organisms/JShuttle.vue.js.map +1 -0
- package/components/organisms/JShuttle.vue2.cjs +2 -0
- package/components/organisms/JShuttle.vue2.cjs.map +1 -0
- package/components/organisms/JShuttle.vue2.js +216 -0
- package/components/organisms/JShuttle.vue2.js.map +1 -0
- 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/Card.vue.cjs +1 -1
- package/components/shadcn/Card.vue.cjs.map +1 -1
- package/components/shadcn/Card.vue.js +1 -1
- package/components/shadcn/Card.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 +4 -4
- 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 +3 -3
- 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/TabsContent.vue.cjs +1 -1
- package/components/shadcn/TabsContent.vue.cjs.map +1 -1
- package/components/shadcn/TabsContent.vue.js +1 -1
- package/components/shadcn/TabsContent.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 +10 -10
- 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 +9 -8
- package/components/shadcn/index.js.map +1 -1
- package/components/templates/JLayout.vue.cjs.map +1 -1
- package/components/templates/JLayout.vue.js.map +1 -1
- package/index.cjs +1 -1
- package/index.js +73 -67
- package/package.json +1 -1
- package/types/index.d.ts +1025 -766
- package/assets/jwms-portal-frontend-DntSIcYt.css +0 -1
- package/components/molecules/JFormField.vue3.cjs +0 -2
- package/components/molecules/JFormField.vue3.cjs.map +0 -1
- package/components/molecules/JFormField.vue3.js +0 -6
- package/components/molecules/JFormField.vue3.js.map +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"),c=require("../../../lib/utils.cjs"),S={key:1,class:"w-4 flex-shrink-0"},E={key:0,class:"border-l border-border/60 ml-[14px] pl-[6px]"},N=e.defineComponent({__name:"JDynamicMenuItem",props:{item:{},level:{default:0},permissions:{default:()=>[]},activePath:{},expandedKeys:{default:()=>new Set},favorites:{default:()=>[]},onFavoriteToggle:{},isFavorite:{},styletype:{default:"default"},className:{},maxDepth:{default:10},disableNavigation:{type:Boolean,default:!1},activeKey:{default:null}},emits:["menuClick","expandChange"],setup(t,{emit:g}){const n=t,s=g,x=F.useRouter(),p=e.computed(()=>c.hasMenuPermission(n.item.menuKey,n.permissions)),m=e.computed(()=>n.activeKey!==void 0&&n.activeKey!==null?n.item.menuKey===n.activeKey:!n.item.path||!n.activePath?!1:n.activePath===n.item.path),l=e.computed(()=>n.item.menuType==="F"||Array.isArray(n.item.children)&&n.item.children.length>0),d=e.computed(()=>{if(!l.value)return!1;const a=n.item.menuKey||n.item.label;return n.expandedKeys?.has(a)??!1}),v=e.computed(()=>n.item.disabled||!p.value),C=e.computed(()=>({paddingLeft:"8px",paddingRight:"8px"})),b=a=>{if(v.value){a.preventDefault();return}if(l.value){const i=n.item.menuKey||n.item.label,r=!d.value;s("expandChange",i,r),s("menuClick",{menuItem:n.item,path:[n.item],event:a})}else!n.disableNavigation&&n.item.path&&x.push(n.item.path),s("menuClick",{menuItem:n.item,path:[n.item],event:a})},h={default:{itemClass:"flex items-center gap-1.5 py-1 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"}},u=e.computed(()=>h[n.styletype]??h.default),B=e.computed(()=>d.value?"chevronDown":"chevronRight"),k=e.computed(()=>{if(!l.value||!Array.isArray(n.item.children))return 0;const a=i=>{let r=0;for(const o of i)r++,Array.isArray(o.children)&&o.children.length>0&&(r+=a(o.children));return r};return a(n.item.children)});return(a,i)=>{const r=e.resolveComponent("JDynamicMenuItem",!0);return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(c.cn)("w-full",t.className))},[e.createElementVNode("div",{class:e.normalizeClass(e.unref(c.cn)(u.value.itemClass,{"bg-primary/5 text-primary border-l-2 border-primary shadow-sm":m.value,"hover:bg-accent/50":!v.value&&!m.value,"opacity-50 cursor-not-allowed":v.value,"font-medium":m.value,"font-semibold":l.value})),style:e.normalizeStyle(C.value),onClick:b},[l.value?(e.openBlock(),e.createBlock(f.default,{key:0,name:B.value,size:u.value.iconSize,class:"flex-shrink-0 opacity-60",style:{"stroke-width":"1.5"}},null,8,["name","size"])):(e.openBlock(),e.createElementBlock("span",S)),t.item.icon&&!l.value?(e.openBlock(),e.createBlock(f.default,{key:2,name:t.item.icon,size:u.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):e.createCommentVNode("",!0),e.createElementVNode("span",{class:e.normalizeClass(u.value.labelClass)},e.toDisplayString(t.item.label),3),l.value&&k.value>0?(e.openBlock(),e.createElementBlock("span",{key:3,class:e.normalizeClass(e.unref(c.cn)("text-muted-foreground ml-1 flex-shrink-0",n.styletype==="minimal"?"text-[10px]":"text-xs"))}," ("+e.toDisplayString(k.value)+") ",3)):e.createCommentVNode("",!0),t.item.menuKey&&t.item.menuType==="L"&&t.onFavoriteToggle?(e.openBlock(),e.createElementBlock("button",{key:4,class:e.normalizeClass(e.unref(c.cn)("opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0",n.styletype==="minimal"?"p-0.5":"p-1",t.isFavorite&&t.isFavorite(t.item.menuKey)&&"opacity-100")),onClick:i[0]||(i[0]=e.withModifiers(o=>t.onFavoriteToggle(t.item.menuKey),["stop"]))},[e.createVNode(f.default,{name:(t.isFavorite&&t.isFavorite(t.item.menuKey),"star"),size:u.value.iconSize,class:e.normalizeClass(t.isFavorite&&t.isFavorite(t.item.menuKey)?"text-yellow-500 fill-yellow-500":"text-muted-foreground")},null,8,["name","size","class"])],2)):e.createCommentVNode("",!0)],6),l.value&&d.value&&t.item.children&&Array.isArray(t.item.children)&&t.item.children.length>0&&t.level+1<t.maxDepth?(e.openBlock(),e.createElementBlock("div",E,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.item.children,(o,z)=>(e.openBlock(),e.createBlock(r,{key:o.menuKey||o.label||z,item:o,level:t.level+1,"max-depth":t.maxDepth,permissions:t.permissions,"active-path":t.activePath,"expanded-keys":t.expandedKeys,favorites:t.favorites,"on-favorite-toggle":t.onFavoriteToggle,"is-favorite":t.isFavorite,styletype:t.styletype,"disable-navigation":t.disableNavigation,"active-key":t.activeKey,onMenuClick:i[1]||(i[1]=y=>s("menuClick",y)),onExpandChange:i[2]||(i[2]=(y,K)=>s("expandChange",y,K))},null,8,["item","level","max-depth","permissions","active-path","expanded-keys","favorites","on-favorite-toggle","is-favorite","styletype","disable-navigation","active-key"]))),128))])):e.createCommentVNode("",!0)],2)}}});exports.default=N;
|
|
2
2
|
//# sourceMappingURL=JDynamicMenuItem.vue.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JDynamicMenuItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\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":"0rBAkBA,MAAMA,EAAQC,EAyCRC,EAAOC,EAOPC,EAASC,EAAAA,UAAA,EAOTC,EAAkBC,EAAAA,SAAS,IACxBC,EAAAA,kBAAkBR,EAAM,KAAK,QAASA,EAAM,WAAW,CAC/D,EAMKS,EAAWF,EAAAA,SAAS,IAEpBP,EAAM,YAAc,QAAaA,EAAM,YAAc,KAChDA,EAAM,KAAK,UAAYA,EAAM,UAGlC,CAACA,EAAM,KAAK,MAAQ,CAACA,EAAM,WAAmB,GAC3CA,EAAM,aAAeA,EAAM,KAAK,IACxC,EAMKU,EAAWH,EAAAA,SAAS,IACjBP,EAAM,KAAK,WAAa,KAAQ,MAAM,QAAQA,EAAM,KAAK,QAAQ,GAAKA,EAAM,KAAK,SAAS,OAAS,CAC3G,EAKKW,EAAaJ,EAAAA,SAAS,IAAM,CAChC,GAAI,CAACG,EAAS,MAAO,MAAO,GAC5B,MAAME,EAAMZ,EAAM,KAAK,SAAWA,EAAM,KAAK,MAC7C,OAAOA,EAAM,cAAc,IAAIY,CAAG,GAAK,EACzC,CAAC,EAKKC,EAAaN,EAAAA,SAAS,IACnBP,EAAM,KAAK,UAAY,CAACM,EAAgB,KAChD,EAMKQ,EAAcP,EAAAA,SAAS,KAKpB,CAAE,YAAa,GADD,IAFPP,EAAM,OAAS,GACA,EAEQ,IAAA,EACtC,EAKKe,EAAmBC,GAAsB,CAC7C,GAAIH,EAAW,MAAO,CACpBG,EAAM,eAAA,EACN,MACF,CAEA,GAAIN,EAAS,MAAO,CAElB,MAAME,EAAMZ,EAAM,KAAK,SAAWA,EAAM,KAAK,MACvCiB,EAAc,CAACN,EAAW,MAChCT,EAAK,eAAgBU,EAAKK,CAAW,EAErCf,EAAK,YAAa,CAChB,SAAUF,EAAM,KAChB,KAAM,CAACA,EAAM,IAAI,EACjB,MAAAgB,CAAA,CACD,CACH,KAEM,CAAChB,EAAM,mBAAqBA,EAAM,KAAK,MACzCI,EAAO,KAAKJ,EAAM,KAAK,IAAI,EAI7BE,EAAK,YAAa,CAChB,SAAUF,EAAM,KAChB,KAAM,CAACA,EAAM,IAAI,EACjB,MAAAgB,CAAA,CACD,CAEL,EAKME,EAID,CACH,QAAS,CACP,UAAW,iFACX,WAAY,kBACZ,SAAU,IAAA,EAEZ,QAAS,CACP,UAAW,qFACX,WAAY,0BACZ,SAAU,IAAA,CACZ,EAGIC,EAASZ,EAAAA,SAAS,IACfW,EAAclB,EAAM,SAAS,GAAKkB,EAAc,OACxD,EAKKE,EAAcb,EAAAA,SAAS,IACpBI,EAAW,MAAQ,cAAgB,cAC3C,uFAICU,EAAAA,mBA8EM,MAAA,CA9EA,MAAKC,EAAAA,eAAEC,QAAAC,EAAAA,EAAA,EAAE,SAAWvB,EAAA,SAAS,CAAA,CAAA,GAEjCwB,EAAAA,mBAiDM,MAAA,CAhDH,uBAAOF,EAAAA,MAAAC,IAAA,EAAaL,EAAA,MAAO,8CAAqEV,EAAA,MAA4C,qBAAA,CAAAI,EAAA,QAAeJ,EAAA,sCAAsDI,EAAA,oBAAsCJ,EAAA,KAAA,IASvP,uBAAOK,EAAA,KAAW,EAClB,QAAOC,CAAA,GAIAL,EAAA,qBADRgB,EAAAA,YAKEC,EAAAA,QAAA,OAHC,KAAMP,EAAA,MACN,KAAMD,EAAA,MAAO,SACd,MAAM,eAAA,4BAERS,EAAAA,YAAAP,EAAAA,mBAAyC,OAAzCQ,CAAyC,GAIjC5B,EAAA,KAAK,oBADbyB,EAAAA,YAKEC,EAAAA,QAAA,OAHC,KAAM1B,EAAA,KAAK,KACX,KAAMkB,EAAA,MAAO,SACd,MAAM,eAAA,uDAIRM,EAAAA,mBAAwD,OAAA,CAAjD,MAAKH,EAAAA,eAAEH,EAAA,MAAO,UAAU,CAAA,EAAKW,EAAAA,gBAAA7B,EAAA,KAAK,KAAK,EAAA,CAAA,EAItCA,EAAA,KAAK,SAAWA,OAAK,gBAAoBA,EAAA,gCADjDoB,EAAAA,mBAcS,SAAA,OAZN,uBAAOE,EAAAA,MAAAC,IAAA,+FAAwHxB,EAAM,YAAS,UAAA,QAAA,MAA6CC,EAAA,YAAcA,EAAA,WAAWA,EAAA,KAAK,OAAO,GAAA,aAAA,GAKhO,QAAK8B,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAC,gBAAAC,GAAOhC,EAAA,iBAAiBA,EAAA,KAAK,OAAO,EAAA,CAAA,MAAA,CAAA,EAAA,GAE1CiC,EAAAA,YAIEP,EAAAA,QAAA,CAHC,MAAM1B,EAAA,YAAcA,aAAWA,EAAA,KAAK,OAAO,EAAA,QAC3C,KAAMkB,EAAA,MAAO,SACb,uBAAOlB,EAAA,YAAcA,aAAWA,EAAA,KAAK,OAAO,EAAA,kCAAA,uBAAA,CAAA,uEAQ3CS,EAAA,OAAYC,SAAcV,EAAA,KAAK,UAAY,MAAM,QAAQA,OAAK,QAAQ,GAAKA,EAAA,KAAK,SAAS,OAAM,GAASA,EAAA,MAAK,EAAQA,EAAA,UAD7H2B,EAAAA,UAAA,EAAAP,EAAAA,mBAsBM,MAtBNc,EAsBM,EAlBJP,EAAAA,UAAA,EAAA,EAAAP,EAAAA,mBAiBEe,EAAAA,2BAhByBnC,EAAA,KAAK,SAAQ,CAA9BoC,EAAOC,mBADjBZ,EAAAA,YAiBEa,EAAA,CAfC,IAAKF,EAAM,SAAWA,EAAM,OAASC,EACrC,KAAMD,EACN,MAAOpC,EAAA,MAAK,EACZ,YAAWA,EAAA,SACX,YAAaA,EAAA,YACb,cAAaA,EAAA,WACb,gBAAeA,EAAA,aACf,UAAWA,EAAA,UACX,qBAAoBA,EAAA,iBACpB,cAAaA,EAAA,WACb,UAAWA,EAAA,UACX,qBAAoBA,EAAA,kBACpB,aAAYA,EAAA,UACZ,YAAU8B,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAE,GAAE/B,EAAI,YAAc+B,CAAM,GACpC,eAAaF,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,CAAGS,EAASC,IAAavC,EAAI,eAAiBsC,EAASC,CAAQ,EAAA"}
|
|
1
|
+
{"version":3,"file":"JDynamicMenuItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRouter } from 'vue-router'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport { cn, hasMenuPermission } from '@/lib/utils'\n\n/**\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\n * Recursive Menu Item Component\n * \n * @description\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(\n defineProps<{\n /** 메뉴 아이템 */\n item: SidebarMenuItem\n /** 메뉴 레벨 (들여쓰기용, 0부터 시작) */\n level?: number\n /** 권한 목록 */\n permissions?: MenuPermission[]\n /** 활성화된 메뉴 경로 */\n activePath?: string\n /** 확장된 메뉴 키 목록 */\n expandedKeys?: Set<number | string>\n /** 즐겨찾기 메뉴 키 목록 */\n favorites?: (number | string)[]\n /** 즐겨찾기 변경 핸들러 */\n onFavoriteToggle?: (menuKey: number | string | undefined) => void\n /** 즐겨찾기 확인 함수 */\n isFavorite?: (menuKey: number | string | undefined) => boolean\n /** 스타일 타입 */\n styletype?: StyleType\n /** 추가 CSS 클래스 */\n className?: string\n /** 최대 깊이 제한 (무한 루프 방지, 기본값: 10) */\n maxDepth?: number\n /** 네비게이션 비활성화 (true일 때 router.push 건너뛰고 emit만 수행) */\n disableNavigation?: boolean\n /** 활성화된 메뉴 키 (menuKey 기반 활성화, activePath보다 우선) */\n activeKey?: number | string | null\n }>(),\n {\n level: 0,\n permissions: () => [],\n expandedKeys: () => new Set(),\n favorites: () => [],\n styletype: 'default',\n maxDepth: 10,\n disableNavigation: false,\n activeKey: null,\n },\n)\n\nconst emit = defineEmits<{\n /** 메뉴 클릭 이벤트 */\n menuClick: [event: MenuClickEvent]\n /** 확장 상태 변경 이벤트 */\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\n}>()\n\nconst router = useRouter()\n\n/**\n * 권한 체크 함수\n * Permission check function\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\n */\nconst checkPermission = computed(() => {\n return hasMenuPermission(props.item.menuKey, props.permissions)\n})\n\n/**\n * 메뉴가 활성화되어 있는지 여부\n * activeKey가 제공되면 menuKey 매칭, 아니면 경로 매칭\n */\nconst isActive = computed(() => {\n // activeKey가 제공되면 menuKey 기반 매칭 (우선순위 높음)\n if (props.activeKey !== undefined && props.activeKey !== null) {\n return props.item.menuKey === props.activeKey\n }\n // 경로 기반 매칭 (기본 동작)\n if (!props.item.path || !props.activePath) return false\n return props.activePath === props.item.path\n})\n\n/**\n * 메뉴 타입이 폴더인지 여부\n * 순환 참조 방지: children이 유효한 배열인지 확인\n */\nconst isFolder = computed(() => {\n return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\n})\n\n/**\n * 메뉴가 확장되어 있는지 여부\n */\nconst isExpanded = computed(() => {\n if (!isFolder.value) return false\n const key = props.item.menuKey || props.item.label\n return props.expandedKeys?.has(key) ?? false\n})\n\n/**\n * 메뉴가 비활성화되어 있는지 여부\n */\nconst isDisabled = computed(() => {\n return props.item.disabled || !checkPermission.value\n})\n\n/**\n * 레벨별 들여쓰기 스타일\n * 모든 레벨에서 동일한 padding 사용 (들여쓰기는 컨테이너의 ml로 조절)\n */\nconst indentStyle = computed(() => {\n return { paddingLeft: '8px', paddingRight: '8px' }\n})\n\n\n\n/**\n * 메뉴 클릭 핸들러\n */\nconst handleMenuClick = (event: MouseEvent) => {\n if (isDisabled.value) {\n event.preventDefault()\n return\n }\n\n if (isFolder.value) {\n // 폴더 타입: 확장/축소 토글\n const key = props.item.menuKey || props.item.label\n const newExpanded = !isExpanded.value\n emit('expandChange', key, newExpanded)\n // 폴더도 메뉴 클릭 이벤트 발생\n emit('menuClick', {\n menuItem: props.item,\n path: [props.item],\n event,\n })\n } else {\n // 링크 타입: 라우팅 (disableNavigation이 false일 때만)\n if (!props.disableNavigation && props.item.path) {\n router.push(props.item.path)\n }\n \n // 메뉴 클릭 이벤트 발생\n emit('menuClick', {\n menuItem: props.item,\n path: [props.item], // 단순화된 경로 (필요시 부모 경로 포함하도록 확장 가능)\n event,\n })\n }\n}\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n itemClass: string\n labelClass: string\n iconSize: 'sm' | 'md'\n}> = {\n default: {\n itemClass: 'flex items-center gap-1.5 py-1 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm',\n },\n minimal: {\n itemClass: 'flex items-center gap-1 py-1 rounded-md cursor-pointer transition-colors group',\n labelClass: 'flex-1 truncate text-xs',\n iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * Chevron 아이콘 컴포넌트\n */\nconst ChevronIcon = computed(() => {\n return isExpanded.value ? 'chevronDown' : 'chevronRight'\n})\n\n/**\n * 하위 메뉴 갯수 계산 (재귀적으로 모든 하위 메뉴 포함)\n * 폴더 타입인 경우에만 표시\n */\nconst childrenCount = computed(() => {\n if (!isFolder.value || !Array.isArray(props.item.children)) {\n return 0\n }\n \n const countChildren = (items: SidebarMenuItem[]): number => {\n let count = 0\n for (const item of items) {\n count++ // 현재 아이템 카운트\n if (Array.isArray(item.children) && item.children.length > 0) {\n count += countChildren(item.children) // 재귀적으로 하위 메뉴 카운트\n }\n }\n return count\n }\n \n return countChildren(props.item.children)\n})\n</script>\n\n<template>\n <div :class=\"cn('w-full', className)\">\n <!-- 메뉴 아이템 -->\n <div\n :class=\"cn(\n preset.itemClass,\n {\n 'bg-primary/5 text-primary border-l-2 border-primary shadow-sm': isActive,\n 'hover:bg-accent/50': !isDisabled && !isActive,\n 'opacity-50 cursor-not-allowed': isDisabled,\n 'font-medium': isActive,\n 'font-semibold': isFolder, // 폴더인 경우 볼드체\n }\n )\"\n :style=\"indentStyle\"\n @click=\"handleMenuClick\"\n >\n <!-- Chevron 아이콘 (폴더 타입만, 덜 굵게) -->\n <JIcon\n v-if=\"isFolder\"\n :name=\"ChevronIcon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0 opacity-60\"\n style=\"stroke-width: 1.5;\"\n />\n <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\n\n <!-- 메뉴 아이콘 (폴더가 아닌 경우만 표시) -->\n <JIcon\n v-if=\"item.icon && !isFolder\"\n :name=\"item.icon\"\n :size=\"preset.iconSize\"\n class=\"flex-shrink-0\"\n />\n\n <!-- 메뉴 라벨 -->\n <span :class=\"preset.labelClass\">{{ item.label }}</span>\n \n <!-- 하위 메뉴 갯수 (폴더인 경우만) -->\n <span\n v-if=\"isFolder && childrenCount > 0\"\n :class=\"cn(\n 'text-muted-foreground ml-1 flex-shrink-0',\n props.styletype === 'minimal' ? 'text-[10px]' : 'text-xs'\n )\"\n >\n ({{ childrenCount }})\n </span>\n \n <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\n <button\n v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\n :class=\"cn(\n 'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\n props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\n isFavorite && isFavorite(item.menuKey) && 'opacity-100'\n )\"\n @click.stop=\"onFavoriteToggle(item.menuKey)\"\n >\n <JIcon\n :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\n :size=\"preset.iconSize\"\n :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\n />\n </button>\n </div>\n\n <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\n <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\n <div\n v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\n class=\"border-l border-border/60 ml-[14px] pl-[6px]\"\n >\n <JDynamicMenuItem\n v-for=\"(child, index) in item.children\"\n :key=\"child.menuKey || child.label || index\"\n :item=\"child\"\n :level=\"level + 1\"\n :max-depth=\"maxDepth\"\n :permissions=\"permissions\"\n :active-path=\"activePath\"\n :expanded-keys=\"expandedKeys\"\n :favorites=\"favorites\"\n :on-favorite-toggle=\"onFavoriteToggle\"\n :is-favorite=\"isFavorite\"\n :styletype=\"styletype\"\n :disable-navigation=\"disableNavigation\"\n :active-key=\"activeKey\"\n @menu-click=\"emit('menuClick', $event)\"\n @expand-change=\"(menuKey, expanded) => emit('expandChange', menuKey, expanded)\"\n />\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","childrenCount","countChildren","items","count","item","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_createBlock","JIcon","_openBlock","_hoisted_1","_toDisplayString","_cache","_withModifiers","$event","_createVNode","_hoisted_2","_Fragment","child","index","_component_JDynamicMenuItem","menuKey","expanded"],"mappings":"guBAkBA,MAAMA,EAAQC,EAyCRC,EAAOC,EAOPC,EAASC,EAAAA,UAAA,EAOTC,EAAkBC,EAAAA,SAAS,IACxBC,EAAAA,kBAAkBR,EAAM,KAAK,QAASA,EAAM,WAAW,CAC/D,EAMKS,EAAWF,EAAAA,SAAS,IAEpBP,EAAM,YAAc,QAAaA,EAAM,YAAc,KAChDA,EAAM,KAAK,UAAYA,EAAM,UAGlC,CAACA,EAAM,KAAK,MAAQ,CAACA,EAAM,WAAmB,GAC3CA,EAAM,aAAeA,EAAM,KAAK,IACxC,EAMKU,EAAWH,EAAAA,SAAS,IACjBP,EAAM,KAAK,WAAa,KAAQ,MAAM,QAAQA,EAAM,KAAK,QAAQ,GAAKA,EAAM,KAAK,SAAS,OAAS,CAC3G,EAKKW,EAAaJ,EAAAA,SAAS,IAAM,CAChC,GAAI,CAACG,EAAS,MAAO,MAAO,GAC5B,MAAME,EAAMZ,EAAM,KAAK,SAAWA,EAAM,KAAK,MAC7C,OAAOA,EAAM,cAAc,IAAIY,CAAG,GAAK,EACzC,CAAC,EAKKC,EAAaN,EAAAA,SAAS,IACnBP,EAAM,KAAK,UAAY,CAACM,EAAgB,KAChD,EAMKQ,EAAcP,EAAAA,SAAS,KACpB,CAAE,YAAa,MAAO,aAAc,KAAA,EAC5C,EAOKQ,EAAmBC,GAAsB,CAC7C,GAAIH,EAAW,MAAO,CACpBG,EAAM,eAAA,EACN,MACF,CAEA,GAAIN,EAAS,MAAO,CAElB,MAAME,EAAMZ,EAAM,KAAK,SAAWA,EAAM,KAAK,MACvCiB,EAAc,CAACN,EAAW,MAChCT,EAAK,eAAgBU,EAAKK,CAAW,EAErCf,EAAK,YAAa,CAChB,SAAUF,EAAM,KAChB,KAAM,CAACA,EAAM,IAAI,EACjB,MAAAgB,CAAA,CACD,CACH,KAEM,CAAChB,EAAM,mBAAqBA,EAAM,KAAK,MACzCI,EAAO,KAAKJ,EAAM,KAAK,IAAI,EAI7BE,EAAK,YAAa,CAChB,SAAUF,EAAM,KAChB,KAAM,CAACA,EAAM,IAAI,EACjB,MAAAgB,CAAA,CACD,CAEL,EAKME,EAID,CACH,QAAS,CACP,UAAW,mFACX,WAAY,0BACZ,SAAU,IAAA,EAEZ,QAAS,CACP,UAAW,iFACX,WAAY,0BACZ,SAAU,IAAA,CACZ,EAGIC,EAASZ,EAAAA,SAAS,IACfW,EAAclB,EAAM,SAAS,GAAKkB,EAAc,OACxD,EAKKE,EAAcb,EAAAA,SAAS,IACpBI,EAAW,MAAQ,cAAgB,cAC3C,EAMKU,EAAgBd,EAAAA,SAAS,IAAM,CACnC,GAAI,CAACG,EAAS,OAAS,CAAC,MAAM,QAAQV,EAAM,KAAK,QAAQ,EACvD,MAAO,GAGT,MAAMsB,EAAiBC,GAAqC,CAC1D,IAAIC,EAAQ,EACZ,UAAWC,KAAQF,EACjBC,IACI,MAAM,QAAQC,EAAK,QAAQ,GAAKA,EAAK,SAAS,OAAS,IACzDD,GAASF,EAAcG,EAAK,QAAQ,GAGxC,OAAOD,CACT,EAEA,OAAOF,EAActB,EAAM,KAAK,QAAQ,CAC1C,CAAC,uFAIC0B,EAAAA,mBA2FM,MAAA,CA3FA,MAAKC,EAAAA,eAAEC,QAAAC,EAAAA,EAAA,EAAE,SAAW5B,EAAA,SAAS,CAAA,CAAA,GAEjC6B,EAAAA,mBA8DM,MAAA,CA7DH,uBAAOF,EAAAA,MAAAC,IAAA,EAAYV,EAAA,MAAO,2EAAgGV,EAAA,MAA2C,qBAAA,CAAAI,EAAA,QAAeJ,EAAA,sCAAqDI,EAAA,oBAAqCJ,EAAA,sBAAqCC,EAAA,KAAA,IAUnT,uBAAOI,EAAA,KAAW,EAClB,QAAOC,CAAA,GAIAL,EAAA,qBADRqB,EAAAA,YAMEC,EAAAA,QAAA,OAJC,KAAMZ,EAAA,MACN,KAAMD,EAAA,MAAO,SACd,MAAM,2BACN,MAAA,CAAA,eAAA,KAAA,CAAA,4BAEFc,EAAAA,YAAAP,EAAAA,mBAAyC,OAAzCQ,CAAyC,GAIjCjC,EAAA,KAAK,MAAI,CAAKS,EAAA,qBADtBqB,EAAAA,YAKEC,UAAA,OAHC,KAAM/B,EAAA,KAAK,KACX,KAAMkB,EAAA,MAAO,SACd,MAAM,eAAA,uDAIRW,EAAAA,mBAAwD,OAAA,CAAjD,MAAKH,EAAAA,eAAER,EAAA,MAAO,UAAU,CAAA,EAAKgB,EAAAA,gBAAAlC,EAAA,KAAK,KAAK,EAAA,CAAA,EAItCS,EAAA,OAAYW,EAAA,MAAa,iBADjCK,EAAAA,mBAQO,OAAA,OANJ,uBAAOE,EAAAA,MAAAC,IAAA,6CAAoE7B,EAAM,YAAS,UAAA,cAAA,SAAA,IAI5F,KACEmC,EAAAA,gBAAGd,EAAA,KAAa,EAAG,KACtB,CAAA,+BAIQpB,EAAA,KAAK,SAAWA,OAAK,gBAAoBA,EAAA,gCADjDyB,EAAAA,mBAcS,SAAA,OAZN,uBAAOE,EAAAA,MAAAC,IAAA,+FAAsH7B,EAAM,YAAS,UAAA,QAAA,MAA4CC,EAAA,YAAcA,EAAA,WAAWA,EAAA,KAAK,OAAO,GAAA,aAAA,GAK7N,QAAKmC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAC,gBAAAC,GAAOrC,EAAA,iBAAiBA,EAAA,KAAK,OAAO,EAAA,CAAA,MAAA,CAAA,EAAA,GAE1CsC,EAAAA,YAIEP,EAAAA,QAAA,CAHC,MAAM/B,EAAA,YAAcA,aAAWA,EAAA,KAAK,OAAO,EAAA,QAC3C,KAAMkB,EAAA,MAAO,SACb,uBAAOlB,EAAA,YAAcA,aAAWA,EAAA,KAAK,OAAO,EAAA,kCAAA,uBAAA,CAAA,uEAQ3CS,EAAA,OAAYC,SAAcV,EAAA,KAAK,UAAY,MAAM,QAAQA,OAAK,QAAQ,GAAKA,EAAA,KAAK,SAAS,OAAM,GAASA,EAAA,MAAK,EAAQA,EAAA,UAD7HgC,EAAAA,UAAA,EAAAP,EAAAA,mBAsBM,MAtBNc,EAsBM,EAlBJP,EAAAA,UAAA,EAAA,EAAAP,EAAAA,mBAiBEe,EAAAA,2BAhByBxC,EAAA,KAAK,SAAQ,CAA9ByC,EAAOC,mBADjBZ,EAAAA,YAiBEa,EAAA,CAfC,IAAKF,EAAM,SAAWA,EAAM,OAASC,EACrC,KAAMD,EACN,MAAOzC,EAAA,MAAK,EACZ,YAAWA,EAAA,SACX,YAAaA,EAAA,YACb,cAAaA,EAAA,WACb,gBAAeA,EAAA,aACf,UAAWA,EAAA,UACX,qBAAoBA,EAAA,iBACpB,cAAaA,EAAA,WACb,UAAWA,EAAA,UACX,qBAAoBA,EAAA,kBACpB,aAAYA,EAAA,UACZ,YAAUmC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAE,GAAEpC,EAAI,YAAcoC,CAAM,GACpC,eAAaF,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,CAAGS,EAASC,IAAa5C,EAAI,eAAiB2C,EAASC,CAAQ,EAAA"}
|