webcake-ui-kit 1.2.5 → 1.2.7

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,6 +1,6 @@
1
1
  {
2
2
  "name": "webcake-ui-kit",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "UI Kit for Vue 2 && 3 - Pure Options API",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -2,7 +2,7 @@
2
2
  <Menu
3
3
  :open="isOpen"
4
4
  :placement="placement"
5
- :offset="4"
5
+ :offset="offset"
6
6
  :close-on-esc="closeOnEsc"
7
7
  :disabled="disabled"
8
8
  :persistent="persistent"
@@ -12,12 +12,13 @@
12
12
  :width="width"
13
13
  :overlay-class-name="overlayClassName"
14
14
  :overlay-style="overlayStyle"
15
+ :anchor-width="dropdownMatchSelectWidth"
15
16
  >
16
17
  <template #trigger="{ triggerRef }">
17
18
  <span
18
19
  :ref="triggerRef"
19
20
  class="ui-dropdown__anchor"
20
- @click="triggerHasClick && handleClick()"
21
+ @click.stop="triggerHasClick && handleClick()"
21
22
  @mouseenter="triggerHasHover && handleMouseEnter()"
22
23
  @mouseleave="triggerHasHover && handleMouseLeave()"
23
24
  >
@@ -100,7 +101,9 @@ export default {
100
101
  overlayClassName: { type: [String, Array, Object], default: null },
101
102
  overlayStyle: { type: [Object, Array], default: null },
102
103
  showChecked: { type: Boolean, default: false },
103
- persistent: { type: Boolean, default: false }
104
+ persistent: { type: Boolean, default: false },
105
+ dropdownMatchSelectWidth: { type: Boolean, default: false },
106
+ offset: { type: Number, default: 4 }
104
107
  },
105
108
  emits: ['change', 'update:modelValue', 'open', 'close', 'select'],
106
109
  data() {
@@ -8,7 +8,13 @@
8
8
  <Typography v-if="hasTitle" class="ui-empty__title" variant="paragraph-regular" weight="medium">
9
9
  <slot name="title">{{ title }}</slot>
10
10
  </Typography>
11
- <Typography v-if="hasDescription" class="ui-empty__description" variant="paragraph-small" weight="regular">
11
+ <Typography
12
+ v-if="hasDescription"
13
+ class="ui-empty__description"
14
+ variant="paragraph-small"
15
+ weight="regular"
16
+ color="muted-fg"
17
+ >
12
18
  <slot name="description">{{ description }}</slot>
13
19
  </Typography>
14
20
  </div>
@@ -9,7 +9,7 @@
9
9
  $attrs.class
10
10
  ]"
11
11
  >
12
- <span v-if="hasPrefix" class="ui-input__decoration ui-input__prefix" aria-hidden="true">
12
+ <span v-if="hasPrefix()" class="ui-input__decoration ui-input__prefix" aria-hidden="true">
13
13
  <slot name="prefix"></slot>
14
14
  </span>
15
15
  <input
@@ -28,7 +28,7 @@
28
28
  @blur="onBlur"
29
29
  @keydown="onKeydown"
30
30
  />
31
- <span v-if="hasSuffix" class="ui-input__decoration ui-input__suffix" aria-hidden="true">
31
+ <span v-if="hasSuffix()" class="ui-input__decoration ui-input__suffix" aria-hidden="true">
32
32
  <slot name="suffix"></slot>
33
33
  </span>
34
34
  </span>
@@ -63,15 +63,15 @@ export default {
63
63
  computed: {
64
64
  currentValue() {
65
65
  return this.modelValue !== undefined ? this.modelValue : this.value
66
- },
67
- hasPrefix() {
68
- return !!this.$slots['prefix']
69
- },
70
- hasSuffix() {
71
- return !!this.$slots['suffix']
72
66
  }
73
67
  },
74
68
  methods: {
69
+ hasPrefix() {
70
+ return !!((this.$scopedSlots && this.$scopedSlots['prefix']) || this.$slots['prefix'])
71
+ },
72
+ hasSuffix() {
73
+ return !!((this.$scopedSlots && this.$scopedSlots['suffix']) || this.$slots['suffix'])
74
+ },
75
75
  focus() {
76
76
  if (this.$refs.input) this.$refs.input.focus()
77
77
  },
@@ -1,7 +1,6 @@
1
1
  .ui-menuitem {
2
2
  display: flex;
3
3
  align-items: center;
4
- min-height: 36px;
5
4
  border-radius: var(--radius-8, 8px);
6
5
  font-family: var(--font-family-body);
7
6
  font-size: var(--paragraph-small-font-size);
@@ -42,7 +41,7 @@
42
41
  /* Sizes */
43
42
 
44
43
  .ui-menuitem--sm {
45
- min-height: 28px;
44
+ height: 28px;
46
45
  padding: var(--spacing-2xs) var(--spacing-xs);
47
46
  gap: var(--spacing-xs);
48
47
  font-size: var(--paragraph-mini-font-size);
@@ -50,13 +49,13 @@
50
49
  }
51
50
 
52
51
  .ui-menuitem--md {
53
- min-height: 32px;
52
+ height: 32px;
54
53
  padding: var(--spacing-5p5) var(--spacing-xs);
55
54
  gap: var(--spacing-xs);
56
55
  }
57
56
 
58
57
  .ui-menuitem--lg {
59
- min-height: 36px;
58
+ height: 36px;
60
59
  padding: var(--spacing-7p5) var(--spacing-sm);
61
60
  gap: var(--spacing-sm);
62
61
  }
@@ -87,6 +86,9 @@
87
86
  }
88
87
 
89
88
  .ui-menuitem__suffix {
89
+ display: inline-flex;
90
+ align-items: center;
91
+ justify-content: center;
90
92
  font-size: var(--paragraph-small-font-size);
91
93
  line-height: var(--paragraph-small-line-height);
92
94
  color: var(--muted-fg);
@@ -35,7 +35,7 @@
35
35
  @keydown.space.prevent="toggle()"
36
36
  >
37
37
  <span class="ui-select__left">
38
- <span v-if="hasIconSlot" class="ui-select__icon">
38
+ <span v-if="hasIconSlot()" class="ui-select__icon">
39
39
  <slot name="icon" />
40
40
  </span>
41
41
  <span v-if="prepend" class="ui-select__prepend">{{ prepend }}</span>
@@ -87,7 +87,7 @@
87
87
  </div>
88
88
  </template>
89
89
  <div class="ui-select__list" :style="listStyle">
90
- <div v-if="hasContentHeader">
90
+ <div v-if="hasContentHeader()">
91
91
  <slot name="contentHeader"></slot>
92
92
  </div>
93
93
  <slot>
@@ -97,6 +97,10 @@
97
97
  :value="opt.value"
98
98
  :label="opt.label"
99
99
  :disabled="opt.disabled"
100
+ :children="opt.children"
101
+ :placement="opt.placement"
102
+ :width="opt.width"
103
+ :offset="opt.offset"
100
104
  />
101
105
  </slot>
102
106
  <slot v-if="showEmpty" name="empty">
@@ -110,7 +114,7 @@
110
114
  </template>
111
115
  </Empty>
112
116
  </slot>
113
- <div v-if="hasContentFooter">
117
+ <div v-if="hasContentFooter()">
114
118
  <slot name="contentFooter"></slot>
115
119
  </div>
116
120
  </div>
@@ -159,7 +163,7 @@ export default {
159
163
  default: false
160
164
  },
161
165
  value: {
162
- type: [String, Array],
166
+ type: [String, Number, Array],
163
167
  default: null
164
168
  },
165
169
  modelValue: {
@@ -255,8 +259,16 @@ export default {
255
259
  var base = this.mode === 'tags' ? this.options.concat(this.tagOptions) : this.options
256
260
  return base.map(function (opt) {
257
261
  return typeof opt === 'string'
258
- ? { label: opt, value: opt, disabled: false }
259
- : { label: opt.label || opt.value, value: opt.value, disabled: !!opt.disabled }
262
+ ? { label: opt, value: opt, disabled: false, children: [], placement: 'right-start' }
263
+ : {
264
+ label: opt.label || opt.value,
265
+ value: opt.value,
266
+ disabled: !!opt.disabled,
267
+ children: opt.children || [],
268
+ placement: opt.placement || 'right-start',
269
+ width: opt.width || null,
270
+ offset: opt.offset || null
271
+ }
260
272
  })
261
273
  },
262
274
  filteredOptions() {
@@ -273,7 +285,17 @@ export default {
273
285
  if (this.isMultiMode) return ''
274
286
  if (!this.effectiveValue) return ''
275
287
  if (this.labelCache[this.effectiveValue]) return this.labelCache[this.effectiveValue]
276
- const opt = this.normalizedOptions.find(o => o.value === this.effectiveValue)
288
+ const findOpt = (opts, val) => {
289
+ for (let i = 0; i < opts.length; i++) {
290
+ if (opts[i].value === val) return opts[i]
291
+ if (opts[i].children && opts[i].children.length > 0) {
292
+ const found = findOpt(opts[i].children, val)
293
+ if (found) return found
294
+ }
295
+ }
296
+ return null
297
+ }
298
+ const opt = findOpt(this.normalizedOptions, this.effectiveValue)
277
299
  if (opt) return opt.label
278
300
  return this._findLabelInSlot(this.effectiveValue) || this.effectiveValue
279
301
  },
@@ -281,10 +303,18 @@ export default {
281
303
  if (!this.isMultiMode) return []
282
304
  var values = Array.isArray(this.effectiveValue) ? this.effectiveValue : []
283
305
  var self = this
306
+ const findOpt = (opts, val) => {
307
+ for (let i = 0; i < opts.length; i++) {
308
+ if (opts[i].value === val) return opts[i]
309
+ if (opts[i].children && opts[i].children.length > 0) {
310
+ const found = findOpt(opts[i].children, val)
311
+ if (found) return found
312
+ }
313
+ }
314
+ return null
315
+ }
284
316
  return values.map(function (v) {
285
- var opt = self.normalizedOptions.find(function (o) {
286
- return o.value === v
287
- })
317
+ var opt = findOpt(self.normalizedOptions, v)
288
318
  return { value: v, label: opt ? opt.label : self.labelCache[v] || v }
289
319
  })
290
320
  },
@@ -297,15 +327,6 @@ export default {
297
327
  if (this.normalizedOptions.length === 0 && Object.keys(this.slotOptionVisible).length === 0) return true
298
328
  return false
299
329
  },
300
- hasContentFooter() {
301
- return !!((this.$scopedSlots && this.$scopedSlots['contentFooter']) || this.$slots['contentFooter'])
302
- },
303
- hasContentHeader() {
304
- return !!((this.$scopedSlots && this.$scopedSlots['contentHeader']) || this.$slots['contentHeader'])
305
- },
306
- hasIconSlot() {
307
- return !!((this.$scopedSlots && this.$scopedSlots['icon']) || this.$slots['icon'])
308
- },
309
330
  listStyle() {
310
331
  if (typeof this.listHeight === 'number') return { maxHeight: this.listHeight + 'px' }
311
332
  return { maxHeight: this.listHeight }
@@ -356,6 +377,15 @@ export default {
356
377
  },
357
378
 
358
379
  methods: {
380
+ hasContentFooter() {
381
+ return !!((this.$scopedSlots && this.$scopedSlots['contentFooter']) || this.$slots['contentFooter'])
382
+ },
383
+ hasContentHeader() {
384
+ return !!((this.$scopedSlots && this.$scopedSlots['contentHeader']) || this.$slots['contentHeader'])
385
+ },
386
+ hasIconSlot() {
387
+ return !!((this.$scopedSlots && this.$scopedSlots['icon']) || this.$slots['icon'])
388
+ },
359
389
  onMenuChange(v) {
360
390
  this.isOpen = v
361
391
  },
@@ -15,26 +15,54 @@
15
15
  :aria-disabled="isDisabled ? 'true' : null"
16
16
  @click="handleClick"
17
17
  >
18
- <span v-if="hasPrefix" class="ui-select-option__prefix">
18
+ <span v-if="hasPrefix()" class="ui-select-option__prefix">
19
19
  <slot name="prefix" />
20
20
  </span>
21
21
  <span class="ui-select-option--label">
22
22
  <slot>{{ label || value }}</slot>
23
23
  </span>
24
- <span v-if="hasSuffix" class="ui-select-option__suffix">
24
+ <span v-if="hasSuffix()" class="ui-select-option__suffix">
25
25
  <slot name="suffix" />
26
26
  </span>
27
- <WkiCheck v-if="isSelected && (isMultiMode || isShowChecked)" :size="16" color="var(--muted-fg)" />
27
+ <WkiCheck
28
+ v-if="isSelected && (isMultiMode || isShowChecked) && children.length === 0"
29
+ :size="16"
30
+ color="var(--muted-fg)"
31
+ />
32
+
33
+ <Dropdown v-if="children.length > 0" :placement="placement" :width="width || 280" :offset="offset">
34
+ <div class="expand-trigger">
35
+ <WkiChevronRight :size="16" />
36
+ </div>
37
+ <template #overlay>
38
+ <div class="ui-select__list">
39
+ <SelectOption
40
+ v-for="opt in children"
41
+ :key="opt.value"
42
+ :value="opt.value"
43
+ :label="opt.label"
44
+ :disabled="opt.disabled"
45
+ :children="opt.children"
46
+ :placement="opt.placement"
47
+ :width="opt.width"
48
+ :offset="opt.offset"
49
+ />
50
+ </div>
51
+ </template>
52
+ </Dropdown>
28
53
  </div>
29
54
  </template>
30
55
 
31
56
  <script>
32
- import { WkiCheck } from '../../icons'
57
+ import { WkiCheck, WkiChevronRight } from '../../icons'
58
+ import Dropdown from '../dropdown/Dropdown.vue'
33
59
  export default {
34
60
  name: 'SelectOption',
35
61
 
36
62
  components: {
37
- WkiCheck
63
+ WkiCheck,
64
+ Dropdown,
65
+ WkiChevronRight
38
66
  },
39
67
 
40
68
  inject: {
@@ -43,7 +71,7 @@ export default {
43
71
 
44
72
  props: {
45
73
  value: {
46
- type: String,
74
+ type: [String, Number],
47
75
  required: true
48
76
  },
49
77
  label: {
@@ -67,6 +95,22 @@ export default {
67
95
  validator: function (v) {
68
96
  return ['default', 'destructive'].indexOf(v) !== -1
69
97
  }
98
+ },
99
+ children: {
100
+ type: Array,
101
+ default: () => []
102
+ },
103
+ placement: {
104
+ type: String,
105
+ default: 'right-start'
106
+ },
107
+ width: {
108
+ type: [String, Number],
109
+ default: null
110
+ },
111
+ offset: {
112
+ type: [String, Number],
113
+ default: null
70
114
  }
71
115
  },
72
116
 
@@ -100,12 +144,6 @@ export default {
100
144
  // explicit prop wins; otherwise inherit from parent Select; else regular
101
145
  return this.size || (this.select && (this.select.optionSize || this.select.size)) || 'md'
102
146
  },
103
- hasPrefix() {
104
- return !!((this.$scopedSlots && this.$scopedSlots.prefix) || this.$slots.prefix)
105
- },
106
- hasSuffix() {
107
- return !!((this.$scopedSlots && this.$scopedSlots.suffix) || this.$slots.suffix)
108
- },
109
147
  isHidden() {
110
148
  if (!this.select || !this.select.filterText) return false
111
149
  var q = this.select.filterText.toLowerCase()
@@ -118,10 +156,27 @@ export default {
118
156
  isSelected() {
119
157
  if (!this.select) return false
120
158
  var val = this.select.effectiveValue
121
- if (this.select.isMultiMode) {
122
- return Array.isArray(val) && val.indexOf(this.value) !== -1
159
+
160
+ const isMatch = v => {
161
+ if (this.select.isMultiMode) {
162
+ return Array.isArray(val) && val.indexOf(v) !== -1
163
+ }
164
+ return val !== undefined && val === v
165
+ }
166
+
167
+ if (isMatch(this.value)) return true
168
+ if (this.select.isMultiMode) return false
169
+
170
+ const checkChildren = children => {
171
+ if (!children || !children.length) return false
172
+ for (let i = 0; i < children.length; i++) {
173
+ if (isMatch(children[i].value)) return true
174
+ if (checkChildren(children[i].children)) return true
175
+ }
176
+ return false
123
177
  }
124
- return val !== undefined && val === this.value
178
+
179
+ return checkChildren(this.children)
125
180
  },
126
181
  isDisabled() {
127
182
  return this.disabled || !!(this.select && this.select.disabled)
@@ -135,6 +190,12 @@ export default {
135
190
  },
136
191
 
137
192
  methods: {
193
+ hasPrefix() {
194
+ return !!((this.$scopedSlots && this.$scopedSlots.prefix) || this.$slots.prefix)
195
+ },
196
+ hasSuffix() {
197
+ return !!((this.$scopedSlots && this.$scopedSlots.suffix) || this.$slots.suffix)
198
+ },
138
199
  _registerLabel() {
139
200
  if (!this.select) return
140
201
  var labelEl = this.$el && this.$el.querySelector('.ui-select-option--label')
@@ -98,3 +98,65 @@
98
98
  font-size: var(--paragraph-mini-font-size);
99
99
  line-height: var(--paragraph-mini-line-height);
100
100
  }
101
+
102
+ .ui-select-option ::v-deep .ui-dropdown__anchor {
103
+ align-self: stretch;
104
+ display: flex;
105
+ align-items: center;
106
+ --submenu-offset: calc(var(--spacing-2xs) + 1px);
107
+ }
108
+
109
+ .ui-select-option--sm ::v-deep .ui-dropdown__anchor {
110
+ margin-top: calc(var(--spacing-2xs) * -1 - var(--submenu-offset));
111
+ margin-right: calc(var(--spacing-xs) * -1);
112
+ margin-bottom: calc(var(--spacing-2xs) * -1 + var(--submenu-offset));
113
+ padding: 0 var(--spacing-xs);
114
+ }
115
+
116
+ .ui-select-option--md ::v-deep .ui-dropdown__anchor {
117
+ margin-top: calc(var(--spacing-5p5) * -1 - var(--submenu-offset));
118
+ margin-right: calc(var(--spacing-xs) * -1);
119
+ margin-bottom: calc(var(--spacing-5p5) * -1 + var(--submenu-offset));
120
+ padding: 0 var(--spacing-xs);
121
+ }
122
+
123
+ .ui-select-option--lg ::v-deep .ui-dropdown__anchor {
124
+ margin-top: calc(var(--spacing-7p5) * -1 - var(--submenu-offset));
125
+ margin-right: calc(var(--spacing-sm) * -1);
126
+ margin-bottom: calc(var(--spacing-7p5) * -1 + var(--submenu-offset));
127
+ padding: 0 var(--spacing-sm);
128
+ }
129
+
130
+ .expand-trigger {
131
+ display: flex;
132
+ align-items: center;
133
+ transform: translateY(var(--submenu-offset));
134
+ }
135
+
136
+ .ui-select__list {
137
+ display: flex;
138
+ flex-direction: column;
139
+ overflow-y: auto;
140
+ padding: var(--spacing-2xs);
141
+ gap: var(--spacing-3xs);
142
+ }
143
+
144
+ .ui-select__list::-webkit-scrollbar {
145
+ width: 6px;
146
+ }
147
+
148
+ .ui-select__list::-webkit-scrollbar-thumb {
149
+ background: var(--border-primary);
150
+ border-radius: 3px;
151
+ }
152
+
153
+ .ui-select__list ::v-deep .select-empty {
154
+ height: 100px;
155
+ width: 100px;
156
+ }
157
+
158
+ .ui-select__list ::v-deep .select-empty img {
159
+ width: 100%;
160
+ height: 100%;
161
+ object-fit: cover;
162
+ }
@@ -57,8 +57,8 @@ export default {
57
57
  customHeaderRow: { type: Function, default: null },
58
58
  rowDraggable: { type: Object, default: null },
59
59
  enableFixedLeft: { type: Boolean, default: false },
60
- rowHeight: { type: Number, default: 36 },
61
- headerHeight: { type: Number, default: 50 },
60
+ rowHeight: { type: Number, default: 50 },
61
+ headerHeight: { type: Number, default: 36 },
62
62
  emptyText: { type: String, default: '' },
63
63
  height: { type: [Number, String], default: 0 }
64
64
  },
@@ -176,6 +176,8 @@ export default {
176
176
  this.tableCtx.layout.scroll = v.scroll
177
177
  this.tableCtx.layout.tableLayout = v.scroll && v.scroll.y ? 'fixed' : 'auto'
178
178
  this.tableCtx.layout.height = v.height
179
+ this.tableCtx.layout.headerHeight = v.headerHeight
180
+ this.tableCtx.layout.rowHeight = v.rowHeight
179
181
  }
180
182
  },
181
183
 
@@ -186,7 +188,6 @@ export default {
186
188
  ;((this.tableCtx.data.display = v.sortKey ? sortData(v.source, v.sortKey, v.sortOrder) : v.source),
187
189
  (this.tableCtx.data.loading = v.loading))
188
190
  this.tableCtx.data.emptyText = v.emptyText
189
- console.log(this.tableCtx.data.emptyText)
190
191
  }
191
192
  },
192
193
 
@@ -119,7 +119,15 @@ export default {
119
119
 
120
120
  // Case 1 & 2 — wrapper có overflow-x khi scroll.x
121
121
  contentStyle: function () {
122
- return this.hasScrollX ? { overflow: 'auto hidden' } : {}
122
+ let style = {}
123
+ if (this.hasScrollX) {
124
+ style = { overflow: 'auto hidden' }
125
+ }
126
+
127
+ if (this.minHeight > 0) {
128
+ style.minHeight = `${this.toCssSize(this.minHeight)}`
129
+ }
130
+ return style
123
131
  },
124
132
 
125
133
  // Case 1 & 2 — table style
@@ -170,7 +178,6 @@ export default {
170
178
  this.isPingedRight = scrollLeft < scrollWidth - clientWidth
171
179
  },
172
180
  updateScrollState() {
173
- console.log('update')
174
181
  if (this.$refs.bodyRef) {
175
182
  const { width } = getTargetScrollBarSize(this.$refs.bodyRef)
176
183
  this.tableContext.layout.scrollBarWidth = width == 'auto' ? '0px' : width
@@ -187,9 +194,6 @@ export default {
187
194
  this.$nextTick(() => {
188
195
  this.updateScrollState()
189
196
  })
190
- },
191
- hasScrollY(val) {
192
- console.log('hasScrollY', val)
193
197
  }
194
198
  },
195
199
 
@@ -21,12 +21,24 @@ export default {
21
21
  if (!myFloat || typeof document === 'undefined') return false
22
22
  const portalRoot = document.querySelector('[data-wk-portal-root]')
23
23
  if (!portalRoot || !portalRoot.contains(target)) return false
24
- const floatingEls = portalRoot.querySelectorAll('[id^="wk-menu-"]')
25
- for (let i = 0; i < floatingEls.length; i++) {
26
- const fp = floatingEls[i]
27
- if (!fp.contains(target)) continue
24
+
25
+ const getFloatingPanel = el => {
26
+ let curr = el
27
+ while (curr && curr !== portalRoot) {
28
+ if (curr.id && curr.id.startsWith('wk-menu-')) return curr
29
+ curr = curr.parentElement
30
+ }
31
+ return null
32
+ }
33
+
34
+ let currTarget = target
35
+ while (currTarget) {
36
+ const fp = getFloatingPanel(currTarget)
37
+ if (!fp) return false
28
38
  const trig = fp._wkTrigger
29
- if (trig && myFloat.contains(trig)) return true
39
+ if (!trig) return false
40
+ if (myFloat.contains(trig)) return true
41
+ currTarget = trig
30
42
  }
31
43
  return false
32
44
  },