ketekny-ui-kit 1.0.127 → 1.0.129
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/index.js +2 -1
- package/package.json +1 -1
- package/src/layout/kMultiColPage.vue +5 -0
- package/src/layout/kTwoColPage.vue +319 -48
package/README.md
CHANGED
|
@@ -205,7 +205,7 @@ Components:
|
|
|
205
205
|
- `kSelectButton`, `kTags`, `kSearch`, `kArrayList`, `kList`, `kTextArea`
|
|
206
206
|
- `kDialog`, `kDrawer`
|
|
207
207
|
- `kAppHeader`, `kAppFooter`, `kAppMain`, `kHero`
|
|
208
|
-
- `kSingleColPage`, `
|
|
208
|
+
- `kSingleColPage`, `kMultiColPage`, `twoColLayout`, `legacyTwoColLayout`
|
|
209
209
|
|
|
210
210
|
Other exports:
|
|
211
211
|
|
|
@@ -242,4 +242,3 @@ ISC
|
|
|
242
242
|
|
|
243
243
|
- Live demo/docs: https://ui.ketekny.gr
|
|
244
244
|
- npm: https://www.npmjs.com/package/ketekny-ui-kit
|
|
245
|
-
|
package/index.js
CHANGED
|
@@ -40,6 +40,7 @@ import kAppFooter from './src/layout/kAppFooter.vue'
|
|
|
40
40
|
import kAppMain from './src/layout/kAppMain.vue'
|
|
41
41
|
import kHero from './src/layout/kHero.vue'
|
|
42
42
|
import kSingleColPage from './src/layout/kSingleColPage.vue'
|
|
43
|
+
import kMultiColPage from './src/layout/kMultiColPage.vue'
|
|
43
44
|
import kTwoColPage from './src/layout/kTwoColPage.vue'
|
|
44
45
|
import twoColLayout from './src/twoColLayout/components/TwoColLayout.vue'
|
|
45
46
|
import legacyTwoColLayout from './src/layout/twoColLayout.vue'
|
|
@@ -66,7 +67,7 @@ export {
|
|
|
66
67
|
kDialog, kDrawer,
|
|
67
68
|
|
|
68
69
|
// Layout Components
|
|
69
|
-
kAppHeader, kAppFooter, kAppMain, kHero, kSingleColPage, kTwoColPage, twoColLayout, legacyTwoColLayout,
|
|
70
|
+
kAppHeader, kAppFooter, kAppMain, kHero, kSingleColPage, kMultiColPage, kTwoColPage, twoColLayout, legacyTwoColLayout,
|
|
70
71
|
|
|
71
72
|
// Plugins
|
|
72
73
|
toastPlugin, confirmPlugin, alertPlugin, tooltipPlugin, inputDialogPlugin,
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
v-if="showMobileOverlay"
|
|
11
11
|
class="fixed inset-0 z-20 bg-slate-950/40 backdrop-blur-[1px] lg:hidden"
|
|
12
12
|
:style="mobileOverlayStyle"
|
|
13
|
-
@click="
|
|
13
|
+
@click="closeMobileSidebar"
|
|
14
14
|
/>
|
|
15
15
|
|
|
16
16
|
<section
|
|
@@ -59,31 +59,73 @@
|
|
|
59
59
|
<div class="flex min-h-0 flex-1 flex-col overflow-hidden bg-slate-100 py-4">
|
|
60
60
|
<div
|
|
61
61
|
class="lg:hidden"
|
|
62
|
-
:class="mobileSidebarOpen ? 'mx-auto flex w-full max-w-none px-4 pb-3 sm:px-6' : 'fixed
|
|
62
|
+
:class="mobileSidebarOpen ? 'mx-auto flex w-full max-w-none items-center justify-between gap-3 px-4 pb-3 sm:px-6' : 'pointer-events-none fixed inset-x-0 top-1/2 z-20 flex -translate-y-1/2'"
|
|
63
63
|
>
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
<button
|
|
65
|
+
v-if="hasLeftSidebar"
|
|
66
|
+
type="button"
|
|
67
|
+
:aria-label="mobileSidebarButtonAriaLabel('left')"
|
|
68
|
+
class="mobile-sidebar-trigger pointer-events-auto"
|
|
69
|
+
:class="mobileSidebarButtonClass('left')"
|
|
70
|
+
@click="toggleMobileSidebar('left')"
|
|
71
|
+
>
|
|
72
|
+
<kIcon
|
|
73
|
+
:name="mobileSidebarIconName('left')"
|
|
74
|
+
size="18"
|
|
75
|
+
/>
|
|
76
|
+
<span v-if="mobileSidebarButtonShowsLabel('left')" class="mobile-sidebar-trigger__label">
|
|
77
|
+
{{ mobileSidebarButtonLabel('left') }}
|
|
78
|
+
</span>
|
|
79
|
+
</button>
|
|
80
|
+
<button
|
|
81
|
+
v-if="hasRightSidebar"
|
|
82
|
+
type="button"
|
|
83
|
+
:aria-label="mobileSidebarButtonAriaLabel('right')"
|
|
84
|
+
class="mobile-sidebar-trigger pointer-events-auto"
|
|
85
|
+
:class="mobileSidebarButtonClass('right')"
|
|
86
|
+
@click="toggleMobileSidebar('right')"
|
|
87
|
+
>
|
|
88
|
+
<kIcon
|
|
89
|
+
:name="mobileSidebarIconName('right')"
|
|
90
|
+
size="18"
|
|
91
|
+
/>
|
|
92
|
+
<span v-if="mobileSidebarButtonShowsLabel('right')" class="mobile-sidebar-trigger__label">
|
|
93
|
+
{{ mobileSidebarButtonLabel('right') }}
|
|
94
|
+
</span>
|
|
95
|
+
</button>
|
|
74
96
|
</div>
|
|
75
97
|
<div class="mx-auto flex min-h-0 w-full max-w-none flex-1 gap-2">
|
|
76
98
|
<aside
|
|
77
|
-
v-if="
|
|
78
|
-
class="min-h-0 shrink-0 overflow-y-auto bg-slate-100
|
|
79
|
-
:class="mobileSidebarClass"
|
|
80
|
-
:style="mobileSidebarStyle"
|
|
99
|
+
v-if="showLeftSidebar"
|
|
100
|
+
class="min-h-0 shrink-0 overflow-y-auto bg-slate-100"
|
|
101
|
+
:class="[mobileSidebarClass('left'), sidebarSurfaceClass('left')]"
|
|
102
|
+
:style="mobileSidebarStyle('left')"
|
|
81
103
|
>
|
|
82
|
-
<
|
|
104
|
+
<div v-if="effectiveSidebarLeftTitle" :class="sidebarTitleWrapClass('left')">
|
|
105
|
+
<p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-500">
|
|
106
|
+
{{ effectiveSidebarLeftTitle }}
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
<slot name="sidebar-left" v-bind="sidebarSlotProps('left')">
|
|
110
|
+
<slot name="sidebar" v-bind="sidebarSlotProps('left')" />
|
|
111
|
+
</slot>
|
|
83
112
|
</aside>
|
|
84
113
|
<section class="min-h-0 min-w-0 flex-1 overflow-y-auto px-2">
|
|
85
114
|
<slot name="content" />
|
|
86
115
|
</section>
|
|
116
|
+
<aside
|
|
117
|
+
v-if="showRightSidebar"
|
|
118
|
+
class="min-h-0 shrink-0 overflow-y-auto bg-slate-100"
|
|
119
|
+
:class="[mobileSidebarClass('right'), sidebarSurfaceClass('right')]"
|
|
120
|
+
:style="mobileSidebarStyle('right')"
|
|
121
|
+
>
|
|
122
|
+
<div v-if="sidebarRightTitle" :class="sidebarTitleWrapClass('right')">
|
|
123
|
+
<p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-500">
|
|
124
|
+
{{ sidebarRightTitle }}
|
|
125
|
+
</p>
|
|
126
|
+
</div>
|
|
127
|
+
<slot name="sidebar-right" v-bind="sidebarSlotProps('right')" />
|
|
128
|
+
</aside>
|
|
87
129
|
</div>
|
|
88
130
|
</div>
|
|
89
131
|
</main>
|
|
@@ -92,6 +134,7 @@
|
|
|
92
134
|
<script>
|
|
93
135
|
import packageJson from '../../package.json'
|
|
94
136
|
import kButton from '../ui/kButton.vue'
|
|
137
|
+
import kIcon from '../ui/kIcon.vue'
|
|
95
138
|
|
|
96
139
|
const LG_VIEWPORT_QUERY = '(min-width: 1024px)'
|
|
97
140
|
const MAIN_MENU_TOGGLE_EVENT = 'ketekny:two-col-layout-mobile-menu'
|
|
@@ -100,8 +143,8 @@ const MOBILE_SWIPE_OPEN_DISTANCE = 52
|
|
|
100
143
|
const MOBILE_SWIPE_VERTICAL_TOLERANCE = 48
|
|
101
144
|
|
|
102
145
|
export default {
|
|
103
|
-
name: '
|
|
104
|
-
components: { kButton },
|
|
146
|
+
name: 'MultiColPage',
|
|
147
|
+
components: { kButton, kIcon },
|
|
105
148
|
props: {
|
|
106
149
|
appTitle: {
|
|
107
150
|
type: String,
|
|
@@ -123,13 +166,47 @@ export default {
|
|
|
123
166
|
type: String,
|
|
124
167
|
default: '300px',
|
|
125
168
|
},
|
|
169
|
+
sidebarLeftWidth: {
|
|
170
|
+
type: String,
|
|
171
|
+
default: '',
|
|
172
|
+
},
|
|
173
|
+
sidebarDense: {
|
|
174
|
+
type: Boolean,
|
|
175
|
+
default: false,
|
|
176
|
+
},
|
|
177
|
+
sidebarLeftDense: {
|
|
178
|
+
type: Boolean,
|
|
179
|
+
default: false,
|
|
180
|
+
},
|
|
181
|
+
sidebarTitle: {
|
|
182
|
+
type: String,
|
|
183
|
+
default: '',
|
|
184
|
+
},
|
|
185
|
+
sidebarLeftTitle: {
|
|
186
|
+
type: String,
|
|
187
|
+
default: '',
|
|
188
|
+
},
|
|
189
|
+
sidebarRightWidth: {
|
|
190
|
+
type: String,
|
|
191
|
+
default: '',
|
|
192
|
+
},
|
|
193
|
+
sidebarRightDense: {
|
|
194
|
+
type: Boolean,
|
|
195
|
+
default: false,
|
|
196
|
+
},
|
|
197
|
+
sidebarRightTitle: {
|
|
198
|
+
type: String,
|
|
199
|
+
default: '',
|
|
200
|
+
},
|
|
126
201
|
},
|
|
127
202
|
data() {
|
|
128
203
|
return {
|
|
129
204
|
isDesktopViewport: true,
|
|
130
205
|
mobileSidebarOpen: false,
|
|
206
|
+
mobileSidebarSide: 'left',
|
|
131
207
|
viewportQuery: null,
|
|
132
208
|
touchGesture: {
|
|
209
|
+
side: 'left',
|
|
133
210
|
startX: 0,
|
|
134
211
|
startY: 0,
|
|
135
212
|
currentX: 0,
|
|
@@ -151,33 +228,42 @@ export default {
|
|
|
151
228
|
if (!target) return ''
|
|
152
229
|
return target.replace(/^https?:\/\//i, '').replace(/\/$/, '')
|
|
153
230
|
},
|
|
154
|
-
|
|
231
|
+
hasLeftSidebar() {
|
|
232
|
+
return Boolean(this.$slots['sidebar-left'] || this.$slots.sidebar)
|
|
233
|
+
},
|
|
234
|
+
hasRightSidebar() {
|
|
235
|
+
return Boolean(this.$slots['sidebar-right'])
|
|
236
|
+
},
|
|
237
|
+
effectiveSidebarLeftWidth() {
|
|
238
|
+
return this.sidebarLeftWidth || this.sidebarWidth || '300px'
|
|
239
|
+
},
|
|
240
|
+
effectiveSidebarLeftTitle() {
|
|
241
|
+
return this.sidebarLeftTitle || this.sidebarTitle
|
|
242
|
+
},
|
|
243
|
+
effectiveSidebarLeftDense() {
|
|
244
|
+
return this.sidebarLeftDense || this.sidebarDense
|
|
245
|
+
},
|
|
246
|
+
effectiveSidebarRightWidth() {
|
|
247
|
+
return this.sidebarRightWidth || this.effectiveSidebarLeftWidth
|
|
248
|
+
},
|
|
249
|
+
sidebarLeftStyle() {
|
|
155
250
|
return {
|
|
156
|
-
minWidth: this.
|
|
157
|
-
maxWidth: this.
|
|
251
|
+
minWidth: this.effectiveSidebarLeftWidth,
|
|
252
|
+
maxWidth: this.effectiveSidebarLeftWidth,
|
|
158
253
|
}
|
|
159
254
|
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
255
|
+
sidebarRightStyle() {
|
|
256
|
+
return {
|
|
257
|
+
minWidth: this.effectiveSidebarRightWidth,
|
|
258
|
+
maxWidth: this.effectiveSidebarRightWidth,
|
|
259
|
+
}
|
|
163
260
|
},
|
|
164
261
|
dragOffset() {
|
|
165
262
|
if (!this.touchGesture.trackingEdgeSwipe) return 0
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
mobileSidebarStyle() {
|
|
169
|
-
const baseStyle = this.sidebarStyle
|
|
170
|
-
if (this.isDesktopViewport || this.mobileSidebarOpen || !this.touchGesture.trackingEdgeSwipe) {
|
|
171
|
-
return baseStyle
|
|
172
|
-
}
|
|
173
|
-
const widthValue = Number.parseFloat(this.sidebarWidth)
|
|
174
|
-
const effectiveWidth = Number.isFinite(widthValue) ? widthValue : 300
|
|
175
|
-
const clampedOffset = Math.min(this.dragOffset, effectiveWidth)
|
|
176
|
-
return {
|
|
177
|
-
...baseStyle,
|
|
178
|
-
transform: `translateX(${clampedOffset - effectiveWidth}px)`,
|
|
179
|
-
transition: 'none',
|
|
263
|
+
if (this.touchGesture.side === 'right') {
|
|
264
|
+
return Math.max(0, this.touchGesture.startX - this.touchGesture.currentX)
|
|
180
265
|
}
|
|
266
|
+
return Math.max(0, this.touchGesture.currentX - this.touchGesture.startX)
|
|
181
267
|
},
|
|
182
268
|
showMobileOverlay() {
|
|
183
269
|
return !this.isDesktopViewport && (this.mobileSidebarOpen || this.touchGesture.trackingEdgeSwipe)
|
|
@@ -189,8 +275,13 @@ export default {
|
|
|
189
275
|
opacity: String(progress),
|
|
190
276
|
}
|
|
191
277
|
},
|
|
192
|
-
|
|
193
|
-
|
|
278
|
+
showLeftSidebar() {
|
|
279
|
+
if (!this.hasLeftSidebar) return false
|
|
280
|
+
return this.showSidebarForSide('left')
|
|
281
|
+
},
|
|
282
|
+
showRightSidebar() {
|
|
283
|
+
if (!this.hasRightSidebar) return false
|
|
284
|
+
return this.showSidebarForSide('right')
|
|
194
285
|
},
|
|
195
286
|
},
|
|
196
287
|
mounted() {
|
|
@@ -206,10 +297,116 @@ export default {
|
|
|
206
297
|
window.removeEventListener(MAIN_MENU_TOGGLE_EVENT, this.handleMainMenuToggle)
|
|
207
298
|
},
|
|
208
299
|
methods: {
|
|
300
|
+
sidebarSlotProps(side) {
|
|
301
|
+
return {
|
|
302
|
+
side,
|
|
303
|
+
title: side === 'right' ? this.sidebarRightTitle : this.effectiveSidebarLeftTitle,
|
|
304
|
+
dense: side === 'right' ? this.sidebarRightDense : this.effectiveSidebarLeftDense,
|
|
305
|
+
isDesktopViewport: this.isDesktopViewport,
|
|
306
|
+
mobileSidebarOpen: this.mobileSidebarOpen,
|
|
307
|
+
mobileSidebarSide: this.mobileSidebarSide,
|
|
308
|
+
isMobileSidebarActive: this.mobileSidebarOpen && this.mobileSidebarSide === side,
|
|
309
|
+
toggleMobileSidebar: () => this.toggleMobileSidebar(side),
|
|
310
|
+
closeMobileSidebar: this.closeMobileSidebar,
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
showSidebarForSide(side) {
|
|
314
|
+
if (this.isDesktopViewport) return true
|
|
315
|
+
return (
|
|
316
|
+
(this.mobileSidebarOpen && this.mobileSidebarSide === side) ||
|
|
317
|
+
(this.touchGesture.trackingEdgeSwipe && this.touchGesture.side === side)
|
|
318
|
+
)
|
|
319
|
+
},
|
|
320
|
+
mobileSidebarClass(side) {
|
|
321
|
+
if (this.isDesktopViewport) return ''
|
|
322
|
+
const horizontalClass = side === 'right'
|
|
323
|
+
? 'fixed right-0 top-0 border-l border-slate-200'
|
|
324
|
+
: 'fixed left-0 top-0 border-r border-slate-200'
|
|
325
|
+
return `${horizontalClass} z-20 h-full max-w-[85vw] bg-slate-100 py-4 shadow-xl shadow-slate-900/20 transition-transform duration-200 ease-out`
|
|
326
|
+
},
|
|
327
|
+
mobileSidebarStyle(side) {
|
|
328
|
+
const baseStyle = side === 'right' ? this.sidebarRightStyle : this.sidebarLeftStyle
|
|
329
|
+
if (
|
|
330
|
+
this.isDesktopViewport ||
|
|
331
|
+
this.mobileSidebarOpen ||
|
|
332
|
+
!this.touchGesture.trackingEdgeSwipe ||
|
|
333
|
+
this.touchGesture.side !== side
|
|
334
|
+
) {
|
|
335
|
+
return baseStyle
|
|
336
|
+
}
|
|
337
|
+
const widthValue = Number.parseFloat(
|
|
338
|
+
side === 'right' ? this.effectiveSidebarRightWidth : this.effectiveSidebarLeftWidth
|
|
339
|
+
)
|
|
340
|
+
const effectiveWidth = Number.isFinite(widthValue) ? widthValue : 300
|
|
341
|
+
const clampedOffset = Math.min(this.dragOffset, effectiveWidth)
|
|
342
|
+
const hiddenOffset = effectiveWidth - clampedOffset
|
|
343
|
+
const direction = side === 'right' ? hiddenOffset : clampedOffset - effectiveWidth
|
|
344
|
+
return {
|
|
345
|
+
...baseStyle,
|
|
346
|
+
transform: `translateX(${direction}px)`,
|
|
347
|
+
transition: 'none',
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
mobileSidebarButtonClass(side) {
|
|
351
|
+
if (this.mobileSidebarOpen) {
|
|
352
|
+
return side === 'right' ? 'ml-auto' : ''
|
|
353
|
+
}
|
|
354
|
+
const title = this.sidebarTitleForSide(side)
|
|
355
|
+
const edgeClass = side === 'right'
|
|
356
|
+
? 'ml-auto rounded-l-xl rounded-r-none border-r-0'
|
|
357
|
+
: 'rounded-l-none rounded-r-xl border-l-0'
|
|
358
|
+
if (title) {
|
|
359
|
+
return `${edgeClass} mobile-sidebar-trigger--titled min-h-0 min-w-0 px-2 py-3`
|
|
360
|
+
}
|
|
361
|
+
return side === 'right'
|
|
362
|
+
? 'ml-auto rounded-full'
|
|
363
|
+
: 'rounded-full'
|
|
364
|
+
},
|
|
365
|
+
sidebarTitleForSide(side) {
|
|
366
|
+
return side === 'right' ? this.sidebarRightTitle : this.effectiveSidebarLeftTitle
|
|
367
|
+
},
|
|
368
|
+
sidebarDenseForSide(side) {
|
|
369
|
+
return side === 'right' ? this.sidebarRightDense : this.effectiveSidebarLeftDense
|
|
370
|
+
},
|
|
371
|
+
sidebarSurfaceClass(side) {
|
|
372
|
+
if (this.sidebarDenseForSide(side)) {
|
|
373
|
+
return ''
|
|
374
|
+
}
|
|
375
|
+
return side === 'right'
|
|
376
|
+
? 'px-4 sm:px-6 lg:mr-8 lg:pl-4 lg:pr-0'
|
|
377
|
+
: 'px-4 sm:px-6 lg:ml-8 lg:pr-4 lg:pl-0'
|
|
378
|
+
},
|
|
379
|
+
sidebarTitleWrapClass(side) {
|
|
380
|
+
return this.sidebarDenseForSide(side) ? '' : 'pb-3'
|
|
381
|
+
},
|
|
382
|
+
mobileSidebarButtonLabel(side) {
|
|
383
|
+
if (this.mobileSidebarOpen && this.mobileSidebarSide === side) {
|
|
384
|
+
return 'Hide sidebar'
|
|
385
|
+
}
|
|
386
|
+
return this.sidebarTitleForSide(side) || 'Show sidebar'
|
|
387
|
+
},
|
|
388
|
+
mobileSidebarButtonAriaLabel(side) {
|
|
389
|
+
if (this.mobileSidebarOpen && this.mobileSidebarSide === side) return null
|
|
390
|
+
return this.sidebarTitleForSide(side) || 'Show sidebar'
|
|
391
|
+
},
|
|
392
|
+
mobileSidebarButtonShowsLabel(side) {
|
|
393
|
+
if (this.mobileSidebarOpen && this.mobileSidebarSide === side) return true
|
|
394
|
+
return Boolean(this.sidebarTitleForSide(side))
|
|
395
|
+
},
|
|
396
|
+
mobileSidebarIconName(side) {
|
|
397
|
+
if (side === 'right') {
|
|
398
|
+
return this.mobileSidebarOpen && this.mobileSidebarSide === side
|
|
399
|
+
? 'PanelRightClose'
|
|
400
|
+
: 'PanelRightOpen'
|
|
401
|
+
}
|
|
402
|
+
return this.mobileSidebarOpen && this.mobileSidebarSide === side
|
|
403
|
+
? 'PanelLeftClose'
|
|
404
|
+
: 'PanelLeftOpen'
|
|
405
|
+
},
|
|
209
406
|
handleMainMenuToggle(event) {
|
|
210
407
|
if (this.isDesktopViewport) return
|
|
211
408
|
if (event?.detail?.open) {
|
|
212
|
-
this.
|
|
409
|
+
this.closeMobileSidebar()
|
|
213
410
|
}
|
|
214
411
|
},
|
|
215
412
|
handleViewportChange(event) {
|
|
@@ -218,13 +415,22 @@ export default {
|
|
|
218
415
|
},
|
|
219
416
|
syncSidebarForViewport() {
|
|
220
417
|
if (!this.isDesktopViewport) {
|
|
221
|
-
this.
|
|
222
|
-
this.resetTouchState()
|
|
418
|
+
this.closeMobileSidebar()
|
|
223
419
|
}
|
|
224
420
|
},
|
|
225
|
-
toggleMobileSidebar() {
|
|
421
|
+
toggleMobileSidebar(side = 'left') {
|
|
226
422
|
if (this.isDesktopViewport) return
|
|
227
|
-
this.
|
|
423
|
+
if ((side === 'left' && !this.hasLeftSidebar) || (side === 'right' && !this.hasRightSidebar)) return
|
|
424
|
+
if (this.mobileSidebarOpen && this.mobileSidebarSide === side) {
|
|
425
|
+
this.mobileSidebarOpen = false
|
|
426
|
+
} else {
|
|
427
|
+
this.mobileSidebarOpen = true
|
|
428
|
+
this.mobileSidebarSide = side
|
|
429
|
+
}
|
|
430
|
+
this.resetTouchState()
|
|
431
|
+
},
|
|
432
|
+
closeMobileSidebar() {
|
|
433
|
+
this.mobileSidebarOpen = false
|
|
228
434
|
this.resetTouchState()
|
|
229
435
|
},
|
|
230
436
|
handleTouchStart(event) {
|
|
@@ -234,11 +440,19 @@ export default {
|
|
|
234
440
|
}
|
|
235
441
|
const touch = event.touches?.[0]
|
|
236
442
|
if (!touch) return
|
|
443
|
+
const viewportWidth = typeof window === 'undefined' ? 0 : window.innerWidth
|
|
444
|
+
const isLeftEdge = this.hasLeftSidebar && touch.clientX <= MOBILE_EDGE_SWIPE_THRESHOLD
|
|
445
|
+
const isRightEdge = this.hasRightSidebar && viewportWidth - touch.clientX <= MOBILE_EDGE_SWIPE_THRESHOLD
|
|
446
|
+
if (!isLeftEdge && !isRightEdge) {
|
|
447
|
+
this.resetTouchState()
|
|
448
|
+
return
|
|
449
|
+
}
|
|
237
450
|
this.touchGesture = {
|
|
451
|
+
side: isRightEdge ? 'right' : 'left',
|
|
238
452
|
startX: touch.clientX,
|
|
239
453
|
startY: touch.clientY,
|
|
240
454
|
currentX: touch.clientX,
|
|
241
|
-
trackingEdgeSwipe:
|
|
455
|
+
trackingEdgeSwipe: true,
|
|
242
456
|
}
|
|
243
457
|
},
|
|
244
458
|
handleTouchMove(event) {
|
|
@@ -255,14 +469,18 @@ export default {
|
|
|
255
469
|
this.resetTouchState()
|
|
256
470
|
return
|
|
257
471
|
}
|
|
258
|
-
const deltaX = this.touchGesture.
|
|
472
|
+
const deltaX = this.touchGesture.side === 'right'
|
|
473
|
+
? this.touchGesture.startX - this.touchGesture.currentX
|
|
474
|
+
: this.touchGesture.currentX - this.touchGesture.startX
|
|
259
475
|
if (deltaX >= MOBILE_SWIPE_OPEN_DISTANCE) {
|
|
260
476
|
this.mobileSidebarOpen = true
|
|
477
|
+
this.mobileSidebarSide = this.touchGesture.side
|
|
261
478
|
}
|
|
262
479
|
this.resetTouchState()
|
|
263
480
|
},
|
|
264
481
|
resetTouchState() {
|
|
265
482
|
this.touchGesture = {
|
|
483
|
+
side: 'left',
|
|
266
484
|
startX: 0,
|
|
267
485
|
startY: 0,
|
|
268
486
|
currentX: 0,
|
|
@@ -277,3 +495,56 @@ export default {
|
|
|
277
495
|
},
|
|
278
496
|
}
|
|
279
497
|
</script>
|
|
498
|
+
|
|
499
|
+
<style scoped>
|
|
500
|
+
.mobile-sidebar-trigger {
|
|
501
|
+
display: inline-flex;
|
|
502
|
+
align-items: center;
|
|
503
|
+
justify-content: center;
|
|
504
|
+
gap: 0.5rem;
|
|
505
|
+
min-height: 3.5rem;
|
|
506
|
+
padding: 0.875rem;
|
|
507
|
+
border: 1px solid rgb(203 213 225);
|
|
508
|
+
background: rgb(239 246 255);
|
|
509
|
+
color: rgb(15 23 42);
|
|
510
|
+
box-shadow: 0 10px 25px -15px rgba(15, 23, 42, 0.45);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.mobile-sidebar-trigger--titled {
|
|
514
|
+
flex-direction: column;
|
|
515
|
+
width: 3rem;
|
|
516
|
+
min-height: 8.5rem;
|
|
517
|
+
padding: 0.75rem 0.35rem;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.mobile-sidebar-trigger:focus-visible {
|
|
521
|
+
outline: 2px solid rgb(37 99 235);
|
|
522
|
+
outline-offset: 2px;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.mobile-sidebar-trigger__label {
|
|
526
|
+
display: inline-block;
|
|
527
|
+
font-size: 0.625rem;
|
|
528
|
+
font-weight: 700;
|
|
529
|
+
letter-spacing: 0.08em;
|
|
530
|
+
text-transform: uppercase;
|
|
531
|
+
white-space: nowrap;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.mobile-sidebar-trigger--titled .mobile-sidebar-trigger__label {
|
|
535
|
+
writing-mode: vertical-rl;
|
|
536
|
+
text-orientation: mixed;
|
|
537
|
+
line-height: 1;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.mobile-sidebar-trigger--titled :deep(svg) {
|
|
541
|
+
width: 0.9rem;
|
|
542
|
+
height: 0.9rem;
|
|
543
|
+
flex-shrink: 0;
|
|
544
|
+
display: block;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.mobile-sidebar-trigger--titled.rounded-l-none .mobile-sidebar-trigger__label {
|
|
548
|
+
transform: rotate(180deg);
|
|
549
|
+
}
|
|
550
|
+
</style>
|