@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
@@ -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,29 @@
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,
27
+ id: null,
25
28
  };
26
29
  },
27
30
 
28
31
  computed: {
29
- // gets slot element
30
- slotElm() {
31
- return this.$slots.default[0].elm;
32
- },
33
-
34
- slotLength() {
35
- return this.$slots.default.length;
36
- },
32
+ // id() {
33
+ // return `dropdown-menu-trigger-${this.apiDropdownMenu().id}`;
34
+ // },
37
35
 
38
- content() {
39
- return this.api().content;
36
+ componentContent() {
37
+ return this.apiDropdownMenu().componentContent;
40
38
  },
41
39
 
42
40
  items() {
43
- return this.api().items;
41
+ return this.apiDropdownMenu().items;
44
42
  },
45
43
 
46
44
  firstMenuItem() {
@@ -52,16 +50,37 @@ export default {
52
50
  },
53
51
  },
54
52
 
53
+ watch: {
54
+ expanded() {
55
+ this.toggleAriaHasPopup();
56
+ },
57
+ },
58
+
55
59
  mounted() {
56
- this.api().registerTrigger(this);
57
- this.addEventListenerToSlotElm();
60
+ this.id = `dropdown-menu-trigger-${this.apiDropdownMenu().id}`;
61
+
62
+ const trigger = {
63
+ id: this.id,
64
+ el: this.$el,
65
+ cancel: this.cancel,
66
+ focus: this.focus,
67
+ };
68
+
69
+ this.apiDropdownMenu().registerTrigger(trigger);
70
+
71
+ this.setTrigger();
72
+ this.addTriggerEvents();
58
73
  },
59
74
 
60
75
  destroyed() {
61
- this.slotElm.removeEventListener('click', this.onClick());
76
+ this.trigger.removeEventListener('click', this.onClick);
62
77
  },
63
78
 
64
79
  methods: {
80
+ setTrigger() {
81
+ this.trigger = this.$el.querySelector(':first-child');
82
+ },
83
+
65
84
  /**
66
85
  * Add event listener to slot element
67
86
  *
@@ -72,138 +91,94 @@ export default {
72
91
  * Slot must have only one child element. It avoids
73
92
  * errors related to adding the event listener.
74
93
  */
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();
94
+ addTriggerEvents() {
95
+ this.trigger.addEventListener('click', (e) => {
96
+ this.onClick(e);
85
97
  });
86
98
  },
87
99
 
88
- /**
89
- * Shows content/menu if not already visible
90
- */
91
- showContent() {
92
- if (!this.expanded) {
93
- this.toggleExpanded();
94
- this.content.show();
100
+ init(e) {
101
+ if (!this.componentContent) {
102
+ return;
95
103
  }
96
- },
97
104
 
98
- /**
99
- * Focus slot element if it exists and toggle expanded
100
- */
101
- focus() {
102
- if (!this.slotElm) return;
103
-
104
- this.slotElm.focus();
105
- this.toggleExpanded();
106
- },
105
+ if (this.expanded) {
106
+ this.cancel();
107
+ return;
108
+ }
107
109
 
108
- /**
109
- * Toggles aria expanded attribute/state
110
- */
111
- toggleExpanded() {
112
- this.expanded = !this.expanded;
113
- },
110
+ this.expanded = true;
114
111
 
115
- /**
116
- * Sets aria expanded attribute/state to false
117
- */
118
- hideExpanded() {
119
- this.expanded = false;
112
+ // delay stop propagation to close other visible
113
+ // dropdowns and delay click event to control
114
+ // this dropdown visibility
115
+ setTimeout(() => e.stopImmediatePropagation(), 50);
116
+ setTimeout(() => this.showComponentContent(), 100);
120
117
  },
121
118
 
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;
119
+ cancel() {
120
+ if (!this.componentContent) {
132
121
  return;
133
122
  }
134
123
 
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;
124
+ this.expanded = false;
147
125
 
148
- this.toggleExpanded();
126
+ this.hideComponentContent();
127
+ },
149
128
 
150
- this.$nextTick(() => {
151
- if (this.expanded) this.content.show();
152
- else this.content.hide();
153
- });
129
+ focus() {
130
+ if (this.trigger) this.trigger.focus();
154
131
  },
155
132
 
156
- /**
157
- * 1. Set aria expanded attribute/state to false
158
- * 2. Close the menu
159
- */
160
- hide() {
161
- this.hideExpanded();
133
+ showComponentContent() {
134
+ this.componentContent.show();
135
+ },
162
136
 
163
- this.$nextTick(() => {
164
- this.content.hide();
165
- });
137
+ hideComponentContent() {
138
+ this.componentContent.hide();
166
139
  },
167
140
 
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;
141
+ toggleAriaHasPopup() {
142
+ if (this.expanded) {
143
+ this.hasPopup = this.componentContent !== null;
144
+ this.controls = this.hasPopup ? this.componentContent.id : null;
176
145
 
177
- this.showContent();
146
+ return;
147
+ }
178
148
 
179
- this.$nextTick(() => {
180
- this.firstMenuItem.focus();
181
- });
149
+ this.hasPopup = null;
150
+ this.controls = null;
182
151
  },
183
152
 
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;
153
+ onClick(e) {
154
+ this.init(e);
155
+ },
192
156
 
193
- this.showContent();
157
+ onKeyDownOrUp(e) {
158
+ if (!this.expanded) {
159
+ this.$el.click(e);
160
+ }
194
161
 
162
+ const keyCode = e.code;
163
+ const listItemPosition =
164
+ keyCode === 'ArrowDown'
165
+ ? 'firstMenuItem'
166
+ : keyCode === 'ArrowUp'
167
+ ? 'lastMenuItem'
168
+ : null;
169
+
170
+ // settimeout here is delaying the focusing the element
171
+ // since it is not rendered yet. All items will only
172
+ // be available when the content is fully visible.
195
173
  this.$nextTick(() => {
196
- this.lastMenuItem.focus();
174
+ setTimeout(() => this[listItemPosition].focus(), 100);
197
175
  });
198
176
  },
199
177
 
200
- onKeyesc() {
201
- if (!this.content) return;
202
-
203
- if (this.expanded) {
204
- this.toggleExpanded();
205
- this.content.hide();
206
- }
178
+ // change it to a better name or move the methods inside to another function
179
+ onKeyEsc() {
180
+ this.cancel();
181
+ this.focus();
207
182
  },
208
183
  },
209
184
  };
@@ -1,7 +1,27 @@
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 :class="[headless ? 'form-feedback--text' : 'text-sm text-gray-500']">
23
+ <slot />
24
+ </span>
5
25
  </div>
6
26
  </template>
7
27
 
@@ -17,32 +37,23 @@ export default {
17
37
  },
18
38
 
19
39
  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
40
  hideIcon: {
32
41
  type: Boolean,
33
42
  default: false,
34
43
  },
44
+ headless: {
45
+ type: Boolean,
46
+ default: false,
47
+ },
48
+ variant: {
49
+ type: [String, Object],
50
+ default: '',
51
+ },
35
52
  },
36
53
 
37
54
  computed: {
38
- classes() {
39
- const classes = {};
40
-
41
- if (this.variant) {
42
- classes[`form-feedback--${this.variant}`] = true;
43
- }
44
-
45
- return classes;
55
+ isError() {
56
+ return this.variant === 'error';
46
57
  },
47
58
 
48
59
  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" />
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>