ketekny-ui-kit 1.0.63 → 1.0.64
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/index.js +2 -1
- package/package.json +1 -1
- package/src/layout/twoColLayout.vue +388 -0
package/index.js
CHANGED
|
@@ -34,6 +34,7 @@ import kAppHeader from './src/layout/kAppHeader.vue'
|
|
|
34
34
|
import kAppFooter from './src/layout/kAppFooter.vue'
|
|
35
35
|
import kAppMain from './src/layout/kAppMain.vue'
|
|
36
36
|
import kHero from './src/layout/kHero.vue'
|
|
37
|
+
import twoColLayout from './src/layout/twoColLayout.vue'
|
|
37
38
|
|
|
38
39
|
// Toast/Confirm/Alert Plugins
|
|
39
40
|
import toastPlugin from './src/plugins/toastPlugin.js'
|
|
@@ -57,7 +58,7 @@ export {
|
|
|
57
58
|
kDialog, kDrawer,
|
|
58
59
|
|
|
59
60
|
// Layout Components
|
|
60
|
-
kAppHeader, kAppFooter, kAppMain, kHero,
|
|
61
|
+
kAppHeader, kAppFooter, kAppMain, kHero, twoColLayout,
|
|
61
62
|
|
|
62
63
|
// Plugins
|
|
63
64
|
toastPlugin, confirmPlugin, alertPlugin, tooltipPlugin, inputDialogPlugin,
|
package/package.json
CHANGED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="relative flex min-h-screen flex-col bg-[#edf4fb] lg:h-screen lg:flex-row lg:overflow-hidden"
|
|
4
|
+
>
|
|
5
|
+
<button
|
|
6
|
+
v-show="isSidebarCollapsed"
|
|
7
|
+
type="button"
|
|
8
|
+
:aria-label="expandLabel"
|
|
9
|
+
v-tooltip.right="expandLabel"
|
|
10
|
+
class="fixed left-0 top-4 z-[70] hidden h-10 w-10 items-center justify-center rounded-r-full border border-white/15 bg-[#121b2e] text-white shadow-2xl shadow-black/50 transition-all duration-300 hover:bg-[#1a2640] lg:flex"
|
|
11
|
+
@click="toggleSidebar"
|
|
12
|
+
>
|
|
13
|
+
<slot name="collapsed-toggle-icon">
|
|
14
|
+
<kIcon :name="expandIcon" size="16" class="text-white" />
|
|
15
|
+
</slot>
|
|
16
|
+
</button>
|
|
17
|
+
|
|
18
|
+
<aside
|
|
19
|
+
class="flex h-screen w-full flex-col overflow-y-auto 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:w-[360px] lg:shrink-0 lg:border-r lg:border-white/10 lg:will-change-transform"
|
|
20
|
+
:class="
|
|
21
|
+
isSidebarCollapsed
|
|
22
|
+
? 'lg:relative lg:z-30 lg:w-[76px]'
|
|
23
|
+
: 'lg:relative lg:z-30 lg:translate-x-0'
|
|
24
|
+
"
|
|
25
|
+
>
|
|
26
|
+
<div class="relative border-b border-white/10">
|
|
27
|
+
<button
|
|
28
|
+
v-if="logoSrc || title"
|
|
29
|
+
type="button"
|
|
30
|
+
class="block w-full text-left"
|
|
31
|
+
v-tooltip.right.html="isSidebarCollapsed ? collapsedContactTooltip : logoAlt"
|
|
32
|
+
@click="$emit('logo-click')"
|
|
33
|
+
>
|
|
34
|
+
<img
|
|
35
|
+
v-if="logoSrc"
|
|
36
|
+
:alt="logoAlt"
|
|
37
|
+
:src="isSidebarCollapsed ? collapsedLogoSrc : logoSrc"
|
|
38
|
+
class="cursor-pointer px-4 py-4 transition-all duration-300"
|
|
39
|
+
/>
|
|
40
|
+
<div v-if="title && !isSidebarCollapsed" class="py-3 mt-3 text-lg text-center bg-white/10">
|
|
41
|
+
{{ title }}
|
|
42
|
+
</div>
|
|
43
|
+
</button>
|
|
44
|
+
|
|
45
|
+
<slot name="sidebar-header" />
|
|
46
|
+
|
|
47
|
+
<button
|
|
48
|
+
v-show="!isSidebarCollapsed"
|
|
49
|
+
type="button"
|
|
50
|
+
:aria-label="collapseLabel"
|
|
51
|
+
v-tooltip.left="collapseLabel"
|
|
52
|
+
class="absolute right-2 top-2 z-[70] hidden h-10 w-10 items-center justify-center rounded-full border border-white/15 bg-[#121b2e] text-white shadow-2xl shadow-black/50 transition hover:bg-[#1a2640] lg:flex"
|
|
53
|
+
@click.stop="toggleSidebar"
|
|
54
|
+
>
|
|
55
|
+
<slot name="toggle-icon">
|
|
56
|
+
<kIcon
|
|
57
|
+
:name="collapseIcon"
|
|
58
|
+
size="16"
|
|
59
|
+
class="text-white"
|
|
60
|
+
/>
|
|
61
|
+
</slot>
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="flex-1 pt-4">
|
|
66
|
+
<div v-if="isSidebarCollapsed" class="mx-1 mt-3 flex flex-col items-center gap-2">
|
|
67
|
+
<SidebarIconLink
|
|
68
|
+
v-for="item in normalizedMainMenu"
|
|
69
|
+
:key="item.key"
|
|
70
|
+
:icon="item.icon"
|
|
71
|
+
:active="item.active"
|
|
72
|
+
:to="item.to"
|
|
73
|
+
:href="item.href"
|
|
74
|
+
:title="item.title"
|
|
75
|
+
:is-external="item.isExternal"
|
|
76
|
+
:target="item.target"
|
|
77
|
+
:rel="item.rel"
|
|
78
|
+
:on-click="item.onClick"
|
|
79
|
+
v-tooltip.right="item.label"
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
<div v-else-if="mainMenu.length" class="mx-2 mt-3 space-y-3">
|
|
83
|
+
<component
|
|
84
|
+
v-for="item in normalizedMainMenu"
|
|
85
|
+
:key="item.key"
|
|
86
|
+
:is="item.isLink ? RouterLink : item.isExternal ? 'a' : 'button'"
|
|
87
|
+
v-bind="item.bindings"
|
|
88
|
+
class="flex w-full items-start gap-3 rounded-2xl border px-4 py-4 text-left transition-all duration-200"
|
|
89
|
+
:class="item.classes"
|
|
90
|
+
>
|
|
91
|
+
<span
|
|
92
|
+
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"
|
|
93
|
+
:class="item.active ? 'bg-slate-950 text-white' : ''"
|
|
94
|
+
>
|
|
95
|
+
<kIcon :name="item.icon" />
|
|
96
|
+
</span>
|
|
97
|
+
<span class="min-w-0">
|
|
98
|
+
<span class="block font-semibold leading-tight">
|
|
99
|
+
{{ item.label }}
|
|
100
|
+
</span>
|
|
101
|
+
<span
|
|
102
|
+
v-if="item.description"
|
|
103
|
+
class="mt-1 block text-sm"
|
|
104
|
+
:class="item.active ? 'text-slate-700' : 'text-slate-300'"
|
|
105
|
+
>
|
|
106
|
+
{{ item.description }}
|
|
107
|
+
</span>
|
|
108
|
+
</span>
|
|
109
|
+
</component>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div v-if="account " class="shrink-0 border-t border-white/10 bg-slate-900/80 px-1 py-4 text-white/80">
|
|
114
|
+
<div v-if="isSidebarCollapsed" class="flex flex-col gap-3">
|
|
115
|
+
<div v-if="account.loggedIn" class="flex flex-col items-center gap-2">
|
|
116
|
+
<SidebarIconLink icon="UserRoundCog" :on-click="() => $emit('edit-profile')" :title="'Επεξεργασία προφίλ'" v-tooltip.right="'Επεξεργασία προφίλ'" />
|
|
117
|
+
<SidebarIconLink icon="LogOut" warning :on-click="() => $emit('signout')" :title="'Αποσύνδεση'" v-tooltip.right="'Αποσύνδεση'" />
|
|
118
|
+
</div>
|
|
119
|
+
<div v-else class="flex flex-col items-center gap-2">
|
|
120
|
+
<SidebarIconLink icon="LogIn" :on-click="() => $emit('signin')" :title="'Sign in'" v-tooltip.right="'Sign in'" />
|
|
121
|
+
<SidebarIconLink icon="UserPlus" :on-click="() => $emit('signup')" :title="'Sign up'" v-tooltip.right="'Sign up'" />
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
<div v-else class="flex flex-col gap-3 px-3">
|
|
125
|
+
<div class="min-w-0">
|
|
126
|
+
<p v-if="account.loggedIn" class="mt-2 truncate text-lg font-semibold text-white">
|
|
127
|
+
{{ accountDisplayName }}
|
|
128
|
+
</p>
|
|
129
|
+
<p v-if="account.loggedIn && account.email" class="truncate text-sm text-slate-300">
|
|
130
|
+
{{ account.email }}
|
|
131
|
+
</p>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="flex flex-wrap gap-2">
|
|
134
|
+
<kButton
|
|
135
|
+
v-if="!account.loggedIn"
|
|
136
|
+
secondary
|
|
137
|
+
icon="LogIn"
|
|
138
|
+
label="Sign in"
|
|
139
|
+
@click="$emit('signin')"
|
|
140
|
+
/>
|
|
141
|
+
<kButton
|
|
142
|
+
v-if="!account.loggedIn"
|
|
143
|
+
secondary
|
|
144
|
+
icon="UserPlus"
|
|
145
|
+
label="Sign up"
|
|
146
|
+
@click="$emit('signup')"
|
|
147
|
+
/>
|
|
148
|
+
<kButton
|
|
149
|
+
v-if="account.loggedIn"
|
|
150
|
+
secondary
|
|
151
|
+
icon="UserRoundCog"
|
|
152
|
+
label="Επεξεργασία προφίλ"
|
|
153
|
+
@click="$emit('edit-profile')"
|
|
154
|
+
/>
|
|
155
|
+
<kButton
|
|
156
|
+
v-if="account.loggedIn"
|
|
157
|
+
icon="LogOut"
|
|
158
|
+
warning
|
|
159
|
+
label="Αποσύνδεση"
|
|
160
|
+
@click="$emit('signout')"
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<div
|
|
167
|
+
v-if="showContactInfo && !isSidebarCollapsed"
|
|
168
|
+
class="shrink-0 border-t border-white/10 p-4 text-white/50"
|
|
169
|
+
>
|
|
170
|
+
<div class="flex flex-col gap-2">
|
|
171
|
+
<p v-if="!isSidebarCollapsed" class="text-sm font-semibold uppercase tracking-[0.25em] text-white/60">
|
|
172
|
+
KETEKNY AE
|
|
173
|
+
</p>
|
|
174
|
+
<div class="flex flex-wrap items-center gap-x-3 text-sm leading-relaxed" :class="isSidebarCollapsed ? 'justify-center' : ''">
|
|
175
|
+
<span v-if="isSidebarCollapsed" class="sr-only">KETEKNY AE contact info</span>
|
|
176
|
+
<span class="whitespace-nowrap">Βερανζέρου 13, 10677, Αθήνα</span>
|
|
177
|
+
<span class="whitespace-nowrap">210 3648 337</span>
|
|
178
|
+
<span class="whitespace-nowrap">
|
|
179
|
+
<a href="mailto:grammateia@ketekny.gr" class="whitespace-nowrap hover:underline">
|
|
180
|
+
grammateia@ketekny.gr
|
|
181
|
+
</a>
|
|
182
|
+
</span>
|
|
183
|
+
<a
|
|
184
|
+
href="https://ketekny.gr"
|
|
185
|
+
target="_blank"
|
|
186
|
+
rel="noreferrer"
|
|
187
|
+
class="whitespace-nowrap hover:underline"
|
|
188
|
+
>
|
|
189
|
+
https://ketekny.gr
|
|
190
|
+
</a>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
</aside>
|
|
196
|
+
|
|
197
|
+
<main class="min-h-screen min-w-0 flex-1 overflow-y-auto overflow-x-hidden lg:h-full lg:min-h-0">
|
|
198
|
+
<div class="w-full">
|
|
199
|
+
<slot />
|
|
200
|
+
</div>
|
|
201
|
+
</main>
|
|
202
|
+
</div>
|
|
203
|
+
</template>
|
|
204
|
+
|
|
205
|
+
<script>
|
|
206
|
+
import kButton from '../ui/kButton.vue'
|
|
207
|
+
import kIcon from '../ui/kIcon.vue'
|
|
208
|
+
import { RouterLink } from 'vue-router'
|
|
209
|
+
|
|
210
|
+
const SidebarIconLink = {
|
|
211
|
+
props: {
|
|
212
|
+
icon: { type: String, required: true },
|
|
213
|
+
active: { type: Boolean, default: false },
|
|
214
|
+
to: { type: [String, Object], default: null },
|
|
215
|
+
href: { type: String, default: null },
|
|
216
|
+
title: { type: String, default: '' },
|
|
217
|
+
isExternal: { type: Boolean, default: false },
|
|
218
|
+
target: { type: String, default: '_blank' },
|
|
219
|
+
rel: { type: String, default: 'noreferrer' },
|
|
220
|
+
onClick: { type: Function, default: null },
|
|
221
|
+
warning: { type: Boolean, default: false },
|
|
222
|
+
tooltip: { type: String, default: '' },
|
|
223
|
+
},
|
|
224
|
+
methods: {
|
|
225
|
+
handleClick(event) {
|
|
226
|
+
if (this.onClick) this.onClick(event)
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
template: `
|
|
230
|
+
<component
|
|
231
|
+
:is="to ? RouterLink : isExternal ? 'a' : 'button'"
|
|
232
|
+
:to="to"
|
|
233
|
+
:href="href"
|
|
234
|
+
:target="isExternal ? target : null"
|
|
235
|
+
:rel="isExternal ? rel : null"
|
|
236
|
+
:type="to || isExternal ? null : 'button'"
|
|
237
|
+
:title="title"
|
|
238
|
+
class="group relative flex h-14 w-14 items-center justify-center rounded-xl border border-white/15 transition"
|
|
239
|
+
:class="active ? 'bg-slate-950 text-white' : warning ? 'bg-[#d97706] text-white hover:bg-[#b45309]' : 'bg-white/5 text-white hover:border-cyan-400/40 hover:bg-white/10'"
|
|
240
|
+
@click="handleClick"
|
|
241
|
+
>
|
|
242
|
+
<kIcon :name="icon" size="20" class="text-current" />
|
|
243
|
+
</component>
|
|
244
|
+
`,
|
|
245
|
+
components: { RouterLink, kIcon },
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const DEFAULT_STORAGE_KEY = 'two-col-layout-sidebar-collapsed'
|
|
249
|
+
|
|
250
|
+
export default {
|
|
251
|
+
components: {
|
|
252
|
+
kButton,
|
|
253
|
+
kIcon,
|
|
254
|
+
RouterLink,
|
|
255
|
+
SidebarIconLink,
|
|
256
|
+
},
|
|
257
|
+
emits: ['logo-click', 'signin', 'signup', 'signout', 'edit-profile'],
|
|
258
|
+
props: {
|
|
259
|
+
title: { type: String, default: '' },
|
|
260
|
+
collapsedLogoSrc: {
|
|
261
|
+
type: String,
|
|
262
|
+
default: 'https://s3.ketekny.gr/public/web-apps/logos/logo-icon-dark.png',
|
|
263
|
+
},
|
|
264
|
+
logoSrc: { type: String, default: '' },
|
|
265
|
+
logoAlt: { type: String, default: 'Logo' },
|
|
266
|
+
collapseLabel: { type: String, default: 'Collapse sidebar' },
|
|
267
|
+
expandLabel: { type: String, default: 'Expand sidebar' },
|
|
268
|
+
collapseIcon: { type: String, default: 'ArrowLeftToLine' },
|
|
269
|
+
expandIcon: { type: String, default: 'ArrowRightToLine' },
|
|
270
|
+
storageKey: { type: String, default: DEFAULT_STORAGE_KEY },
|
|
271
|
+
defaultCollapsed: { type: Boolean, default: false },
|
|
272
|
+
mainMenu: { type: Array, default: () => [] },
|
|
273
|
+
showContactInfo: { type: Boolean, default: false },
|
|
274
|
+
account: { type: Object, default: null },
|
|
275
|
+
},
|
|
276
|
+
data() {
|
|
277
|
+
return {
|
|
278
|
+
isSidebarCollapsed: false,
|
|
279
|
+
mdCollapseQuery: null,
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
created() {
|
|
283
|
+
this.restoreSidebarState()
|
|
284
|
+
},
|
|
285
|
+
mounted() {
|
|
286
|
+
this.mdCollapseQuery = window.matchMedia('(min-width: 768px) and (max-width: 1023px)')
|
|
287
|
+
this.syncSidebarForViewport()
|
|
288
|
+
this.mdCollapseQuery.addEventListener('change', this.syncSidebarForViewport)
|
|
289
|
+
},
|
|
290
|
+
beforeUnmount() {
|
|
291
|
+
this.mdCollapseQuery?.removeEventListener('change', this.syncSidebarForViewport)
|
|
292
|
+
},
|
|
293
|
+
methods: {
|
|
294
|
+
restoreSidebarState() {
|
|
295
|
+
if (typeof window === 'undefined') {
|
|
296
|
+
this.isSidebarCollapsed = this.defaultCollapsed
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const stored = window.localStorage.getItem(this.storageKey)
|
|
301
|
+
if (stored == null) {
|
|
302
|
+
this.isSidebarCollapsed = this.defaultCollapsed
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
this.isSidebarCollapsed = stored === 'true'
|
|
307
|
+
},
|
|
308
|
+
syncSidebarForViewport() {
|
|
309
|
+
if (typeof window === 'undefined') return
|
|
310
|
+
|
|
311
|
+
const isMd = window.matchMedia('(min-width: 768px) and (max-width: 1023px)').matches
|
|
312
|
+
if (isMd) {
|
|
313
|
+
this.isSidebarCollapsed = true
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.restoreSidebarState()
|
|
318
|
+
},
|
|
319
|
+
persistSidebarState() {
|
|
320
|
+
if (typeof window === 'undefined') return
|
|
321
|
+
|
|
322
|
+
window.localStorage.setItem(this.storageKey, this.isSidebarCollapsed ? 'true' : 'false')
|
|
323
|
+
},
|
|
324
|
+
toggleSidebar() {
|
|
325
|
+
this.isSidebarCollapsed = !this.isSidebarCollapsed
|
|
326
|
+
this.persistSidebarState()
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
computed: {
|
|
330
|
+
normalizedMainMenu() {
|
|
331
|
+
return this.mainMenu.map((item, index) => {
|
|
332
|
+
const isExternal = Boolean(item?.href)
|
|
333
|
+
const isLink = Boolean(item?.to)
|
|
334
|
+
const active = Boolean(item?.active)
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
key: item?.key || item?.label || index,
|
|
338
|
+
label: item?.label || '',
|
|
339
|
+
description: item?.description || '',
|
|
340
|
+
icon: item?.icon || 'Dot',
|
|
341
|
+
active,
|
|
342
|
+
isExternal,
|
|
343
|
+
isLink,
|
|
344
|
+
bindings: isLink
|
|
345
|
+
? {
|
|
346
|
+
to: item.to,
|
|
347
|
+
title: item.title || item.label,
|
|
348
|
+
'aria-current': active ? 'page' : null,
|
|
349
|
+
}
|
|
350
|
+
: isExternal
|
|
351
|
+
? {
|
|
352
|
+
href: item.href,
|
|
353
|
+
target: item.target || '_blank',
|
|
354
|
+
rel: item.rel || 'noreferrer',
|
|
355
|
+
title: item.title || item.label,
|
|
356
|
+
}
|
|
357
|
+
: {
|
|
358
|
+
type: 'button',
|
|
359
|
+
title: item.title || item.label,
|
|
360
|
+
},
|
|
361
|
+
classes: active
|
|
362
|
+
? 'border-white/70 bg-white/90 text-slate-950 shadow-lg shadow-black/20'
|
|
363
|
+
: 'border-white/10 bg-white/5 text-white hover:border-cyan-400/40 hover:bg-white/10',
|
|
364
|
+
}
|
|
365
|
+
})
|
|
366
|
+
},
|
|
367
|
+
accountDisplayName() {
|
|
368
|
+
if (!this.account) return ''
|
|
369
|
+
return [this.account.firstName, this.account.lastName].filter(Boolean).join(' ').trim() || 'Account'
|
|
370
|
+
},
|
|
371
|
+
collapsedContactTooltip() {
|
|
372
|
+
return `
|
|
373
|
+
<div style="min-width: 220px;">
|
|
374
|
+
<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;">
|
|
375
|
+
KETEKNY AE
|
|
376
|
+
</div>
|
|
377
|
+
<div style="display: flex; flex-direction: column; gap: 4px; font-size: 13px; line-height: 1.4; color: rgba(255,255,255,0.9);">
|
|
378
|
+
<div>Βερανζέρου 13, 10677, Αθήνα</div>
|
|
379
|
+
<div>210 3648 337</div>
|
|
380
|
+
<div>grammateia@ketekny.gr</div>
|
|
381
|
+
<div>https://ketekny.gr</div>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
`
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
}
|
|
388
|
+
</script>
|