@veritree/ui 0.19.2-9 → 0.20.0-0

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 (65) hide show
  1. package/index.js +64 -68
  2. package/mixins/floating-ui-content.js +81 -0
  3. package/mixins/floating-ui-item.js +266 -0
  4. package/mixins/floating-ui.js +67 -0
  5. package/mixins/form-control-icon.js +53 -0
  6. package/mixins/form-control.js +71 -0
  7. package/nuxt.js +3 -0
  8. package/package.json +7 -3
  9. package/src/components/Alert/VTAlert.vue +55 -14
  10. package/src/components/Avatar/VTAvatar.vue +32 -29
  11. package/src/components/Button/VTButton.vue +9 -5
  12. package/src/components/Checkbox/VTCheckbox.vue +134 -0
  13. package/src/components/Checkbox/VTCheckboxLabel.vue +3 -0
  14. package/src/components/Checkbox/VTCheckboxText.vue +20 -0
  15. package/src/components/Dialog/VTDialog.vue +16 -21
  16. package/src/components/Dialog/VTDialogClose.vue +13 -19
  17. package/src/components/Dialog/VTDialogContent.vue +17 -12
  18. package/src/components/Dialog/VTDialogFooter.vue +9 -14
  19. package/src/components/Dialog/VTDialogHeader.vue +11 -13
  20. package/src/components/Dialog/VTDialogMain.vue +6 -13
  21. package/src/components/Dialog/VTDialogOverlay.vue +9 -13
  22. package/src/components/Dialog/VTDialogTitle.vue +8 -5
  23. package/src/components/Disclosure/VTDisclosureContent.vue +1 -1
  24. package/src/components/Disclosure/VTDisclosureDetails.vue +1 -1
  25. package/src/components/Disclosure/VTDisclosureHeader.vue +2 -2
  26. package/src/components/Disclosure/VTDisclosureIcon.vue +1 -1
  27. package/src/components/DropdownMenu/VTDropdownMenu.vue +42 -25
  28. package/src/components/DropdownMenu/VTDropdownMenuContent.vue +29 -64
  29. package/src/components/DropdownMenu/VTDropdownMenuDivider.vue +7 -13
  30. package/src/components/DropdownMenu/VTDropdownMenuItem.vue +11 -135
  31. package/src/components/DropdownMenu/VTDropdownMenuLabel.vue +3 -12
  32. package/src/components/DropdownMenu/VTDropdownMenuTrigger.vue +95 -115
  33. package/src/components/Form/VTFormFeedback.vue +39 -22
  34. package/src/components/Form/VTFormGroup.vue +5 -7
  35. package/src/components/Form/VTFormLabel.vue +22 -0
  36. package/src/components/Form/VTFormRow.vue +5 -0
  37. package/src/components/Form/VTInput.vue +40 -0
  38. package/src/components/Form/VTInputIcon.vue +35 -0
  39. package/src/components/Form/VTInputPassword.vue +55 -0
  40. package/src/components/Form/VTTextarea.vue +22 -0
  41. package/src/components/Listbox/VTListbox.vue +119 -47
  42. package/src/components/Listbox/VTListboxContent.vue +18 -114
  43. package/src/components/Listbox/VTListboxItem.vue +112 -163
  44. package/src/components/Listbox/VTListboxLabel.vue +3 -14
  45. package/src/components/Listbox/VTListboxList.vue +8 -38
  46. package/src/components/Listbox/VTListboxSearch.vue +75 -67
  47. package/src/components/Listbox/VTListboxTrigger.vue +73 -84
  48. package/src/components/Popover/VTPopover.vue +39 -26
  49. package/src/components/Popover/VTPopoverContent.vue +23 -58
  50. package/src/components/Popover/VTPopoverDivider.vue +4 -11
  51. package/src/components/Popover/VTPopoverItem.vue +13 -10
  52. package/src/components/Popover/VTPopoverTrigger.vue +120 -20
  53. package/src/components/ProgressBar/VTProgressBar.vue +21 -3
  54. package/src/components/Skeleton/VTSkeleton.vue +11 -0
  55. package/src/components/Skeleton/VTSkeletonItem.vue +9 -0
  56. package/src/components/Tabs/VTTab.vue +14 -12
  57. package/src/components/Tabs/VTTabPanel.vue +1 -1
  58. package/src/components/Tooltip/VTTooltip.vue +65 -0
  59. package/src/components/Tooltip/VTTooltipContent.vue +59 -0
  60. package/src/components/Tooltip/VTTooltipTrigger.vue +98 -0
  61. package/src/components/Utils/FloatingUi.vue +93 -0
  62. package/src/components/Input/VTInput.vue +0 -82
  63. package/src/components/Input/VTInputDate.vue +0 -36
  64. package/src/components/Input/VTInputFile.vue +0 -60
  65. package/src/components/Input/VTInputUpload.vue +0 -54
@@ -3,20 +3,14 @@
3
3
  :is="as"
4
4
  :id="id"
5
5
  :to="to"
6
- :class="{
7
- MenuItem: headless,
8
- '-mx-3 flex min-w-max items-center gap-3 px-3 py-2 text-inherit no-underline':
9
- !headless,
10
- 'hover:bg-secondary-200/10': !dark,
11
- 'text-white hover:bg-fd-450 focus:bg-fd-450': dark,
12
- 'pointer-events-none opacity-75': disabled,
13
- }"
14
- :tabindex="tabIndex"
6
+ :class="classComputed"
15
7
  :aria-disabled="disabled"
8
+ :tabindex="tabIndex"
9
+ class="-mx-3"
16
10
  role="menuitem"
17
11
  @click.stop.prevent="onClick"
18
- @keydown.down.prevent="focusPreviousItem"
19
- @keydown.up.prevent="focusNextItem"
12
+ @keydown.down.prevent="focusNextItem"
13
+ @keydown.up.prevent="focusPreviousItem"
20
14
  @keydown.home.prevent="focusFirstItem"
21
15
  @keydown.end.prevent="focusLastItem"
22
16
  @keydown.esc.prevent="onKeyEsc"
@@ -28,11 +22,13 @@
28
22
  </template>
29
23
 
30
24
  <script>
31
- import { genId } from '../../utils/ids';
25
+ import { floatingUiItemMixin } from '../../../mixins/floating-ui-item';
32
26
 
33
27
  export default {
34
28
  name: 'VTDropdownMenuItem',
35
29
 
30
+ mixins: [floatingUiItemMixin],
31
+
36
32
  inject: ['apiDropdownMenu'],
37
33
 
38
34
  props: {
@@ -44,138 +40,18 @@ export default {
44
40
  type: String,
45
41
  default: null,
46
42
  },
47
- disabled: {
48
- type: Boolean,
49
- default: false,
50
- },
51
43
  },
52
44
 
53
45
  data() {
54
46
  return {
55
- id: `menuitem-${genId()}`,
56
- index: null,
57
- tabIndex: 0,
47
+ apiInjected: this.apiDropdownMenu,
48
+ componentName: 'dropdown-menu-item',
58
49
  };
59
50
  },
60
51
 
61
52
  computed: {
62
- dark() {
63
- return this.apiDropdownMenu().isDark;
64
- },
65
-
66
- headless() {
67
- return this.apiDropdownMenu().isHeadless;
68
- },
69
-
70
53
  as() {
71
- return this.href
72
- ? 'a'
73
- : this.to
74
- ? resolveComponent('NuxtLink')
75
- : 'button';
76
- },
77
-
78
- items() {
79
- return this.apiDropdownMenu().items;
80
- },
81
-
82
- el() {
83
- return this.$el;
84
- },
85
-
86
- trigger() {
87
- return this.apiDropdownMenu().trigger;
88
- },
89
-
90
- content() {
91
- return this.apiDropdownMenu().content;
92
- },
93
- },
94
-
95
- mounted() {
96
- const item = {
97
- focus: this.focus,
98
- el: this.el,
99
- };
100
-
101
- this.apiDropdownMenu().registerItem(item);
102
-
103
- this.index = this.items.length - 1;
104
- },
105
-
106
- methods: {
107
- focus() {
108
- if (!this.el) return;
109
-
110
- this.tabIndex = -1;
111
- this.el.focus();
112
- },
113
-
114
- focusFirstItem() {
115
- this.setFocusToItem(0);
116
- },
117
-
118
- focusLastItem() {
119
- this.setFocusToItem(this.items.length - 1);
120
- },
121
-
122
- /**
123
- * Focus the previous item in the menu.
124
- * If is the first item, jump to the last item.
125
- */
126
- focusPreviousItem() {
127
- const isLast = this.index === this.items.length - 1;
128
- const goToIndex = isLast ? 0 : this.index + 1;
129
-
130
- this.setFocusToItem(goToIndex);
131
- },
132
-
133
- /**
134
- * Focus the next item in the menu.
135
- * If is the last item, jump to the first item.
136
- */
137
- focusNextItem() {
138
- const isFirst = this.index === 0;
139
- const goToIndex = isFirst ? this.items.length - 1 : this.index - 1;
140
-
141
- this.setFocusToItem(goToIndex);
142
- },
143
-
144
- /**
145
- * Focus item by remove its tabindex and calling
146
- * focus to the element.
147
- *
148
- * @param {Number, String} goToIndex
149
- */
150
- setFocusToItem(goToIndex) {
151
- this.tabIndex = 0;
152
- this.items[goToIndex].focus();
153
- },
154
-
155
- /**
156
- * Hides content/menu and focus on trigger
157
- */
158
- leaveMenu() {
159
- if (this.content) this.content.hide();
160
- if (this.trigger) this.trigger.focus();
161
- },
162
-
163
- onKeyEsc() {
164
- this.leaveMenu();
165
- },
166
-
167
- onClick(ev) {
168
- if (this.disabled) return;
169
-
170
- // Nuxtlink doesn't understand enter as a click
171
- // so, we need to force it here
172
- if (ev.key === 'Enter') {
173
- ev.target.click();
174
- return;
175
- }
176
-
177
- this.$emit('click');
178
- this.$nextTick(() => this.leaveMenu());
54
+ return this.href ? 'a' : this.to ? 'NuxtLink' : 'button';
179
55
  },
180
56
  },
181
57
  };
@@ -1,11 +1,6 @@
1
1
  <template>
2
2
  <span
3
- :class="{
4
- MenuLabel: headless,
5
- 'mb-2 block text-xs uppercase': !headless,
6
- 'text-inherit': !dark,
7
- 'text-white': dark,
8
- }"
3
+ :class="[headless ? 'dropdown-menu-label' : 'mb-2 block text-xs uppercase']"
9
4
  >
10
5
  <slot></slot>
11
6
  </span>
@@ -15,15 +10,11 @@
15
10
  export default {
16
11
  name: 'VTDropdownMenuLabel',
17
12
 
18
- inject: ['api'],
13
+ inject: ['apiDropdownMenu'],
19
14
 
20
15
  computed: {
21
- dark() {
22
- return this.api().isDark;
23
- },
24
-
25
16
  headless() {
26
- return this.api().isHeadless;
17
+ return this.apiDropdownMenu().isHeadless;
27
18
  },
28
19
  },
29
20
  };
@@ -1,11 +1,12 @@
1
1
  <template>
2
2
  <div
3
+ :id="id"
3
4
  :aria-haspopup="hasPopup"
4
5
  :aria-expanded="expanded"
5
6
  :aria-controls="controls"
6
- @keydown.down.prevent="onKeyArrowDown"
7
- @keydown.up.prevent="onKeyArrowUp"
8
- @keydown.esc.prevent="onKeyesc"
7
+ @keydown.down.prevent="onKeyDownOrUp"
8
+ @keydown.up.prevent="onKeyDownOrUp"
9
+ @keydown.esc.stop="onKeyEsc"
9
10
  >
10
11
  <slot></slot>
11
12
  </div>
@@ -19,17 +20,20 @@ export default {
19
20
 
20
21
  data() {
21
22
  return {
22
- trigger: null,
23
23
  expanded: false,
24
- controls: null,
25
24
  hasPopup: false,
25
+ controls: null,
26
+ trigger: null,
26
27
  };
27
28
  },
28
29
 
29
30
  computed: {
30
- // gets slot element
31
- content() {
32
- return this.apiDropdownMenu().content;
31
+ id() {
32
+ return `dropdown-menu-trigger-${this.apiDropdownMenu().id}`;
33
+ },
34
+
35
+ componentContent() {
36
+ return this.apiDropdownMenu().componentContent;
33
37
  },
34
38
 
35
39
  items() {
@@ -45,14 +49,28 @@ export default {
45
49
  },
46
50
  },
47
51
 
52
+ watch: {
53
+ expanded() {
54
+ this.toggleAriaHasPopup();
55
+ },
56
+ },
57
+
48
58
  mounted() {
49
- this.apiDropdownMenu().registerTrigger(this);
59
+ const trigger = {
60
+ id: this.id,
61
+ el: this.$el,
62
+ cancel: this.cancel,
63
+ focus: this.focus,
64
+ };
65
+
66
+ this.apiDropdownMenu().registerTrigger(trigger);
67
+
50
68
  this.setTrigger();
51
69
  this.addTriggerEvents();
52
70
  },
53
71
 
54
72
  destroyed() {
55
- this.trigger.removeEventListener('click', this.onClick());
73
+ this.trigger.removeEventListener('click', this.onClick);
56
74
  },
57
75
 
58
76
  methods: {
@@ -60,140 +78,102 @@ export default {
60
78
  this.trigger = this.$el.querySelector(':first-child');
61
79
  },
62
80
 
63
- addTriggerEvents() {
64
- this.trigger.addEventListener('click', (e) => {
65
- if (this.expanded) {
66
- this.onClick();
67
- return;
68
- }
69
-
70
- // delay stop propagation to close other visible
71
- // dropdowns and delay click event to control
72
- // this dropdown visibility
73
- setTimeout(() => e.stopImmediatePropagation(), 50);
74
- setTimeout(() => this.onClick(), 100);
75
- });
76
- },
77
-
78
81
  /**
79
- * Shows content/menu if not already visible
82
+ * Add event listener to slot element
83
+ *
84
+ * The click event has to be added to the slot child element
85
+ * since we are not setting the onclick on the component
86
+ * itself.
87
+ *
88
+ * Slot must have only one child element. It avoids
89
+ * errors related to adding the event listener.
80
90
  */
81
- showContent() {
82
- if (!this.expanded) {
83
- this.toggleExpanded();
84
- this.content.show();
85
- }
91
+ addTriggerEvents() {
92
+ this.trigger.addEventListener('click', this.onClick);
86
93
  },
87
94
 
88
- /**
89
- * Focus slot element if it exists and toggle expanded
90
- */
91
- focus() {
92
- if (!this.trigger) return;
95
+ init(e) {
96
+ if (!this.componentContent) {
97
+ return;
98
+ }
93
99
 
94
- this.trigger.focus();
95
- this.toggleExpanded();
96
- },
100
+ if (this.expanded) {
101
+ this.cancel();
102
+ return;
103
+ }
97
104
 
98
- /**
99
- * Toggles aria expanded attribute/state
100
- */
101
- toggleExpanded() {
102
- this.expanded = !this.expanded;
103
- },
105
+ this.expanded = true;
104
106
 
105
- /**
106
- * Sets aria expanded attribute/state to false
107
- */
108
- hideExpanded() {
109
- this.expanded = false;
107
+ // delay stop propagation to close other visible
108
+ // dropdowns and delay click event to control
109
+ // this dropdown visibility
110
+ setTimeout(() => e.stopImmediatePropagation(), 50);
111
+ setTimeout(() => this.showComponentContent(), 100);
110
112
  },
111
113
 
112
- /**
113
- * Toggles aria popup/controls attribute/state
114
- */
115
- toggleHasPopup() {
116
- if (!this.content) return;
117
-
118
- this.hasPopup = !this.hasPopup;
119
-
120
- if (!this.hasPopup) {
121
- this.controls = null;
114
+ cancel() {
115
+ if (!this.componentContent) {
122
116
  return;
123
117
  }
124
118
 
125
- this.controls = this.content.id;
126
- },
127
-
128
- /**
129
- * On click, do the following:
130
- *
131
- * 1. Toggle aria expanded attribute/state
132
- * 2. Open the menu if it's closed
133
- * 3. Close the menu if it's open
134
- */
135
- onClick() {
136
- if (!this.content) return;
119
+ this.expanded = false;
137
120
 
138
- this.toggleExpanded();
121
+ this.hideComponentContent();
122
+ },
139
123
 
140
- this.$nextTick(() => {
141
- if (this.expanded) this.content.show();
142
- else this.content.hide();
143
- });
124
+ focus() {
125
+ if (this.trigger) this.trigger.focus();
144
126
  },
145
127
 
146
- /**
147
- * 1. Set aria expanded attribute/state to false
148
- * 2. Close the menu
149
- */
150
- hide() {
151
- this.hideExpanded();
128
+ showComponentContent() {
129
+ this.componentContent.show();
130
+ },
152
131
 
153
- this.$nextTick(() => {
154
- this.content.hide();
155
- });
132
+ hideComponentContent() {
133
+ this.componentContent.hide();
156
134
  },
157
135
 
158
- /**
159
- * On key arrow down, do the following:
160
- *
161
- * 1. if the menu is not expanded, expand it and focus the first menu item
162
- * 2. if the menu is expanded, focus the first menu item
163
- */
164
- onKeyArrowDown() {
165
- if (!this.content) return;
136
+ toggleAriaHasPopup() {
137
+ if (this.expanded) {
138
+ this.hasPopup = this.componentContent !== null;
139
+ this.controls = this.hasPopup ? this.componentContent.id : null;
166
140
 
167
- this.showContent();
141
+ return;
142
+ }
168
143
 
169
- this.$nextTick(() => {
170
- this.firstMenuItem.focus();
171
- });
144
+ this.hasPopup = null;
145
+ this.controls = null;
172
146
  },
173
147
 
174
- /**
175
- * On key arrow up, do the following:
176
- *
177
- * 1. if the menu is not expanded, expand it and focus the last menu item
178
- * 2. if the menu is expanded, focus the last menu item
179
- */
180
- onKeyArrowUp() {
181
- if (!this.content) return;
148
+ onClick(e) {
149
+ this.init(e);
150
+ },
182
151
 
183
- this.showContent();
152
+ onKeyDownOrUp(e) {
153
+ if (!this.expanded) {
154
+ this.$el.click(e);
155
+ }
184
156
 
157
+ const keyCode = e.code;
158
+ const listItemPosition =
159
+ keyCode === 'ArrowDown'
160
+ ? 'firstMenuItem'
161
+ : keyCode === 'ArrowUp'
162
+ ? 'lastMenuItem'
163
+ : null;
164
+
165
+ // settimeout here is delaying the focusing the element
166
+ // since it is not rendered yet. All items will only
167
+ // be available when the content is fully visible.
185
168
  this.$nextTick(() => {
186
- this.lastMenuItem.focus();
169
+ setTimeout(() => this[listItemPosition].focus(), 100);
187
170
  });
188
171
  },
189
172
 
190
- onKeyesc() {
191
- if (!this.content) return;
192
-
193
- if (this.expanded) {
194
- this.toggleExpanded();
195
- this.content.hide();
196
- }
173
+ // change it to a better name or move the methods inside to another function
174
+ onKeyEsc() {
175
+ this.cancel();
176
+ this.focus();
197
177
  },
198
178
  },
199
179
  };
@@ -1,7 +1,33 @@
1
1
  <template>
2
- <div class="form-feedback" :class="classes">
3
- <component :is="icon" v-if="showIcon" />
4
- <span class="text-gray-500"><slot></slot></span>
2
+ <div
3
+ :class="[
4
+ headless ? 'form-feedback' : 'mt-1 flex items-baseline gap-2',
5
+ // variant styles
6
+ headless ? `form-feedback--${variant}` : null,
7
+ ]"
8
+ >
9
+ <component
10
+ v-if="showIcon"
11
+ :is="icon"
12
+ :class="[
13
+ headless ? 'form-feedback__icon' : 'relative top-1 h-4 w-4 shrink-0',
14
+ // variant styles
15
+ headless
16
+ ? `form-feedback__icon--${variant}`
17
+ : isError
18
+ ? 'text-error-500'
19
+ : null,
20
+ ]"
21
+ />
22
+ <span
23
+ :class="[
24
+ headless
25
+ ? 'form-feedback--text'
26
+ : 'text-sm lowercase text-gray-500 first-letter:uppercase',
27
+ ]"
28
+ >
29
+ <slot />
30
+ </span>
5
31
  </div>
6
32
  </template>
7
33
 
@@ -17,32 +43,23 @@ export default {
17
43
  },
18
44
 
19
45
  props: {
20
- variant: {
21
- type: [String, Object],
22
- default: '',
23
- validator(value) {
24
- if (value === '' || typeof value === 'object') {
25
- return true;
26
- }
27
-
28
- return ['success', 'warning', 'error'].includes(value);
29
- },
30
- },
31
46
  hideIcon: {
32
47
  type: Boolean,
33
48
  default: false,
34
49
  },
50
+ headless: {
51
+ type: Boolean,
52
+ default: false,
53
+ },
54
+ variant: {
55
+ type: [String, Object],
56
+ default: '',
57
+ },
35
58
  },
36
59
 
37
60
  computed: {
38
- classes() {
39
- const classes = {};
40
-
41
- if (this.variant) {
42
- classes[`form-feedback--${this.variant}`] = true;
43
- }
44
-
45
- return classes;
61
+ isError() {
62
+ return this.variant === 'error';
46
63
  },
47
64
 
48
65
  icon() {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <component :is="as" :class="classes" class="form-group">
2
+ <component :is="as" :class="[headless ? 'form-control' : 'mt-3']">
3
3
  <slot></slot>
4
4
  </component>
5
5
  </template>
@@ -11,12 +11,10 @@ export default {
11
11
  type: String,
12
12
  default: 'div',
13
13
  },
14
- },
15
-
16
- data() {
17
- return {
18
- classes: {},
19
- };
14
+ headless: {
15
+ type: Boolean,
16
+ default: false,
17
+ },
20
18
  },
21
19
  };
22
20
  </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <label
3
+ :class="[
4
+ headless
5
+ ? 'form-label'
6
+ : 'mb-1 flex justify-between gap-3 text-sm font-semibold',
7
+ ]"
8
+ >
9
+ <slot />
10
+ </label>
11
+ </template>
12
+
13
+ <script>
14
+ export default {
15
+ props: {
16
+ headless: {
17
+ type: Boolean,
18
+ default: false,
19
+ },
20
+ },
21
+ };
22
+ </script>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="grid grid-cols-1 md:grid-cols-2 md:gap-4">
3
+ <slot />
4
+ </div>
5
+ </template>
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <input
3
+ :class="classComputed"
4
+ :value="modelValue"
5
+ :disabled="disabled"
6
+ v-bind="attrsComputed"
7
+ />
8
+ </template>
9
+
10
+ <script>
11
+ import {
12
+ formControlMixin,
13
+ formControlStyleMixin,
14
+ } from '../../../mixins/form-control';
15
+ export default {
16
+ mixins: [formControlMixin, formControlStyleMixin],
17
+ data() {
18
+ return {
19
+ name: 'input',
20
+ };
21
+ },
22
+ };
23
+ </script>
24
+
25
+ <style scoped>
26
+ /* input[type='date']::-webkit-inner-spin-button,
27
+ input[type='date']::-webkit-calendar-picker-indicator {
28
+ position: absolute;
29
+ opacity: 0;
30
+ } */
31
+ input[type='number'] {
32
+ appearance: textfield;
33
+ }
34
+
35
+ input[type='number']::-webkit-inner-spin-button,
36
+ input[type='number']::-webkit-outer-spin-button {
37
+ appearance: none;
38
+ -webkit-appearance: none;
39
+ }
40
+ </style>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <div :class="classComputedWrapper">
3
+ <input
4
+ :class="classComputed"
5
+ :value="value"
6
+ :disabled="disabled"
7
+ v-bind="attrsComputed"
8
+ />
9
+ <div :class="classComputedWrapperIcon">
10
+ <component :is="icon" class="h-5 w-5" />
11
+ </div>
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+ import { formControlIconMixin } from '../../../mixins/form-control-icon';
17
+
18
+ export default {
19
+ name: 'VTInputIcon',
20
+
21
+ mixins: [formControlIconMixin],
22
+
23
+ props: {
24
+ icon: {
25
+ type: String,
26
+ default: null,
27
+ required: true,
28
+ },
29
+ iconPlacement: {
30
+ type: String,
31
+ default: 'left',
32
+ },
33
+ },
34
+ };
35
+ </script>