webcake-ui-kit 1.3.10 → 1.3.12

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.3.10",
3
+ "version": "1.3.12",
4
4
  "description": "UI Kit for Vue 2 && 3 - Pure Options API",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -11,8 +11,6 @@
11
11
  font-weight: 500;
12
12
  white-space: nowrap;
13
13
  transition: box-shadow 0.15s ease;
14
- border-width: 1px;
15
- border-style: solid;
16
14
  height: 20px;
17
15
  }
18
16
 
@@ -24,39 +22,33 @@
24
22
  }
25
23
 
26
24
  .ui-badge--primary {
27
- background: var(--positive-subtle);
28
- color: var(--primary-brand-fg);
29
- border-color: var(--positive-border-subtle);
25
+ background: var(--color-emerald-100);
26
+ color: var(--color-emerald-700);
30
27
  }
31
28
  .ui-badge--secondary {
32
29
  background: var(--tertiary-bg);
33
30
  color: var(--secondary-fg);
34
- border: none;
35
31
  }
36
32
  .ui-badge--outline {
37
33
  background: var(--outline);
38
34
  color: var(--primary-fg);
39
- border-color: var(--border-primary);
35
+ border: 1px solid var(--border-primary);
40
36
  }
41
37
  .ui-badge--ghost {
42
38
  background: var(--ghost);
43
39
  color: var(--primary-fg);
44
- border: none;
45
40
  }
46
41
  .ui-badge--destructive {
47
- background: var(--destructive-subtle);
48
- color: var(--destructive);
49
- border-color: var(--destructive-border-subtle);
42
+ background: var(--color-red-100);
43
+ color: var(--color-red-700);
50
44
  }
51
45
  .ui-badge--info {
52
- background: var(--infor-subtle);
53
- color: var(--infor);
54
- border-color: var(--infor-border-subtle);
46
+ background: var(--color-blue-100);
47
+ color: var(--color-blue-700);
55
48
  }
56
49
  .ui-badge--warning {
57
- background: var(--warning-subtle);
58
- color: var(--warning);
59
- border-color: var(--warning-border-subtle);
50
+ background: var(--color-amber-100);
51
+ color: var(--color-amber-700);
60
52
  }
61
53
 
62
54
  .ui-badge:focus-visible {
@@ -10,24 +10,29 @@
10
10
  @change="onChange"
11
11
  />
12
12
  <span class="ui-checkbox__box" aria-hidden="true">
13
- <WkiCheck v-if="isChecked" :size="14" />
13
+ <template v-if="isChecked">
14
+ <WkiMinus v-if="iconType === 'minus'" :size="14" />
15
+ <WkiCheck v-else :size="14" />
16
+ </template>
14
17
  </span>
15
18
  </span>
16
19
  </template>
17
20
 
18
21
  <script>
19
22
  import WkiCheck from '../../icons/Check.vue'
23
+ import WkiMinus from '../../icons/Minus.vue'
20
24
 
21
25
  export default {
22
26
  name: 'Checkbox',
23
- components: { WkiCheck },
27
+ components: { WkiCheck, WkiMinus },
24
28
  inheritAttrs: false,
25
29
  model: { prop: 'checked', event: 'change' },
26
30
  props: {
27
31
  checked: { type: Boolean, default: false },
28
32
  modelValue: { type: Boolean, default: undefined },
29
33
  error: { type: Boolean, default: false },
30
- disabled: { type: Boolean, default: false }
34
+ disabled: { type: Boolean, default: false },
35
+ iconType: { type: String, default: 'default', validator: v => ['default', 'minus'].includes(v) }
31
36
  },
32
37
  emits: ['change', 'update:modelValue'],
33
38
  computed: {
@@ -5,6 +5,7 @@
5
5
  :checked="isChecked"
6
6
  :error="error"
7
7
  :disabled="disabled"
8
+ :iconType="iconType"
8
9
  @change="onChange"
9
10
  />
10
11
  <span class="ui-checkbox-group__label">
@@ -30,7 +31,8 @@ export default {
30
31
  modelValue: { type: Boolean, default: undefined },
31
32
  error: { type: Boolean, default: false },
32
33
  disabled: { type: Boolean, default: false },
33
- label: { type: String, default: '' }
34
+ label: { type: String, default: '' },
35
+ iconType: { type: String, default: 'default', validator: v => ['default', 'minus'].includes(v) }
34
36
  },
35
37
  emits: ['change', 'update:modelValue'],
36
38
  computed: {
@@ -8,6 +8,7 @@
8
8
  disabled && 'ui-input--disabled',
9
9
  $attrs.class
10
10
  ]"
11
+ @click="onClick"
11
12
  >
12
13
  <span v-if="hasPrefix()" class="ui-input__decoration ui-input__prefix" aria-hidden="true">
13
14
  <slot name="prefix"></slot>
@@ -59,7 +60,7 @@ export default {
59
60
  readonly: { type: Boolean, default: false },
60
61
  centered: { type: Boolean, default: false }
61
62
  },
62
- emits: ['input', 'change', 'update:modelValue', 'focus', 'blur', 'pressEnter'],
63
+ emits: ['input', 'change', 'update:modelValue', 'focus', 'blur', 'pressEnter', 'click'],
63
64
  computed: {
64
65
  currentValue() {
65
66
  return this.modelValue !== undefined ? this.modelValue : this.value
@@ -94,6 +95,9 @@ export default {
94
95
  if (e.key === 'Enter') {
95
96
  this.$emit('pressEnter', e.target.value, e)
96
97
  }
98
+ },
99
+ onClick(e) {
100
+ this.$emit('click', e)
97
101
  }
98
102
  }
99
103
  }
@@ -4,7 +4,7 @@
4
4
  :disabled="disabled || loading"
5
5
  :placement="placement"
6
6
  :offset="4"
7
- :anchor-width="dropdownMatchSelectWidth"
7
+ :anchor-width="effectiveDropdownMatchSelectWidth"
8
8
  :close-on-select="false"
9
9
  @change="onMenuChange"
10
10
  @close="onMenuClose"
@@ -87,37 +87,50 @@
87
87
  </span>
88
88
  </div>
89
89
  </template>
90
- <div class="ui-select__list" :style="listStyle">
91
- <div v-if="hasContentHeader()">
92
- <slot name="contentHeader"></slot>
90
+ <div class="ui-select__columns">
91
+ <div class="ui-select__list" :style="listStyle">
92
+ <div v-if="hasContentHeader()">
93
+ <slot name="contentHeader"></slot>
94
+ </div>
95
+ <slot>
96
+ <SelectOption
97
+ v-for="opt in filteredOptions"
98
+ :key="opt.value"
99
+ :value="opt.value"
100
+ :label="opt.label"
101
+ :mark-label="opt.markLabel"
102
+ :disabled="opt.disabled"
103
+ :children="opt.children"
104
+ :placement="opt.placement"
105
+ :offset="opt.offset"
106
+ />
107
+ </slot>
108
+ <slot v-if="showEmpty" name="empty">
109
+ <Empty description="No data">
110
+ <template #media>
111
+ <div class="select-empty">
112
+ <img
113
+ src="https://content.pancake.vn/web-media-262/2e/2a/0c/96/c2b58ae9f06c4dc2a3b83016e56d5b4132ff40637862ba23435e43a0-w:224-h:224-l:3927-t:image/png.png"
114
+ />
115
+ </div>
116
+ </template>
117
+ </Empty>
118
+ </slot>
119
+ <div v-if="hasContentFooter()">
120
+ <slot name="contentFooter"></slot>
121
+ </div>
93
122
  </div>
94
- <slot>
123
+ <div v-for="(col, idx) in expandedColumns" :key="col.ownerValue" class="ui-select__list" :style="listStyle">
95
124
  <SelectOption
96
- v-for="opt in filteredOptions"
125
+ v-for="opt in col.children"
97
126
  :key="opt.value"
98
127
  :value="opt.value"
99
128
  :label="opt.label"
100
129
  :mark-label="opt.markLabel"
101
130
  :disabled="opt.disabled"
102
- :children="opt.children"
103
- :placement="opt.placement"
104
- :width="opt.width"
105
- :offset="opt.offset"
131
+ :children="opt.children || []"
132
+ :column-depth="idx + 1"
106
133
  />
107
- </slot>
108
- <slot v-if="showEmpty" name="empty">
109
- <Empty description="No data">
110
- <template #media>
111
- <div class="select-empty">
112
- <img
113
- src="https://content.pancake.vn/web-media-262/2e/2a/0c/96/c2b58ae9f06c4dc2a3b83016e56d5b4132ff40637862ba23435e43a0-w:224-h:224-l:3927-t:image/png.png"
114
- />
115
- </div>
116
- </template>
117
- </Empty>
118
- </slot>
119
- <div v-if="hasContentFooter()">
120
- <slot name="contentFooter"></slot>
121
134
  </div>
122
135
  </div>
123
136
  </Menu>
@@ -203,6 +216,10 @@ export default {
203
216
  type: [Number, String],
204
217
  default: 256
205
218
  },
219
+ listWidth: {
220
+ type: [Number, String],
221
+ default: null
222
+ },
206
223
  mode: {
207
224
  type: String,
208
225
  default: 'single',
@@ -236,7 +253,8 @@ export default {
236
253
  labelCache: {},
237
254
  filterText: '',
238
255
  tagOptions: [],
239
- slotOptionVisible: {}
256
+ slotOptionVisible: {},
257
+ expandedColumns: []
240
258
  }
241
259
  },
242
260
 
@@ -274,7 +292,6 @@ export default {
274
292
  disabled: !!opt.disabled,
275
293
  children: opt.children || [],
276
294
  placement: opt.placement || 'right-start',
277
- width: opt.width || null,
278
295
  offset: opt.offset || null
279
296
  }
280
297
  })
@@ -335,9 +352,25 @@ export default {
335
352
  if (this.normalizedOptions.length === 0 && Object.keys(this.slotOptionVisible).length === 0) return true
336
353
  return false
337
354
  },
355
+ effectiveDropdownMatchSelectWidth() {
356
+ if (
357
+ this.normalizedOptions.some(function (opt) {
358
+ return opt.children && opt.children.length > 0
359
+ })
360
+ ) {
361
+ return false
362
+ }
363
+ return this.dropdownMatchSelectWidth
364
+ },
338
365
  listStyle() {
339
- if (typeof this.listHeight === 'number') return { maxHeight: this.listHeight + 'px' }
340
- return { maxHeight: this.listHeight }
366
+ var style = {}
367
+ style.maxHeight = typeof this.listHeight === 'number' ? this.listHeight + 'px' : this.listHeight
368
+ if (this.listWidth != null) {
369
+ var w = typeof this.listWidth === 'number' ? this.listWidth + 'px' : this.listWidth
370
+ style.width = w
371
+ style.minWidth = w
372
+ }
373
+ return style
341
374
  },
342
375
  externalClass() {
343
376
  // Vue 3: class is in $attrs (inheritAttrs:false keeps it off the root)
@@ -567,6 +600,29 @@ export default {
567
600
  return null
568
601
  }
569
602
  return scan(this._slotNodes())
603
+ },
604
+ expandColumn(depth, ownerValue, children) {
605
+ var existing = this.expandedColumns[depth]
606
+ if (existing && existing.ownerValue === ownerValue) {
607
+ this.expandedColumns = this.expandedColumns.slice(0, depth)
608
+ } else {
609
+ this.expandedColumns = this.expandedColumns.slice(0, depth).concat([
610
+ {
611
+ ownerValue: ownerValue,
612
+ children: children || []
613
+ }
614
+ ])
615
+ }
616
+ },
617
+ openColumn(depth, ownerValue, children) {
618
+ var existing = this.expandedColumns[depth]
619
+ if (existing && existing.ownerValue === ownerValue) return
620
+ this.expandedColumns = this.expandedColumns.slice(0, depth).concat([
621
+ {
622
+ ownerValue: ownerValue,
623
+ children: children || []
624
+ }
625
+ ])
570
626
  }
571
627
  }
572
628
  }
@@ -239,6 +239,7 @@
239
239
  overflow-y: auto;
240
240
  padding: var(--spacing-2xs);
241
241
  gap: var(--spacing-3xs);
242
+ flex: 1 0 0;
242
243
  }
243
244
 
244
245
  .ui-select__list::-webkit-scrollbar {
@@ -250,6 +251,20 @@
250
251
  border-radius: 3px;
251
252
  }
252
253
 
254
+ .ui-select__columns {
255
+ display: flex;
256
+ align-items: stretch;
257
+ }
258
+
259
+ .ui-select__columns > .ui-select__list {
260
+ flex: 1 1 0;
261
+ min-width: 160px;
262
+ }
263
+
264
+ .ui-select__columns > .ui-select__list + .ui-select__list {
265
+ border-left: 1px solid var(--border-primary);
266
+ }
267
+
253
268
  .ui-select__list ::v-deep .select-empty {
254
269
  height: 100px;
255
270
  width: 100px;
@@ -1,68 +1,56 @@
1
1
  <template>
2
2
  <div
3
3
  v-show="!isHidden"
4
- class="ui-select-option"
5
- :class="[
6
- effectiveSize === 'sm' ? 'ui-select-option--sm' : null,
7
- effectiveSize === 'md' ? 'ui-select-option--md' : null,
8
- effectiveSize === 'lg' ? 'ui-select-option--lg' : null,
9
- variant === 'destructive' ? 'ui-select-option--destructive' : null,
10
- isDisabled ? 'ui-select-option--disabled' : null,
11
- isSelected ? 'ui-select-option--selected' : null
12
- ]"
13
- role="option"
14
- :aria-selected="isSelected ? 'true' : 'false'"
15
- :aria-disabled="isDisabled ? 'true' : null"
16
- @click.stop="handleClick"
4
+ class="ui-select-option-wrapper"
5
+ :class="{ 'ui-select-option-wrapper--expanded': isExpanded }"
17
6
  >
18
- <span v-if="hasPrefix()" class="ui-select-option__prefix">
19
- <slot name="prefix" />
20
- </span>
21
- <span class="ui-select-option--label">
22
- <slot>{{ label || value }}</slot>
23
- </span>
24
- <span v-if="hasSuffix()" class="ui-select-option__suffix">
25
- <slot name="suffix" />
26
- </span>
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">
7
+ <div
8
+ class="ui-select-option"
9
+ :class="[
10
+ effectiveSize === 'sm' ? 'ui-select-option--sm' : null,
11
+ effectiveSize === 'md' ? 'ui-select-option--md' : null,
12
+ effectiveSize === 'lg' ? 'ui-select-option--lg' : null,
13
+ variant === 'destructive' ? 'ui-select-option--destructive' : null,
14
+ isDisabled ? 'ui-select-option--disabled' : null,
15
+ isSelected ? 'ui-select-option--selected' : null,
16
+ hasChildren ? 'ui-select-option--has-children' : null,
17
+ isExpanded ? 'ui-select-option--expanded' : null
18
+ ]"
19
+ role="option"
20
+ :aria-selected="isSelected ? 'true' : 'false'"
21
+ :aria-disabled="isDisabled ? 'true' : null"
22
+ :aria-haspopup="hasChildren ? 'true' : null"
23
+ :aria-expanded="hasChildren ? (isExpanded ? 'true' : 'false') : null"
24
+ @click.stop="handleClick"
25
+ >
26
+ <span v-if="hasPrefix()" class="ui-select-option__prefix">
27
+ <slot name="prefix" />
28
+ </span>
29
+ <span class="ui-select-option--label">
30
+ <slot>{{ label || value }}</slot>
31
+ </span>
32
+ <span v-if="hasSuffix()" class="ui-select-option__suffix">
33
+ <slot name="suffix" />
34
+ </span>
35
+ <WkiCheck
36
+ v-if="isSelected && (isMultiMode || isShowChecked) && !hasChildren"
37
+ :size="16"
38
+ color="var(--muted-fg)"
39
+ />
40
+ <span v-if="hasChildren" class="ui-select-option__expand">
35
41
  <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
- :mark-label="opt.markLabel"
45
- :disabled="opt.disabled"
46
- :children="opt.children"
47
- :placement="opt.placement"
48
- :width="opt.width"
49
- :offset="opt.offset"
50
- />
51
- </div>
52
- </template>
53
- </Dropdown>
42
+ </span>
43
+ </div>
54
44
  </div>
55
45
  </template>
56
46
 
57
47
  <script>
58
48
  import { WkiCheck, WkiChevronRight } from '../../icons'
59
- import Dropdown from '../dropdown/Dropdown.vue'
60
49
  export default {
61
50
  name: 'SelectOption',
62
51
 
63
52
  components: {
64
53
  WkiCheck,
65
- Dropdown,
66
54
  WkiChevronRight
67
55
  },
68
56
 
@@ -109,21 +97,28 @@ export default {
109
97
  type: String,
110
98
  default: 'right-start'
111
99
  },
112
- width: {
113
- type: [String, Number],
114
- default: null
115
- },
116
100
  offset: {
117
101
  type: [String, Number],
118
102
  default: null
103
+ },
104
+ columnDepth: {
105
+ type: Number,
106
+ default: 0
119
107
  }
120
108
  },
121
109
 
122
110
  emits: [],
123
111
 
112
+ data() {
113
+ return {}
114
+ },
115
+
124
116
  mounted() {
125
117
  this._registerLabel()
126
118
  if (this.select) this.select.setSlotOptionVisible(this.value, !this.isHidden)
119
+ if (this.hasChildren && this.isSelected && this.select && typeof this.select.openColumn === 'function') {
120
+ this.select.openColumn(this.columnDepth, this.value, this.children)
121
+ }
127
122
  },
128
123
 
129
124
  updated() {
@@ -141,6 +136,11 @@ export default {
141
136
  watch: {
142
137
  isHidden(val) {
143
138
  if (this.select) this.select.setSlotOptionVisible(this.value, !val)
139
+ },
140
+ isSelected(val) {
141
+ if (val && this.hasChildren && this.select && typeof this.select.openColumn === 'function') {
142
+ this.select.openColumn(this.columnDepth, this.value, this.children)
143
+ }
144
144
  }
145
145
  },
146
146
 
@@ -149,6 +149,14 @@ export default {
149
149
  // explicit prop wins; otherwise inherit from parent Select; else regular
150
150
  return this.size || (this.select && (this.select.optionSize || this.select.size)) || 'md'
151
151
  },
152
+ hasChildren() {
153
+ return Array.isArray(this.children) && this.children.length > 0
154
+ },
155
+ isExpanded() {
156
+ if (!this.select || !this.hasChildren) return false
157
+ var col = this.select.expandedColumns && this.select.expandedColumns[this.columnDepth]
158
+ return !!(col && col.ownerValue === this.value)
159
+ },
152
160
  isHidden() {
153
161
  if (!this.select || !this.select.filterText) return false
154
162
  var q = this.select.filterText.toLowerCase()
@@ -214,6 +222,12 @@ export default {
214
222
  },
215
223
  handleClick() {
216
224
  if (this.isDisabled) return
225
+ if (this.hasChildren) {
226
+ if (this.select && typeof this.select.expandColumn === 'function') {
227
+ this.select.expandColumn(this.columnDepth, this.value, this.children)
228
+ }
229
+ return
230
+ }
217
231
  if (this.select && typeof this.select.select === 'function') {
218
232
  this.select.select(this.value)
219
233
  }
@@ -223,4 +237,3 @@ export default {
223
237
  </script>
224
238
 
225
239
  <style src="./select_option.css" scoped></style>
226
- <style src="./select_option_sub.css"></style>
@@ -1,6 +1,17 @@
1
+ /* Single root: lays the option row and its (optional) inline child column
2
+ side by side. Top-aligned so the expanded column starts at the option row. */
3
+ .ui-select-option-wrapper {
4
+ display: flex;
5
+ align-items: flex-start;
6
+ width: 100%;
7
+ min-width: 0;
8
+ }
9
+
1
10
  .ui-select-option {
2
11
  display: flex;
3
12
  align-items: center;
13
+ flex: 1 1 0;
14
+ min-width: 0;
4
15
  min-height: 32px;
5
16
  border-radius: var(--radius);
6
17
  font-family: var(--font-family-body);
@@ -98,3 +109,16 @@
98
109
  font-size: var(--paragraph-mini-font-size);
99
110
  line-height: var(--paragraph-mini-line-height);
100
111
  }
112
+
113
+ /* Chevron affordance for options that expand a child column. */
114
+ .ui-select-option__expand {
115
+ display: inline-flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ flex-shrink: 0;
119
+ color: var(--muted-fg);
120
+ }
121
+
122
+ .ui-select-option--expanded {
123
+ background: var(--accent-bg);
124
+ }
@@ -1,61 +0,0 @@
1
- .ui-select-option .ui-dropdown__anchor {
2
- align-self: stretch;
3
- display: flex;
4
- align-items: center;
5
- --submenu-offset: calc(var(--spacing-2xs) + 1px);
6
- }
7
-
8
- .ui-select-option--sm .ui-dropdown__anchor {
9
- margin-top: calc(var(--spacing-2xs) * -1 - var(--submenu-offset));
10
- margin-right: calc(var(--spacing-xs) * -1);
11
- margin-bottom: calc(var(--spacing-2xs) * -1 + var(--submenu-offset));
12
- padding: 0 var(--spacing-xs);
13
- }
14
-
15
- .ui-select-option--md .ui-dropdown__anchor {
16
- margin-top: calc(var(--spacing-5p5) * -1 - var(--submenu-offset));
17
- margin-right: calc(var(--spacing-xs) * -1);
18
- margin-bottom: calc(var(--spacing-5p5) * -1 + var(--submenu-offset));
19
- padding: 0 var(--spacing-xs);
20
- }
21
-
22
- .ui-select-option--lg .ui-dropdown__anchor {
23
- margin-top: calc(var(--spacing-7p5) * -1 - var(--submenu-offset));
24
- margin-right: calc(var(--spacing-sm) * -1);
25
- margin-bottom: calc(var(--spacing-7p5) * -1 + var(--submenu-offset));
26
- padding: 0 var(--spacing-sm);
27
- }
28
-
29
- .expand-trigger {
30
- display: flex;
31
- align-items: center;
32
- transform: translateY(var(--submenu-offset));
33
- }
34
-
35
- .ui-select__list {
36
- display: flex;
37
- flex-direction: column;
38
- overflow-y: auto;
39
- padding: var(--spacing-2xs);
40
- gap: var(--spacing-3xs);
41
- }
42
-
43
- .ui-select__list::-webkit-scrollbar {
44
- width: 6px;
45
- }
46
-
47
- .ui-select__list::-webkit-scrollbar-thumb {
48
- background: var(--border-primary);
49
- border-radius: 3px;
50
- }
51
-
52
- .ui-select__list .select-empty {
53
- height: 100px;
54
- width: 100px;
55
- }
56
-
57
- .ui-select__list .select-empty img {
58
- width: 100%;
59
- height: 100%;
60
- object-fit: cover;
61
- }