ketekny-ui-kit 1.0.75 → 1.0.77
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 +175 -345
- package/src/layout/twoColSidebarPanel.vue +329 -0
- package/src/style/style.css +6 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
class="relative flex min-h-screen flex-col bg-[#edf4fb] lg:h-screen lg:flex-row lg:overflow-hidden"
|
|
4
4
|
>
|
|
5
5
|
<button
|
|
6
|
-
v-show="isSidebarCollapsed"
|
|
6
|
+
v-show="isDesktopViewport && isSidebarCollapsed"
|
|
7
7
|
type="button"
|
|
8
8
|
:aria-label="expandLabel"
|
|
9
9
|
v-tooltip.right="expandLabel"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
</button>
|
|
17
17
|
|
|
18
18
|
<button
|
|
19
|
-
v-show="!isSidebarCollapsed"
|
|
19
|
+
v-show="isDesktopViewport && !isSidebarCollapsed"
|
|
20
20
|
type="button"
|
|
21
21
|
:aria-label="collapseLabel"
|
|
22
22
|
v-tooltip.left="collapseLabel"
|
|
@@ -29,229 +29,91 @@
|
|
|
29
29
|
</button>
|
|
30
30
|
|
|
31
31
|
<aside
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
? 'lg:relative lg:z-30 lg:w-[76px]'
|
|
36
|
-
: 'lg:relative lg:z-30 lg:translate-x-0'
|
|
37
|
-
"
|
|
32
|
+
v-if="isDesktopViewport"
|
|
33
|
+
class="hidden lg:flex lg:shrink-0 transition-all duration-300 ease-out"
|
|
34
|
+
:class="isSidebarCollapsed ? 'lg:w-[76px]' : 'lg:w-[360px]'"
|
|
38
35
|
>
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<slot name="sidebar-header" />
|
|
63
|
-
</div>
|
|
36
|
+
<SidebarPanel
|
|
37
|
+
:title="title"
|
|
38
|
+
:collapsed-logo-src="collapsedLogoSrc"
|
|
39
|
+
:logo-src="logoSrc"
|
|
40
|
+
:logo-alt="logoAlt"
|
|
41
|
+
:show-sponsors="showSponsors"
|
|
42
|
+
:menu-items="normalizedMainMenu"
|
|
43
|
+
:show-contact-info="showContactInfo"
|
|
44
|
+
:account="account"
|
|
45
|
+
:contact-email="contactEmail"
|
|
46
|
+
:collapsed="isSidebarCollapsed"
|
|
47
|
+
@logo-click="handleLogoClick"
|
|
48
|
+
@signin="handleSignin"
|
|
49
|
+
@signup="handleSignup"
|
|
50
|
+
@signout="handleSignout"
|
|
51
|
+
@edit-profile="handleEditProfile"
|
|
52
|
+
>
|
|
53
|
+
<template #sidebar-header>
|
|
54
|
+
<slot name="sidebar-header" />
|
|
55
|
+
</template>
|
|
56
|
+
</SidebarPanel>
|
|
57
|
+
</aside>
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
:active="item.active"
|
|
75
|
-
v-bind="item.bindings"
|
|
76
|
-
v-tooltip.right="item.label"
|
|
77
|
-
/>
|
|
78
|
-
</div>
|
|
79
|
-
<div v-else-if="mainMenu.length" class="mx-2 mt-3 space-y-3">
|
|
80
|
-
<component
|
|
81
|
-
v-for="item in normalizedMainMenu"
|
|
82
|
-
:key="item.key"
|
|
83
|
-
:is="menuItemTag(item)"
|
|
84
|
-
v-bind="item.bindings"
|
|
85
|
-
class="flex w-full items-start gap-3 rounded-2xl border px-4 py-4 text-left transition-all duration-200"
|
|
86
|
-
:class="item.classes"
|
|
87
|
-
>
|
|
88
|
-
<span
|
|
89
|
-
class="mt-0.5 flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-white/10 text-cyan-100 transition-colors duration-200"
|
|
90
|
-
:class="item.active ? 'bg-slate-950 text-white' : ''"
|
|
91
|
-
>
|
|
92
|
-
<kIcon :name="item.icon" />
|
|
93
|
-
</span>
|
|
94
|
-
<span class="min-w-0">
|
|
95
|
-
<span class="block font-semibold leading-tight">
|
|
96
|
-
{{ item.label }}
|
|
97
|
-
</span>
|
|
98
|
-
<span
|
|
99
|
-
v-if="item.description"
|
|
100
|
-
class="mt-1 block text-sm"
|
|
101
|
-
:class="item.active ? 'text-slate-700' : 'text-slate-300'"
|
|
102
|
-
>
|
|
103
|
-
{{ item.description }}
|
|
104
|
-
</span>
|
|
105
|
-
</span>
|
|
106
|
-
</component>
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
59
|
+
<button
|
|
60
|
+
v-if="!isDesktopViewport && !mobileMenuOpen"
|
|
61
|
+
type="button"
|
|
62
|
+
class="fixed bottom-4 left-4 z-[80] flex h-14 w-14 items-center justify-center rounded-full bg-[#121b2e] text-white shadow-2xl shadow-black/40 transition-transform duration-200 hover:scale-105 hover:bg-[#1a2640] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
|
|
63
|
+
aria-label="Open menu"
|
|
64
|
+
@click="mobileMenuOpen = true"
|
|
65
|
+
>
|
|
66
|
+
<kIcon name="Menu" size="22" class="text-white" />
|
|
67
|
+
</button>
|
|
109
68
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
:title="'Επεξεργασία προφίλ'"
|
|
120
|
-
v-tooltip.right="'Επεξεργασία προφίλ'"
|
|
121
|
-
/>
|
|
122
|
-
<SidebarIconLink
|
|
123
|
-
icon="LogOut"
|
|
124
|
-
warning
|
|
125
|
-
:on-click="() => $emit('signout')"
|
|
126
|
-
:title="'Αποσύνδεση'"
|
|
127
|
-
v-tooltip.right="'Αποσύνδεση'"
|
|
128
|
-
/>
|
|
129
|
-
</div>
|
|
130
|
-
<div v-else class="flex flex-col items-center gap-2">
|
|
131
|
-
<SidebarIconLink
|
|
132
|
-
icon="LogIn"
|
|
133
|
-
:on-click="() => $emit('signin')"
|
|
134
|
-
:title="'Sign in'"
|
|
135
|
-
v-tooltip.right="'Sign in'"
|
|
136
|
-
/>
|
|
137
|
-
<SidebarIconLink
|
|
138
|
-
icon="UserPlus"
|
|
139
|
-
:on-click="() => $emit('signup')"
|
|
140
|
-
:title="'Sign up'"
|
|
141
|
-
v-tooltip.right="'Sign up'"
|
|
142
|
-
/>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
<div v-else class="flex flex-col gap-3 px-3">
|
|
146
|
-
<div class="min-w-0">
|
|
147
|
-
<p
|
|
148
|
-
v-if="account.loggedIn"
|
|
149
|
-
class="mt-2 truncate text-lg font-semibold text-white"
|
|
150
|
-
>
|
|
151
|
-
{{ accountDisplayName }}
|
|
152
|
-
</p>
|
|
153
|
-
<p
|
|
154
|
-
v-if="account.loggedIn && displayEmail"
|
|
155
|
-
class="truncate text-sm text-slate-300"
|
|
156
|
-
>
|
|
157
|
-
{{ displayEmail }}
|
|
158
|
-
</p>
|
|
159
|
-
</div>
|
|
160
|
-
<div class="flex flex-wrap justify-between gap-2">
|
|
161
|
-
<kButton
|
|
162
|
-
v-if="!account.loggedIn"
|
|
163
|
-
secondary
|
|
164
|
-
icon="LogIn"
|
|
165
|
-
label="Σύνδεση"
|
|
166
|
-
@click="$emit('signin')"
|
|
167
|
-
/>
|
|
168
|
-
<kButton
|
|
169
|
-
v-if="!account.loggedIn"
|
|
170
|
-
secondary
|
|
171
|
-
icon="UserPlus"
|
|
172
|
-
label="Εγγραφή"
|
|
173
|
-
@click="$emit('signup')"
|
|
174
|
-
/>
|
|
175
|
-
<kButton
|
|
176
|
-
v-if="account.loggedIn"
|
|
177
|
-
variant="soft"
|
|
178
|
-
icon="UserRoundCog"
|
|
179
|
-
label="Επεξεργασία προφίλ"
|
|
180
|
-
@click="$emit('edit-profile')"
|
|
181
|
-
/>
|
|
182
|
-
<kButton
|
|
183
|
-
v-if="account.loggedIn"
|
|
184
|
-
icon="LogOut"
|
|
185
|
-
warning
|
|
186
|
-
label="Αποσύνδεση"
|
|
187
|
-
@click="$emit('signout')"
|
|
188
|
-
/>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
69
|
+
<transition name="mobile-menu-backdrop">
|
|
70
|
+
<button
|
|
71
|
+
v-if="!isDesktopViewport && mobileMenuOpen"
|
|
72
|
+
type="button"
|
|
73
|
+
class="fixed inset-0 z-[85] bg-black/50 backdrop-blur-sm"
|
|
74
|
+
aria-label="Close menu"
|
|
75
|
+
@click="closeMobileMenu"
|
|
76
|
+
/>
|
|
77
|
+
</transition>
|
|
192
78
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
79
|
+
<transition name="mobile-menu-panel">
|
|
80
|
+
<aside
|
|
81
|
+
v-if="!isDesktopViewport && mobileMenuOpen"
|
|
82
|
+
class="fixed inset-y-0 left-0 z-[90] flex h-[100dvh] w-[min(90vw,420px)] max-w-full overflow-hidden shadow-2xl"
|
|
83
|
+
role="dialog"
|
|
84
|
+
aria-modal="true"
|
|
196
85
|
>
|
|
197
|
-
<div class="flex
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
>
|
|
217
|
-
{{ contactEmail }}
|
|
218
|
-
</a>
|
|
219
|
-
</span>
|
|
220
|
-
<a
|
|
221
|
-
href="https://ketekny.gr"
|
|
222
|
-
target="_blank"
|
|
223
|
-
rel="noreferrer"
|
|
224
|
-
class="whitespace-nowrap hover:underline"
|
|
86
|
+
<div class="relative flex h-full w-full flex-col bg-transparent">
|
|
87
|
+
<div class="flex-1 overflow-y-auto bg-transparent">
|
|
88
|
+
<SidebarPanel
|
|
89
|
+
:title="title"
|
|
90
|
+
:collapsed-logo-src="collapsedLogoSrc"
|
|
91
|
+
:logo-src="logoSrc"
|
|
92
|
+
:logo-alt="logoAlt"
|
|
93
|
+
:show-sponsors="showSponsors"
|
|
94
|
+
:menu-items="normalizedMainMenu"
|
|
95
|
+
:show-contact-info="showContactInfo"
|
|
96
|
+
:account="account"
|
|
97
|
+
:contact-email="contactEmail"
|
|
98
|
+
:collapsed="false"
|
|
99
|
+
@logo-click="handleLogoClick"
|
|
100
|
+
@item-click="closeMobileMenu"
|
|
101
|
+
@signin="handleSignin"
|
|
102
|
+
@signup="handleSignup"
|
|
103
|
+
@signout="handleSignout"
|
|
104
|
+
@edit-profile="handleEditProfile"
|
|
225
105
|
>
|
|
226
|
-
|
|
227
|
-
|
|
106
|
+
<template #sidebar-header>
|
|
107
|
+
<slot name="sidebar-header" />
|
|
108
|
+
</template>
|
|
109
|
+
</SidebarPanel>
|
|
228
110
|
</div>
|
|
229
111
|
</div>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
v-if="showSponsors && !isSidebarCollapsed"
|
|
233
|
-
class="flex flex-col gap-3 px-4 py-1 bg-[#0d4b87] mt-2"
|
|
234
|
-
>
|
|
235
|
-
<div>
|
|
236
|
-
<img
|
|
237
|
-
src="https://s3.ketekny.gr/public/web-apps/sponsors/Greece 2.0_NextGeneration_White_gr.png"
|
|
238
|
-
alt="Tameio Anakampsis"
|
|
239
|
-
class="mx-auto max-h-14 w-auto object-contain"
|
|
240
|
-
/>
|
|
241
|
-
</div>
|
|
242
|
-
<div>
|
|
243
|
-
<img
|
|
244
|
-
src="https://s3.ketekny.gr/public/web-apps/sponsors/yp_ygeias_dark.png"
|
|
245
|
-
alt="Ypourgeio Ygeias"
|
|
246
|
-
class="mx-auto max-h-14 w-auto object-contain"
|
|
247
|
-
/>
|
|
248
|
-
</div>
|
|
249
|
-
</div>
|
|
250
|
-
</div>
|
|
251
|
-
</aside>
|
|
112
|
+
</aside>
|
|
113
|
+
</transition>
|
|
252
114
|
|
|
253
115
|
<main
|
|
254
|
-
class="min-h-
|
|
116
|
+
class="min-h-0 min-w-0 flex-1 overflow-visible lg:h-full lg:min-h-0 lg:overflow-y-auto lg:overflow-x-hidden"
|
|
255
117
|
>
|
|
256
118
|
<div class="w-full">
|
|
257
119
|
<slot />
|
|
@@ -260,68 +122,18 @@
|
|
|
260
122
|
</div>
|
|
261
123
|
</template>
|
|
262
124
|
|
|
263
|
-
|
|
264
|
-
import { h } from "vue";
|
|
265
|
-
import kButton from "../ui/kButton.vue";
|
|
125
|
+
<script>
|
|
266
126
|
import kIcon from "../ui/kIcon.vue";
|
|
267
|
-
import
|
|
268
|
-
|
|
269
|
-
const SidebarIconLink = {
|
|
270
|
-
props: {
|
|
271
|
-
icon: { type: String, required: true },
|
|
272
|
-
active: { type: Boolean, default: false },
|
|
273
|
-
to: { type: [String, Object], default: null },
|
|
274
|
-
href: { type: String, default: null },
|
|
275
|
-
title: { type: String, default: "" },
|
|
276
|
-
isExternal: { type: Boolean, default: false },
|
|
277
|
-
target: { type: String, default: "_blank" },
|
|
278
|
-
rel: { type: String, default: "noreferrer" },
|
|
279
|
-
onClick: { type: Function, default: null },
|
|
280
|
-
warning: { type: Boolean, default: false },
|
|
281
|
-
tooltip: { type: String, default: "" },
|
|
282
|
-
},
|
|
283
|
-
methods: {
|
|
284
|
-
handleClick(event) {
|
|
285
|
-
if (this.onClick) this.onClick(event);
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
render() {
|
|
289
|
-
const isLink = Boolean(this.to);
|
|
290
|
-
const tag = isLink ? RouterLink : this.isExternal ? "a" : "button";
|
|
291
|
-
|
|
292
|
-
return h(
|
|
293
|
-
tag,
|
|
294
|
-
{
|
|
295
|
-
to: isLink ? this.to : undefined,
|
|
296
|
-
href: this.href,
|
|
297
|
-
target: this.isExternal ? this.target : null,
|
|
298
|
-
rel: this.isExternal ? this.rel : null,
|
|
299
|
-
type: isLink || this.isExternal ? null : "button",
|
|
300
|
-
title: this.title,
|
|
301
|
-
class: [
|
|
302
|
-
"group relative flex h-14 w-14 items-center justify-center rounded-xl border border-white/15 transition",
|
|
303
|
-
this.active
|
|
304
|
-
? "bg-slate-950 text-white"
|
|
305
|
-
: this.warning
|
|
306
|
-
? "bg-[#d97706] text-white hover:bg-[#b45309]"
|
|
307
|
-
: "bg-white/5 text-white hover:border-cyan-400/40 hover:bg-white/10",
|
|
308
|
-
isLink ? "cursor-pointer" : "",
|
|
309
|
-
],
|
|
310
|
-
onClick: this.handleClick,
|
|
311
|
-
},
|
|
312
|
-
[h(kIcon, { name: this.icon, size: "20", color: "#ffffff" })]
|
|
313
|
-
);
|
|
314
|
-
},
|
|
315
|
-
};
|
|
127
|
+
import SidebarPanel from "./twoColSidebarPanel.vue";
|
|
316
128
|
|
|
317
129
|
const DEFAULT_STORAGE_KEY = "two-col-layout-sidebar-collapsed";
|
|
130
|
+
const TAILWIND_MD_QUERY = "(min-width: 768px) and (max-width: 1023px)";
|
|
131
|
+
const TAILWIND_LG_QUERY = "(min-width: 1024px)";
|
|
318
132
|
|
|
319
133
|
export default {
|
|
320
134
|
components: {
|
|
321
|
-
kButton,
|
|
322
135
|
kIcon,
|
|
323
|
-
|
|
324
|
-
SidebarIconLink,
|
|
136
|
+
SidebarPanel,
|
|
325
137
|
},
|
|
326
138
|
emits: ["logo-click", "signin", "signup", "signout", "edit-profile"],
|
|
327
139
|
props: {
|
|
@@ -346,56 +158,110 @@ export default {
|
|
|
346
158
|
},
|
|
347
159
|
data() {
|
|
348
160
|
return {
|
|
349
|
-
isSidebarCollapsed:
|
|
161
|
+
isSidebarCollapsed: null,
|
|
350
162
|
mdCollapseQuery: null,
|
|
163
|
+
lgViewportQuery: null,
|
|
164
|
+
isDesktopViewport: false,
|
|
165
|
+
mobileMenuOpen: false,
|
|
351
166
|
};
|
|
352
167
|
},
|
|
353
168
|
created() {
|
|
354
|
-
this.
|
|
169
|
+
this.isSidebarCollapsed = this.getInitialSidebarState();
|
|
170
|
+
|
|
171
|
+
if (typeof window !== "undefined") {
|
|
172
|
+
this.isDesktopViewport = window.matchMedia(TAILWIND_LG_QUERY).matches;
|
|
173
|
+
}
|
|
355
174
|
},
|
|
356
175
|
mounted() {
|
|
357
|
-
this.mdCollapseQuery = window.matchMedia(
|
|
358
|
-
|
|
359
|
-
|
|
176
|
+
this.mdCollapseQuery = window.matchMedia(TAILWIND_MD_QUERY);
|
|
177
|
+
this.lgViewportQuery = window.matchMedia(TAILWIND_LG_QUERY);
|
|
178
|
+
this.isDesktopViewport = this.lgViewportQuery.matches;
|
|
179
|
+
|
|
360
180
|
this.syncSidebarForViewport();
|
|
361
|
-
this.mdCollapseQuery.addEventListener(
|
|
362
|
-
|
|
363
|
-
this.syncSidebarForViewport
|
|
364
|
-
);
|
|
181
|
+
this.mdCollapseQuery.addEventListener("change", this.syncSidebarForViewport);
|
|
182
|
+
this.lgViewportQuery.addEventListener("change", this.syncDesktopViewport);
|
|
365
183
|
},
|
|
366
184
|
beforeUnmount() {
|
|
367
185
|
this.mdCollapseQuery?.removeEventListener(
|
|
368
186
|
"change",
|
|
369
187
|
this.syncSidebarForViewport
|
|
370
188
|
);
|
|
189
|
+
this.lgViewportQuery?.removeEventListener(
|
|
190
|
+
"change",
|
|
191
|
+
this.syncDesktopViewport
|
|
192
|
+
);
|
|
193
|
+
if (typeof document !== "undefined") {
|
|
194
|
+
document.body.style.overflow = "";
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
watch: {
|
|
198
|
+
mobileMenuOpen(isOpen) {
|
|
199
|
+
if (typeof document === "undefined") return;
|
|
200
|
+
|
|
201
|
+
document.body.style.overflow = isOpen ? "hidden" : "";
|
|
202
|
+
},
|
|
371
203
|
},
|
|
372
204
|
methods: {
|
|
373
|
-
|
|
205
|
+
isMdViewport() {
|
|
206
|
+
if (typeof window === "undefined") return false;
|
|
207
|
+
|
|
208
|
+
return window.matchMedia(TAILWIND_MD_QUERY).matches;
|
|
209
|
+
},
|
|
210
|
+
getInitialSidebarState() {
|
|
374
211
|
if (typeof window === "undefined") {
|
|
375
|
-
|
|
376
|
-
|
|
212
|
+
return this.defaultCollapsed;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (this.isMdViewport()) {
|
|
216
|
+
return true;
|
|
377
217
|
}
|
|
378
218
|
|
|
379
219
|
const stored = window.localStorage.getItem(this.storageKey);
|
|
380
220
|
if (stored == null) {
|
|
381
|
-
|
|
382
|
-
return;
|
|
221
|
+
return this.defaultCollapsed;
|
|
383
222
|
}
|
|
384
223
|
|
|
385
|
-
|
|
224
|
+
return stored === "true";
|
|
386
225
|
},
|
|
387
226
|
syncSidebarForViewport() {
|
|
388
227
|
if (typeof window === "undefined") return;
|
|
389
228
|
|
|
390
|
-
|
|
391
|
-
"(min-width: 768px) and (max-width: 1023px)"
|
|
392
|
-
).matches;
|
|
393
|
-
if (isMd) {
|
|
229
|
+
if (this.isMdViewport()) {
|
|
394
230
|
this.isSidebarCollapsed = true;
|
|
395
231
|
return;
|
|
396
232
|
}
|
|
397
233
|
|
|
398
|
-
this.
|
|
234
|
+
this.isSidebarCollapsed = this.getInitialSidebarState();
|
|
235
|
+
},
|
|
236
|
+
syncDesktopViewport(event) {
|
|
237
|
+
this.isDesktopViewport = event.matches;
|
|
238
|
+
|
|
239
|
+
if (event.matches) {
|
|
240
|
+
this.mobileMenuOpen = false;
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
closeMobileMenu() {
|
|
244
|
+
this.mobileMenuOpen = false;
|
|
245
|
+
},
|
|
246
|
+
handleLogoClick() {
|
|
247
|
+
this.closeMobileMenu();
|
|
248
|
+
this.$emit("logo-click");
|
|
249
|
+
},
|
|
250
|
+
handleSignin() {
|
|
251
|
+
this.closeMobileMenu();
|
|
252
|
+
this.$emit("signin");
|
|
253
|
+
},
|
|
254
|
+
handleSignup() {
|
|
255
|
+
this.closeMobileMenu();
|
|
256
|
+
this.$emit("signup");
|
|
257
|
+
},
|
|
258
|
+
handleSignout() {
|
|
259
|
+
this.closeMobileMenu();
|
|
260
|
+
this.$emit("signout");
|
|
261
|
+
},
|
|
262
|
+
handleEditProfile() {
|
|
263
|
+
this.closeMobileMenu();
|
|
264
|
+
this.$emit("edit-profile");
|
|
399
265
|
},
|
|
400
266
|
persistSidebarState() {
|
|
401
267
|
if (typeof window === "undefined") return;
|
|
@@ -409,11 +275,6 @@ export default {
|
|
|
409
275
|
this.isSidebarCollapsed = !this.isSidebarCollapsed;
|
|
410
276
|
this.persistSidebarState();
|
|
411
277
|
},
|
|
412
|
-
menuItemTag(item) {
|
|
413
|
-
if (item?.to) return RouterLink;
|
|
414
|
-
if (item?.href) return "a";
|
|
415
|
-
return "button";
|
|
416
|
-
},
|
|
417
278
|
},
|
|
418
279
|
computed: {
|
|
419
280
|
normalizedMainMenu() {
|
|
@@ -453,56 +314,25 @@ export default {
|
|
|
453
314
|
};
|
|
454
315
|
});
|
|
455
316
|
},
|
|
456
|
-
menuItemBindings(item) {
|
|
457
|
-
if (item?.to) {
|
|
458
|
-
return {
|
|
459
|
-
to: item.to,
|
|
460
|
-
title: item.title || item.label,
|
|
461
|
-
"aria-current": item.active ? "page" : null,
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (item?.href) {
|
|
466
|
-
return {
|
|
467
|
-
href: item.href,
|
|
468
|
-
target: item.target || "_blank",
|
|
469
|
-
rel: item.rel || "noreferrer",
|
|
470
|
-
title: item.title || item.label,
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return {
|
|
475
|
-
type: "button",
|
|
476
|
-
title: item?.title || item?.label,
|
|
477
|
-
};
|
|
478
|
-
},
|
|
479
|
-
accountDisplayName() {
|
|
480
|
-
if (!this.account) return "";
|
|
481
|
-
return (
|
|
482
|
-
[this.account.firstName, this.account.lastName]
|
|
483
|
-
.filter(Boolean)
|
|
484
|
-
.join(" ")
|
|
485
|
-
.trim() || "Account"
|
|
486
|
-
);
|
|
487
|
-
},
|
|
488
|
-
displayEmail() {
|
|
489
|
-
return this.account?.email || "";
|
|
490
|
-
},
|
|
491
|
-
collapsedContactTooltip() {
|
|
492
|
-
return `
|
|
493
|
-
<div style="min-width: 220px;">
|
|
494
|
-
<div style="font-weight: 700; text-transform: uppercase; letter-spacing: 0.25em; font-size: 11px; color: rgba(255,255,255,0.65); margin-bottom: 8px;">
|
|
495
|
-
KETEKNY AE
|
|
496
|
-
</div>
|
|
497
|
-
<div style="display: flex; flex-direction: column; gap: 4px; font-size: 13px; line-height: 1.4; color: rgba(255,255,255,0.9);">
|
|
498
|
-
<div>Βερανζέρου 13, 10677, Αθήνα</div>
|
|
499
|
-
<div>210 3648 337</div>
|
|
500
|
-
<div>${this.contactEmail}</div>
|
|
501
|
-
<div>https://ketekny.gr</div>
|
|
502
|
-
</div>
|
|
503
|
-
</div>
|
|
504
|
-
`;
|
|
505
|
-
},
|
|
506
317
|
},
|
|
507
318
|
};
|
|
508
319
|
</script>
|
|
320
|
+
|
|
321
|
+
<style scoped>
|
|
322
|
+
.mobile-menu-panel-enter-active,
|
|
323
|
+
.mobile-menu-panel-leave-active,
|
|
324
|
+
.mobile-menu-backdrop-enter-active,
|
|
325
|
+
.mobile-menu-backdrop-leave-active {
|
|
326
|
+
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.mobile-menu-panel-enter-from,
|
|
330
|
+
.mobile-menu-panel-leave-to {
|
|
331
|
+
transform: translateX(-100%);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.mobile-menu-backdrop-enter-from,
|
|
335
|
+
.mobile-menu-backdrop-leave-to {
|
|
336
|
+
opacity: 0;
|
|
337
|
+
}
|
|
338
|
+
</style>
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="flex h-full w-full flex-col overflow-x-hidden bg-[radial-gradient(circle_at_0_0,#ffffff0f,#0000_34%),linear-gradient(#101a2d_0%,#0a1120_52%,#070d19_100%)] text-white shadow-2xl transition-all duration-300 ease-out lg:relative lg:z-30 lg:will-change-transform"
|
|
4
|
+
>
|
|
5
|
+
<div class="relative">
|
|
6
|
+
<button
|
|
7
|
+
v-if="logoSrc"
|
|
8
|
+
type="button"
|
|
9
|
+
class="block w-full text-left"
|
|
10
|
+
v-tooltip.right.html="collapsed ? collapsedContactTooltip : logoAlt"
|
|
11
|
+
@click="$emit('logo-click')"
|
|
12
|
+
>
|
|
13
|
+
<img
|
|
14
|
+
:alt="logoAlt"
|
|
15
|
+
:src="collapsed ? collapsedLogoSrc : logoSrc"
|
|
16
|
+
class="px-4 py-4 transition-all duration-300 cursor-pointer"
|
|
17
|
+
/>
|
|
18
|
+
</button>
|
|
19
|
+
<div
|
|
20
|
+
v-if="title && !collapsed"
|
|
21
|
+
class="py-3 text-xl font-bold text-center"
|
|
22
|
+
>
|
|
23
|
+
{{ title }}
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<slot name="sidebar-header" />
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="flex-1 pt-4">
|
|
30
|
+
<div v-if="collapsed" class="flex flex-col items-center gap-2 mx-1 mt-3">
|
|
31
|
+
<component
|
|
32
|
+
:is="menuItemComponent(item)"
|
|
33
|
+
v-for="item in menuItems"
|
|
34
|
+
:key="item.key"
|
|
35
|
+
v-bind="item.bindings"
|
|
36
|
+
class="relative flex items-center justify-center transition border group h-14 w-14 rounded-xl border-white/15"
|
|
37
|
+
:class="item.classes"
|
|
38
|
+
v-tooltip.right="item.label"
|
|
39
|
+
@click="$emit('item-click', item)"
|
|
40
|
+
>
|
|
41
|
+
<kIcon
|
|
42
|
+
:name="item.icon"
|
|
43
|
+
size="20"
|
|
44
|
+
:color="item.active ? '#0f172a' : '#ffffff'"
|
|
45
|
+
/>
|
|
46
|
+
</component>
|
|
47
|
+
</div>
|
|
48
|
+
<div v-else-if="menuItems.length" class="mx-2 mt-3 space-y-3">
|
|
49
|
+
<component
|
|
50
|
+
:is="menuItemComponent(item)"
|
|
51
|
+
v-for="item in menuItems"
|
|
52
|
+
:key="item.key"
|
|
53
|
+
v-bind="item.bindings"
|
|
54
|
+
class="flex items-start w-full gap-3 px-4 py-4 text-left transition-all duration-200 border rounded-2xl"
|
|
55
|
+
:class="item.classes"
|
|
56
|
+
@click="$emit('item-click', item)"
|
|
57
|
+
>
|
|
58
|
+
<span
|
|
59
|
+
class="mt-0.5 flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-white/10 text-cyan-100 transition-colors duration-200"
|
|
60
|
+
:class="
|
|
61
|
+
item.active
|
|
62
|
+
? 'bg-slate-950 text-white'
|
|
63
|
+
: item.label === 'Settings'
|
|
64
|
+
? 'bg-sky-500/20 text-sky-50 ring-1 ring-sky-400/25'
|
|
65
|
+
: ''
|
|
66
|
+
"
|
|
67
|
+
>
|
|
68
|
+
<kIcon :name="item.icon" />
|
|
69
|
+
</span>
|
|
70
|
+
<span class="min-w-0">
|
|
71
|
+
<span class="block font-semibold leading-tight">
|
|
72
|
+
{{ item.label }}
|
|
73
|
+
</span>
|
|
74
|
+
<span
|
|
75
|
+
v-if="item.description"
|
|
76
|
+
class="block mt-1 text-sm"
|
|
77
|
+
:class="item.active ? 'text-slate-700' : 'text-slate-300'"
|
|
78
|
+
>
|
|
79
|
+
{{ item.description }}
|
|
80
|
+
</span>
|
|
81
|
+
</span>
|
|
82
|
+
</component>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div
|
|
87
|
+
v-if="account"
|
|
88
|
+
class="px-1 pb-4 border-b shrink-0 border-white/10 bg-slate-900/80 text-white/80"
|
|
89
|
+
>
|
|
90
|
+
<div v-if="collapsed" class="flex flex-col gap-3">
|
|
91
|
+
<div v-if="account.loggedIn" class="flex flex-col items-center gap-2">
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
class="relative flex items-center justify-center text-white transition border group h-14 w-14 rounded-xl border-white/15 bg-white/5 hover:border-cyan-400/40 hover:bg-white/10"
|
|
95
|
+
:title="editProfileLabel"
|
|
96
|
+
v-tooltip.right="editProfileLabel"
|
|
97
|
+
@click="$emit('edit-profile')"
|
|
98
|
+
>
|
|
99
|
+
<kIcon name="UserRoundCog" size="20" color="#ffffff" />
|
|
100
|
+
</button>
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
class="group relative flex h-14 w-14 items-center justify-center rounded-xl border border-white/15 bg-[#d97706] text-white transition hover:bg-[#b45309]"
|
|
104
|
+
:title="signOutLabel"
|
|
105
|
+
v-tooltip.right="signOutLabel"
|
|
106
|
+
@click="$emit('signout')"
|
|
107
|
+
>
|
|
108
|
+
<kIcon name="LogOut" size="20" color="#ffffff" />
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
<div v-else class="flex flex-col items-center gap-2">
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
class="relative flex items-center justify-center text-white transition border group h-14 w-14 rounded-xl border-white/15 bg-white/5 hover:border-cyan-400/40 hover:bg-white/10"
|
|
115
|
+
:title="signInLabel"
|
|
116
|
+
v-tooltip.right="signInLabel"
|
|
117
|
+
@click="$emit('signin')"
|
|
118
|
+
>
|
|
119
|
+
<kIcon name="LogIn" size="20" color="#ffffff" />
|
|
120
|
+
</button>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
class="relative flex items-center justify-center text-white transition border group h-14 w-14 rounded-xl border-white/15 bg-white/5 hover:border-cyan-400/40 hover:bg-white/10"
|
|
124
|
+
:title="signUpLabel"
|
|
125
|
+
v-tooltip.right="signUpLabel"
|
|
126
|
+
@click="$emit('signup')"
|
|
127
|
+
>
|
|
128
|
+
<kIcon name="UserPlus" size="20" color="#ffffff" />
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div v-else class="flex flex-col gap-3 px-3">
|
|
133
|
+
<div class="min-w-0">
|
|
134
|
+
<p
|
|
135
|
+
v-if="account.loggedIn"
|
|
136
|
+
class="mt-2 text-lg font-semibold text-white truncate"
|
|
137
|
+
>
|
|
138
|
+
{{ accountDisplayName }}
|
|
139
|
+
</p>
|
|
140
|
+
<p
|
|
141
|
+
v-if="account.loggedIn && displayEmail"
|
|
142
|
+
class="text-sm truncate text-slate-300"
|
|
143
|
+
>
|
|
144
|
+
{{ displayEmail }}
|
|
145
|
+
</p>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="flex flex-wrap justify-between gap-2">
|
|
148
|
+
<kButton
|
|
149
|
+
v-if="!account.loggedIn"
|
|
150
|
+
secondary
|
|
151
|
+
icon="LogIn"
|
|
152
|
+
:label="signInLabel"
|
|
153
|
+
@click="$emit('signin')"
|
|
154
|
+
/>
|
|
155
|
+
<kButton
|
|
156
|
+
v-if="!account.loggedIn"
|
|
157
|
+
secondary
|
|
158
|
+
icon="UserPlus"
|
|
159
|
+
:label="signUpLabel"
|
|
160
|
+
@click="$emit('signup')"
|
|
161
|
+
/>
|
|
162
|
+
<kButton
|
|
163
|
+
v-if="account.loggedIn"
|
|
164
|
+
variant="soft"
|
|
165
|
+
icon="UserRoundCog"
|
|
166
|
+
:label="editProfileLabel"
|
|
167
|
+
@click="$emit('edit-profile')"
|
|
168
|
+
/>
|
|
169
|
+
<kButton
|
|
170
|
+
v-if="account.loggedIn"
|
|
171
|
+
icon="LogOut"
|
|
172
|
+
warning
|
|
173
|
+
:label="signOutLabel"
|
|
174
|
+
@click="$emit('signout')"
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div
|
|
181
|
+
v-if="!collapsed"
|
|
182
|
+
class="border-t shrink-0 border-white/10 text-white/50"
|
|
183
|
+
>
|
|
184
|
+
<div v-if="showContactInfo" class="flex flex-col gap-2 px-4">
|
|
185
|
+
<p class="text-sm font-semibold uppercase tracking-[0.25em] text-white/60">
|
|
186
|
+
KETEKNY AE
|
|
187
|
+
</p>
|
|
188
|
+
<div class="flex flex-wrap items-center text-sm gap-x-3">
|
|
189
|
+
<span class="whitespace-nowrap">{{ contactAddressLabel }}</span>
|
|
190
|
+
<span class="whitespace-nowrap">210 3648 337</span>
|
|
191
|
+
<span class="whitespace-nowrap">
|
|
192
|
+
<a
|
|
193
|
+
:href="`mailto:${contactEmail}`"
|
|
194
|
+
class="whitespace-nowrap hover:underline"
|
|
195
|
+
>
|
|
196
|
+
{{ contactEmail }}
|
|
197
|
+
</a>
|
|
198
|
+
</span>
|
|
199
|
+
<a
|
|
200
|
+
href="https://ketekny.gr"
|
|
201
|
+
target="_blank"
|
|
202
|
+
rel="noreferrer"
|
|
203
|
+
class="whitespace-nowrap hover:underline"
|
|
204
|
+
>
|
|
205
|
+
https://ketekny.gr
|
|
206
|
+
</a>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div
|
|
211
|
+
v-if="showSponsors"
|
|
212
|
+
class="mt-2 flex flex-col gap-3 bg-[#0d4b87] px-4 py-1"
|
|
213
|
+
>
|
|
214
|
+
<div>
|
|
215
|
+
<img
|
|
216
|
+
src="https://s3.ketekny.gr/public/web-apps/sponsors/Greece 2.0_NextGeneration_White_gr.png"
|
|
217
|
+
alt="Tameio Anakampsis"
|
|
218
|
+
class="object-contain w-auto mx-auto max-h-14"
|
|
219
|
+
/>
|
|
220
|
+
</div>
|
|
221
|
+
<div>
|
|
222
|
+
<img
|
|
223
|
+
src="https://s3.ketekny.gr/public/web-apps/sponsors/yp_ygeias_dark.png"
|
|
224
|
+
alt="Ypourgeio Ygeias"
|
|
225
|
+
class="object-contain w-auto mx-auto max-h-14"
|
|
226
|
+
/>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</template>
|
|
232
|
+
|
|
233
|
+
<script>
|
|
234
|
+
import { RouterLink } from "vue-router";
|
|
235
|
+
import kButton from "../ui/kButton.vue";
|
|
236
|
+
import kIcon from "../ui/kIcon.vue";
|
|
237
|
+
|
|
238
|
+
const LABELS = {
|
|
239
|
+
editProfile: "Το προφίλ μου",
|
|
240
|
+
signOut: "Αποσύνδεση",
|
|
241
|
+
signIn: "Σύνδεση",
|
|
242
|
+
signUp: "Εγγραφή",
|
|
243
|
+
contactAddress: "Βερανζέου 13, 10677, Αθήνα",
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export default {
|
|
247
|
+
name: "TwoColSidebarPanel",
|
|
248
|
+
components: {
|
|
249
|
+
kButton,
|
|
250
|
+
kIcon,
|
|
251
|
+
RouterLink,
|
|
252
|
+
},
|
|
253
|
+
emits: [
|
|
254
|
+
"logo-click",
|
|
255
|
+
"item-click",
|
|
256
|
+
"signin",
|
|
257
|
+
"signup",
|
|
258
|
+
"signout",
|
|
259
|
+
"edit-profile",
|
|
260
|
+
],
|
|
261
|
+
props: {
|
|
262
|
+
title: { type: String, default: "" },
|
|
263
|
+
collapsedLogoSrc: {
|
|
264
|
+
type: String,
|
|
265
|
+
default: "https://s3.ketekny.gr/public/web-apps/logos/logo-icon-dark.png",
|
|
266
|
+
},
|
|
267
|
+
logoSrc: { type: String, default: "" },
|
|
268
|
+
logoAlt: { type: String, default: "Logo" },
|
|
269
|
+
showSponsors: { type: Boolean, default: false },
|
|
270
|
+
menuItems: { type: Array, default: () => [] },
|
|
271
|
+
showContactInfo: { type: Boolean, default: false },
|
|
272
|
+
account: { type: Object, default: null },
|
|
273
|
+
contactEmail: { type: String, default: "grammateia@ketekny.gr" },
|
|
274
|
+
collapsed: { type: Boolean, default: false },
|
|
275
|
+
},
|
|
276
|
+
computed: {
|
|
277
|
+
accountDisplayName() {
|
|
278
|
+
if (!this.account) return "";
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
[this.account.firstName, this.account.lastName]
|
|
282
|
+
.filter(Boolean)
|
|
283
|
+
.join(" ")
|
|
284
|
+
.trim() || "Account"
|
|
285
|
+
);
|
|
286
|
+
},
|
|
287
|
+
displayEmail() {
|
|
288
|
+
return this.account?.email || "";
|
|
289
|
+
},
|
|
290
|
+
editProfileLabel() {
|
|
291
|
+
return LABELS.editProfile;
|
|
292
|
+
},
|
|
293
|
+
signOutLabel() {
|
|
294
|
+
return LABELS.signOut;
|
|
295
|
+
},
|
|
296
|
+
signInLabel() {
|
|
297
|
+
return LABELS.signIn;
|
|
298
|
+
},
|
|
299
|
+
signUpLabel() {
|
|
300
|
+
return LABELS.signUp;
|
|
301
|
+
},
|
|
302
|
+
contactAddressLabel() {
|
|
303
|
+
return LABELS.contactAddress;
|
|
304
|
+
},
|
|
305
|
+
collapsedContactTooltip() {
|
|
306
|
+
return `
|
|
307
|
+
<div style="min-width: 220px;">
|
|
308
|
+
<div style="font-weight: 700; text-transform: uppercase; letter-spacing: 0.25em; font-size: 11px; color: rgba(255,255,255,0.65); margin-bottom: 8px;">
|
|
309
|
+
KETEKNY AE
|
|
310
|
+
</div>
|
|
311
|
+
<div style="display: flex; flex-direction: column; gap: 4px; font-size: 13px; line-height: 1.4; color: rgba(255,255,255,0.9);">
|
|
312
|
+
<div>${LABELS.contactAddress}</div>
|
|
313
|
+
<div>210 3648 337</div>
|
|
314
|
+
<div>${this.contactEmail}</div>
|
|
315
|
+
<div>https://ketekny.gr</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
`;
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
methods: {
|
|
322
|
+
menuItemComponent(item) {
|
|
323
|
+
if (item?.isLink) return RouterLink;
|
|
324
|
+
if (item?.isExternal) return "a";
|
|
325
|
+
return "button";
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
</script>
|
package/src/style/style.css
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
@import url("https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap");
|
|
2
|
+
|
|
1
3
|
@tailwind base;
|
|
2
4
|
@tailwind components;
|
|
3
5
|
@tailwind utilities;
|
|
4
6
|
|
|
7
|
+
body {
|
|
8
|
+
font-family: "Ubuntu", Helvetica, Arial, sans-serif;
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
|
|
6
12
|
h1, h2, h3, h4{
|
|
7
13
|
color: theme('colors.primary');
|