ketekny-ui-kit 1.0.89 → 1.0.91
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 +1 -0
- package/src/layout/twoColSidebarMenuList.vue +68 -13
- package/src/layout/twoColSidebarPanel.vue +26 -6
- package/src/style/style.css +9 -40
- package/src/twoColLayout/components/MultilevelSidebar.vue +422 -0
- package/src/twoColLayout/components/SidebarMenuItem.vue +217 -0
- package/src/twoColLayout/components/TwoColLayout.vue +125 -0
- package/tailwind.config.js +25 -0
package/package.json
CHANGED
|
@@ -12,23 +12,29 @@
|
|
|
12
12
|
<component
|
|
13
13
|
:is="menuItemComponent(item)"
|
|
14
14
|
v-bind="item.bindings"
|
|
15
|
-
class="sidebar-menu-item flex min-w-0 flex-1
|
|
15
|
+
class="sidebar-menu-item relative flex min-h-[38px] min-w-0 flex-1 items-center gap-2.5 rounded-md px-3 py-2 text-left outline-none transition-colors duration-150"
|
|
16
16
|
:class="itemRowClasses(item)"
|
|
17
17
|
@click="handleItemClick(item, $event)"
|
|
18
18
|
>
|
|
19
19
|
<span
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
v-if="item.active"
|
|
21
|
+
class="absolute left-0 top-1/2 h-5 w-1 -translate-y-1/2 rounded-r bg-[#2491ff]"
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
/>
|
|
24
|
+
<span
|
|
25
|
+
class="flex h-4 w-4 shrink-0 items-center justify-center transition-colors duration-150"
|
|
26
|
+
:class="iconWrapClasses(item)"
|
|
22
27
|
>
|
|
23
28
|
<kIcon
|
|
24
29
|
v-if="showFullIcon(item)"
|
|
25
30
|
:name="item.icon"
|
|
26
|
-
size="
|
|
27
|
-
|
|
31
|
+
size="16"
|
|
32
|
+
color="currentColor"
|
|
33
|
+
class="sidebar-menu-item-icon"
|
|
28
34
|
/>
|
|
29
35
|
<span
|
|
30
36
|
v-else
|
|
31
|
-
class="h-1.5 w-1.5 rounded-full bg-current
|
|
37
|
+
class="h-1.5 w-1.5 rounded-full bg-current"
|
|
32
38
|
/>
|
|
33
39
|
</span>
|
|
34
40
|
<span class="min-w-0 flex-1">
|
|
@@ -37,18 +43,25 @@
|
|
|
37
43
|
</span>
|
|
38
44
|
<span
|
|
39
45
|
v-if="item.description"
|
|
40
|
-
class="
|
|
46
|
+
class="block truncate"
|
|
41
47
|
:class="[itemDescriptionClasses, item.active ? 'text-[#9fa6b5]' : 'sidebar-menu-item-description text-[#6f7786]']"
|
|
42
48
|
>
|
|
43
49
|
{{ item.description }}
|
|
44
50
|
</span>
|
|
45
51
|
</span>
|
|
52
|
+
<span
|
|
53
|
+
v-if="item.badge != null"
|
|
54
|
+
class="ml-auto rounded-full px-1.5 py-0.5 text-[11px] font-medium leading-none tabular-nums transition-colors duration-150"
|
|
55
|
+
:class="badgeClasses(item)"
|
|
56
|
+
>
|
|
57
|
+
{{ item.badge }}
|
|
58
|
+
</span>
|
|
46
59
|
</component>
|
|
47
60
|
|
|
48
61
|
<button
|
|
49
62
|
v-if="item.hasChildren"
|
|
50
63
|
type="button"
|
|
51
|
-
class="flex w-8 shrink-0 appearance-none items-center justify-center border-0 bg-transparent text-[#6f7786] outline-none transition
|
|
64
|
+
class="flex w-8 shrink-0 appearance-none items-center justify-center border-0 bg-transparent text-[#6f7786] outline-none transition-colors duration-150 hover:text-[#e5e7eb]"
|
|
52
65
|
:aria-expanded="isExpanded(item)"
|
|
53
66
|
:aria-label="`${isExpanded(item) ? 'Collapse' : 'Expand'} ${item.label}`"
|
|
54
67
|
@click.stop="toggle(item.key)"
|
|
@@ -65,7 +78,7 @@
|
|
|
65
78
|
|
|
66
79
|
<div
|
|
67
80
|
v-if="item.hasChildren && isExpanded(item)"
|
|
68
|
-
class="
|
|
81
|
+
class="pl-3"
|
|
69
82
|
>
|
|
70
83
|
<TwoColSidebarMenuList
|
|
71
84
|
:items="item.children"
|
|
@@ -130,12 +143,28 @@ export default {
|
|
|
130
143
|
showFullIcon(item) {
|
|
131
144
|
return this.level === 0 && Boolean(item?.icon);
|
|
132
145
|
},
|
|
146
|
+
iconWrapClasses(item) {
|
|
147
|
+
if (item.active) return "text-[#eef2f7]";
|
|
148
|
+
if (item.hasActiveChild) return "text-[#cfd6e4]";
|
|
149
|
+
return "text-[#7b8494] group-hover:text-[#eef2f7]";
|
|
150
|
+
},
|
|
151
|
+
badgeClasses(item) {
|
|
152
|
+
if (item.active) {
|
|
153
|
+
return "bg-white/12 text-[#eef2f7]";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return "sidebar-menu-item-badge bg-white/8 text-[#8e97a8]";
|
|
157
|
+
},
|
|
133
158
|
itemRowClasses(item) {
|
|
134
159
|
if (item.active) {
|
|
135
|
-
return "sidebar-menu-item--active
|
|
160
|
+
return "sidebar-menu-item--active bg-white/10 text-[#eef2f7]";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (item.hasActiveChild) {
|
|
164
|
+
return "text-[#eef2f7]";
|
|
136
165
|
}
|
|
137
166
|
|
|
138
|
-
return "text-[#
|
|
167
|
+
return "text-[#c7cfdb] hover:bg-white/6 hover:text-[#eef2f7]";
|
|
139
168
|
},
|
|
140
169
|
toggle(key) {
|
|
141
170
|
this.$emit("toggle-branch", key);
|
|
@@ -145,11 +174,37 @@ export default {
|
|
|
145
174
|
</script>
|
|
146
175
|
|
|
147
176
|
<style scoped>
|
|
177
|
+
.sidebar-menu-item-icon,
|
|
178
|
+
.sidebar-menu-item-description {
|
|
179
|
+
transition: color 150ms ease-out;
|
|
180
|
+
}
|
|
181
|
+
|
|
148
182
|
.sidebar-menu-item--active {
|
|
149
|
-
box-shadow: inset 0 0 0 1px rgba(
|
|
183
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.sidebar-menu-item-icon {
|
|
187
|
+
color: currentColor !important;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.sidebar-menu-item--active .sidebar-menu-item-icon {
|
|
191
|
+
color: #eef2f7 !important;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.sidebar-menu-item:hover .sidebar-menu-item-icon {
|
|
195
|
+
color: #eef2f7 !important;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.sidebar-menu-item:hover {
|
|
199
|
+
color: #eef2f7;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.sidebar-menu-item:hover .sidebar-menu-item-badge {
|
|
203
|
+
color: #eef2f7;
|
|
150
204
|
}
|
|
151
205
|
|
|
206
|
+
.sidebar-menu-item--active .sidebar-menu-item-description,
|
|
152
207
|
.sidebar-menu-item:hover .sidebar-menu-item-description {
|
|
153
|
-
color: #
|
|
208
|
+
color: #eef2f7;
|
|
154
209
|
}
|
|
155
210
|
</style>
|
|
@@ -211,33 +211,34 @@
|
|
|
211
211
|
<div v-else class="flex flex-col px-1">
|
|
212
212
|
<div
|
|
213
213
|
v-if="helpUrl || account.loggedIn"
|
|
214
|
+
class="flex flex-col gap-0.5"
|
|
214
215
|
>
|
|
215
216
|
<a
|
|
216
217
|
v-if="helpUrl"
|
|
217
218
|
:href="helpUrl"
|
|
218
219
|
target="_blank"
|
|
219
220
|
rel="noreferrer"
|
|
220
|
-
class="flex w-full items-center gap-
|
|
221
|
+
class="sidebar-footer-item flex min-h-[38px] w-full items-center gap-2.5 rounded-md bg-transparent px-3 py-2 text-left text-[15px] font-medium text-[#c7cfdb] outline-none transition-colors duration-150 hover:bg-white/6 hover:text-[#eef2f7] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
|
|
221
222
|
>
|
|
222
|
-
<kIcon name="HelpCircle" size="16" color="
|
|
223
|
+
<kIcon name="HelpCircle" size="16" color="currentColor" class="sidebar-footer-item-icon" />
|
|
223
224
|
<span>Βοήθεια και υποστήριξη</span>
|
|
224
225
|
</a>
|
|
225
226
|
<button
|
|
226
227
|
v-if="account.loggedIn"
|
|
227
228
|
type="button"
|
|
228
|
-
class="flex w-full items-center gap-
|
|
229
|
+
class="sidebar-footer-item flex min-h-[38px] w-full items-center gap-2.5 rounded-md bg-transparent px-3 py-2 text-left text-[15px] font-medium text-[#c7cfdb] outline-none transition-colors duration-150 hover:bg-white/6 hover:text-[#eef2f7] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
|
|
229
230
|
@click="$emit('edit-profile')"
|
|
230
231
|
>
|
|
231
|
-
<kIcon name="UserRoundCog" size="16" color="
|
|
232
|
+
<kIcon name="UserRoundCog" size="16" color="currentColor" class="sidebar-footer-item-icon" />
|
|
232
233
|
<span>Το προφίλ μου</span>
|
|
233
234
|
</button>
|
|
234
235
|
<button
|
|
235
236
|
v-if="account.loggedIn"
|
|
236
237
|
type="button"
|
|
237
|
-
class="flex w-full items-center gap-
|
|
238
|
+
class="sidebar-footer-item sidebar-footer-item--warning flex min-h-[38px] w-full items-center gap-2.5 rounded-md bg-transparent px-3 py-2 text-left text-[15px] font-medium text-[#f59e0b] outline-none transition-colors duration-150 hover:bg-[#3a2410] hover:text-[#fbbf24] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#f59e0b]/40"
|
|
238
239
|
@click="$emit('signout')"
|
|
239
240
|
>
|
|
240
|
-
<kIcon name="LogOut" size="16" color="
|
|
241
|
+
<kIcon name="LogOut" size="16" color="currentColor" class="sidebar-footer-item-icon" />
|
|
241
242
|
<span>Αποσύνδεση</span>
|
|
242
243
|
</button>
|
|
243
244
|
</div>
|
|
@@ -631,3 +632,22 @@ export default {
|
|
|
631
632
|
},
|
|
632
633
|
};
|
|
633
634
|
</script>
|
|
635
|
+
|
|
636
|
+
<style scoped>
|
|
637
|
+
.sidebar-footer-item-icon {
|
|
638
|
+
color: #9ca3af !important;
|
|
639
|
+
transition: color 200ms ease-out;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.sidebar-footer-item:hover .sidebar-footer-item-icon {
|
|
643
|
+
color: #ffffff !important;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.sidebar-footer-item--warning .sidebar-footer-item-icon {
|
|
647
|
+
color: #f59e0b !important;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.sidebar-footer-item--warning:hover .sidebar-footer-item-icon {
|
|
651
|
+
color: #fbbf24 !important;
|
|
652
|
+
}
|
|
653
|
+
</style>
|
package/src/style/style.css
CHANGED
|
@@ -1,48 +1,17 @@
|
|
|
1
|
-
@import url(
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap');
|
|
2
2
|
|
|
3
3
|
@tailwind base;
|
|
4
4
|
@tailwind components;
|
|
5
5
|
@tailwind utilities;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
h1, h2, h3, h4{
|
|
13
|
-
color: theme('colors.primary');
|
|
14
|
-
font-weight: bold;
|
|
15
|
-
line-height: 1.2;
|
|
16
|
-
}
|
|
17
|
-
h1{
|
|
18
|
-
font-size: clamp(1.625rem, 4vw + 0.75rem, 2.5rem);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
h2{
|
|
22
|
-
font-size: clamp(1.375rem, 3vw + 0.7rem, 2rem);
|
|
23
|
-
}
|
|
24
|
-
h3{
|
|
25
|
-
font-size: clamp(1.125rem, 2vw + 0.6rem, 1.5rem);
|
|
7
|
+
html,
|
|
8
|
+
body,
|
|
9
|
+
#app {
|
|
10
|
+
height: 100%;
|
|
26
11
|
}
|
|
27
|
-
h4{
|
|
28
|
-
font-size: clamp(1rem, 1.5vw + 0.55rem, 1.25rem);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.group{
|
|
32
|
-
border: 1px dotted #ccc;
|
|
33
|
-
padding: 10px;
|
|
34
|
-
border-radius: 5px;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.inputLabel{
|
|
38
|
-
font-weight: bold;
|
|
39
|
-
color: theme('colors.primary');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.menuList{
|
|
43
|
-
@apply p-2;
|
|
44
|
-
li{
|
|
45
|
-
@apply p-2 rounded cursor-pointer ;
|
|
46
12
|
|
|
47
|
-
|
|
13
|
+
body {
|
|
14
|
+
margin: 0;
|
|
15
|
+
font-family: 'Ubuntu', ui-sans-serif, system-ui, sans-serif;
|
|
16
|
+
-webkit-font-smoothing: antialiased;
|
|
48
17
|
}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import {
|
|
3
|
+
Search,
|
|
4
|
+
Bell,
|
|
5
|
+
Building2,
|
|
6
|
+
CircleUser,
|
|
7
|
+
CircleHelp,
|
|
8
|
+
X,
|
|
9
|
+
LogOut,
|
|
10
|
+
PanelLeftClose,
|
|
11
|
+
PanelLeftOpen,
|
|
12
|
+
UserRound,
|
|
13
|
+
} from '@lucide/vue'
|
|
14
|
+
import SidebarMenuItem from './SidebarMenuItem.vue'
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
name: 'MultilevelSidebar',
|
|
18
|
+
components: {
|
|
19
|
+
Search,
|
|
20
|
+
Bell,
|
|
21
|
+
Building2,
|
|
22
|
+
CircleUser,
|
|
23
|
+
CircleHelp,
|
|
24
|
+
X,
|
|
25
|
+
LogOut,
|
|
26
|
+
PanelLeftClose,
|
|
27
|
+
PanelLeftOpen,
|
|
28
|
+
UserRound,
|
|
29
|
+
SidebarMenuItem,
|
|
30
|
+
},
|
|
31
|
+
props: {
|
|
32
|
+
title: {
|
|
33
|
+
type: String,
|
|
34
|
+
default: "",
|
|
35
|
+
},
|
|
36
|
+
width: {
|
|
37
|
+
type: String,
|
|
38
|
+
default: "360px",
|
|
39
|
+
},
|
|
40
|
+
collapsedWidth: {
|
|
41
|
+
type: String,
|
|
42
|
+
default: "76px",
|
|
43
|
+
},
|
|
44
|
+
mainMenu: {
|
|
45
|
+
type: Array,
|
|
46
|
+
default: () => [],
|
|
47
|
+
},
|
|
48
|
+
account: {
|
|
49
|
+
type: Object,
|
|
50
|
+
default: null,
|
|
51
|
+
},
|
|
52
|
+
contactEmail: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: "grammateia@ketekny.gr",
|
|
55
|
+
},
|
|
56
|
+
helpUrl: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: "https://help.ketekny.gr",
|
|
59
|
+
},
|
|
60
|
+
mobileOpen: {
|
|
61
|
+
type: Boolean,
|
|
62
|
+
default: false,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
emits: ['select', 'logo-click', 'signin', 'signup', 'signout', 'edit-profile', 'item-click'],
|
|
66
|
+
data() {
|
|
67
|
+
return {
|
|
68
|
+
activeId: 'dashboard',
|
|
69
|
+
query: '',
|
|
70
|
+
collapsed: false,
|
|
71
|
+
openIds: new Set(['projects']),
|
|
72
|
+
footerLinks: [
|
|
73
|
+
{ id: 'my-profile', label: 'Το προφίλ μου', icon: 'UserRound' },
|
|
74
|
+
{ id: 'help', label: 'Βοήθεια', icon: 'CircleHelp' },
|
|
75
|
+
{ id: 'sign-out', label: 'Αποσύνδεση', icon: 'LogOut' },
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
computed: {
|
|
80
|
+
sidebarWidthStyle() {
|
|
81
|
+
return {
|
|
82
|
+
width: this.collapsed ? this.collapsedWidth : this.width,
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
collapsedTitle() {
|
|
86
|
+
if (!this.title) return ''
|
|
87
|
+
const words = this.title.trim().split(/\s+/).filter(Boolean)
|
|
88
|
+
return words.slice(0, 3).map(word => word.charAt(0)).join('').toUpperCase()
|
|
89
|
+
},
|
|
90
|
+
accountDisplayName() {
|
|
91
|
+
if (!this.account) return 'Ava Mitchell'
|
|
92
|
+
return [this.account.firstName, this.account.lastName].filter(Boolean).join(' ') || 'Account'
|
|
93
|
+
},
|
|
94
|
+
accountEmail() {
|
|
95
|
+
return this.account?.email || 'ava@nimbus.io'
|
|
96
|
+
},
|
|
97
|
+
filteredMenu() {
|
|
98
|
+
const q = this.query.trim().toLowerCase()
|
|
99
|
+
if (!q) return this.mainMenu
|
|
100
|
+
return this.filterTree(this.mainMenu, q)
|
|
101
|
+
},
|
|
102
|
+
effectiveOpenIds() {
|
|
103
|
+
const q = this.query.trim().toLowerCase()
|
|
104
|
+
if (!q) return this.openIds
|
|
105
|
+
const all = new Set()
|
|
106
|
+
const collect = (nodes) => {
|
|
107
|
+
for (const node of nodes) {
|
|
108
|
+
if (node.children) {
|
|
109
|
+
all.add(node.id)
|
|
110
|
+
collect(node.children)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
collect(this.filteredMenu)
|
|
115
|
+
return all
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
methods: {
|
|
119
|
+
toggleCollapsed() {
|
|
120
|
+
this.collapsed = !this.collapsed
|
|
121
|
+
},
|
|
122
|
+
toggle(id) {
|
|
123
|
+
if (this.openIds.has(id)) this.openIds.delete(id)
|
|
124
|
+
else this.openIds.add(id)
|
|
125
|
+
},
|
|
126
|
+
select(itemOrId) {
|
|
127
|
+
const id = typeof itemOrId === 'string' ? itemOrId : itemOrId?.id
|
|
128
|
+
if (!id) return
|
|
129
|
+
this.activeId = id
|
|
130
|
+
this.$emit('select', id)
|
|
131
|
+
},
|
|
132
|
+
navigateItem(item) {
|
|
133
|
+
const to = item?.link?.to ?? item?.to
|
|
134
|
+
const href = item?.link?.href ?? item?.href
|
|
135
|
+
|
|
136
|
+
if (to && this.$router) {
|
|
137
|
+
this.$router.push(to).catch(() => {})
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (href) {
|
|
142
|
+
window.open(href, '_blank', 'noopener,noreferrer')
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return false
|
|
147
|
+
},
|
|
148
|
+
filterTree(nodes, q) {
|
|
149
|
+
const result = []
|
|
150
|
+
for (const node of nodes) {
|
|
151
|
+
const matches = node.label.toLowerCase().includes(q)
|
|
152
|
+
const matchedChildren = node.children ? this.filterTree(node.children, q) : []
|
|
153
|
+
if (matches) result.push(node)
|
|
154
|
+
else if (matchedChildren.length) result.push({ ...node, children: matchedChildren })
|
|
155
|
+
}
|
|
156
|
+
return result
|
|
157
|
+
},
|
|
158
|
+
handleFooterSelect(id) {
|
|
159
|
+
if (id === 'help') {
|
|
160
|
+
window.open(this.helpUrl || 'https://help.ketekny.gr', '_blank', 'noopener,noreferrer')
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (id === 'my-profile') {
|
|
164
|
+
this.$emit('edit-profile')
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
if (id === 'sign-out') {
|
|
168
|
+
this.$emit('signout')
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
this.select(id)
|
|
172
|
+
},
|
|
173
|
+
handleLogoClick() {
|
|
174
|
+
this.$emit('logo-click')
|
|
175
|
+
},
|
|
176
|
+
handleMenuSelect(item) {
|
|
177
|
+
const isExternalLink = Boolean(item?.link?.href ?? item?.href)
|
|
178
|
+
if (!isExternalLink) {
|
|
179
|
+
this.select(item)
|
|
180
|
+
}
|
|
181
|
+
this.$emit('item-click', item)
|
|
182
|
+
this.navigateItem(item)
|
|
183
|
+
},
|
|
184
|
+
clearQuery() {
|
|
185
|
+
this.query = ''
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
</script>
|
|
190
|
+
|
|
191
|
+
<template>
|
|
192
|
+
<aside
|
|
193
|
+
class="fixed inset-y-0 left-0 z-40 flex h-full shrink-0 flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground transition-[width,transform] duration-200 ease-out lg:static lg:translate-x-0"
|
|
194
|
+
:style="sidebarWidthStyle"
|
|
195
|
+
:class="[
|
|
196
|
+
mobileOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0',
|
|
197
|
+
]"
|
|
198
|
+
>
|
|
199
|
+
<div class="flex items-center px-4 py-4" :class="collapsed ? 'justify-center' : 'gap-2.5'">
|
|
200
|
+
<img
|
|
201
|
+
:src="
|
|
202
|
+
collapsed
|
|
203
|
+
? 'https://s3.ketekny.gr/public/web-apps/logos/logo-icon-dark.png'
|
|
204
|
+
: 'https://support.ketekny.gr/logo-dark.png'
|
|
205
|
+
"
|
|
206
|
+
alt="Ketekny logo"
|
|
207
|
+
class="object-contain"
|
|
208
|
+
:class="collapsed ? 'size-9 shrink-0 rounded-lg' : 'h-10 min-w-0 flex-1'"
|
|
209
|
+
@click="handleLogoClick"
|
|
210
|
+
/>
|
|
211
|
+
<button
|
|
212
|
+
v-if="!collapsed"
|
|
213
|
+
type="button"
|
|
214
|
+
class="ml-auto hidden rounded-md p-1.5 text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-foreground lg:inline-flex"
|
|
215
|
+
aria-label="Collapse sidebar"
|
|
216
|
+
@click="toggleCollapsed"
|
|
217
|
+
>
|
|
218
|
+
<PanelLeftClose class="size-4" :stroke-width="2" />
|
|
219
|
+
</button>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<div v-if="title && !collapsed" class="px-4 pb-2">
|
|
223
|
+
<p class="text-xl font-bold text-center text-sidebar-foreground">
|
|
224
|
+
{{ title }}
|
|
225
|
+
</p>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<div v-else-if="title && collapsed" class="px-3 pb-2">
|
|
229
|
+
<div
|
|
230
|
+
v-tooltip.right="title"
|
|
231
|
+
class="flex h-10 w-10 items-center justify-center rounded-md border border-sidebar-border/30 bg-sidebar-accent/30 text-[10px] font-bold tracking-[0.18em] text-sidebar-muted"
|
|
232
|
+
>
|
|
233
|
+
{{ collapsedTitle }}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div v-if="collapsed" class="justify-center hidden px-3 pb-1 lg:flex">
|
|
238
|
+
<button
|
|
239
|
+
type="button"
|
|
240
|
+
v-tooltip.right="'Ανάπτυξη πλαϊνού μενού'"
|
|
241
|
+
class="flex h-10 w-10 items-center justify-center rounded-md text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-foreground"
|
|
242
|
+
aria-label="Ανάπτυξη πλαϊνού μενού"
|
|
243
|
+
@click="toggleCollapsed"
|
|
244
|
+
>
|
|
245
|
+
<PanelLeftOpen class="size-4" :stroke-width="2" />
|
|
246
|
+
</button>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div v-if="!collapsed" class="px-3 pb-2">
|
|
250
|
+
<div class="relative">
|
|
251
|
+
<Search
|
|
252
|
+
class="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-sidebar-muted"
|
|
253
|
+
:stroke-width="2"
|
|
254
|
+
/>
|
|
255
|
+
<input
|
|
256
|
+
v-model="query"
|
|
257
|
+
type="text"
|
|
258
|
+
placeholder="Αναζήτηση μενού..."
|
|
259
|
+
aria-label="Αναζήτηση μενού"
|
|
260
|
+
class="w-full py-2 pl-8 pr-9 text-sm border rounded-md border-sidebar-border/35 bg-sidebar-accent/40 text-sidebar-foreground placeholder:text-sidebar-muted focus:border-white/15 focus:outline-none focus:ring-1 focus:ring-white/10"
|
|
261
|
+
/>
|
|
262
|
+
<button
|
|
263
|
+
v-if="query"
|
|
264
|
+
type="button"
|
|
265
|
+
class="absolute right-2 top-1/2 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-md text-sidebar-muted transition-colors hover:bg-sidebar-accent hover:text-sidebar-foreground"
|
|
266
|
+
aria-label="Καθαρισμός αναζήτησης"
|
|
267
|
+
@click="clearQuery"
|
|
268
|
+
>
|
|
269
|
+
<X class="size-3.5" :stroke-width="2" />
|
|
270
|
+
</button>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<nav
|
|
275
|
+
class="flex-1 px-3 py-2"
|
|
276
|
+
:class="collapsed ? 'overflow-visible' : 'overflow-y-auto'"
|
|
277
|
+
aria-label="Main navigation"
|
|
278
|
+
>
|
|
279
|
+
<ul class="flex flex-col gap-0.5">
|
|
280
|
+
<SidebarMenuItem
|
|
281
|
+
v-for="item in filteredMenu"
|
|
282
|
+
:key="item.id"
|
|
283
|
+
:item="item"
|
|
284
|
+
:level="0"
|
|
285
|
+
:active-id="activeId"
|
|
286
|
+
:open-ids="effectiveOpenIds"
|
|
287
|
+
:collapsed="collapsed"
|
|
288
|
+
@select="handleMenuSelect"
|
|
289
|
+
@toggle="toggle"
|
|
290
|
+
/>
|
|
291
|
+
</ul>
|
|
292
|
+
<p
|
|
293
|
+
v-if="!collapsed && filteredMenu.length === 0"
|
|
294
|
+
class="px-3 py-6 text-sm text-center text-sidebar-muted"
|
|
295
|
+
>
|
|
296
|
+
Δεν βρέθηκαν αποτελέσματα.
|
|
297
|
+
</p>
|
|
298
|
+
</nav>
|
|
299
|
+
|
|
300
|
+
<div class="p-3 mt-auto border-t border-sidebar-border">
|
|
301
|
+
<div
|
|
302
|
+
class="flex items-center py-2 rounded-md hover:bg-sidebar-accent"
|
|
303
|
+
:class="collapsed ? 'justify-center px-0' : 'gap-2.5 px-2'"
|
|
304
|
+
>
|
|
305
|
+
<CircleUser class="size-8 shrink-0 text-sidebar-muted" :stroke-width="1.75" />
|
|
306
|
+
<div v-if="!collapsed" class="flex-1 min-w-0">
|
|
307
|
+
<p class="text-sm font-medium truncate text-sidebar-foreground">{{ accountDisplayName }}</p>
|
|
308
|
+
<p class="text-xs truncate text-sidebar-muted">{{ accountEmail }}</p>
|
|
309
|
+
</div>
|
|
310
|
+
<button
|
|
311
|
+
v-if="!collapsed"
|
|
312
|
+
type="button"
|
|
313
|
+
class="rounded-md p-1.5 text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-foreground"
|
|
314
|
+
aria-label="Notifications"
|
|
315
|
+
>
|
|
316
|
+
<Bell class="size-4" :stroke-width="2" />
|
|
317
|
+
</button>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<div
|
|
321
|
+
class="pt-2 mt-2 border-t border-sidebar-border/70"
|
|
322
|
+
:class="collapsed ? 'flex flex-col items-center gap-1' : 'space-y-1'"
|
|
323
|
+
>
|
|
324
|
+
<button
|
|
325
|
+
v-for="link in footerLinks"
|
|
326
|
+
:key="link.id"
|
|
327
|
+
v-if="collapsed"
|
|
328
|
+
type="button"
|
|
329
|
+
v-tooltip.right="link.label"
|
|
330
|
+
class="flex items-center w-full text-sm transition-colors duration-150 rounded-md"
|
|
331
|
+
:class="[
|
|
332
|
+
'justify-center px-0 py-2',
|
|
333
|
+
activeId === link.id
|
|
334
|
+
? 'bg-brand text-brand-foreground font-medium'
|
|
335
|
+
: link.id === 'sign-out'
|
|
336
|
+
? 'text-orange-300 hover:bg-orange-500/10 hover:text-orange-200'
|
|
337
|
+
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground',
|
|
338
|
+
]"
|
|
339
|
+
:aria-label="collapsed ? link.label : undefined"
|
|
340
|
+
@click="handleFooterSelect(link.id)"
|
|
341
|
+
>
|
|
342
|
+
<component
|
|
343
|
+
:is="link.icon"
|
|
344
|
+
class="size-4 shrink-0"
|
|
345
|
+
:class="
|
|
346
|
+
activeId === link.id
|
|
347
|
+
? 'text-brand-foreground'
|
|
348
|
+
: link.id === 'sign-out'
|
|
349
|
+
? 'text-orange-400'
|
|
350
|
+
: 'text-sidebar-muted'
|
|
351
|
+
"
|
|
352
|
+
:stroke-width="2"
|
|
353
|
+
/>
|
|
354
|
+
</button>
|
|
355
|
+
<button
|
|
356
|
+
v-for="link in footerLinks"
|
|
357
|
+
:key="`${link.id}-expanded`"
|
|
358
|
+
v-else
|
|
359
|
+
type="button"
|
|
360
|
+
class="flex items-center w-full text-sm transition-colors duration-150 rounded-md"
|
|
361
|
+
:class="[
|
|
362
|
+
'gap-2.5 px-2 py-2 text-left',
|
|
363
|
+
activeId === link.id
|
|
364
|
+
? 'bg-brand text-brand-foreground font-medium'
|
|
365
|
+
: link.id === 'sign-out'
|
|
366
|
+
? 'text-orange-300 hover:bg-orange-500/10 hover:text-orange-200'
|
|
367
|
+
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground',
|
|
368
|
+
]"
|
|
369
|
+
@click="handleFooterSelect(link.id)"
|
|
370
|
+
>
|
|
371
|
+
<component
|
|
372
|
+
:is="link.icon"
|
|
373
|
+
class="size-4 shrink-0"
|
|
374
|
+
:class="
|
|
375
|
+
activeId === link.id
|
|
376
|
+
? 'text-brand-foreground'
|
|
377
|
+
: link.id === 'sign-out'
|
|
378
|
+
? 'text-orange-400'
|
|
379
|
+
: 'text-sidebar-muted'
|
|
380
|
+
"
|
|
381
|
+
:stroke-width="2"
|
|
382
|
+
/>
|
|
383
|
+
<span class="truncate">{{ link.label }}</span>
|
|
384
|
+
</button>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<div
|
|
388
|
+
v-if="!collapsed"
|
|
389
|
+
class="mt-3 border-t border-sidebar-border/70 px-2 pt-2 text-[11px] leading-snug text-sidebar-foreground/90"
|
|
390
|
+
>
|
|
391
|
+
<div class="flex items-center gap-1.5 text-sidebar-foreground">
|
|
392
|
+
<Building2 class="size-3 shrink-0 text-sidebar-muted" :stroke-width="2" />
|
|
393
|
+
<p class="font-semibold uppercase tracking-[0.28em]">KETEKNY AE</p>
|
|
394
|
+
</div>
|
|
395
|
+
<div class="mt-1.5 pl-[18px]">
|
|
396
|
+
<p class="truncate ">Βερανζέρου 13, 10677, Αθήνα
|
|
397
|
+
<span> </span>
|
|
398
|
+
<a href="tel:+302103648337" class="transition-colors hover:text-sidebar-foreground">
|
|
399
|
+
210 3648 337
|
|
400
|
+
</a></p>
|
|
401
|
+
|
|
402
|
+
<div class="flex flex-wrap items-center gap-x-3">
|
|
403
|
+
<a
|
|
404
|
+
:href="`mailto:${contactEmail}`"
|
|
405
|
+
class="transition-colors hover:text-sidebar-foreground"
|
|
406
|
+
>
|
|
407
|
+
{{ contactEmail }}
|
|
408
|
+
</a>
|
|
409
|
+
<a
|
|
410
|
+
:href="helpUrl || 'https://help.ketekny.gr'"
|
|
411
|
+
target="_blank"
|
|
412
|
+
rel="noreferrer"
|
|
413
|
+
class="truncate transition-colors hover:text-sidebar-foreground"
|
|
414
|
+
>
|
|
415
|
+
{{ helpUrl || 'https://help.ketekny.gr' }}
|
|
416
|
+
</a>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
</aside>
|
|
422
|
+
</template>
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { ChevronRight, ExternalLink } from '@lucide/vue'
|
|
3
|
+
import * as icons from '@lucide/vue'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: 'SidebarMenuItem',
|
|
7
|
+
components: {
|
|
8
|
+
ChevronRight,
|
|
9
|
+
ExternalLink,
|
|
10
|
+
},
|
|
11
|
+
props: {
|
|
12
|
+
item: { type: Object, required: true },
|
|
13
|
+
level: { type: Number, default: 0 },
|
|
14
|
+
activeId: { type: String, default: '' },
|
|
15
|
+
openIds: { type: Object, required: true },
|
|
16
|
+
collapsed: { type: Boolean, default: false },
|
|
17
|
+
},
|
|
18
|
+
emits: ['select', 'toggle'],
|
|
19
|
+
computed: {
|
|
20
|
+
hasChildren() {
|
|
21
|
+
return Array.isArray(this.item.children) && this.item.children.length > 0
|
|
22
|
+
},
|
|
23
|
+
isOpen() {
|
|
24
|
+
return this.openIds.has(this.item.id)
|
|
25
|
+
},
|
|
26
|
+
IconComponent() {
|
|
27
|
+
return this.item.icon ? icons[this.item.icon] : null
|
|
28
|
+
},
|
|
29
|
+
isActiveTrail() {
|
|
30
|
+
return this.hasChildren && this.containsActive(this.item)
|
|
31
|
+
},
|
|
32
|
+
isActiveLeaf() {
|
|
33
|
+
return !this.hasChildren && this.item.id === this.activeId
|
|
34
|
+
},
|
|
35
|
+
isActive() {
|
|
36
|
+
return this.isActiveLeaf || this.isActiveTrail
|
|
37
|
+
},
|
|
38
|
+
isCollapsedTop() {
|
|
39
|
+
return this.collapsed && this.level === 0
|
|
40
|
+
},
|
|
41
|
+
isExternalLink() {
|
|
42
|
+
return !this.hasChildren && Boolean(this.item?.link?.href ?? this.item?.href)
|
|
43
|
+
},
|
|
44
|
+
indentStyle() {
|
|
45
|
+
return {
|
|
46
|
+
paddingLeft: this.level === 0 ? '0.75rem' : `${this.level * 0.875 + 0.75}rem`,
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
methods: {
|
|
51
|
+
containsActive(node) {
|
|
52
|
+
if (node.id === this.activeId) return true
|
|
53
|
+
if (!node.children) return false
|
|
54
|
+
return node.children.some((child) => this.containsActive(child))
|
|
55
|
+
},
|
|
56
|
+
handleClick() {
|
|
57
|
+
if (this.hasChildren && !this.isCollapsedTop) {
|
|
58
|
+
this.$emit('toggle', this.item.id)
|
|
59
|
+
} else if (!this.hasChildren) {
|
|
60
|
+
this.$emit('select', this.item)
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
onEnter(el) {
|
|
64
|
+
el.style.height = '0'
|
|
65
|
+
el.offsetHeight
|
|
66
|
+
el.style.height = `${el.scrollHeight}px`
|
|
67
|
+
},
|
|
68
|
+
onAfterEnter(el) {
|
|
69
|
+
el.style.height = 'auto'
|
|
70
|
+
},
|
|
71
|
+
onLeave(el) {
|
|
72
|
+
el.style.height = `${el.scrollHeight}px`
|
|
73
|
+
el.offsetHeight
|
|
74
|
+
el.style.height = '0'
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<template>
|
|
81
|
+
<li :class="isCollapsedTop ? 'group/fly relative' : ''">
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
:style="isCollapsedTop ? undefined : indentStyle"
|
|
85
|
+
class="group relative flex w-full items-center rounded-md text-left text-sm transition-colors duration-150"
|
|
86
|
+
:class="[
|
|
87
|
+
isCollapsedTop ? 'h-10 justify-center px-0' : 'gap-2.5 py-2 pr-2',
|
|
88
|
+
isActiveLeaf || (isCollapsedTop && isActive)
|
|
89
|
+
? 'bg-brand text-brand-foreground font-medium'
|
|
90
|
+
: isActiveTrail
|
|
91
|
+
? 'text-sidebar-foreground'
|
|
92
|
+
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground',
|
|
93
|
+
]"
|
|
94
|
+
:aria-expanded="hasChildren && !isCollapsedTop ? isOpen : undefined"
|
|
95
|
+
:aria-label="isCollapsedTop ? item.label : undefined"
|
|
96
|
+
@click="handleClick"
|
|
97
|
+
>
|
|
98
|
+
<span
|
|
99
|
+
v-if="isActiveLeaf && !isCollapsedTop"
|
|
100
|
+
class="absolute left-0 top-1/2 h-5 w-1 -translate-y-1/2 rounded-r bg-brand-foreground/80"
|
|
101
|
+
aria-hidden="true"
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
<component
|
|
105
|
+
:is="IconComponent"
|
|
106
|
+
v-if="IconComponent"
|
|
107
|
+
class="size-4 shrink-0"
|
|
108
|
+
:class="[
|
|
109
|
+
isActiveLeaf || (isCollapsedTop && isActive)
|
|
110
|
+
? 'text-brand-foreground'
|
|
111
|
+
: 'text-sidebar-muted group-hover:text-sidebar-foreground',
|
|
112
|
+
]"
|
|
113
|
+
:stroke-width="2"
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
<template v-if="!isCollapsedTop">
|
|
117
|
+
<span class="flex-1 truncate">{{ item.label }}</span>
|
|
118
|
+
|
|
119
|
+
<span
|
|
120
|
+
v-if="item.badge != null"
|
|
121
|
+
class="ml-auto rounded-full px-1.5 py-0.5 text-xs font-medium tabular-nums"
|
|
122
|
+
:class="isActiveLeaf ? 'bg-brand-foreground/20 text-brand-foreground' : 'bg-sidebar-accent text-sidebar-muted'"
|
|
123
|
+
>
|
|
124
|
+
{{ item.badge }}
|
|
125
|
+
</span>
|
|
126
|
+
|
|
127
|
+
<ExternalLink
|
|
128
|
+
v-if="isExternalLink"
|
|
129
|
+
class="size-3.5 shrink-0"
|
|
130
|
+
:class="isActiveLeaf ? 'text-brand-foreground' : 'text-sidebar-muted'"
|
|
131
|
+
:stroke-width="2"
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
<ChevronRight
|
|
135
|
+
v-if="hasChildren"
|
|
136
|
+
class="size-4 shrink-0 text-sidebar-muted transition-transform duration-200"
|
|
137
|
+
:class="isOpen ? 'rotate-90' : ''"
|
|
138
|
+
:stroke-width="2"
|
|
139
|
+
/>
|
|
140
|
+
</template>
|
|
141
|
+
|
|
142
|
+
<span
|
|
143
|
+
v-if="isCollapsedTop && hasChildren"
|
|
144
|
+
class="absolute right-1.5 top-1.5 size-1.5 rounded-full"
|
|
145
|
+
:class="isActive ? 'bg-brand-foreground/80' : 'bg-sidebar-muted/60'"
|
|
146
|
+
aria-hidden="true"
|
|
147
|
+
/>
|
|
148
|
+
</button>
|
|
149
|
+
|
|
150
|
+
<Transition
|
|
151
|
+
v-if="!isCollapsedTop"
|
|
152
|
+
@enter="onEnter"
|
|
153
|
+
@after-enter="onAfterEnter"
|
|
154
|
+
@leave="onLeave"
|
|
155
|
+
>
|
|
156
|
+
<ul
|
|
157
|
+
v-if="hasChildren && isOpen"
|
|
158
|
+
class="overflow-hidden border-l border-sidebar-border/25 ml-4 pl-2 transition-[height] duration-200 ease-out"
|
|
159
|
+
>
|
|
160
|
+
<SidebarMenuItem
|
|
161
|
+
v-for="child in item.children"
|
|
162
|
+
:key="child.id"
|
|
163
|
+
:item="child"
|
|
164
|
+
:level="level + 1"
|
|
165
|
+
:active-id="activeId"
|
|
166
|
+
:open-ids="openIds"
|
|
167
|
+
@select="$emit('select', $event)"
|
|
168
|
+
@toggle="$emit('toggle', $event)"
|
|
169
|
+
/>
|
|
170
|
+
</ul>
|
|
171
|
+
</Transition>
|
|
172
|
+
|
|
173
|
+
<div
|
|
174
|
+
v-if="isCollapsedTop"
|
|
175
|
+
class="invisible absolute left-full top-0 z-50 pl-2 opacity-0 transition-opacity duration-150 group-hover/fly:visible group-hover/fly:opacity-100"
|
|
176
|
+
>
|
|
177
|
+
<div
|
|
178
|
+
class="min-w-56 rounded-lg border border-sidebar-border bg-sidebar p-2 shadow-xl shadow-black/40"
|
|
179
|
+
role="menu"
|
|
180
|
+
:aria-label="item.label"
|
|
181
|
+
>
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
class="mb-1 flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm font-semibold"
|
|
185
|
+
:class="isActive ? 'text-brand' : 'text-sidebar-foreground hover:bg-sidebar-accent'"
|
|
186
|
+
@click="!hasChildren && $emit('select', item)"
|
|
187
|
+
>
|
|
188
|
+
<component
|
|
189
|
+
:is="IconComponent"
|
|
190
|
+
v-if="IconComponent"
|
|
191
|
+
class="size-4 shrink-0"
|
|
192
|
+
:stroke-width="2"
|
|
193
|
+
/>
|
|
194
|
+
<span class="truncate">{{ item.label }}</span>
|
|
195
|
+
<ExternalLink
|
|
196
|
+
v-if="isExternalLink"
|
|
197
|
+
class="ml-auto size-3.5 shrink-0 text-sidebar-muted"
|
|
198
|
+
:stroke-width="2"
|
|
199
|
+
/>
|
|
200
|
+
</button>
|
|
201
|
+
|
|
202
|
+
<ul v-if="hasChildren" class="flex flex-col gap-0.5">
|
|
203
|
+
<SidebarMenuItem
|
|
204
|
+
v-for="child in item.children"
|
|
205
|
+
:key="child.id"
|
|
206
|
+
:item="child"
|
|
207
|
+
:level="0"
|
|
208
|
+
:active-id="activeId"
|
|
209
|
+
:open-ids="openIds"
|
|
210
|
+
@select="$emit('select', $event)"
|
|
211
|
+
@toggle="$emit('toggle', $event)"
|
|
212
|
+
/>
|
|
213
|
+
</ul>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</li>
|
|
217
|
+
</template>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex h-screen bg-canvas text-canvas-foreground">
|
|
3
|
+
<div v-if="mobileMenuOpen" class="fixed inset-0 z-30 bg-slate-950/50 backdrop-blur-[1px] lg:hidden" @click="closeMenu" />
|
|
4
|
+
|
|
5
|
+
<MultilevelSidebar
|
|
6
|
+
:title="title"
|
|
7
|
+
:width="width"
|
|
8
|
+
:collapsed-width="collapsedWidth"
|
|
9
|
+
:main-menu="mainMenu"
|
|
10
|
+
:mobile-open="mobileMenuOpen"
|
|
11
|
+
:account="account"
|
|
12
|
+
:contact-email="contactEmail"
|
|
13
|
+
:help-url="helpUrl"
|
|
14
|
+
@logo-click="handleLogoClick"
|
|
15
|
+
@signin="handleSignin"
|
|
16
|
+
@signup="handleSignup"
|
|
17
|
+
@signout="handleSignout"
|
|
18
|
+
@edit-profile="handleEditProfile"
|
|
19
|
+
@item-click="handleItemClick"
|
|
20
|
+
@select="handleSelect"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<main class="flex flex-col flex-1 overflow-y-auto">
|
|
24
|
+
<slot />
|
|
25
|
+
</main>
|
|
26
|
+
|
|
27
|
+
<button
|
|
28
|
+
v-if="showBurger && !mobileMenuOpen"
|
|
29
|
+
type="button"
|
|
30
|
+
class="fixed z-50 inline-flex items-center justify-center text-white transition-colors rounded-full shadow-lg bottom-5 left-5 size-14 bg-sidebar shadow-black/25 hover:bg-sidebar-accent lg:hidden"
|
|
31
|
+
aria-label="Open menu"
|
|
32
|
+
@click="toggleMenu">
|
|
33
|
+
<Menu class="size-6" :stroke-width="2.25" />
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<script>
|
|
39
|
+
import { Menu } from "@lucide/vue";
|
|
40
|
+
import MultilevelSidebar from "./MultilevelSidebar.vue";
|
|
41
|
+
|
|
42
|
+
export default {
|
|
43
|
+
name: "TwoColLayout",
|
|
44
|
+
components: {
|
|
45
|
+
Menu,
|
|
46
|
+
MultilevelSidebar,
|
|
47
|
+
},
|
|
48
|
+
props: {
|
|
49
|
+
title: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: "",
|
|
52
|
+
},
|
|
53
|
+
width: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: "360px",
|
|
56
|
+
},
|
|
57
|
+
collapsedWidth: {
|
|
58
|
+
type: String,
|
|
59
|
+
default: "76px",
|
|
60
|
+
},
|
|
61
|
+
mainMenu: {
|
|
62
|
+
type: Array,
|
|
63
|
+
default: () => [],
|
|
64
|
+
},
|
|
65
|
+
account: {
|
|
66
|
+
type: Object,
|
|
67
|
+
default: null,
|
|
68
|
+
},
|
|
69
|
+
contactEmail: {
|
|
70
|
+
type: String,
|
|
71
|
+
default: "grammateia@ketekny.gr",
|
|
72
|
+
},
|
|
73
|
+
helpUrl: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: "https://help.ketekny.gr",
|
|
76
|
+
},
|
|
77
|
+
showBurger: {
|
|
78
|
+
type: Boolean,
|
|
79
|
+
default: true,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
emits: ["select-menu", "logo-click", "signin", "signup", "signout", "edit-profile", "item-click"],
|
|
83
|
+
data() {
|
|
84
|
+
return {
|
|
85
|
+
mobileMenuOpen: false,
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
methods: {
|
|
89
|
+
toggleMenu() {
|
|
90
|
+
this.mobileMenuOpen = !this.mobileMenuOpen;
|
|
91
|
+
},
|
|
92
|
+
closeMenu() {
|
|
93
|
+
this.mobileMenuOpen = false;
|
|
94
|
+
},
|
|
95
|
+
handleSelect(id) {
|
|
96
|
+
this.closeMenu();
|
|
97
|
+
this.$emit("select-menu", id);
|
|
98
|
+
},
|
|
99
|
+
handleLogoClick() {
|
|
100
|
+
this.closeMenu();
|
|
101
|
+
this.$emit("logo-click");
|
|
102
|
+
},
|
|
103
|
+
handleSignin() {
|
|
104
|
+
this.closeMenu();
|
|
105
|
+
this.$emit("signin");
|
|
106
|
+
},
|
|
107
|
+
handleSignup() {
|
|
108
|
+
this.closeMenu();
|
|
109
|
+
this.$emit("signup");
|
|
110
|
+
},
|
|
111
|
+
handleSignout() {
|
|
112
|
+
this.closeMenu();
|
|
113
|
+
this.$emit("signout");
|
|
114
|
+
},
|
|
115
|
+
handleEditProfile() {
|
|
116
|
+
this.closeMenu();
|
|
117
|
+
this.$emit("edit-profile");
|
|
118
|
+
},
|
|
119
|
+
handleItemClick(item) {
|
|
120
|
+
this.closeMenu();
|
|
121
|
+
this.$emit("item-click", item);
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
</script>
|
package/tailwind.config.js
CHANGED
|
@@ -6,6 +6,10 @@ export default {
|
|
|
6
6
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
|
7
7
|
"./preview/**/*.{vue,js,ts}",
|
|
8
8
|
],
|
|
9
|
+
fontFamily: {
|
|
10
|
+
sans: ['Ubuntu', 'ui-sans-serif', 'system-ui', 'sans-serif'],
|
|
11
|
+
mono: ['Geist Mono', 'ui-monospace', 'monospace'],
|
|
12
|
+
},
|
|
9
13
|
safelist: [
|
|
10
14
|
// Runtime-generated semantic classes used by alert/message/toast components.
|
|
11
15
|
{ pattern: /(bg|text|border)-semantic-(success|warning|error|info)-(bg|border|text|disabled|button)/ },
|
|
@@ -18,7 +22,28 @@ export default {
|
|
|
18
22
|
],
|
|
19
23
|
theme: {
|
|
20
24
|
extend: {
|
|
25
|
+
|
|
21
26
|
colors: {
|
|
27
|
+
sidebar: {
|
|
28
|
+
DEFAULT: 'hsl(222 18% 11%)',
|
|
29
|
+
foreground: 'hsl(220 14% 80%)',
|
|
30
|
+
muted: 'hsl(220 10% 55%)',
|
|
31
|
+
accent: 'hsl(222 16% 17%)',
|
|
32
|
+
border: 'hsl(220 14% 100% / 0.06)',
|
|
33
|
+
},
|
|
34
|
+
brand: {
|
|
35
|
+
DEFAULT: 'hsl(2 78% 55%)',
|
|
36
|
+
foreground: 'hsl(0 0% 100%)',
|
|
37
|
+
},
|
|
38
|
+
canvas: {
|
|
39
|
+
DEFAULT: 'hsl(220 20% 97%)',
|
|
40
|
+
foreground: 'hsl(222 30% 12%)',
|
|
41
|
+
muted: 'hsl(220 10% 45%)',
|
|
42
|
+
card: 'hsl(0 0% 100%)',
|
|
43
|
+
border: 'hsl(220 14% 90%)',
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
|
|
22
47
|
default: "#1B2A33",
|
|
23
48
|
header: "#256D96",
|
|
24
49
|
background: "#FEFCF6",
|