@veritree/ui 0.19.2-2 → 0.19.2-21

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 (62) hide show
  1. package/mixins/floating-ui-content.js +81 -0
  2. package/mixins/floating-ui-item.js +266 -0
  3. package/mixins/floating-ui.js +67 -0
  4. package/mixins/form-control-icon.js +53 -0
  5. package/mixins/form-control.js +73 -0
  6. package/package.json +7 -3
  7. package/src/components/Avatar/VTAvatar.vue +32 -29
  8. package/src/components/Button/VTButton.vue +9 -5
  9. package/src/components/Dialog/VTDialog.vue +6 -11
  10. package/src/components/Dialog/VTDialogClose.vue +9 -9
  11. package/src/components/Dialog/VTDialogContent.vue +9 -9
  12. package/src/components/Dialog/VTDialogFooter.vue +5 -5
  13. package/src/components/Dialog/VTDialogHeader.vue +8 -8
  14. package/src/components/Dialog/VTDialogMain.vue +8 -8
  15. package/src/components/Dialog/VTDialogOverlay.vue +8 -8
  16. package/src/components/Dialog/VTDialogTitle.vue +4 -4
  17. package/src/components/Disclosure/VTDisclosureContent.vue +1 -1
  18. package/src/components/Disclosure/VTDisclosureDetails.vue +2 -2
  19. package/src/components/Disclosure/VTDisclosureHeader.vue +1 -1
  20. package/src/components/Disclosure/VTDisclosureIcon.vue +1 -1
  21. package/src/components/Drawer/VTDrawer.vue +14 -16
  22. package/src/components/Drawer/VTDrawerClose.vue +9 -9
  23. package/src/components/Drawer/VTDrawerContent.vue +8 -8
  24. package/src/components/Drawer/VTDrawerFooter.vue +3 -3
  25. package/src/components/Drawer/VTDrawerHeader.vue +4 -4
  26. package/src/components/Drawer/VTDrawerMain.vue +5 -5
  27. package/src/components/Drawer/VTDrawerOverlay.vue +6 -6
  28. package/src/components/Drawer/VTDrawerTitle.vue +5 -5
  29. package/src/components/DropdownMenu/VTDropdownMenu.vue +27 -29
  30. package/src/components/DropdownMenu/VTDropdownMenuContent.vue +27 -70
  31. package/src/components/DropdownMenu/VTDropdownMenuDivider.vue +8 -5
  32. package/src/components/DropdownMenu/VTDropdownMenuItem.vue +14 -123
  33. package/src/components/DropdownMenu/VTDropdownMenuLabel.vue +3 -3
  34. package/src/components/DropdownMenu/VTDropdownMenuTrigger.vue +96 -121
  35. package/src/components/Form/VTFormFeedback.vue +33 -22
  36. package/src/components/Form/VTFormGroup.vue +5 -7
  37. package/src/components/Form/VTFormLabel.vue +22 -0
  38. package/src/components/Form/VTFormRow.vue +5 -0
  39. package/src/components/Form/VTInput.vue +40 -0
  40. package/src/components/Form/VTInputIcon.vue +35 -0
  41. package/src/components/Form/VTInputPassword.vue +55 -0
  42. package/src/components/Form/VTTextarea.vue +22 -0
  43. package/src/components/Listbox/VTListbox.vue +122 -50
  44. package/src/components/Listbox/VTListboxContent.vue +20 -116
  45. package/src/components/Listbox/VTListboxItem.vue +116 -166
  46. package/src/components/Listbox/VTListboxLabel.vue +3 -14
  47. package/src/components/Listbox/VTListboxList.vue +10 -40
  48. package/src/components/Listbox/VTListboxSearch.vue +76 -68
  49. package/src/components/Listbox/VTListboxTrigger.vue +75 -86
  50. package/src/components/Popover/VTPopover.vue +24 -30
  51. package/src/components/Popover/VTPopoverContent.vue +24 -59
  52. package/src/components/Popover/VTPopoverDivider.vue +4 -11
  53. package/src/components/Popover/VTPopoverItem.vue +21 -14
  54. package/src/components/Popover/VTPopoverTrigger.vue +126 -21
  55. package/src/components/Tabs/VTTab.vue +10 -11
  56. package/src/components/Tabs/VTTabGroup.vue +9 -7
  57. package/src/components/Tabs/VTTabPanel.vue +4 -5
  58. package/src/components/Transitions/FadeInOut.vue +2 -2
  59. package/src/components/Utils/FloatingUi.vue +87 -0
  60. package/package-lock.json +0 -13
  61. package/src/components/Modal/VTModal.vue +0 -69
  62. package/src/utils/genId.js +0 -13
@@ -0,0 +1,81 @@
1
+ import FloatingUi from '../src/components/Utils/FloatingUi.vue';
2
+
3
+ export const floatingUiContentMixin = {
4
+ components: {
5
+ FloatingUi,
6
+ },
7
+
8
+ props: {
9
+ headless: {
10
+ type: Boolean,
11
+ default: false,
12
+ },
13
+ },
14
+
15
+ data() {
16
+ return {
17
+ el: null,
18
+ visible: false,
19
+ };
20
+ },
21
+
22
+ mounted() {
23
+ document.addEventListener('click', this.onDocumentClick);
24
+ },
25
+
26
+ destroyed() {
27
+ document.removeEventListener('click', this.onDocumentClick);
28
+ },
29
+
30
+ methods: {
31
+ show() {
32
+ if (this.visible) {
33
+ return;
34
+ }
35
+
36
+ this.visible = true;
37
+
38
+ this.$nextTick(() => {
39
+ this.component.setActive();
40
+
41
+ setTimeout(() => {
42
+ this.el = document.getElementById(this.id);
43
+ this.$emit('shown');
44
+ }, 100);
45
+ });
46
+ },
47
+
48
+ hide() {
49
+ if (!this.visible) {
50
+ return;
51
+ }
52
+
53
+ this.visible = false;
54
+
55
+ this.$nextTick(() => {
56
+ this.component.clearActive();
57
+
58
+ setTimeout(() => {
59
+ this.el = document.getElementById(this.id);
60
+ this.$emit('hidden');
61
+ }, 100);
62
+ });
63
+ },
64
+
65
+ onDocumentClick(e) {
66
+ if (!e) {
67
+ return;
68
+ }
69
+
70
+ e.stopPropagation();
71
+
72
+ if (this.visible && !this.el.contains(e.target)) {
73
+ this.componentTrigger.cancel();
74
+ }
75
+ },
76
+
77
+ setActiveDescedant(id) {
78
+ this.activeDescedant = id;
79
+ },
80
+ },
81
+ }
@@ -0,0 +1,266 @@
1
+ export const floatingUiItemMixin = {
2
+ props: {
3
+ headless: {
4
+ type: Boolean,
5
+ default: false,
6
+ },
7
+ disabled: {
8
+ type: Boolean,
9
+ default: false,
10
+ },
11
+ },
12
+
13
+ computed: {
14
+ id() {
15
+ return `${this.componentName}-${this.apiInjected().id}-${this.index}`;
16
+ },
17
+
18
+ items() {
19
+ return this.apiInjected().items;
20
+ },
21
+
22
+ componentContent() {
23
+ return this.apiInjected().componentContent;
24
+ },
25
+
26
+ componentTrigger() {
27
+ return this.apiInjected().componentTrigger;
28
+ },
29
+
30
+ classComputed() {
31
+ return [
32
+ // default styles
33
+ this.headless
34
+ ? `${this.componentName}`
35
+ : 'relative z-10 flex items-center gap-2 px-3 py-2 text-inherit no-underline cursor-pointer',
36
+ // disabled state styles
37
+ this.headless
38
+ ? this.disabled
39
+ ? `${this.componentName}--disabled`
40
+ : null
41
+ : this.disabled
42
+ ? 'pointer-events-none opacity-75'
43
+ : null,
44
+ // selected state styles
45
+ this.headless
46
+ ? this.selected
47
+ ? `${this.componentName}--selected`
48
+ : null
49
+ : this.selected
50
+ ? 'bg-gray-200'
51
+ : null,
52
+ ];
53
+ },
54
+ },
55
+
56
+ data() {
57
+ return {
58
+ index: null,
59
+ selected: false,
60
+ tabIndex: 0,
61
+ eventType: null,
62
+ };
63
+ },
64
+
65
+ watch: {
66
+ selected(newValue) {
67
+ if (!newValue || !this.componentContent) {
68
+ return;
69
+ }
70
+
71
+ this.componentContent.setActiveDescedant(this.id);
72
+
73
+ // do not scroll into view if it's a mouse event
74
+ if (this.eventType && this.eventType.includes('mouse')) {
75
+ return;
76
+ }
77
+
78
+ this.$el.scrollIntoView({ block: 'nearest' });
79
+ },
80
+ },
81
+
82
+ mounted() {
83
+ this.init();
84
+ this.addMouseEventListeners();
85
+ },
86
+
87
+ beforeUnmount() {
88
+ this.apiInjected().unregisterItem(this.id);
89
+ this.removeMouseEventListeners();
90
+ },
91
+
92
+ methods: {
93
+ init() {
94
+ this.index = this.items.length;
95
+
96
+ const item = {
97
+ id: this.id,
98
+ text: '',
99
+ select: this.select,
100
+ unselect: this.unselect,
101
+ isSelected: this.isSelected,
102
+ focus: this.focus,
103
+ onClick: this.onClick,
104
+ };
105
+
106
+ this.apiInjected().registerItem(item);
107
+ },
108
+
109
+ /**
110
+ * mousemove and mouseleave events will be attached to
111
+ * the element here instead of using vue @mousemove
112
+ * and @mouseleave in the element since they did
113
+ * not worker properly
114
+ */
115
+ addMouseEventListeners() {
116
+ this.$el.addEventListener('mousemove', this.onMousemove);
117
+ this.$el.addEventListener('mouseleave', this.onMouseleave);
118
+ },
119
+
120
+ /**
121
+ * remove the mousemove and mouseleave added for
122
+ * avoiding memory leaks, if @mousemove and
123
+ * @mouseleave were in the element, this
124
+ * wound't be needed
125
+ */
126
+ removeMouseEventListeners() {
127
+ this.$el.removeEventListener('mousemove', this.onMousemove);
128
+ this.$el.removeEventListener('mouseleave', this.onMouseleave);
129
+ },
130
+
131
+ select(eventType) {
132
+ this.eventType = eventType;
133
+ this.selected = true;
134
+ },
135
+
136
+ unselect() {
137
+ this.selected = false;
138
+ },
139
+
140
+ isSelected() {
141
+ return this.selected;
142
+ },
143
+
144
+ focus() {
145
+ if (!this.$el) return;
146
+
147
+ this.tabIndex = -1;
148
+ this.selected = true;
149
+ this.$el.focus();
150
+ },
151
+
152
+ focusFirstItem() {
153
+ const selectedIndex = this.getItemSelectedIndex();
154
+ const newSelectedIndex = 0;
155
+
156
+ this.setFocusToItem(selectedIndex, newSelectedIndex);
157
+ },
158
+
159
+ focusLastItem() {
160
+ const selectedIndex = this.getItemSelectedIndex();
161
+ const newSelectedIndex = this.items.length - 1;
162
+
163
+ this.setFocusToItem(selectedIndex, newSelectedIndex);
164
+ },
165
+
166
+ /**
167
+ * Focus the next item in the menu.
168
+ * If is the last item, jump to the first item.
169
+ */
170
+ focusNextItem() {
171
+ const selectedIndex = this.getItemSelectedIndex();
172
+ const isLast = selectedIndex === this.items.length - 1;
173
+ const firstItemIndex = 0;
174
+ const nextItemIndex = selectedIndex + 1;
175
+ const newSelectedIndex = isLast ? firstItemIndex : nextItemIndex;
176
+
177
+ this.setFocusToItem(selectedIndex, newSelectedIndex);
178
+ },
179
+
180
+ /**
181
+ * Focus the previous item in the menu.
182
+ * If is the first item, jump to the last item.
183
+ */
184
+ focusPreviousItem() {
185
+ const selectedIndex = this.getItemSelectedIndex();
186
+ const isFirst = selectedIndex === 0;
187
+ const lastItemIndex = this.items.length - 1;
188
+ const previousItemIndex = selectedIndex - 1;
189
+ const newSelectedIndex = isFirst ? lastItemIndex : previousItemIndex;
190
+
191
+ this.setFocusToItem(selectedIndex, newSelectedIndex);
192
+ },
193
+
194
+ /**
195
+ * Focus/select item by removing its tabindex and calling
196
+ * focus to the element.
197
+ *
198
+ * @param {Number, String} selectedIndex
199
+ * @param {Number, String} newSelectedIndex
200
+ */
201
+ setFocusToItem(selectedIndex, newSelectedIndex) {
202
+ this.tabIndex = 0;
203
+
204
+ // before focusing, let's unselect selected
205
+ // item that were previously focused
206
+ if (selectedIndex >= 0) {
207
+ this.items[selectedIndex].unselect();
208
+ }
209
+
210
+ // focus new item
211
+ if (this.items[newSelectedIndex]) {
212
+ this.items[newSelectedIndex].focus();
213
+ }
214
+ },
215
+
216
+ getItemSelectedIndex() {
217
+ return this.items.findIndex((item) => item.isSelected());
218
+ },
219
+
220
+ leaveMenu() {
221
+ if (this.componentTrigger) {
222
+ this.componentTrigger.cancel();
223
+ this.componentTrigger.focus();
224
+ }
225
+ },
226
+
227
+ onClick() {
228
+ if (this.disabled) {
229
+ return;
230
+ }
231
+
232
+ this.value !== undefined
233
+ ? this.apiInjected().emit(this.value)
234
+ : this.$emit('click');
235
+
236
+ this.$nextTick(() => this.leaveMenu());
237
+ },
238
+
239
+ onKeyEsc() {
240
+ this.leaveMenu();
241
+ },
242
+
243
+ onMousemove(event) {
244
+ if (this.selected) {
245
+ return;
246
+ }
247
+
248
+ // unselect all items before selecting new one
249
+ for (const item of this.items) {
250
+ item.unselect();
251
+ }
252
+
253
+ /**
254
+ * Select item passing the event type to decide if
255
+ * scrolling will be disabled or not. It is
256
+ * expected that on mouse move event, the scroll
257
+ * doesn't happen automatically.
258
+ */
259
+ this.select(event.type);
260
+ },
261
+
262
+ onMouseleave() {
263
+ this.unselect();
264
+ },
265
+ },
266
+ };
@@ -0,0 +1,67 @@
1
+ import { computePosition, flip, shift, offset, size } from '@floating-ui/dom';
2
+
3
+ export const floatingUiMixin = {
4
+ data() {
5
+ return {
6
+ component: null,
7
+ componentTrigger: null,
8
+ componentContent: null,
9
+ active: false,
10
+ };
11
+ },
12
+
13
+ watch: {
14
+ active(newVal) {
15
+ if (newVal) this.$nextTick(() => this.positionContentToTrigger());
16
+ },
17
+ },
18
+
19
+ mounted() {
20
+ this.component = {
21
+ setActive: this.setActive,
22
+ clearActive: this.clearActive,
23
+ };
24
+ },
25
+
26
+ methods: {
27
+ setActive() {
28
+ this.active = true;
29
+ },
30
+
31
+ clearActive() {
32
+ this.active = null;
33
+ },
34
+
35
+ positionContentToTrigger() {
36
+ const trigger = document.getElementById(this.componentTrigger.id);
37
+ const content = document.getElementById(this.componentContent.id);
38
+ const minWidthLimit = Number(this.floatingUiMinWidth);
39
+
40
+ computePosition(trigger, content, {
41
+ placement: this.placement,
42
+ middleware: [
43
+ offset(5),
44
+ flip(),
45
+ shift({ padding: 5 }),
46
+ size({
47
+ apply({ rects }) {
48
+ if (!minWidthLimit) return;
49
+
50
+ const width = rects.reference.width;
51
+ const minWidth = width < minWidthLimit ? minWidthLimit : width;
52
+
53
+ Object.assign(content.style, {
54
+ minWidth: `${minWidth}px`,
55
+ });
56
+ },
57
+ }),
58
+ ],
59
+ }).then(({ x, y }) => {
60
+ Object.assign(content.style, {
61
+ left: `${x}px`,
62
+ top: `${y}px`,
63
+ });
64
+ });
65
+ },
66
+ },
67
+ };
@@ -0,0 +1,53 @@
1
+ import { formControlMixin, formControlStyleMixin } from '../mixins/form-control';
2
+
3
+ export const formControlIconMixin = {
4
+ inheritAttrs: false,
5
+
6
+ mixins: [formControlMixin, formControlStyleMixin],
7
+
8
+ data() {
9
+ return {
10
+ name: 'input',
11
+ };
12
+ },
13
+
14
+ computed: {
15
+ isLeft() {
16
+ return this.iconPlacement === 'left';
17
+ },
18
+
19
+ isRight() {
20
+ return this.iconPlacement === 'right';
21
+ },
22
+
23
+ classComputedWrapper() {
24
+ return [
25
+ this.headless ? 'form-control-wrapper' : 'relative',
26
+ // placement styles
27
+ this.headless
28
+ ? `form-control-icon--${this.placement}`
29
+ : this.isLeft
30
+ ? '[&_input]:pl-9'
31
+ : this.isRight
32
+ ? '[&_input]:pr-9'
33
+ : null,
34
+ ];
35
+ },
36
+
37
+ classComputedWrapperIcon() {
38
+ return [
39
+ this.headless
40
+ ? 'form-control-wrapper__icon'
41
+ : `absolute top-0 bottom-0 flex w-9 justify-center items-center text-gray-400`,
42
+ // placement styles
43
+ this.headless
44
+ ? null
45
+ : this.isLeft
46
+ ? 'left-0'
47
+ : this.isRight
48
+ ? 'right-0'
49
+ : null,
50
+ ];
51
+ },
52
+ },
53
+ };
@@ -0,0 +1,73 @@
1
+ export const formControlMixin = {
2
+ props: {
3
+ disabled: {
4
+ type: Boolean,
5
+ default: false,
6
+ },
7
+ headless: {
8
+ type: Boolean,
9
+ default: false,
10
+ },
11
+ modelValue: {
12
+ type: [String, Number, Object, Array],
13
+ default: null,
14
+ },
15
+ variant: {
16
+ type: [String, Object, Function],
17
+ default: '',
18
+ },
19
+ },
20
+
21
+ computed: {
22
+ attrsComputed() {
23
+ // `Object.assign` merges objects together to form a new object
24
+ return Object.assign(
25
+ {},
26
+ // We add all the listeners from the parent
27
+ this.$attrs,
28
+ // Then we can add custom listeners or override the
29
+ // behavior of some listeners.
30
+ {
31
+ // This ensures that the component works with v-model
32
+ onInput: (event) => {
33
+ // if (this.lazy) return;
34
+ this.$emit('update:modelValue', event.target.value);
35
+ },
36
+ onBlur: (event) => {
37
+ this.$emit('blur', event);
38
+ },
39
+ }
40
+ );
41
+ },
42
+
43
+
44
+
45
+ isError() {
46
+ return this.variant === 'error';
47
+ },
48
+ },
49
+ };
50
+
51
+ export const formControlStyleMixin = {
52
+ computed: {
53
+ classComputed() {
54
+ return [
55
+ this.headless
56
+ ? `${this.name}`
57
+ : 'leading-0 flex outline-none w-full max-w-full appearance-none items-center justify-between rounded border border-solid px-3 py-2 font-inherit text-base text-inherit file:hidden focus:border-secondary-200 appearance-none',
58
+ // variant styles
59
+ this.headless
60
+ ? `${this.name}--${this.variant}`
61
+ : this.isError
62
+ ? 'border-error-300'
63
+ : 'border-gray-300',
64
+ // height styles
65
+ this.headless
66
+ ? null
67
+ : this.name === 'textarea'
68
+ ? 'min-h-10' // limit it because input type number height can be different from other input types
69
+ : 'h-12 md:h-10',
70
+ ];
71
+ },
72
+ },
73
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veritree/ui",
3
- "version": "0.19.2-2",
3
+ "version": "0.19.2-21",
4
4
  "description": "veritree ui library",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -11,9 +11,13 @@
11
11
  "access": "public"
12
12
  },
13
13
  "dependencies": {
14
- "@veritree/icons": "^0.36.1-0"
14
+ "@floating-ui/dom": "^1.0.4",
15
+ "@veritree/icons": "^0.45.1"
15
16
  },
16
17
  "devDependencies": {
17
- "@nuxt/kit": "^3.0.0-rc.12"
18
+ "@nuxt/kit": "^3.0.0",
19
+ "prettier": "^2.7.1",
20
+ "prettier-plugin-tailwindcss": "^0.1.13",
21
+ "tailwindcss": "^3.2.4"
18
22
  }
19
23
  }
@@ -1,14 +1,21 @@
1
1
  <template>
2
2
  <div
3
- :class="{
4
- Avatar: headless,
5
- 'flex items-center justify-center overflow-hidden rounded-full':
6
- !headless,
7
- 'h-10 w-10': !small,
8
- 'h-8 w-8': small,
9
- 'border border-solid': !dark,
10
- 'bg-white text-fd-500': dark,
11
- }"
3
+ :class="[
4
+ // default styles
5
+ headless
6
+ ? 'avatar'
7
+ : 'flex items-center justify-center overflow-hidden rounded-full bg-white border border-solid',
8
+ // variant styles
9
+ headless ? `avatar--${variant}` : null,
10
+ // sizes styles
11
+ headless
12
+ ? `avatar--${size}`
13
+ : isSmall
14
+ ? 'h-8 w-8'
15
+ : isLarge
16
+ ? 'h-10 w-10'
17
+ : null,
18
+ ]"
12
19
  >
13
20
  <slot></slot>
14
21
  </div>
@@ -18,32 +25,28 @@
18
25
  export default {
19
26
  name: 'VTAvatar',
20
27
 
21
- provide() {
22
- return {
23
- api: () => {
24
- const { dark: isDark, headless: isHeadless, light: isLight } = this;
25
-
26
- return {
27
- isDark,
28
- isHeadless,
29
- isLight,
30
- };
31
- },
32
- };
33
- },
34
-
35
28
  props: {
36
29
  headless: {
37
30
  type: Boolean,
38
31
  default: false,
39
32
  },
40
- dark: {
41
- type: Boolean,
42
- default: false,
33
+ variant: {
34
+ type: String,
35
+ default: 'primary',
43
36
  },
44
- small: {
45
- type: Boolean,
46
- default: false,
37
+ size: {
38
+ type: String,
39
+ default: 'large',
40
+ },
41
+ },
42
+
43
+ computed: {
44
+ isLarge() {
45
+ return this.size === 'large';
46
+ },
47
+
48
+ isSmall() {
49
+ return this.size === 'small';
47
50
  },
48
51
  },
49
52
  };
@@ -16,11 +16,11 @@
16
16
  headless
17
17
  ? `button--${variant}`
18
18
  : isPrimary
19
- ? 'border-transparent bg-secondary-400 text-white hover:bg-secondary-500 active:bg-secondary-600 disabled:bg-gray-200 disabled:text-gray-400'
19
+ ? 'bg-secondary-400 hover:bg-secondary-500 active:bg-secondary-600 border-transparent text-white disabled:bg-gray-200 disabled:text-gray-400'
20
20
  : isSecondary
21
21
  ? 'border-gray-400 bg-white text-gray-700 hover:bg-gray-100 active:bg-gray-200 disabled:border-gray-300 disabled:text-gray-400'
22
22
  : isTertiary
23
- ? 'border-transparent text-secondary-400 hover:text-secondary-500 active:text-secondary-500 disabled:text-gray-400'
23
+ ? 'text-secondary-400 hover:text-secondary-500 active:text-secondary-500 border-transparent disabled:text-gray-400'
24
24
  : isIcon
25
25
  ? 'text-primary-100 focus-within:bg-gray-200 hover:bg-gray-200 active:bg-gray-300'
26
26
  : null,
@@ -30,7 +30,7 @@
30
30
  : isLarge
31
31
  ? isIcon
32
32
  ? 'h-8 w-8'
33
- : 'h-10'
33
+ : 'h-12 sm:h-10'
34
34
  : isSmall
35
35
  ? isIcon
36
36
  ? 'h-6 w-6 p-1'
@@ -41,7 +41,7 @@
41
41
  <VTSpinner v-if="busy" class="absolute inset-0 m-auto" />
42
42
  <span
43
43
  :class="[
44
- headless ? null : 'inline-flex items-center gap-2 self-center mx-auto',
44
+ headless ? null : 'mx-auto inline-flex items-center gap-2 self-center',
45
45
  headless && busy ? 'button--busy' : busy ? 'invisible' : null,
46
46
  ]"
47
47
  >
@@ -115,7 +115,11 @@ export default {
115
115
  },
116
116
 
117
117
  tag() {
118
- return this.$attrs.href ? 'a' : this.to ? 'NuxtLink' : 'button';
118
+ return this.$attrs.href
119
+ ? 'a'
120
+ : this.to
121
+ ? resolveComponent('NuxtLink')
122
+ : 'button';
119
123
  },
120
124
 
121
125
  type() {