ketekny-ui-kit 1.0.116 → 1.0.118

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ketekny-ui-kit",
3
3
  "type": "module",
4
- "version": "1.0.116",
4
+ "version": "1.0.118",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -1,8 +1,15 @@
1
1
  <template>
2
- <main class="flex min-h-0 w-full max-w-none flex-1 flex-col overflow-hidden bg-slate-100">
2
+ <main
3
+ class="flex min-h-0 w-full max-w-none flex-1 flex-col overflow-hidden bg-slate-100"
4
+ @touchstart.passive="handleTouchStart"
5
+ @touchmove.passive="handleTouchMove"
6
+ @touchend="handleTouchEnd"
7
+ @touchcancel="resetTouchState"
8
+ >
3
9
  <div
4
- v-if="mobileSidebarOpen && !isDesktopViewport"
10
+ v-if="showMobileOverlay"
5
11
  class="fixed inset-0 z-20 bg-slate-950/40 backdrop-blur-[1px] lg:hidden"
12
+ :style="mobileOverlayStyle"
6
13
  @click="toggleMobileSidebar"
7
14
  />
8
15
 
@@ -58,9 +65,10 @@
58
65
  :label="mobileSidebarOpen ? 'Hide sidebar' : 'Show sidebar'"
59
66
  :icon="mobileSidebarOpen ? 'PanelLeftClose' : 'PanelLeftOpen'"
60
67
  variant="soft"
68
+ size="large"
61
69
  :icon-only="!mobileSidebarOpen"
62
70
  :aria-label="mobileSidebarOpen ? null : 'Show sidebar'"
63
- :class="mobileSidebarOpen ? '' : 'rounded-full shadow-lg shadow-slate-900/15'"
71
+ :class="mobileSidebarOpen ? '' : 'rounded-full text-slate-900 shadow-lg shadow-slate-900/15'"
64
72
  @click="toggleMobileSidebar"
65
73
  />
66
74
  </div>
@@ -69,7 +77,7 @@
69
77
  v-if="showSidebar"
70
78
  class="min-h-0 shrink-0 overflow-y-auto bg-slate-100 px-4 sm:px-6 lg:ml-8 lg:pr-4 lg:pl-0"
71
79
  :class="mobileSidebarClass"
72
- :style="sidebarStyle"
80
+ :style="mobileSidebarStyle"
73
81
  >
74
82
  <slot name="sidebar" />
75
83
  </aside>
@@ -87,6 +95,9 @@ import kButton from '../ui/kButton.vue'
87
95
 
88
96
  const LG_VIEWPORT_QUERY = '(min-width: 1024px)'
89
97
  const MAIN_MENU_TOGGLE_EVENT = 'ketekny:two-col-layout-mobile-menu'
98
+ const MOBILE_EDGE_SWIPE_THRESHOLD = 72
99
+ const MOBILE_SWIPE_OPEN_DISTANCE = 52
100
+ const MOBILE_SWIPE_VERTICAL_TOLERANCE = 48
90
101
 
91
102
  export default {
92
103
  name: 'PageTwoColShell',
@@ -118,6 +129,12 @@ export default {
118
129
  isDesktopViewport: true,
119
130
  mobileSidebarOpen: false,
120
131
  viewportQuery: null,
132
+ touchGesture: {
133
+ startX: 0,
134
+ startY: 0,
135
+ currentX: 0,
136
+ trackingEdgeSwipe: false,
137
+ },
121
138
  }
122
139
  },
123
140
  computed: {
@@ -142,10 +159,38 @@ export default {
142
159
  },
143
160
  mobileSidebarClass() {
144
161
  if (this.isDesktopViewport) return ''
145
- return 'fixed left-0 top-0 z-20 h-full max-w-[85vw] border-r border-slate-200 bg-slate-100 py-4 shadow-xl shadow-slate-900/20'
162
+ return 'fixed left-0 top-0 z-20 h-full max-w-[85vw] border-r border-slate-200 bg-slate-100 py-4 shadow-xl shadow-slate-900/20 transition-transform duration-200 ease-out'
163
+ },
164
+ dragOffset() {
165
+ if (!this.touchGesture.trackingEdgeSwipe) return 0
166
+ return Math.max(0, this.touchGesture.currentX - this.touchGesture.startX)
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',
180
+ }
181
+ },
182
+ showMobileOverlay() {
183
+ return !this.isDesktopViewport && (this.mobileSidebarOpen || this.touchGesture.trackingEdgeSwipe)
184
+ },
185
+ mobileOverlayStyle() {
186
+ if (this.mobileSidebarOpen || !this.touchGesture.trackingEdgeSwipe) return null
187
+ const progress = Math.min(this.dragOffset / MOBILE_SWIPE_OPEN_DISTANCE, 1) * 0.4
188
+ return {
189
+ opacity: String(progress),
190
+ }
146
191
  },
147
192
  showSidebar() {
148
- return this.isDesktopViewport || this.mobileSidebarOpen
193
+ return this.isDesktopViewport || this.mobileSidebarOpen || this.touchGesture.trackingEdgeSwipe
149
194
  },
150
195
  },
151
196
  mounted() {
@@ -174,11 +219,55 @@ export default {
174
219
  syncSidebarForViewport() {
175
220
  if (!this.isDesktopViewport) {
176
221
  this.mobileSidebarOpen = false
222
+ this.resetTouchState()
177
223
  }
178
224
  },
179
225
  toggleMobileSidebar() {
180
226
  if (this.isDesktopViewport) return
181
227
  this.mobileSidebarOpen = !this.mobileSidebarOpen
228
+ this.resetTouchState()
229
+ },
230
+ handleTouchStart(event) {
231
+ if (this.isDesktopViewport || this.mobileSidebarOpen) {
232
+ this.resetTouchState()
233
+ return
234
+ }
235
+ const touch = event.touches?.[0]
236
+ if (!touch) return
237
+ this.touchGesture = {
238
+ startX: touch.clientX,
239
+ startY: touch.clientY,
240
+ currentX: touch.clientX,
241
+ trackingEdgeSwipe: touch.clientX <= MOBILE_EDGE_SWIPE_THRESHOLD,
242
+ }
243
+ },
244
+ handleTouchMove(event) {
245
+ if (!this.touchGesture.trackingEdgeSwipe) return
246
+ const touch = event.touches?.[0]
247
+ if (!touch) return
248
+ this.touchGesture.currentX = touch.clientX
249
+ if (Math.abs(touch.clientY - this.touchGesture.startY) > MOBILE_SWIPE_VERTICAL_TOLERANCE) {
250
+ this.resetTouchState()
251
+ }
252
+ },
253
+ handleTouchEnd() {
254
+ if (!this.touchGesture.trackingEdgeSwipe) {
255
+ this.resetTouchState()
256
+ return
257
+ }
258
+ const deltaX = this.touchGesture.currentX - this.touchGesture.startX
259
+ if (deltaX >= MOBILE_SWIPE_OPEN_DISTANCE) {
260
+ this.mobileSidebarOpen = true
261
+ }
262
+ this.resetTouchState()
263
+ },
264
+ resetTouchState() {
265
+ this.touchGesture = {
266
+ startX: 0,
267
+ startY: 0,
268
+ currentX: 0,
269
+ trackingEdgeSwipe: false,
270
+ }
182
271
  },
183
272
  openExternalUrl() {
184
273
  const target = String(this.externalUrl || '').trim()
@@ -43,8 +43,8 @@
43
43
  type="button"
44
44
  class="fixed bottom-0 left-0 z-50 inline-flex size-14 items-center justify-center rounded-tr-2xl transition-colors lg:hidden"
45
45
  :class="theme === 'light'
46
- ? 'border border-slate-200 bg-white text-slate-700 shadow-lg shadow-slate-900/10 hover:bg-slate-100'
47
- : 'bg-sidebar text-white shadow-lg shadow-black/25 hover:bg-sidebar-accent'"
46
+ ? 'border border-rose-200 bg-rose-100 text-rose-900 shadow-lg shadow-slate-900/10 hover:bg-rose-200'
47
+ : ' bg-[#e63933] text-white shadow-lg shadow-black/25'"
48
48
  aria-label="Open menu"
49
49
  @click="toggleMenu">
50
50
  <Menu class="size-6" :stroke-width="2.25" />
@@ -58,7 +58,6 @@ import MultilevelSidebar from "./MultilevelSidebar.vue";
58
58
 
59
59
  const LG_VIEWPORT_QUERY = "(min-width: 1024px)";
60
60
  const MAIN_MENU_TOGGLE_EVENT = "ketekny:two-col-layout-mobile-menu";
61
-
62
61
  export default {
63
62
  name: "TwoColLayout",
64
63
  components: {