webcake-ui-kit 1.2.0 → 1.2.2

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.
Files changed (68) hide show
  1. package/package.json +8 -2
  2. package/src/components/checkbox/Checkbox.vue +1 -1
  3. package/src/components/drawer/Drawer.vue +261 -0
  4. package/src/components/drawer/drawer.css +162 -0
  5. package/src/components/dropdown/Dropdown.vue +15 -9
  6. package/src/components/hover-card/HoverCard.vue +159 -0
  7. package/src/components/hover-card/hover-card.css +10 -0
  8. package/src/components/input-counter/InputCounter.vue +250 -0
  9. package/src/components/input-counter/input-counter.css +209 -0
  10. package/src/components/menu/Menu.vue +4 -2
  11. package/src/components/popover/Popover.vue +144 -0
  12. package/src/components/popover/popover.css +3 -0
  13. package/src/components/select/Select.vue +10 -2
  14. package/src/components/select/select.css +12 -4
  15. package/src/components/select-option/SelectOption.vue +8 -2
  16. package/src/components/table-v2/ColumnDragHandle.vue +103 -0
  17. package/src/components/table-v2/Table.vue +304 -0
  18. package/src/components/table-v2/TableColGroup.vue +55 -0
  19. package/src/components/table-v2/TableEmpty.vue +10 -0
  20. package/src/components/table-v2/TableLoading.vue +25 -0
  21. package/src/components/table-v2/TableResizeObserver.vue +91 -0
  22. package/src/components/table-v2/TableWrapper.vue +207 -0
  23. package/src/components/table-v2/body/DragPreview.vue +65 -0
  24. package/src/components/table-v2/body/MeasureCell.vue +42 -0
  25. package/src/components/table-v2/body/TableBody.vue +133 -0
  26. package/src/components/table-v2/body/TableCell.vue +114 -0
  27. package/src/components/table-v2/body/TableRow.vue +73 -0
  28. package/src/components/table-v2/composables/useColumns.js +19 -0
  29. package/src/components/table-v2/composables/useSorter.js +12 -0
  30. package/src/components/table-v2/composables/useSticky.js +31 -0
  31. package/src/components/table-v2/constants.js +2 -0
  32. package/src/components/table-v2/context/createTableContext.js +80 -0
  33. package/src/components/table-v2/header/TableHeader.vue +15 -0
  34. package/src/components/table-v2/header/TableHeaderCell.vue +89 -0
  35. package/src/components/table-v2/header/TableHeaderRow.vue +107 -0
  36. package/src/components/table-v2/style/table-body.css +0 -0
  37. package/src/components/table-v2/style/table-header.css +0 -0
  38. package/src/components/table-v2/style/table-loading.css +19 -0
  39. package/src/components/table-v2/style/table-wrapper.css +287 -0
  40. package/src/components/table-v2/style/table.css +5 -0
  41. package/src/icons/BoxShadow.vue +35 -0
  42. package/src/icons/Carousel.vue +34 -0
  43. package/src/icons/CloudSync2.vue +42 -0
  44. package/src/icons/CornerBottomLeft.vue +26 -0
  45. package/src/icons/CornerBottomRight.vue +26 -0
  46. package/src/icons/CornerTopLeft.vue +26 -0
  47. package/src/icons/CornerTopRight.vue +26 -0
  48. package/src/icons/DistanceRight.vue +30 -0
  49. package/src/icons/DistanceTop.vue +30 -0
  50. package/src/icons/DropShadow.vue +35 -0
  51. package/src/icons/Fire.vue +26 -0
  52. package/src/icons/GlobeAccess.vue +32 -0
  53. package/src/icons/InnerShadow.vue +33 -0
  54. package/src/icons/Livestream.vue +54 -0
  55. package/src/icons/PaddingBottom.vue +30 -0
  56. package/src/icons/PaddingLeft.vue +30 -0
  57. package/src/icons/PaddingRight.vue +30 -0
  58. package/src/icons/PaddingTop.vue +30 -0
  59. package/src/icons/SkewHeight.vue +32 -0
  60. package/src/icons/SkewWidth.vue +32 -0
  61. package/src/icons/SquareDashedBottomSolid.vue +26 -0
  62. package/src/icons/SquareDashedLeftSolid.vue +26 -0
  63. package/src/icons/SquareDashedRightSolid.vue +26 -0
  64. package/src/icons/StrokeLine.vue +23 -0
  65. package/src/icons/Topline.vue +26 -0
  66. package/src/icons/index.js +25 -0
  67. package/src/index.js +5 -0
  68. package/src/utils/common.js +34 -0
@@ -0,0 +1,250 @@
1
+ <template>
2
+ <span
3
+ :class="[
4
+ 'ui-input-counter',
5
+ `ui-input-counter--size-${size}`,
6
+ `ui-input-counter--round-${roundness}`,
7
+ error && 'ui-input-counter--error',
8
+ disabled && 'ui-input-counter--disabled',
9
+ $attrs.class
10
+ ]"
11
+ >
12
+ <span class="ui-input-counter__main">
13
+ <span v-if="hasPrefix" class="ui-input-counter__decoration ui-input-counter__prefix" aria-hidden="true">
14
+ <slot name="prefix"></slot>
15
+ </span>
16
+ <input
17
+ ref="input"
18
+ class="ui-input-counter__field"
19
+ type="text"
20
+ inputmode="decimal"
21
+ :style="centered ? { textAlign: 'center' } : {}"
22
+ :value="displayValue"
23
+ :placeholder="placeholder"
24
+ :disabled="disabled"
25
+ :readonly="readonly"
26
+ :name="name"
27
+ v-bind="{ ...$attrs, class: undefined }"
28
+ @input="onInput"
29
+ @focus="onFocus"
30
+ @blur="onBlur"
31
+ @keydown="onKeydown"
32
+ />
33
+ <span v-if="hasSuffix" class="ui-input-counter__decoration ui-input-counter__suffix" aria-hidden="true">
34
+ <slot name="suffix"></slot>
35
+ </span>
36
+ </span>
37
+ <span class="ui-input-counter__controls">
38
+ <button
39
+ type="button"
40
+ class="ui-input-counter__btn ui-input-counter__btn--minus"
41
+ aria-label="Decrease"
42
+ tabindex="-1"
43
+ :disabled="!canDecrement"
44
+ @mousedown.prevent
45
+ @click="decrement"
46
+ >
47
+ <WkiMinus :size="12" />
48
+ </button>
49
+ <button
50
+ type="button"
51
+ class="ui-input-counter__btn ui-input-counter__btn--add"
52
+ aria-label="Increase"
53
+ tabindex="-1"
54
+ :disabled="!canIncrement"
55
+ @mousedown.prevent
56
+ @click="increment"
57
+ >
58
+ <WkiPlus :size="12" />
59
+ </button>
60
+ </span>
61
+ </span>
62
+ </template>
63
+
64
+ <script>
65
+ import { WkiMinus, WkiPlus } from '../../icons'
66
+
67
+ export default {
68
+ name: 'InputCounter',
69
+ inheritAttrs: false,
70
+ components: {
71
+ WkiMinus,
72
+ WkiPlus
73
+ },
74
+ props: {
75
+ value: { default: null },
76
+ modelValue: { default: undefined },
77
+ min: { type: Number, default: null },
78
+ max: { type: Number, default: null },
79
+ step: { type: Number, default: 1 },
80
+ precision: { type: Number, default: null },
81
+ placeholder: { type: String, default: '' },
82
+ name: { type: String, default: '' },
83
+ disabled: { type: Boolean, default: false },
84
+ readonly: { type: Boolean, default: false },
85
+ error: { type: Boolean, default: false },
86
+ centered: { type: Boolean, default: false },
87
+ size: {
88
+ type: String,
89
+ default: 'md',
90
+ validator: v => ['xs', 'sm', 'md', 'lg'].includes(v)
91
+ },
92
+ roundness: {
93
+ type: String,
94
+ default: 'default',
95
+ validator: v => ['default', 'round'].includes(v)
96
+ }
97
+ },
98
+ emits: ['input', 'update:modelValue', 'change', 'focus', 'blur', 'pressEnter'],
99
+ data() {
100
+ return {
101
+ focused: false,
102
+ inputText: ''
103
+ }
104
+ },
105
+ computed: {
106
+ currentValue() {
107
+ return this.modelValue !== undefined ? this.modelValue : this.value
108
+ },
109
+ hasPrefix() {
110
+ return !!((this.$scopedSlots && this.$scopedSlots['prefix']) || this.$slots['prefix'])
111
+ },
112
+ hasSuffix() {
113
+ return !!((this.$scopedSlots && this.$scopedSlots['suffix']) || this.$slots['suffix'])
114
+ },
115
+ effectivePrecision() {
116
+ if (this.precision !== null) return this.precision
117
+ const s = String(this.step)
118
+ const dot = s.indexOf('.')
119
+ return dot >= 0 ? s.length - dot - 1 : 0
120
+ },
121
+ numericValue() {
122
+ const v = this.currentValue
123
+ if (v === null || v === undefined || v === '') return null
124
+ const n = Number(v)
125
+ return isNaN(n) ? null : n
126
+ },
127
+ canDecrement() {
128
+ if (this.disabled || this.readonly) return false
129
+ if (this.min === null) return true
130
+ return (this.numericValue === null ? 0 : this.numericValue) > this.min
131
+ },
132
+ canIncrement() {
133
+ if (this.disabled || this.readonly) return false
134
+ if (this.max === null) return true
135
+ return (this.numericValue === null ? 0 : this.numericValue) < this.max
136
+ },
137
+ displayValue() {
138
+ if (this.focused) return this.inputText
139
+ if (this.numericValue === null) return ''
140
+ return this.numericValue.toFixed(this.effectivePrecision)
141
+ }
142
+ },
143
+ methods: {
144
+ focus() {
145
+ if (this.$refs.input) this.$refs.input.focus()
146
+ },
147
+ blur() {
148
+ if (this.$refs.input) this.$refs.input.blur()
149
+ },
150
+ toPrecision(num) {
151
+ return parseFloat(num.toFixed(this.effectivePrecision))
152
+ },
153
+ clamp(num) {
154
+ let v = num
155
+ if (this.max !== null && v > this.max) v = this.max
156
+ if (this.min !== null && v < this.min) v = this.min
157
+ return v
158
+ },
159
+ sanitize(raw) {
160
+ let s = raw.replace(/[^\d.-]/g, '')
161
+ const hasMinus = s.charAt(0) === '-'
162
+ s = s.replace(/-/g, '')
163
+ if (hasMinus) s = '-' + s
164
+ if (this.effectivePrecision === 0) {
165
+ s = s.replace(/\./g, '')
166
+ } else {
167
+ const firstDot = s.indexOf('.')
168
+ if (firstDot >= 0) {
169
+ s = s.slice(0, firstDot + 1) + s.slice(firstDot + 1).replace(/\./g, '')
170
+ }
171
+ }
172
+ return s
173
+ },
174
+ emitValue(v) {
175
+ this.$emit('input', v)
176
+ this.$emit('update:modelValue', v)
177
+ this.$emit('change', v)
178
+ },
179
+ stepBy(direction) {
180
+ if (this.disabled || this.readonly) return
181
+ const base = this.numericValue === null ? 0 : this.numericValue
182
+ const next = this.clamp(this.toPrecision(base + direction * this.step))
183
+ this.emitValue(next)
184
+ if (this.focused) this.inputText = String(next)
185
+ },
186
+ increment() {
187
+ this.stepBy(1)
188
+ },
189
+ decrement() {
190
+ this.stepBy(-1)
191
+ },
192
+ commitInput() {
193
+ const raw = this.inputText
194
+ if (raw === '' || raw === '-') {
195
+ this.emitValue(null)
196
+ return
197
+ }
198
+ const num = parseFloat(raw)
199
+ if (!isNaN(num)) {
200
+ const committed = this.clamp(this.toPrecision(num))
201
+ this.emitValue(committed)
202
+ this.inputText = committed.toFixed(this.effectivePrecision)
203
+ }
204
+ },
205
+ onInput(e) {
206
+ const sanitized = this.sanitize(e.target.value)
207
+ if (sanitized !== e.target.value) {
208
+ const pos = Math.max(0, e.target.selectionStart - (e.target.value.length - sanitized.length))
209
+ e.target.value = sanitized
210
+ try {
211
+ e.target.setSelectionRange(pos, pos)
212
+ } catch (err) {
213
+ console.error(err)
214
+ }
215
+ }
216
+ this.inputText = sanitized
217
+ if (sanitized === '' || sanitized === '-') return
218
+ const num = parseFloat(sanitized)
219
+ if (!isNaN(num)) {
220
+ this.$emit('input', this.toPrecision(num))
221
+ this.$emit('update:modelValue', this.toPrecision(num))
222
+ }
223
+ },
224
+ onFocus(e) {
225
+ this.focused = true
226
+ this.inputText = this.numericValue === null ? '' : String(this.numericValue)
227
+ this.$emit('focus', this.currentValue, e)
228
+ },
229
+ onBlur(e) {
230
+ this.focused = false
231
+ this.commitInput()
232
+ this.$emit('blur', this.currentValue, e)
233
+ },
234
+ onKeydown(e) {
235
+ if (e.key === 'ArrowUp') {
236
+ e.preventDefault()
237
+ this.increment()
238
+ } else if (e.key === 'ArrowDown') {
239
+ e.preventDefault()
240
+ this.decrement()
241
+ } else if (e.key === 'Enter') {
242
+ this.commitInput()
243
+ this.$emit('pressEnter', this.currentValue, e)
244
+ }
245
+ }
246
+ }
247
+ }
248
+ </script>
249
+
250
+ <style src="./input-counter.css" scoped></style>
@@ -0,0 +1,209 @@
1
+ /* Shell — mirrors the vetted Input/InputNumber shell so the counter sits
2
+ consistently alongside the other form controls. */
3
+ .ui-input-counter {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ background: var(--input);
7
+ border: 1px solid var(--border-primary);
8
+ box-shadow: var(--shadow-xs);
9
+ font-family: var(--font-family-body);
10
+ color: var(--primary-fg);
11
+ transition:
12
+ border-color 0.12s ease,
13
+ box-shadow 0.12s ease;
14
+ }
15
+
16
+ /* Size — heights, paddings, gaps, typography. Right padding is trimmed vs the
17
+ plain input because the stepper buttons occupy the trailing edge. */
18
+ .ui-input-counter--size-md {
19
+ height: 36px;
20
+ gap: var(--spacing-sm);
21
+ padding: var(--spacing-7p5) var(--spacing-xs) var(--spacing-7p5) var(--spacing-sm);
22
+ font-size: var(--paragraph-small-font-size);
23
+ line-height: var(--paragraph-small-line-height);
24
+ letter-spacing: var(--paragraph-small-letter-spacing);
25
+ }
26
+
27
+ .ui-input-counter--size-lg {
28
+ height: 40px;
29
+ gap: var(--spacing-sm);
30
+ padding: var(--spacing-9p5) var(--spacing-6) var(--spacing-9p5) var(--spacing-md);
31
+ font-size: var(--paragraph-small-font-size);
32
+ line-height: var(--paragraph-small-line-height);
33
+ letter-spacing: var(--paragraph-small-letter-spacing);
34
+ }
35
+
36
+ .ui-input-counter--size-sm {
37
+ height: 32px;
38
+ gap: var(--spacing-6);
39
+ padding: var(--spacing-5p5) var(--spacing-6) var(--spacing-5p5) var(--spacing-xs);
40
+ font-size: var(--paragraph-small-font-size);
41
+ line-height: var(--paragraph-small-line-height);
42
+ letter-spacing: var(--paragraph-small-letter-spacing);
43
+ }
44
+
45
+ .ui-input-counter--size-xs {
46
+ height: 28px;
47
+ gap: var(--spacing-6);
48
+ padding: var(--spacing-2xs) var(--spacing-2xs) var(--spacing-2xs) var(--spacing-6);
49
+ font-size: var(--paragraph-mini-font-size);
50
+ line-height: var(--paragraph-mini-line-height);
51
+ letter-spacing: var(--paragraph-mini-letter-spacing);
52
+ }
53
+
54
+ .ui-input-counter--size-tiny {
55
+ height: 24px;
56
+ gap: var(--spacing-2xs);
57
+ padding: var(--spacing-2xs) var(--spacing-2xs) var(--spacing-2xs) var(--spacing-6);
58
+ font-size: var(--paragraph-mini-font-size);
59
+ line-height: var(--paragraph-mini-line-height);
60
+ letter-spacing: var(--paragraph-mini-letter-spacing);
61
+ }
62
+
63
+ /* Roundness — Mini uses the smaller default radius per Figma (rounded-xl → radius). */
64
+ .ui-input-counter--round-default {
65
+ border-radius: var(--rounded-xl);
66
+ }
67
+
68
+ .ui-input-counter--size-xs.ui-input-counter--round-default {
69
+ border-radius: var(--radius);
70
+ }
71
+
72
+ .ui-input-counter--size-tiny.ui-input-counter--round-default {
73
+ border-radius: var(--rounded-lg);
74
+ }
75
+
76
+ .ui-input-counter--round-round {
77
+ border-radius: var(--rounded-full);
78
+ }
79
+
80
+ /* Focus (interactive) — wrapper highlights when the inner field is focused. */
81
+ .ui-input-counter:focus-within {
82
+ border-color: var(--border-focus);
83
+ box-shadow: 0 0 0 3px var(--focus-ring);
84
+ }
85
+
86
+ /* Error (logical) — red border, no base shadow. */
87
+ .ui-input-counter--error {
88
+ border-color: var(--destructive-border);
89
+ box-shadow: none;
90
+ }
91
+
92
+ .ui-input-counter--error:focus-within {
93
+ border-color: var(--destructive-border);
94
+ box-shadow: 0 0 0 3px var(--focus-ring-error);
95
+ }
96
+
97
+ /* Disabled — visual only; buttons + field carry their own disabled state. */
98
+ .ui-input-counter--disabled {
99
+ opacity: 0.5;
100
+ background: var(--secondary-bg);
101
+ }
102
+
103
+ .ui-input-counter--disabled:focus-within {
104
+ border-color: var(--border-primary);
105
+ box-shadow: var(--shadow-xs);
106
+ }
107
+
108
+ /* Left group — prefix + field + suffix, grows to fill available width. */
109
+ .ui-input-counter__main {
110
+ display: inline-flex;
111
+ align-items: center;
112
+ flex: 1 1 0;
113
+ min-width: 0;
114
+ gap: var(--spacing-2xs);
115
+ overflow: hidden;
116
+ }
117
+
118
+ /* The native input — strips browser chrome and inherits typography from shell. */
119
+ .ui-input-counter__field {
120
+ flex: 1 1 0;
121
+ min-width: 0;
122
+ width: 100%;
123
+ margin: 0;
124
+ padding: 0;
125
+ background: transparent;
126
+ border: 0;
127
+ outline: 0;
128
+ color: inherit;
129
+ font: inherit;
130
+ letter-spacing: inherit;
131
+ -moz-appearance: textfield;
132
+ appearance: textfield;
133
+ }
134
+
135
+ .ui-input-counter__field::-webkit-inner-spin-button,
136
+ .ui-input-counter__field::-webkit-outer-spin-button {
137
+ -webkit-appearance: none;
138
+ appearance: none;
139
+ margin: 0;
140
+ }
141
+
142
+ .ui-input-counter__field::placeholder {
143
+ color: var(--muted-fg);
144
+ opacity: 1;
145
+ }
146
+
147
+ .ui-input-counter__field:disabled {
148
+ cursor: not-allowed;
149
+ }
150
+
151
+ /* Decoration slot wrappers — prepend/append hints, muted like the placeholder. */
152
+ .ui-input-counter__decoration {
153
+ display: inline-flex;
154
+ align-items: center;
155
+ justify-content: center;
156
+ flex-shrink: 0;
157
+ color: var(--muted-fg);
158
+ }
159
+
160
+ /* Stepper group — two segmented mini buttons pinned to the trailing edge. */
161
+ .ui-input-counter__controls {
162
+ display: inline-flex;
163
+ align-items: center;
164
+ flex-shrink: 0;
165
+ gap: var(--spacing-2xs);
166
+ }
167
+
168
+ .ui-input-counter__btn {
169
+ display: inline-flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ flex-shrink: 0;
173
+ width: 20px;
174
+ height: 20px;
175
+ padding: 0;
176
+ background: var(--input);
177
+ color: var(--secondary-fg);
178
+ border: 1px solid var(--border-primary);
179
+ border-radius: var(--rounded-lg);
180
+ cursor: pointer;
181
+ transition:
182
+ background-color 0.12s ease,
183
+ border-color 0.12s ease,
184
+ box-shadow 0.12s ease;
185
+ }
186
+
187
+ .ui-input-counter__btn > svg {
188
+ display: block;
189
+ width: 12px;
190
+ height: 12px;
191
+ }
192
+
193
+ .ui-input-counter__btn:hover {
194
+ background: var(--ghost-hover);
195
+ }
196
+
197
+ .ui-input-counter__btn:active {
198
+ background: var(--secondary-bg);
199
+ }
200
+
201
+ .ui-input-counter__btn:focus-visible {
202
+ outline: none;
203
+ box-shadow: 0 0 0 3px var(--focus-ring);
204
+ }
205
+
206
+ .ui-input-counter__btn:disabled {
207
+ opacity: 0.5;
208
+ cursor: not-allowed;
209
+ }
@@ -65,6 +65,8 @@ export default {
65
65
  offset: { type: Number, default: 4 },
66
66
  closeOnSelect: { type: Boolean, default: true },
67
67
  closeOnEsc: { type: Boolean, default: true },
68
+ closeOnTab: { type: Boolean, default: true },
69
+ autoFocus: { type: Boolean, default: true },
68
70
  disabled: { type: Boolean, default: false },
69
71
  persistent: { type: Boolean, default: false },
70
72
  anchorWidth: { type: Boolean, default: false },
@@ -196,7 +198,7 @@ export default {
196
198
  this._kmSetFloatEl(float)
197
199
  this._pmAttach(this._triggerEl, float)
198
200
  this._coAttach()
199
- this._fmFocus(float)
201
+ if (this.autoFocus) this._fmFocus(float)
200
202
  })
201
203
  },
202
204
  handleClose() {
@@ -212,7 +214,7 @@ export default {
212
214
  if (this.closeOnEsc) this.closeMenu()
213
215
  },
214
216
  _kmOnTab() {
215
- this.closeMenu()
217
+ if (this.closeOnTab) this.closeMenu()
216
218
  },
217
219
  _coOnOutside() {
218
220
  if (this.persistent) return
@@ -0,0 +1,144 @@
1
+ <template>
2
+ <Menu
3
+ :open="isOpen"
4
+ :placement="placement"
5
+ :offset="offset"
6
+ :close-on-esc="closeOnEsc"
7
+ :close-on-tab="false"
8
+ :disabled="disabled"
9
+ :persistent="persistent"
10
+ :anchor-width="anchorWidth"
11
+ :width="width"
12
+ :overlay-class-name="overlayClassName"
13
+ :overlay-style="overlayStyle"
14
+ @change="onOpenChange"
15
+ @open="$emit('open')"
16
+ @close="$emit('close')"
17
+ >
18
+ <template #trigger="{ triggerRef }">
19
+ <span
20
+ :ref="triggerRef"
21
+ class="ui-popover__anchor"
22
+ @click="triggerHasClick && toggle()"
23
+ @mouseenter="triggerHasHover && handleMouseEnter()"
24
+ @mouseleave="triggerHasHover && handleMouseLeave()"
25
+ >
26
+ <slot name="trigger" :is-open="isOpen"></slot>
27
+ </span>
28
+ </template>
29
+ <div
30
+ style="display: contents"
31
+ @mouseenter="triggerHasHover && handleMouseEnter()"
32
+ @mouseleave="triggerHasHover && handleMouseLeave()"
33
+ >
34
+ <slot :close="close" :is-open="isOpen"></slot>
35
+ </div>
36
+ </Menu>
37
+ </template>
38
+
39
+ <script>
40
+ import Menu from '../menu/Menu.vue'
41
+
42
+ const PLACEMENTS = [
43
+ 'top',
44
+ 'top-start',
45
+ 'top-end',
46
+ 'bottom',
47
+ 'bottom-start',
48
+ 'bottom-end',
49
+ 'left',
50
+ 'left-start',
51
+ 'left-end',
52
+ 'right',
53
+ 'right-start',
54
+ 'right-end'
55
+ ]
56
+
57
+ export default {
58
+ name: 'Popover',
59
+ components: { Menu },
60
+ model: { prop: 'open', event: 'change' },
61
+ props: {
62
+ open: { type: Boolean, default: false },
63
+ modelValue: { type: Boolean, default: undefined },
64
+ placement: {
65
+ type: String,
66
+ default: 'bottom-start',
67
+ validator: v => PLACEMENTS.indexOf(v) !== -1
68
+ },
69
+ offset: { type: Number, default: 8 },
70
+ // 'click', 'hover', or both
71
+ trigger: {
72
+ type: Array,
73
+ default: function () {
74
+ return ['click']
75
+ }
76
+ },
77
+ closeOnEsc: { type: Boolean, default: true },
78
+ disabled: { type: Boolean, default: false },
79
+ persistent: { type: Boolean, default: false },
80
+ anchorWidth: { type: Boolean, default: false },
81
+ width: { type: [String, Number], default: null },
82
+ overlayClassName: { type: [String, Array, Object], default: null },
83
+ overlayStyle: { type: [Object, Array], default: null }
84
+ },
85
+ emits: ['change', 'update:modelValue', 'open', 'close'],
86
+ data() {
87
+ return {
88
+ isOpen: this.modelValue !== undefined ? this.modelValue : this.open
89
+ }
90
+ },
91
+ computed: {
92
+ triggerHasClick() {
93
+ return this.trigger.indexOf('click') !== -1
94
+ },
95
+ triggerHasHover() {
96
+ return this.trigger.indexOf('hover') !== -1
97
+ }
98
+ },
99
+ watch: {
100
+ open(v) {
101
+ if (this.modelValue === undefined) this.isOpen = v
102
+ },
103
+ modelValue(v) {
104
+ if (v !== undefined) this.isOpen = v
105
+ }
106
+ },
107
+ methods: {
108
+ onOpenChange(v) {
109
+ this.isOpen = v
110
+ this.$emit('change', v)
111
+ this.$emit('update:modelValue', v)
112
+ },
113
+ toggle() {
114
+ if (this.disabled) return
115
+ this.onOpenChange(!this.isOpen)
116
+ },
117
+ close() {
118
+ this.onOpenChange(false)
119
+ },
120
+ handleMouseEnter() {
121
+ if (this.disabled) return
122
+ if (this._hoverTimer) {
123
+ clearTimeout(this._hoverTimer)
124
+ this._hoverTimer = null
125
+ }
126
+ this.onOpenChange(true)
127
+ },
128
+ handleMouseLeave() {
129
+ this._hoverTimer = setTimeout(() => {
130
+ this.onOpenChange(false)
131
+ }, 80)
132
+ }
133
+ },
134
+ beforeUnmount() {
135
+ if (this._hoverTimer) clearTimeout(this._hoverTimer)
136
+ },
137
+ // eslint-disable-next-line vue/no-deprecated-destroyed-lifecycle, vue/no-dupe-keys
138
+ beforeDestroy() {
139
+ if (this._hoverTimer) clearTimeout(this._hoverTimer)
140
+ }
141
+ }
142
+ </script>
143
+
144
+ <style src="./popover.css" scoped></style>
@@ -0,0 +1,3 @@
1
+ .ui-popover__anchor {
2
+ display: inline-block;
3
+ }
@@ -97,7 +97,15 @@
97
97
  />
98
98
  </slot>
99
99
  <slot v-if="showEmpty" name="empty">
100
- <Empty description="No data" />
100
+ <Empty description="No data">
101
+ <template #media>
102
+ <div class="select-empty">
103
+ <img
104
+ src="https://content.pancake.vn/web-media-262/2e/2a/0c/96/c2b58ae9f06c4dc2a3b83016e56d5b4132ff40637862ba23435e43a0-w:224-h:224-l:3927-t:image/png.png"
105
+ />
106
+ </div>
107
+ </template>
108
+ </Empty>
101
109
  </slot>
102
110
  </div>
103
111
  </Menu>
@@ -138,7 +146,7 @@ export default {
138
146
  optionSize: {
139
147
  type: String,
140
148
  default: 'md',
141
- validator: v => ['xs', 'md', 'lg'].indexOf(v) !== -1
149
+ validator: v => ['sm', 'md', 'lg'].indexOf(v) !== -1
142
150
  },
143
151
  value: {
144
152
  type: [String, Array],
@@ -213,8 +213,9 @@
213
213
  font-size: 14px;
214
214
  }
215
215
 
216
- .ui-select--multiple.ui-select--xs .ui-select__search::placeholder {
217
- font-size: 12px;
216
+ .ui-select--xs .ui-select__search::placeholder {
217
+ font-size: var(--paragraph-mini-font-size);
218
+ line-height: var(--paragraph-mini-line-height);
218
219
  }
219
220
 
220
221
  .ui-select__search--single {
@@ -247,6 +248,13 @@
247
248
  border-radius: 3px;
248
249
  }
249
250
 
250
- .ui-select__empty {
251
- padding: var(--spacing-sm) var(--spacing-xs);
251
+ .ui-select__list ::v-deep .select-empty {
252
+ height: 100px;
253
+ width: 100px;
254
+ }
255
+
256
+ .ui-select__list ::v-deep .select-empty img {
257
+ width: 100%;
258
+ height: 100%;
259
+ object-fit: cover;
252
260
  }