ketekny-ui-kit 1.0.142 → 1.0.144

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.142",
4
+ "version": "1.0.144",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -102,8 +102,12 @@ export default {
102
102
  default: null,
103
103
  validator: value => value === null || ['dark', 'light'].includes(value),
104
104
  },
105
+ favorites: {
106
+ type: Array,
107
+ default: null,
108
+ },
105
109
  },
106
- emits: ['select', 'logo-click', 'signin', 'signup', 'signout', 'edit-profile', 'item-click'],
110
+ emits: ['select', 'logo-click', 'signin', 'signup', 'signout', 'edit-profile', 'item-click', 'update:favorites'],
107
111
  data() {
108
112
  return {
109
113
  demoAvatarUrl,
@@ -142,6 +146,9 @@ export default {
142
146
  hasAccount() {
143
147
  return Boolean(this.account)
144
148
  },
149
+ isControlledFavorites() {
150
+ return Array.isArray(this.favorites)
151
+ },
145
152
  accountDisplayName() {
146
153
  if (!this.account) return ''
147
154
  return [this.account.firstName, this.account.lastName].filter(Boolean).join(' ') || 'Account'
@@ -158,6 +165,7 @@ export default {
158
165
  const favorites = []
159
166
  const collect = (items) => {
160
167
  for (const item of items) {
168
+ if (item?.isDivider) continue
161
169
  if (item?.children?.length) {
162
170
  collect(item.children)
163
171
  continue
@@ -242,12 +250,18 @@ export default {
242
250
  deep: true,
243
251
  immediate: true,
244
252
  },
253
+ favorites: {
254
+ handler() {
255
+ this.syncFavoritesFromMenu()
256
+ },
257
+ deep: true,
258
+ },
245
259
  $route() {
246
260
  this.syncActiveFromMenu()
247
261
  },
248
262
  favoriteIds: {
249
263
  handler(value) {
250
- this.persistFavorites(value)
264
+ if (!this.isControlledFavorites) this.persistFavorites(value)
251
265
  if (!value.size && this.activeMenuTab === 'favorites') this.activeMenuTab = 'all'
252
266
  },
253
267
  deep: false,
@@ -300,19 +314,33 @@ export default {
300
314
  }
301
315
  return ids
302
316
  },
317
+ normalizeFavorite(value) {
318
+ return String(value ?? '').replace(/^\/+/, '').trim()
319
+ },
320
+ favoriteValue(item) {
321
+ const value = item?.link?.to ?? item?.to ?? item?.link?.href ?? item?.href
322
+ return this.normalizeFavorite(typeof value === 'string' ? value : item?.id)
323
+ },
303
324
  syncFavoritesFromMenu() {
304
- const storedFavorites = this.readStoredFavorites()
325
+ const storedFavorites = this.isControlledFavorites ? this.favorites : this.readStoredFavorites()
305
326
  const storedTab = this.readStoredActiveMenuTab()
327
+ const controlledFavorites = new Set(storedFavorites.map(value => this.normalizeFavorite(value)))
306
328
  const discovered = []
307
329
  const collect = (items) => {
308
330
  for (const item of items) {
309
- if (item?.favorite && !item?.children?.length) discovered.push(item.id)
331
+ if (
332
+ !item?.children?.length
333
+ && (this.isControlledFavorites
334
+ ? controlledFavorites.has(this.favoriteValue(item))
335
+ || controlledFavorites.has(this.normalizeFavorite(item?.id))
336
+ : item?.favorite)
337
+ ) discovered.push(item.id)
310
338
  if (item?.children?.length) collect(item.children)
311
339
  }
312
340
  }
313
341
  collect(this.mainMenu)
314
- this.favoriteIds = new Set([...discovered, ...storedFavorites])
315
- if (storedTab === 'favorites' && (this.favoriteIds.size > 0 || discovered.length > 0)) {
342
+ this.favoriteIds = new Set(this.isControlledFavorites ? discovered : [...discovered, ...storedFavorites])
343
+ if (storedTab === 'favorites' && this.favoriteIds.size > 0) {
316
344
  this.activeMenuTab = 'favorites'
317
345
  } else {
318
346
  this.activeMenuTab = 'all'
@@ -329,7 +357,7 @@ export default {
329
357
  (routeFullPath && itemPath === routeFullPath) ||
330
358
  (routePath && normalizedItemPath === routePath)
331
359
 
332
- if (matchesPath) {
360
+ if (matchesPath && item.selectable !== false) {
333
361
  return {
334
362
  id: item.id,
335
363
  parentIds,
@@ -350,8 +378,9 @@ export default {
350
378
  return
351
379
  }
352
380
 
353
- if (!this.activeId && this.mainMenu[0]?.id) {
354
- this.activeId = this.mainMenu[0].id
381
+ if (!this.activeId) {
382
+ const firstSelectableItem = this.mainMenu.find((item) => !item.isDivider && item.selectable !== false)
383
+ if (firstSelectableItem?.id) this.activeId = firstSelectableItem.id
355
384
  }
356
385
  },
357
386
  toggleCollapsed() {
@@ -375,6 +404,7 @@ export default {
375
404
  this.openIds = new Set([...this.openIds, id])
376
405
  },
377
406
  select(itemOrId) {
407
+ if (typeof itemOrId !== 'string' && (itemOrId?.isDivider || itemOrId?.selectable === false)) return
378
408
  const id = typeof itemOrId === 'string' ? itemOrId : itemOrId?.id
379
409
  if (!id) return
380
410
  this.activeId = id
@@ -399,7 +429,8 @@ export default {
399
429
  filterTree(nodes, q) {
400
430
  const result = []
401
431
  for (const node of nodes) {
402
- const matches = node.label.toLowerCase().includes(q)
432
+ if (node.isDivider) continue
433
+ const matches = String(node.label ?? '').toLowerCase().includes(q)
403
434
  const matchedChildren = node.children ? this.filterTree(node.children, q) : []
404
435
  if (matches) result.push(node)
405
436
  else if (matchedChildren.length) result.push({ ...node, children: matchedChildren })
@@ -440,6 +471,16 @@ export default {
440
471
  if (next.has(item.id)) next.delete(item.id)
441
472
  else next.add(item.id)
442
473
  this.favoriteIds = next
474
+ if (this.isControlledFavorites) {
475
+ const favorites = new Set(this.favorites.map(value => this.normalizeFavorite(value)))
476
+ const value = this.favoriteValue(item)
477
+ if (next.has(item.id)) favorites.add(value)
478
+ else {
479
+ favorites.delete(value)
480
+ favorites.delete(this.normalizeFavorite(item.id))
481
+ }
482
+ this.$emit('update:favorites', Array.from(favorites))
483
+ }
443
484
  if (next.size && this.activeMenuTab !== 'favorites' && this.activeMenuTab !== 'all') {
444
485
  this.activeMenuTab = 'all'
445
486
  }
@@ -30,6 +30,9 @@ export default {
30
30
  hasChildren() {
31
31
  return Array.isArray(this.item.children) && this.item.children.length > 0
32
32
  },
33
+ isDivider() {
34
+ return this.item.isDivider === true
35
+ },
33
36
  isOpen() {
34
37
  return this.openIds.has(this.item.id)
35
38
  },
@@ -41,7 +44,7 @@ export default {
41
44
  return this.hasChildren && this.containsActive(this.item)
42
45
  },
43
46
  isActiveLeaf() {
44
- return !this.hasChildren && this.item.id === this.activeId
47
+ return !this.hasChildren && this.item.selectable !== false && this.item.id === this.activeId
45
48
  },
46
49
  isActive() {
47
50
  return this.isActiveLeaf || this.isActiveTrail
@@ -82,7 +85,7 @@ export default {
82
85
  },
83
86
  methods: {
84
87
  containsActive(node) {
85
- if (node.id === this.activeId) return true
88
+ if (node.selectable !== false && node.id === this.activeId) return true
86
89
  if (!node.children) return false
87
90
  return node.children.some((child) => this.containsActive(child))
88
91
  },
@@ -125,7 +128,18 @@ export default {
125
128
  </script>
126
129
 
127
130
  <template>
128
- <li :class="isCollapsedTop ? 'group/fly relative' : ''">
131
+ <li v-if="isDivider" role="separator" :aria-label="item.label || undefined" class="py-2">
132
+ <div v-if="isCollapsedTop" class="mx-2 border-t border-[var(--sidebar-border)]" />
133
+ <div v-else class="flex items-center gap-2 px-3">
134
+ <span class="h-px flex-1 bg-[var(--sidebar-border)]" />
135
+ <span v-if="item.label" class="shrink-0 text-xs font-medium uppercase tracking-wider text-[var(--sidebar-text-muted)]">
136
+ {{ item.label }}
137
+ </span>
138
+ <span v-if="item.label" class="h-px flex-1 bg-[var(--sidebar-border)]" />
139
+ </div>
140
+ </li>
141
+
142
+ <li v-else :class="isCollapsedTop ? 'group/fly relative' : ''">
129
143
  <button
130
144
  type="button"
131
145
  :style="isCollapsedTop ? undefined : indentStyle"
@@ -16,6 +16,8 @@
16
16
  :account-menu-items="accountMenuItems"
17
17
  :contact-email="contactEmail"
18
18
  :help-url="helpUrl"
19
+ :favorites="favorites"
20
+ @update:favorites="$emit('update:favorites', $event)"
19
21
  @logo-click="handleLogoClick"
20
22
  @signin="handleSignin"
21
23
  @signup="handleSignup"
@@ -118,8 +120,12 @@ export default {
118
120
  default: null,
119
121
  validator: value => value === null || ["dark", "light"].includes(value),
120
122
  },
123
+ favorites: {
124
+ type: Array,
125
+ default: null,
126
+ },
121
127
  },
122
- emits: ["select-menu", "logo-click", "signin", "signup", "signout", "edit-profile", "item-click"],
128
+ emits: ["select-menu", "logo-click", "signin", "signup", "signout", "edit-profile", "item-click", "update:favorites"],
123
129
  data() {
124
130
  return {
125
131
  mobileMenuOpen: false,