@veritree/ui 0.19.2 → 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 (78) 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 +30 -23
  8. package/package.json +9 -4
  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 -6
  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 +22 -32
  16. package/src/components/Dialog/VTDialogClose.vue +19 -25
  17. package/src/components/Dialog/VTDialogContent.vue +24 -19
  18. package/src/components/Dialog/VTDialogFooter.vue +11 -16
  19. package/src/components/Dialog/VTDialogHeader.vue +16 -18
  20. package/src/components/Dialog/VTDialogMain.vue +11 -18
  21. package/src/components/Dialog/VTDialogOverlay.vue +14 -18
  22. package/src/components/Dialog/VTDialogTitle.vue +10 -7
  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/Drawer/VTDrawer.vue +14 -16
  28. package/src/components/Drawer/VTDrawerClose.vue +9 -9
  29. package/src/components/Drawer/VTDrawerContent.vue +8 -8
  30. package/src/components/Drawer/VTDrawerFooter.vue +3 -3
  31. package/src/components/Drawer/VTDrawerHeader.vue +4 -4
  32. package/src/components/Drawer/VTDrawerMain.vue +5 -5
  33. package/src/components/Drawer/VTDrawerOverlay.vue +6 -6
  34. package/src/components/Drawer/VTDrawerTitle.vue +5 -5
  35. package/src/components/DropdownMenu/VTDropdownMenu.vue +45 -28
  36. package/src/components/DropdownMenu/VTDropdownMenuContent.vue +29 -64
  37. package/src/components/DropdownMenu/VTDropdownMenuDivider.vue +8 -14
  38. package/src/components/DropdownMenu/VTDropdownMenuItem.vue +11 -124
  39. package/src/components/DropdownMenu/VTDropdownMenuLabel.vue +3 -12
  40. package/src/components/DropdownMenu/VTDropdownMenuTrigger.vue +91 -121
  41. package/src/components/Form/VTFormFeedback.vue +39 -22
  42. package/src/components/Form/VTFormGroup.vue +5 -7
  43. package/src/components/Form/VTFormLabel.vue +22 -0
  44. package/src/components/Form/VTFormRow.vue +5 -0
  45. package/src/components/Form/VTInput.vue +40 -0
  46. package/src/components/Form/VTInputIcon.vue +35 -0
  47. package/src/components/Form/VTInputPassword.vue +55 -0
  48. package/src/components/Form/VTTextarea.vue +22 -0
  49. package/src/components/Listbox/VTListbox.vue +122 -50
  50. package/src/components/Listbox/VTListboxContent.vue +20 -116
  51. package/src/components/Listbox/VTListboxItem.vue +115 -166
  52. package/src/components/Listbox/VTListboxLabel.vue +3 -14
  53. package/src/components/Listbox/VTListboxList.vue +10 -40
  54. package/src/components/Listbox/VTListboxSearch.vue +76 -68
  55. package/src/components/Listbox/VTListboxTrigger.vue +75 -86
  56. package/src/components/Popover/VTPopover.vue +42 -29
  57. package/src/components/Popover/VTPopoverContent.vue +24 -59
  58. package/src/components/Popover/VTPopoverDivider.vue +4 -11
  59. package/src/components/Popover/VTPopoverItem.vue +21 -14
  60. package/src/components/Popover/VTPopoverTrigger.vue +126 -21
  61. package/src/components/ProgressBar/VTProgressBar.vue +21 -3
  62. package/src/components/Skeleton/VTSkeleton.vue +11 -0
  63. package/src/components/Skeleton/VTSkeletonItem.vue +9 -0
  64. package/src/components/Tabs/VTTab.vue +4 -3
  65. package/src/components/Tabs/VTTabGroup.vue +9 -7
  66. package/src/components/Tabs/VTTabPanel.vue +4 -5
  67. package/src/components/Tooltip/VTTooltip.vue +65 -0
  68. package/src/components/Tooltip/VTTooltipContent.vue +59 -0
  69. package/src/components/Tooltip/VTTooltipTrigger.vue +98 -0
  70. package/src/components/Transitions/FadeInOut.vue +2 -2
  71. package/src/components/Utils/FloatingUi.vue +93 -0
  72. package/package-lock.json +0 -13
  73. package/src/components/Input/VTInput.vue +0 -82
  74. package/src/components/Input/VTInputDate.vue +0 -36
  75. package/src/components/Input/VTInputFile.vue +0 -60
  76. package/src/components/Input/VTInputUpload.vue +0 -54
  77. package/src/components/Modal/VTModal.vue +0 -69
  78. package/src/utils/genId.js +0 -13
@@ -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>
@@ -15,32 +16,28 @@
15
16
  export default {
16
17
  name: 'VTDropdownMenuTrigger',
17
18
 
18
- inject: ['api'],
19
+ inject: ['apiDropdownMenu'],
19
20
 
20
21
  data() {
21
22
  return {
22
23
  expanded: false,
23
24
  hasPopup: false,
24
25
  controls: null,
26
+ trigger: null,
25
27
  };
26
28
  },
27
29
 
28
30
  computed: {
29
- // gets slot element
30
- slotElm() {
31
- return this.$slots.default[0].elm;
31
+ id() {
32
+ return `dropdown-menu-trigger-${this.apiDropdownMenu().id}`;
32
33
  },
33
34
 
34
- slotLength() {
35
- return this.$slots.default.length;
36
- },
37
-
38
- content() {
39
- return this.api().content;
35
+ componentContent() {
36
+ return this.apiDropdownMenu().componentContent;
40
37
  },
41
38
 
42
39
  items() {
43
- return this.api().items;
40
+ return this.apiDropdownMenu().items;
44
41
  },
45
42
 
46
43
  firstMenuItem() {
@@ -52,16 +49,35 @@ export default {
52
49
  },
53
50
  },
54
51
 
52
+ watch: {
53
+ expanded() {
54
+ this.toggleAriaHasPopup();
55
+ },
56
+ },
57
+
55
58
  mounted() {
56
- this.api().registerTrigger(this);
57
- this.addEventListenerToSlotElm();
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
+
68
+ this.setTrigger();
69
+ this.addTriggerEvents();
58
70
  },
59
71
 
60
72
  destroyed() {
61
- this.slotElm.removeEventListener('click', this.onClick());
73
+ this.trigger.removeEventListener('click', this.onClick);
62
74
  },
63
75
 
64
76
  methods: {
77
+ setTrigger() {
78
+ this.trigger = this.$el.querySelector(':first-child');
79
+ },
80
+
65
81
  /**
66
82
  * Add event listener to slot element
67
83
  *
@@ -72,138 +88,92 @@ export default {
72
88
  * Slot must have only one child element. It avoids
73
89
  * errors related to adding the event listener.
74
90
  */
75
- addEventListenerToSlotElm() {
76
- if (!this.slotLength) return;
77
-
78
- if (this.slotLength > 1) {
79
- throw new Error('VTPopoverButton only accepts one item in its slot');
80
- }
81
-
82
- this.slotElm.addEventListener('click', (e) => {
83
- e.stopImmediatePropagation();
84
- this.onClick();
85
- });
91
+ addTriggerEvents() {
92
+ this.trigger.addEventListener('click', this.onClick);
86
93
  },
87
94
 
88
- /**
89
- * Shows content/menu if not already visible
90
- */
91
- showContent() {
92
- if (!this.expanded) {
93
- this.toggleExpanded();
94
- this.content.show();
95
+ init(e) {
96
+ if (!this.componentContent) {
97
+ return;
95
98
  }
96
- },
97
99
 
98
- /**
99
- * Focus slot element if it exists and toggle expanded
100
- */
101
- focus() {
102
- if (!this.slotElm) return;
100
+ if (this.expanded) {
101
+ this.cancel();
102
+ return;
103
+ }
103
104
 
104
- this.slotElm.focus();
105
- this.toggleExpanded();
106
- },
105
+ this.expanded = true;
107
106
 
108
- /**
109
- * Toggles aria expanded attribute/state
110
- */
111
- toggleExpanded() {
112
- this.expanded = !this.expanded;
113
- },
114
-
115
- /**
116
- * Sets aria expanded attribute/state to false
117
- */
118
- hideExpanded() {
119
- 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);
120
112
  },
121
113
 
122
- /**
123
- * Toggles aria popup/controls attribute/state
124
- */
125
- toggleHasPopup() {
126
- if (!this.content) return;
127
-
128
- this.hasPopup = !this.hasPopup;
129
-
130
- if (!this.hasPopup) {
131
- this.controls = null;
114
+ cancel() {
115
+ if (!this.componentContent) {
132
116
  return;
133
117
  }
134
118
 
135
- this.controls = this.content.id;
136
- },
137
-
138
- /**
139
- * On click, do the following:
140
- *
141
- * 1. Toggle aria expanded attribute/state
142
- * 2. Open the menu if it's closed
143
- * 3. Close the menu if it's open
144
- */
145
- onClick() {
146
- if (!this.content) return;
119
+ this.expanded = false;
147
120
 
148
- this.toggleExpanded();
121
+ this.hideComponentContent();
122
+ },
149
123
 
150
- this.$nextTick(() => {
151
- if (this.expanded) this.content.show();
152
- else this.content.hide();
153
- });
124
+ focus() {
125
+ if (this.trigger) this.trigger.focus();
154
126
  },
155
127
 
156
- /**
157
- * 1. Set aria expanded attribute/state to false
158
- * 2. Close the menu
159
- */
160
- hide() {
161
- this.hideExpanded();
128
+ showComponentContent() {
129
+ this.componentContent.show();
130
+ },
162
131
 
163
- this.$nextTick(() => {
164
- this.content.hide();
165
- });
132
+ hideComponentContent() {
133
+ this.componentContent.hide();
166
134
  },
167
135
 
168
- /**
169
- * On key arrow down, do the following:
170
- *
171
- * 1. if the menu is not expanded, expand it and focus the first menu item
172
- * 2. if the menu is expanded, focus the first menu item
173
- */
174
- onKeyArrowDown() {
175
- 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;
176
140
 
177
- this.showContent();
141
+ return;
142
+ }
178
143
 
179
- this.$nextTick(() => {
180
- this.firstMenuItem.focus();
181
- });
144
+ this.hasPopup = null;
145
+ this.controls = null;
182
146
  },
183
147
 
184
- /**
185
- * On key arrow up, do the following:
186
- *
187
- * 1. if the menu is not expanded, expand it and focus the last menu item
188
- * 2. if the menu is expanded, focus the last menu item
189
- */
190
- onKeyArrowUp() {
191
- if (!this.content) return;
148
+ onClick(e) {
149
+ this.init(e);
150
+ },
192
151
 
193
- this.showContent();
152
+ onKeyDownOrUp(e) {
153
+ if (!this.expanded) {
154
+ this.$el.click(e);
155
+ }
194
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.
195
168
  this.$nextTick(() => {
196
- this.lastMenuItem.focus();
169
+ setTimeout(() => this[listItemPosition].focus(), 100);
197
170
  });
198
171
  },
199
172
 
200
- onKeyesc() {
201
- if (!this.content) return;
202
-
203
- if (this.expanded) {
204
- this.toggleExpanded();
205
- this.content.hide();
206
- }
173
+ // change it to a better name or move the methods inside to another function
174
+ onKeyEsc() {
175
+ this.cancel();
176
+ this.focus();
207
177
  },
208
178
  },
209
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>
@@ -0,0 +1,55 @@
1
+ <template>
2
+ <div :class="classComputedWrapper">
3
+ <input
4
+ :class="classComputed"
5
+ :value="modelValue"
6
+ :disabled="disabled"
7
+ :type="reveal ? 'text' : 'password'"
8
+ v-bind="attrsComputed"
9
+ />
10
+ <div :class="classComputedWrapperIcon">
11
+ <VTButton
12
+ v-show="modelValue.length"
13
+ variant="icon"
14
+ @click="reveal = !reveal"
15
+ >
16
+ <component :is="iconVisibility" class="h-5 w-5 text-gray-500" />
17
+ </VTButton>
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script>
23
+ import { IconVisibilityOn, IconVisibilityOff } from '@veritree/icons';
24
+ import { formControlIconMixin } from '../../../mixins/form-control-icon';
25
+
26
+ export default {
27
+ name: 'VTInputPassword',
28
+
29
+ mixins: [formControlIconMixin],
30
+
31
+ components: {
32
+ IconVisibilityOn,
33
+ IconVisibilityOff,
34
+ },
35
+
36
+ props: {
37
+ iconPlacement: {
38
+ type: String,
39
+ default: 'right',
40
+ },
41
+ },
42
+
43
+ data() {
44
+ return {
45
+ reveal: false,
46
+ };
47
+ },
48
+
49
+ computed: {
50
+ iconVisibility() {
51
+ return this.reveal ? 'IconVisibilityOn' : 'IconVisibilityOff';
52
+ },
53
+ },
54
+ };
55
+ </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <textarea
3
+ :class="classComputed"
4
+ :value="value"
5
+ :disabled="disabled"
6
+ v-bind="attrsComputed"
7
+ />
8
+ </template>
9
+
10
+ <script>
11
+ import { formControlMixin } from '../../../mixins/form-control';
12
+
13
+ export default {
14
+ mixins: [formControlMixin],
15
+
16
+ data() {
17
+ return {
18
+ name: 'textarea',
19
+ };
20
+ },
21
+ };
22
+ </script>