ketekny-ui-kit 1.0.84 → 1.0.85
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/package.json +1 -1
- package/src/layout/twoColLayout.vue +36 -10
- package/src/layout/twoColSidebarMenuList.vue +112 -0
- package/src/layout/twoColSidebarPanel.vue +308 -102
- package/src/ui/kSearch.vue +7 -2
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="relative flex min-h-screen flex-col bg-[#edf4fb] lg:h-screen lg:flex-row lg:overflow-
|
|
3
|
+
class="relative flex min-h-screen flex-col bg-[#edf4fb] lg:h-screen lg:flex-row lg:overflow-visible"
|
|
4
4
|
>
|
|
5
5
|
<button
|
|
6
6
|
v-show="isDesktopViewport && isSidebarCollapsed"
|
|
7
7
|
type="button"
|
|
8
8
|
:aria-label="expandLabel"
|
|
9
9
|
v-tooltip.right="expandLabel"
|
|
10
|
-
class="fixed top-0 z-[70] hidden h-10 w-10 items-center justify-center rounded-r-full border-white/15 bg-[#
|
|
10
|
+
class="fixed top-0 z-[70] hidden h-10 w-10 items-center justify-center rounded-r-full border-white/15 bg-[#0b0c0f] text-white shadow-2xl shadow-black/50 transition-all duration-300 hover:bg-[#14161b] lg:left-[42px] lg:translate-x-full lg:flex"
|
|
11
11
|
@click="toggleSidebar"
|
|
12
12
|
>
|
|
13
13
|
<slot name="collapsed-toggle-icon">
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
type="button"
|
|
21
21
|
:aria-label="collapseLabel"
|
|
22
22
|
v-tooltip.left="collapseLabel"
|
|
23
|
-
class="fixed top-0 z-[70] hidden h-10 w-10 items-center justify-center rounded-r-full border-white/15 bg-[#
|
|
23
|
+
class="fixed top-0 z-[70] hidden h-10 w-10 items-center justify-center rounded-r-full border-white/15 bg-[#0b0c0f] text-white shadow-2xl shadow-black/50 transition-all duration-300 hover:bg-[#14161b] lg:left-[326px] lg:translate-x-full lg:flex"
|
|
24
24
|
@click.stop="toggleSidebar"
|
|
25
25
|
>
|
|
26
26
|
<slot name="toggle-icon">
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
:contact-email="contactEmail"
|
|
46
46
|
:help-url="helpUrl"
|
|
47
47
|
:collapsed="isSidebarCollapsed"
|
|
48
|
+
:show-menu-search="showMenuSearch"
|
|
49
|
+
:menu-search-placeholder="menuSearchPlaceholder"
|
|
48
50
|
@logo-click="handleLogoClick"
|
|
49
51
|
@signin="handleSignin"
|
|
50
52
|
@signup="handleSignup"
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
<button
|
|
61
63
|
v-if="!isDesktopViewport && !mobileMenuOpen"
|
|
62
64
|
type="button"
|
|
63
|
-
class="fixed bottom-4 left-4 z-[80] flex h-14 w-14 items-center justify-center rounded-full bg-[#
|
|
65
|
+
class="fixed bottom-4 left-4 z-[80] flex h-14 w-14 items-center justify-center rounded-full bg-[#0b0c0f] text-white shadow-2xl shadow-black/40 transition-transform duration-200 hover:scale-105 hover:bg-[#14161b] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
|
|
64
66
|
aria-label="Open menu"
|
|
65
67
|
@click="mobileMenuOpen = true"
|
|
66
68
|
>
|
|
@@ -98,6 +100,8 @@
|
|
|
98
100
|
:contact-email="contactEmail"
|
|
99
101
|
:help-url="helpUrl"
|
|
100
102
|
:collapsed="false"
|
|
103
|
+
:show-menu-search="showMenuSearch"
|
|
104
|
+
:menu-search-placeholder="menuSearchPlaceholder"
|
|
101
105
|
@logo-click="handleLogoClick"
|
|
102
106
|
@item-click="closeMobileMenu"
|
|
103
107
|
@signin="handleSignin"
|
|
@@ -158,6 +162,11 @@ export default {
|
|
|
158
162
|
account: { type: Object, default: null },
|
|
159
163
|
contactEmail: { type: String, default: "grammateia@ketekny.gr" },
|
|
160
164
|
helpUrl: { type: String, default: "" },
|
|
165
|
+
showMenuSearch: { type: Boolean, default: false },
|
|
166
|
+
menuSearchPlaceholder: {
|
|
167
|
+
type: String,
|
|
168
|
+
default: "Αναζήτηση...",
|
|
169
|
+
},
|
|
161
170
|
},
|
|
162
171
|
data() {
|
|
163
172
|
return {
|
|
@@ -281,17 +290,34 @@ export default {
|
|
|
281
290
|
},
|
|
282
291
|
computed: {
|
|
283
292
|
normalizedMainMenu() {
|
|
284
|
-
|
|
293
|
+
const normalizeItem = (item, index, parentKey = "menu") => {
|
|
285
294
|
const isExternal = Boolean(item?.href);
|
|
286
295
|
const isLink = Boolean(item?.to);
|
|
296
|
+
const children = Array.isArray(item?.children)
|
|
297
|
+
? item.children.map((child, childIndex) =>
|
|
298
|
+
normalizeItem(
|
|
299
|
+
child,
|
|
300
|
+
childIndex,
|
|
301
|
+
item?.key || item?.label || `${parentKey}-${index}`
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
: [];
|
|
305
|
+
const hasActiveChild = children.some(
|
|
306
|
+
child => child.active || child.hasActiveChild
|
|
307
|
+
);
|
|
287
308
|
const active = Boolean(item?.active);
|
|
309
|
+
const key = item?.key || item?.label || `${parentKey}-${index}`;
|
|
288
310
|
|
|
289
311
|
return {
|
|
290
|
-
key
|
|
312
|
+
key,
|
|
291
313
|
label: item?.label || "",
|
|
292
314
|
description: item?.description || "",
|
|
293
315
|
icon: item?.icon || "Dot",
|
|
294
316
|
active,
|
|
317
|
+
hasActiveChild,
|
|
318
|
+
hasChildren: children.length > 0,
|
|
319
|
+
children,
|
|
320
|
+
defaultExpanded: Boolean(item?.defaultExpanded),
|
|
295
321
|
isExternal,
|
|
296
322
|
isLink,
|
|
297
323
|
bindings: isLink
|
|
@@ -311,11 +337,11 @@ export default {
|
|
|
311
337
|
type: "button",
|
|
312
338
|
title: item.title || item.label,
|
|
313
339
|
},
|
|
314
|
-
classes:
|
|
315
|
-
? "border-white/70 bg-white/90 text-slate-950 shadow-lg shadow-black/20"
|
|
316
|
-
: "border-white/10 bg-white/5 text-white hover:border-cyan-400/40 hover:bg-white/10",
|
|
340
|
+
classes: "",
|
|
317
341
|
};
|
|
318
|
-
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
return this.mainMenu.map((item, index) => normalizeItem(item, index));
|
|
319
345
|
},
|
|
320
346
|
},
|
|
321
347
|
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="space-y-1.5">
|
|
3
|
+
<div v-for="item in items" :key="item.key" class="space-y-1.5">
|
|
4
|
+
<div
|
|
5
|
+
class="flex items-stretch"
|
|
6
|
+
:class="item.hasChildren ? 'gap-2' : 'gap-0'"
|
|
7
|
+
>
|
|
8
|
+
<component
|
|
9
|
+
:is="menuItemComponent(item)"
|
|
10
|
+
v-bind="item.bindings"
|
|
11
|
+
class="flex min-w-0 flex-1 appearance-none items-center gap-3 border-0 bg-transparent px-2 py-1.5 text-left outline-none transition-all duration-150"
|
|
12
|
+
:class="itemRowClasses(item)"
|
|
13
|
+
@click="handleItemClick(item, $event)"
|
|
14
|
+
>
|
|
15
|
+
<span
|
|
16
|
+
class="flex h-5 w-5 shrink-0 items-center justify-center text-[#eef2f7] transition-colors duration-150"
|
|
17
|
+
>
|
|
18
|
+
<kIcon :name="item.icon" size="18" color="#eef2f7" />
|
|
19
|
+
</span>
|
|
20
|
+
<span class="min-w-0 flex-1">
|
|
21
|
+
<span class="block truncate text-[15px] font-medium leading-5">
|
|
22
|
+
{{ item.label }}
|
|
23
|
+
</span>
|
|
24
|
+
<span
|
|
25
|
+
v-if="item.description"
|
|
26
|
+
class="mt-1 block truncate text-[12px] leading-4"
|
|
27
|
+
:class="item.active ? 'text-[#9fa6b5]' : 'text-[#6f7786]'"
|
|
28
|
+
>
|
|
29
|
+
{{ item.description }}
|
|
30
|
+
</span>
|
|
31
|
+
</span>
|
|
32
|
+
</component>
|
|
33
|
+
|
|
34
|
+
<button
|
|
35
|
+
v-if="item.hasChildren"
|
|
36
|
+
type="button"
|
|
37
|
+
class="flex w-8 shrink-0 appearance-none items-center justify-center border-0 bg-transparent text-[#eef2f7] outline-none transition hover:bg-white/6 hover:text-white"
|
|
38
|
+
:aria-expanded="isExpanded(item)"
|
|
39
|
+
:aria-label="`${isExpanded(item) ? 'Collapse' : 'Expand'} ${item.label}`"
|
|
40
|
+
@click.stop="toggle(item.key)"
|
|
41
|
+
>
|
|
42
|
+
<kIcon
|
|
43
|
+
name="ChevronDown"
|
|
44
|
+
size="15"
|
|
45
|
+
color="#eef2f7"
|
|
46
|
+
class="transition-transform duration-200"
|
|
47
|
+
:class="isExpanded(item) ? 'rotate-180' : ''"
|
|
48
|
+
/>
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
v-if="item.hasChildren && isExpanded(item)"
|
|
54
|
+
class="ml-2 border-l border-[#242834] pl-4"
|
|
55
|
+
>
|
|
56
|
+
<TwoColSidebarMenuList
|
|
57
|
+
:items="item.children"
|
|
58
|
+
:expanded-state="expandedState"
|
|
59
|
+
@item-click="$emit('item-click', $event)"
|
|
60
|
+
@toggle-branch="$emit('toggle-branch', $event)"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script>
|
|
68
|
+
import { RouterLink } from "vue-router";
|
|
69
|
+
import kIcon from "../ui/kIcon.vue";
|
|
70
|
+
|
|
71
|
+
export default {
|
|
72
|
+
name: "TwoColSidebarMenuList",
|
|
73
|
+
components: {
|
|
74
|
+
RouterLink,
|
|
75
|
+
kIcon,
|
|
76
|
+
},
|
|
77
|
+
props: {
|
|
78
|
+
items: { type: Array, default: () => [] },
|
|
79
|
+
expandedState: { type: Object, required: true },
|
|
80
|
+
},
|
|
81
|
+
emits: ["item-click", "toggle-branch"],
|
|
82
|
+
methods: {
|
|
83
|
+
menuItemComponent(item) {
|
|
84
|
+
if (item?.isLink) return RouterLink;
|
|
85
|
+
if (item?.isExternal) return "a";
|
|
86
|
+
return "button";
|
|
87
|
+
},
|
|
88
|
+
handleItemClick(item, event) {
|
|
89
|
+
event?.stopPropagation?.();
|
|
90
|
+
|
|
91
|
+
if (item?.hasChildren) {
|
|
92
|
+
event?.preventDefault?.();
|
|
93
|
+
this.toggle(item.key);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.$emit("item-click", item);
|
|
98
|
+
},
|
|
99
|
+
isExpanded(item) {
|
|
100
|
+
return Boolean(this.expandedState[item.key]);
|
|
101
|
+
},
|
|
102
|
+
itemRowClasses(item) {
|
|
103
|
+
return item.active
|
|
104
|
+
? "relative rounded-xl bg-[#242424] text-white before:absolute before:bottom-2 before:left-0 before:top-2 before:w-[3px] before:rounded-r-full before:bg-[#2491ff]"
|
|
105
|
+
: "rounded-xl text-[#e5e7eb] hover:bg-[#242424] hover:text-white";
|
|
106
|
+
},
|
|
107
|
+
toggle(key) {
|
|
108
|
+
this.$emit("toggle-branch", key);
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="flex h-full w-full flex-col overflow-
|
|
3
|
+
class="flex h-full w-full flex-col overflow-visible border-r border-[#171a21] bg-[#0b0c0f] text-white shadow-[20px_0_50px_rgba(0,0,0,0.35)] transition-all duration-300 ease-out lg:relative lg:z-30 lg:will-change-transform"
|
|
4
4
|
>
|
|
5
5
|
<div class="relative">
|
|
6
6
|
<button
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
class="px-4 py-4 transition-all duration-300 cursor-pointer"
|
|
17
17
|
/>
|
|
18
18
|
</button>
|
|
19
|
+
|
|
19
20
|
<div
|
|
20
21
|
v-if="title && !collapsed"
|
|
21
22
|
class="py-3 text-xl font-bold text-center"
|
|
@@ -23,81 +24,94 @@
|
|
|
23
24
|
{{ title }}
|
|
24
25
|
</div>
|
|
25
26
|
|
|
26
|
-
<div v-if="helpUrl && !collapsed" class="px-4 pb-2">
|
|
27
|
-
<a
|
|
28
|
-
:href="helpUrl"
|
|
29
|
-
target="_blank"
|
|
30
|
-
rel="noreferrer"
|
|
31
|
-
class="inline-flex items-center gap-2 rounded-md px-2 py-1.5 text-sm font-semibold text-white/85 transition hover:bg-white/10 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
|
|
32
|
-
>
|
|
33
|
-
<kIcon name="HelpCircle" size="16" color="#ffffff" />
|
|
34
|
-
<span>Χρειάζεστε βοήθεια;</span>
|
|
35
|
-
</a>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
27
|
<slot name="sidebar-header" />
|
|
39
28
|
</div>
|
|
40
29
|
|
|
41
|
-
<div class="flex-1 pt-
|
|
42
|
-
<div v-if="collapsed" class="
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
:
|
|
47
|
-
|
|
48
|
-
class="
|
|
49
|
-
|
|
50
|
-
v-tooltip.right="item.label"
|
|
51
|
-
@click="$emit('item-click', item)"
|
|
52
|
-
>
|
|
53
|
-
<kIcon
|
|
54
|
-
:name="item.icon"
|
|
55
|
-
size="20"
|
|
56
|
-
:color="item.active ? '#0f172a' : '#ffffff'"
|
|
57
|
-
/>
|
|
58
|
-
</component>
|
|
30
|
+
<div class="min-h-0 flex-1 overflow-y-auto px-3 pb-3 pt-2">
|
|
31
|
+
<div v-if="showMenuSearch && !collapsed" class="mb-3 px-1">
|
|
32
|
+
<kSearch
|
|
33
|
+
v-model="menuSearchTerm"
|
|
34
|
+
:debounce="0"
|
|
35
|
+
:placeholder="menuSearchPlaceholder"
|
|
36
|
+
input-class="border-[#242834] bg-[#1f1f1f] text-[#eef2f7] placeholder:text-[#8a92a1] shadow-none focus:border-[#3b82f6] focus:ring-[#3b82f6]/20"
|
|
37
|
+
icon-class="text-[#8a92a1]"
|
|
38
|
+
/>
|
|
59
39
|
</div>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
40
|
+
|
|
41
|
+
<div
|
|
42
|
+
v-if="collapsed"
|
|
43
|
+
class="mt-2 flex flex-col items-center gap-1.5"
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
63
46
|
v-for="item in menuItems"
|
|
64
47
|
:key="item.key"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@click="$emit('item-click', item)"
|
|
48
|
+
class="relative"
|
|
49
|
+
@mouseenter="handleCollapsedItemEnter(item, $event)"
|
|
50
|
+
@mouseleave="handleCollapsedItemLeave(item)"
|
|
69
51
|
>
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
52
|
+
<component
|
|
53
|
+
:is="menuItemComponent(item)"
|
|
54
|
+
v-bind="item.bindings"
|
|
55
|
+
class="relative flex h-11 w-11 items-center justify-center rounded-lg border border-transparent text-[#b3b9c6] transition hover:bg-white/6"
|
|
56
|
+
:class="item.classes"
|
|
57
|
+
:title="collapsedItemTooltip(item)"
|
|
58
|
+
@mouseenter="handleCollapsedItemEnter(item, $event)"
|
|
59
|
+
@click="handleCollapsedItemClick(item, $event)"
|
|
79
60
|
>
|
|
80
|
-
<kIcon
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
61
|
+
<kIcon
|
|
62
|
+
:name="item.icon"
|
|
63
|
+
size="18"
|
|
64
|
+
:color="item.active ? '#ffffff' : '#b3b9c6'"
|
|
65
|
+
/>
|
|
66
|
+
</component>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div v-else-if="menuItems.length" class="mt-2">
|
|
70
|
+
<TwoColSidebarMenuList
|
|
71
|
+
:items="filteredMenuItems"
|
|
72
|
+
:expanded-state="expandedBranches"
|
|
73
|
+
@item-click="$emit('item-click', $event)"
|
|
74
|
+
@toggle-branch="toggleBranch"
|
|
75
|
+
/>
|
|
95
76
|
</div>
|
|
96
77
|
</div>
|
|
97
78
|
|
|
79
|
+
<Teleport to="body">
|
|
80
|
+
<div
|
|
81
|
+
v-if="collapsedFlyoutItem"
|
|
82
|
+
class="fixed z-[200]"
|
|
83
|
+
:style="collapsedFlyoutStyle"
|
|
84
|
+
@mouseenter="handleCollapsedItemEnter(collapsedFlyoutItem)"
|
|
85
|
+
@mouseleave="handleCollapsedItemLeave(collapsedFlyoutItem)"
|
|
86
|
+
>
|
|
87
|
+
<div
|
|
88
|
+
class="w-[320px] rounded-2xl border border-[#242834] bg-[#0f1116] p-3 shadow-[0_24px_60px_rgba(0,0,0,0.45)]"
|
|
89
|
+
>
|
|
90
|
+
<div class="mb-2 px-2">
|
|
91
|
+
<div class="text-sm font-semibold text-white">
|
|
92
|
+
{{ collapsedFlyoutItem.label }}
|
|
93
|
+
</div>
|
|
94
|
+
<div
|
|
95
|
+
v-if="collapsedFlyoutItem.description"
|
|
96
|
+
class="mt-0.5 text-xs text-[#7f8794]"
|
|
97
|
+
>
|
|
98
|
+
{{ collapsedFlyoutItem.description }}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<TwoColSidebarMenuList
|
|
103
|
+
:items="collapsedFlyoutItem.children"
|
|
104
|
+
:expanded-state="expandedBranches"
|
|
105
|
+
@item-click="$emit('item-click', $event)"
|
|
106
|
+
@toggle-branch="toggleBranch"
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</Teleport>
|
|
111
|
+
|
|
98
112
|
<div
|
|
99
113
|
v-if="account"
|
|
100
|
-
class="
|
|
114
|
+
class="shrink-0 border-t border-[#171a21] px-3 pb-4 pt-3 text-white/90"
|
|
101
115
|
>
|
|
102
116
|
<div v-if="collapsed" class="flex flex-col gap-3">
|
|
103
117
|
<div v-if="helpUrl" class="flex flex-col items-center gap-2">
|
|
@@ -105,66 +119,98 @@
|
|
|
105
119
|
:href="helpUrl"
|
|
106
120
|
target="_blank"
|
|
107
121
|
rel="noreferrer"
|
|
108
|
-
aria-label="
|
|
109
|
-
title="
|
|
110
|
-
class="relative flex items-center justify-center
|
|
111
|
-
v-tooltip.right="'
|
|
122
|
+
aria-label="Βοήθεια"
|
|
123
|
+
title="Βοήθεια"
|
|
124
|
+
class="relative flex h-11 w-11 items-center justify-center rounded-lg border border-transparent text-[#b3b9c6] transition hover:bg-white/6 hover:text-white"
|
|
125
|
+
v-tooltip.right="'Βοήθεια'"
|
|
112
126
|
>
|
|
113
|
-
<kIcon name="HelpCircle" size="
|
|
127
|
+
<kIcon name="HelpCircle" size="18" color="#b3b9c6" />
|
|
114
128
|
</a>
|
|
115
129
|
</div>
|
|
116
130
|
<div v-if="account.loggedIn" class="flex flex-col items-center gap-2">
|
|
117
131
|
<button
|
|
118
132
|
type="button"
|
|
119
|
-
class="relative flex items-center justify-center
|
|
133
|
+
class="relative flex h-11 w-11 items-center justify-center rounded-lg border border-transparent text-[#b3b9c6] transition hover:bg-white/6 hover:text-white"
|
|
120
134
|
:title="editProfileLabel"
|
|
121
135
|
v-tooltip.right="editProfileLabel"
|
|
122
136
|
@click="$emit('edit-profile')"
|
|
123
137
|
>
|
|
124
|
-
<kIcon name="UserRoundCog" size="
|
|
138
|
+
<kIcon name="UserRoundCog" size="18" color="#b3b9c6" />
|
|
125
139
|
</button>
|
|
126
140
|
<button
|
|
127
141
|
type="button"
|
|
128
|
-
class="group relative flex h-
|
|
142
|
+
class="group relative flex h-11 w-11 items-center justify-center rounded-lg border border-transparent text-[#b3b9c6] transition hover:bg-white/6 hover:text-white"
|
|
129
143
|
:title="signOutLabel"
|
|
130
144
|
v-tooltip.right="signOutLabel"
|
|
131
145
|
@click="$emit('signout')"
|
|
132
146
|
>
|
|
133
|
-
<kIcon name="LogOut" size="
|
|
147
|
+
<kIcon name="LogOut" size="18" color="#b3b9c6" />
|
|
134
148
|
</button>
|
|
135
149
|
</div>
|
|
136
150
|
<div v-else class="flex flex-col items-center gap-2">
|
|
137
151
|
<button
|
|
138
152
|
type="button"
|
|
139
|
-
class="relative flex items-center justify-center
|
|
153
|
+
class="relative flex h-11 w-11 items-center justify-center rounded-lg border border-transparent text-[#b3b9c6] transition hover:bg-white/6 hover:text-white"
|
|
140
154
|
:title="signInLabel"
|
|
141
155
|
v-tooltip.right="signInLabel"
|
|
142
156
|
@click="$emit('signin')"
|
|
143
157
|
>
|
|
144
|
-
<kIcon name="LogIn" size="
|
|
158
|
+
<kIcon name="LogIn" size="18" color="#b3b9c6" />
|
|
145
159
|
</button>
|
|
146
160
|
<button
|
|
147
161
|
type="button"
|
|
148
|
-
class="relative flex items-center justify-center
|
|
162
|
+
class="relative flex h-11 w-11 items-center justify-center rounded-lg border border-transparent text-[#b3b9c6] transition hover:bg-white/6 hover:text-white"
|
|
149
163
|
:title="signUpLabel"
|
|
150
164
|
v-tooltip.right="signUpLabel"
|
|
151
165
|
@click="$emit('signup')"
|
|
152
166
|
>
|
|
153
|
-
<kIcon name="UserPlus" size="
|
|
167
|
+
<kIcon name="UserPlus" size="18" color="#b3b9c6" />
|
|
154
168
|
</button>
|
|
155
169
|
</div>
|
|
156
170
|
</div>
|
|
157
|
-
<div v-else class="flex flex-col
|
|
158
|
-
<div
|
|
171
|
+
<div v-else class="flex flex-col px-1">
|
|
172
|
+
<div
|
|
173
|
+
v-if="helpUrl || account.loggedIn"
|
|
174
|
+
>
|
|
175
|
+
<a
|
|
176
|
+
v-if="helpUrl"
|
|
177
|
+
:href="helpUrl"
|
|
178
|
+
target="_blank"
|
|
179
|
+
rel="noreferrer"
|
|
180
|
+
class="flex w-full items-center gap-3 rounded-xl px-2 py-2 text-lg font-medium text-[#d7dbe3] transition hover:bg-[#242424] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
|
|
181
|
+
>
|
|
182
|
+
<kIcon name="HelpCircle" size="16" color="#d7dbe3" />
|
|
183
|
+
<span>Βοήθεια και υποστήριξη</span>
|
|
184
|
+
</a>
|
|
185
|
+
<button
|
|
186
|
+
v-if="account.loggedIn"
|
|
187
|
+
type="button"
|
|
188
|
+
class="flex w-full items-center gap-3 rounded-xl px-2 py-2 text-left text-lg font-medium text-[#d7dbe3] transition hover:bg-[#242424] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
|
|
189
|
+
@click="$emit('edit-profile')"
|
|
190
|
+
>
|
|
191
|
+
<kIcon name="UserRoundCog" size="16" color="#d7dbe3" />
|
|
192
|
+
<span>Το προφίλ μου</span>
|
|
193
|
+
</button>
|
|
194
|
+
<button
|
|
195
|
+
v-if="account.loggedIn"
|
|
196
|
+
type="button"
|
|
197
|
+
class="flex w-full items-center gap-3 rounded-xl px-2 py-2 text-left text-lg font-medium text-[#f59e0b] transition hover:bg-[#3a1f12] hover:text-[#fbbf24] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#f59e0b]/40"
|
|
198
|
+
@click="$emit('signout')"
|
|
199
|
+
>
|
|
200
|
+
<kIcon name="LogOut" size="16" color="#f59e0b" />
|
|
201
|
+
<span>Αποσύνδεση</span>
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
<div v-if="!account.loggedIn" class="min-w-0">
|
|
159
205
|
<p
|
|
160
206
|
v-if="account.loggedIn"
|
|
161
|
-
class="mt-
|
|
207
|
+
class="mt-1 truncate text-sm font-semibold text-white"
|
|
162
208
|
>
|
|
163
209
|
{{ accountDisplayName }}
|
|
164
210
|
</p>
|
|
165
211
|
<p
|
|
166
212
|
v-if="account.loggedIn && displayEmail"
|
|
167
|
-
class="text-
|
|
213
|
+
class="truncate text-xs text-[#7c8392]"
|
|
168
214
|
>
|
|
169
215
|
{{ displayEmail }}
|
|
170
216
|
</p>
|
|
@@ -184,33 +230,19 @@
|
|
|
184
230
|
:label="signUpLabel"
|
|
185
231
|
@click="$emit('signup')"
|
|
186
232
|
/>
|
|
187
|
-
<kButton
|
|
188
|
-
v-if="account.loggedIn"
|
|
189
|
-
variant="soft"
|
|
190
|
-
icon="UserRoundCog"
|
|
191
|
-
:label="editProfileLabel"
|
|
192
|
-
@click="$emit('edit-profile')"
|
|
193
|
-
/>
|
|
194
|
-
<kButton
|
|
195
|
-
v-if="account.loggedIn"
|
|
196
|
-
icon="LogOut"
|
|
197
|
-
warning
|
|
198
|
-
:label="signOutLabel"
|
|
199
|
-
@click="$emit('signout')"
|
|
200
|
-
/>
|
|
201
233
|
</div>
|
|
202
234
|
</div>
|
|
203
235
|
</div>
|
|
204
236
|
|
|
205
237
|
<div
|
|
206
238
|
v-if="!collapsed"
|
|
207
|
-
class="border-t
|
|
239
|
+
class="shrink-0 border-t border-[#171a21] p-3 text-[#8c93a1]"
|
|
208
240
|
>
|
|
209
|
-
<div v-if="showContactInfo" class="flex flex-col gap-2 px-
|
|
210
|
-
<p class="text-
|
|
241
|
+
<div v-if="showContactInfo" class="flex flex-col gap-2 px-1">
|
|
242
|
+
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#666d79]">
|
|
211
243
|
KETEKNY AE
|
|
212
244
|
</p>
|
|
213
|
-
<div class="flex flex-wrap items-center
|
|
245
|
+
<div class="flex flex-wrap items-center gap-x-3 text-xs">
|
|
214
246
|
<span class="whitespace-nowrap">{{ contactAddressLabel }}</span>
|
|
215
247
|
<span class="whitespace-nowrap">210 3648 337</span>
|
|
216
248
|
<span class="whitespace-nowrap">
|
|
@@ -232,10 +264,7 @@
|
|
|
232
264
|
</div>
|
|
233
265
|
</div>
|
|
234
266
|
|
|
235
|
-
<div
|
|
236
|
-
v-if="showSponsors"
|
|
237
|
-
class="mt-2 flex flex-col gap-3 bg-[#0d4b87] px-4 py-1"
|
|
238
|
-
>
|
|
267
|
+
<div v-if="showSponsors" class="mt-2 -mx-3 flex flex-col gap-3 bg-[#0d4b87] px-4 py-1">
|
|
239
268
|
<div>
|
|
240
269
|
<img
|
|
241
270
|
src="https://s3.ketekny.gr/public/web-apps/sponsors/Greece 2.0_NextGeneration_White_gr.png"
|
|
@@ -259,6 +288,8 @@
|
|
|
259
288
|
import { RouterLink } from "vue-router";
|
|
260
289
|
import kButton from "../ui/kButton.vue";
|
|
261
290
|
import kIcon from "../ui/kIcon.vue";
|
|
291
|
+
import kSearch from "../ui/kSearch.vue";
|
|
292
|
+
import TwoColSidebarMenuList from "./twoColSidebarMenuList.vue";
|
|
262
293
|
|
|
263
294
|
const LABELS = {
|
|
264
295
|
editProfile: "Το προφίλ μου",
|
|
@@ -274,7 +305,9 @@ export default {
|
|
|
274
305
|
components: {
|
|
275
306
|
kButton,
|
|
276
307
|
kIcon,
|
|
308
|
+
kSearch,
|
|
277
309
|
RouterLink,
|
|
310
|
+
TwoColSidebarMenuList,
|
|
278
311
|
},
|
|
279
312
|
emits: [
|
|
280
313
|
"logo-click",
|
|
@@ -299,8 +332,64 @@ export default {
|
|
|
299
332
|
contactEmail: { type: String, default: "grammateia@ketekny.gr" },
|
|
300
333
|
helpUrl: { type: String, default: "" },
|
|
301
334
|
collapsed: { type: Boolean, default: false },
|
|
335
|
+
showMenuSearch: { type: Boolean, default: false },
|
|
336
|
+
menuSearchPlaceholder: {
|
|
337
|
+
type: String,
|
|
338
|
+
default: "Αναζήτηση...",
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
data() {
|
|
342
|
+
return {
|
|
343
|
+
expandedBranches: {},
|
|
344
|
+
collapsedFlyoutKey: null,
|
|
345
|
+
collapsedFlyoutPosition: null,
|
|
346
|
+
collapsedFlyoutCloseTimer: null,
|
|
347
|
+
menuSearchTerm: "",
|
|
348
|
+
};
|
|
302
349
|
},
|
|
303
350
|
computed: {
|
|
351
|
+
filteredMenuItems() {
|
|
352
|
+
if (!this.showMenuSearch) return this.menuItems;
|
|
353
|
+
|
|
354
|
+
const query = this.menuSearchTerm.trim().toLowerCase();
|
|
355
|
+
if (!query) return this.menuItems;
|
|
356
|
+
|
|
357
|
+
const filterItems = items =>
|
|
358
|
+
items.reduce((acc, item) => {
|
|
359
|
+
const filteredChildren = item.children?.length
|
|
360
|
+
? filterItems(item.children)
|
|
361
|
+
: [];
|
|
362
|
+
const matchesSelf =
|
|
363
|
+
item.label?.toLowerCase().includes(query) ||
|
|
364
|
+
item.description?.toLowerCase().includes(query);
|
|
365
|
+
|
|
366
|
+
if (!matchesSelf && filteredChildren.length === 0) {
|
|
367
|
+
return acc;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
acc.push({
|
|
371
|
+
...item,
|
|
372
|
+
children: filteredChildren,
|
|
373
|
+
hasChildren: filteredChildren.length > 0,
|
|
374
|
+
});
|
|
375
|
+
return acc;
|
|
376
|
+
}, []);
|
|
377
|
+
|
|
378
|
+
return filterItems(this.menuItems);
|
|
379
|
+
},
|
|
380
|
+
collapsedFlyoutItem() {
|
|
381
|
+
if (!this.collapsedFlyoutKey) return null;
|
|
382
|
+
|
|
383
|
+
return this.findMenuItemByKey(this.filteredMenuItems, this.collapsedFlyoutKey);
|
|
384
|
+
},
|
|
385
|
+
collapsedFlyoutStyle() {
|
|
386
|
+
if (!this.collapsedFlyoutPosition) return {};
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
left: `${this.collapsedFlyoutPosition.left}px`,
|
|
390
|
+
top: `${this.collapsedFlyoutPosition.top}px`,
|
|
391
|
+
};
|
|
392
|
+
},
|
|
304
393
|
accountDisplayName() {
|
|
305
394
|
if (!this.account) return "";
|
|
306
395
|
|
|
@@ -345,12 +434,129 @@ export default {
|
|
|
345
434
|
`;
|
|
346
435
|
},
|
|
347
436
|
},
|
|
437
|
+
watch: {
|
|
438
|
+
menuItems: {
|
|
439
|
+
immediate: true,
|
|
440
|
+
handler(items) {
|
|
441
|
+
this.expandedBranches = this.buildExpandedState(items, {
|
|
442
|
+
...this.expandedBranches,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
if (
|
|
446
|
+
this.collapsedFlyoutKey &&
|
|
447
|
+
!this.findMenuItemByKey(items, this.collapsedFlyoutKey)
|
|
448
|
+
) {
|
|
449
|
+
this.collapsedFlyoutKey = null;
|
|
450
|
+
this.collapsedFlyoutPosition = null;
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
menuSearchTerm() {
|
|
455
|
+
this.expandedBranches = this.buildExpandedState(this.filteredMenuItems, {
|
|
456
|
+
...this.expandedBranches,
|
|
457
|
+
});
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
beforeUnmount() {
|
|
461
|
+
this.clearCollapsedFlyoutCloseTimer();
|
|
462
|
+
},
|
|
348
463
|
methods: {
|
|
349
464
|
menuItemComponent(item) {
|
|
350
465
|
if (item?.isLink) return RouterLink;
|
|
351
466
|
if (item?.isExternal) return "a";
|
|
352
467
|
return "button";
|
|
353
468
|
},
|
|
469
|
+
collapsedItemTooltip(item) {
|
|
470
|
+
return item?.hasChildren ? null : item.label;
|
|
471
|
+
},
|
|
472
|
+
buildExpandedState(items, state = {}) {
|
|
473
|
+
items.forEach(item => {
|
|
474
|
+
if (!item?.hasChildren) return;
|
|
475
|
+
|
|
476
|
+
const hasActiveDescendant = this.branchHasActiveChild(item.children);
|
|
477
|
+
if (!(item.key in state)) {
|
|
478
|
+
state[item.key] = Boolean(hasActiveDescendant);
|
|
479
|
+
} else if (hasActiveDescendant) {
|
|
480
|
+
state[item.key] = true;
|
|
481
|
+
}
|
|
482
|
+
this.buildExpandedState(item.children, state);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
return state;
|
|
486
|
+
},
|
|
487
|
+
branchHasActiveChild(children = []) {
|
|
488
|
+
return children.some(
|
|
489
|
+
child => child.active || this.branchHasActiveChild(child.children || [])
|
|
490
|
+
);
|
|
491
|
+
},
|
|
492
|
+
toggleBranch(key) {
|
|
493
|
+
this.expandedBranches = {
|
|
494
|
+
...this.expandedBranches,
|
|
495
|
+
[key]: !this.expandedBranches[key],
|
|
496
|
+
};
|
|
497
|
+
},
|
|
498
|
+
findMenuItemByKey(items, key) {
|
|
499
|
+
for (const item of items) {
|
|
500
|
+
if (item.key === key) return item;
|
|
501
|
+
if (item.children?.length) {
|
|
502
|
+
const found = this.findMenuItemByKey(item.children, key);
|
|
503
|
+
if (found) return found;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return null;
|
|
508
|
+
},
|
|
509
|
+
updateCollapsedFlyoutPosition(event) {
|
|
510
|
+
const trigger = event?.currentTarget;
|
|
511
|
+
if (!trigger || typeof window === "undefined") return;
|
|
512
|
+
|
|
513
|
+
const rect = trigger.getBoundingClientRect();
|
|
514
|
+
this.collapsedFlyoutPosition = {
|
|
515
|
+
left: rect.right + 12,
|
|
516
|
+
top: Math.max(12, rect.top),
|
|
517
|
+
};
|
|
518
|
+
},
|
|
519
|
+
clearCollapsedFlyoutCloseTimer() {
|
|
520
|
+
if (this.collapsedFlyoutCloseTimer) {
|
|
521
|
+
window.clearTimeout(this.collapsedFlyoutCloseTimer);
|
|
522
|
+
this.collapsedFlyoutCloseTimer = null;
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
handleCollapsedItemEnter(item, event) {
|
|
526
|
+
if (!item?.hasChildren) return;
|
|
527
|
+
|
|
528
|
+
this.clearCollapsedFlyoutCloseTimer();
|
|
529
|
+
this.collapsedFlyoutKey = item.key;
|
|
530
|
+
if (event) {
|
|
531
|
+
this.updateCollapsedFlyoutPosition(event);
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
handleCollapsedItemLeave(item) {
|
|
535
|
+
if (this.collapsedFlyoutKey !== item?.key) return;
|
|
536
|
+
|
|
537
|
+
this.clearCollapsedFlyoutCloseTimer();
|
|
538
|
+
this.collapsedFlyoutCloseTimer = window.setTimeout(() => {
|
|
539
|
+
if (this.collapsedFlyoutKey !== item?.key) return;
|
|
540
|
+
|
|
541
|
+
this.collapsedFlyoutKey = null;
|
|
542
|
+
this.collapsedFlyoutPosition = null;
|
|
543
|
+
this.collapsedFlyoutCloseTimer = null;
|
|
544
|
+
}, 180);
|
|
545
|
+
},
|
|
546
|
+
showCollapsedFlyout(item) {
|
|
547
|
+
return this.collapsedFlyoutKey === item?.key && item?.hasChildren;
|
|
548
|
+
},
|
|
549
|
+
handleCollapsedItemClick(item, event) {
|
|
550
|
+
if (item?.hasChildren) {
|
|
551
|
+
event?.preventDefault?.();
|
|
552
|
+
this.clearCollapsedFlyoutCloseTimer();
|
|
553
|
+
this.collapsedFlyoutKey = item.key;
|
|
554
|
+
this.updateCollapsedFlyoutPosition(event);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
this.$emit("item-click", item);
|
|
559
|
+
},
|
|
354
560
|
},
|
|
355
561
|
};
|
|
356
562
|
</script>
|
package/src/ui/kSearch.vue
CHANGED
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
type="text"
|
|
9
9
|
v-model="localValue"
|
|
10
10
|
@input="onInput"
|
|
11
|
-
:class="[defaultStyle, disabled ? disabledStyle : '']"
|
|
11
|
+
:class="[defaultStyle, inputClass, disabled ? disabledStyle : '']"
|
|
12
12
|
:placeholder="placeholder"
|
|
13
13
|
:aria-label="!label ? placeholder : null"
|
|
14
14
|
:disabled="disabled"
|
|
15
15
|
/>
|
|
16
|
-
<Search
|
|
16
|
+
<Search
|
|
17
|
+
class="absolute w-4 h-4 -translate-y-1/2 pointer-events-none right-3 top-1/2"
|
|
18
|
+
:class="iconClass"
|
|
19
|
+
/>
|
|
17
20
|
</div>
|
|
18
21
|
</template>
|
|
19
22
|
|
|
@@ -30,6 +33,8 @@ export default {
|
|
|
30
33
|
placeholder: { type: String, default: "Αναζήτηση..." },
|
|
31
34
|
label: { type: String, default: "" },
|
|
32
35
|
disabled: { type: Boolean, default: false },
|
|
36
|
+
inputClass: { type: String, default: "" },
|
|
37
|
+
iconClass: { type: String, default: "text-primary/70" },
|
|
33
38
|
},
|
|
34
39
|
data() {
|
|
35
40
|
return {
|