@veritree/ui 0.21.1-0 → 0.21.1-10

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 (29) hide show
  1. package/mixins/floating-ui-content.js +102 -0
  2. package/mixins/floating-ui-item.js +216 -0
  3. package/mixins/floating-ui.js +14 -6
  4. package/mixins/form-control.js +72 -0
  5. package/package.json +2 -2
  6. package/src/components/Avatar/VTAvatar.vue +32 -29
  7. package/src/components/DropdownMenu/VTDropdownMenu.vue +5 -16
  8. package/src/components/DropdownMenu/VTDropdownMenuContent.vue +17 -65
  9. package/src/components/DropdownMenu/VTDropdownMenuDivider.vue +8 -5
  10. package/src/components/DropdownMenu/VTDropdownMenuItem.vue +9 -143
  11. package/src/components/DropdownMenu/VTDropdownMenuLabel.vue +3 -3
  12. package/src/components/DropdownMenu/VTDropdownMenuTrigger.vue +67 -103
  13. package/src/components/Form/VTInput.vue +28 -40
  14. package/src/components/Form/VTInputQty.vue +165 -0
  15. package/src/components/Form/VTTextarea.vue +22 -0
  16. package/src/components/Listbox/VTListbox.vue +6 -11
  17. package/src/components/Listbox/VTListboxContent.vue +11 -76
  18. package/src/components/Listbox/VTListboxItem.vue +9 -181
  19. package/src/components/Listbox/VTListboxLabel.vue +0 -10
  20. package/src/components/Listbox/VTListboxList.vue +24 -33
  21. package/src/components/Listbox/VTListboxSearch.vue +21 -18
  22. package/src/components/Listbox/VTListboxTrigger.vue +58 -97
  23. package/src/components/Popover/VTPopover.vue +1 -14
  24. package/src/components/Popover/VTPopoverContent.vue +14 -65
  25. package/src/components/Popover/VTPopoverDivider.vue +4 -11
  26. package/src/components/Popover/VTPopoverItem.vue +16 -13
  27. package/src/components/Popover/VTPopoverTrigger.vue +118 -21
  28. package/src/components/Utils/FloatingUi.vue +6 -1
  29. package/src/utils/components.js +0 -18
@@ -0,0 +1,102 @@
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
+ floatingUiClass: {
14
+ type: [String, Function],
15
+ default: null,
16
+ },
17
+ },
18
+
19
+ data() {
20
+ return {
21
+ el: null,
22
+ isMousemove: false,
23
+ visible: false,
24
+ };
25
+ },
26
+
27
+ mounted() {
28
+ document.addEventListener('click', (e) => this.onDocumentClick(e));
29
+ },
30
+
31
+ destroyed() {
32
+ document.removeEventListener('click', this.onDocumentClick);
33
+ },
34
+
35
+ methods: {
36
+ show() {
37
+ if (this.visible) {
38
+ return;
39
+ }
40
+
41
+ this.visible = true;
42
+
43
+ this.$nextTick(() => {
44
+ this.component.setActive();
45
+
46
+ setTimeout(() => {
47
+ this.el = document.getElementById(this.id);
48
+ this.$emit('shown');
49
+ }, 100);
50
+ });
51
+ },
52
+
53
+ hide() {
54
+ if (!this.visible) {
55
+ return;
56
+ }
57
+
58
+ this.visible = false;
59
+
60
+ this.$nextTick(() => {
61
+ this.component.clearActive();
62
+
63
+ setTimeout(() => {
64
+ this.el = document.getElementById(this.id);
65
+ this.$emit('hidden');
66
+ }, 100);
67
+ });
68
+ },
69
+
70
+ onDocumentClick(e) {
71
+ if (!e) {
72
+ return;
73
+ }
74
+
75
+ e.stopPropagation();
76
+
77
+ if (this.visible && !this.el.contains(e.target)) {
78
+ this.componentTrigger.cancel();
79
+ }
80
+ },
81
+
82
+ // Mousemove instead of mouseover to support keyboard navigation.
83
+ // The problem with mouseover is that when scrolling (scrollIntoView),
84
+ // mouseover event gets triggered.
85
+ setMousemove() {
86
+ this.isMousemove = true;
87
+ },
88
+
89
+ unsetMousemove() {
90
+ this.isMousemove = false;
91
+ },
92
+
93
+ getMousemove() {
94
+ return this.isMousemove;
95
+ },
96
+
97
+ //
98
+ setActiveDescedant(id) {
99
+ this.activeDescedant = id;
100
+ },
101
+ },
102
+ }
@@ -0,0 +1,216 @@
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
+ el() {
23
+ return this.$el;
24
+ },
25
+
26
+ componentContent() {
27
+ return this.apiInjected().componentContent;
28
+ },
29
+
30
+ componentTrigger() {
31
+ return this.apiInjected().componentTrigger;
32
+ },
33
+
34
+ classComputed() {
35
+ return [
36
+ // default styles
37
+ this.headless
38
+ ? `${this.componentName}`
39
+ : 'relative z-10 flex items-center gap-2 px-3 py-2 text-inherit no-underline',
40
+ // disabled state styles
41
+ this.headless
42
+ ? this.disabled
43
+ ? `${this.componentName}--disabled`
44
+ : null
45
+ : this.disabled
46
+ ? 'pointer-events-none opacity-75'
47
+ : null,
48
+ // selected state styles
49
+ this.headless
50
+ ? this.selected
51
+ ? `${this.componentName}--selected`
52
+ : null
53
+ : this.selected
54
+ ? 'bg-secondary-200/10'
55
+ : null,
56
+ ];
57
+ },
58
+ },
59
+
60
+ data() {
61
+ return {
62
+ index: null,
63
+ selected: false,
64
+ tabIndex: 0,
65
+ };
66
+ },
67
+
68
+ watch: {
69
+ selected(newValue) {
70
+ if (!newValue || !this.componentContent) return;
71
+
72
+ this.componentContent.setActiveDescedant(this.id);
73
+
74
+ const isMousemove = this.componentContent.getMousemove();
75
+
76
+ if (!isMousemove) {
77
+ this.el.scrollIntoView({ block: 'nearest' });
78
+ }
79
+ },
80
+ },
81
+
82
+ mounted() {
83
+ this.init();
84
+ this.addMouseEventListeners();
85
+ },
86
+
87
+ beforeDestroy() {
88
+ this.apiInjected().unregisterItem(this.id);
89
+ this.removeMouseEventListeners();
90
+ },
91
+
92
+ methods: {
93
+ init() {
94
+ const item = {
95
+ id: this.id,
96
+ select: this.select,
97
+ unselect: this.unselect,
98
+ focus: this.focus,
99
+ onClick: this.onClick,
100
+ };
101
+
102
+ this.apiInjected().registerItem(item);
103
+
104
+ this.index = this.items.length - 1;
105
+ },
106
+
107
+ addMouseEventListeners() {
108
+ this.el.addEventListener('mousemove', this.onMousemove);
109
+ this.el.addEventListener('mouseleave', this.onMouseleave);
110
+ },
111
+
112
+ removeMouseEventListeners() {
113
+ this.el.removeEventListener('mousemove', this.onMousemove);
114
+ this.el.removeEventListener('mouseleave', this.onMouseleave);
115
+ },
116
+
117
+ select() {
118
+ this.selected = true;
119
+ },
120
+
121
+ unselect() {
122
+ this.selected = false;
123
+ },
124
+
125
+ focus() {
126
+ if (!this.el) return;
127
+
128
+ this.tabIndex = -1;
129
+ this.selected = true;
130
+ this.el.focus();
131
+ },
132
+
133
+ focusFirstItem() {
134
+ this.setFocusToItem(0);
135
+ },
136
+
137
+ focusLastItem() {
138
+ this.setFocusToItem(this.items.length - 1);
139
+ },
140
+
141
+ /**
142
+ * Focus the previous item in the menu.
143
+ * If is the first item, jump to the last item.
144
+ */
145
+ focusPreviousItem() {
146
+ const isLast = this.index === this.items.length - 1;
147
+ const goToIndex = isLast ? 0 : this.index + 1;
148
+
149
+ this.setFocusToItem(goToIndex);
150
+ },
151
+
152
+ /**
153
+ * Focus the next item in the menu.
154
+ * If is the last item, jump to the first item.
155
+ */
156
+ focusNextItem() {
157
+ const isFirst = this.index === 0;
158
+ const goToIndex = isFirst ? this.items.length - 1 : this.index - 1;
159
+
160
+ this.setFocusToItem(goToIndex);
161
+ },
162
+
163
+ /**
164
+ * Focus item by remove its tabindex and calling
165
+ * focus to the element.
166
+ *
167
+ * @param {Number, String} goToIndex
168
+ */
169
+ setFocusToItem(goToIndex) {
170
+ this.tabIndex = 0;
171
+ this.selected = false;
172
+
173
+ // set all selected to false
174
+ this.items.forEach((item) => item.unselect());
175
+
176
+ // focus item
177
+ this.items[goToIndex].focus();
178
+ },
179
+
180
+ leaveMenu() {
181
+ if (this.componentTrigger) {
182
+ this.componentTrigger.cancel();
183
+ this.componentTrigger.focus();
184
+ }
185
+ },
186
+
187
+ onClick() {
188
+ if (this.disabled) {
189
+ return;
190
+ }
191
+
192
+ this.value ? this.apiInjected().emit(this.value) : this.$emit('click');
193
+ this.$nextTick(() => this.leaveMenu());
194
+ },
195
+
196
+ onKeyEsc() {
197
+ this.leaveMenu();
198
+ },
199
+
200
+ onMousemove() {
201
+ if (this.selected) {
202
+ return;
203
+ }
204
+
205
+ this.items.forEach((item) => item.unselect());
206
+
207
+ this.select();
208
+ this.componentContent.setMousemove();
209
+ },
210
+
211
+ onMouseleave() {
212
+ this.unselect();
213
+ this.componentContent.unsetMousemove();
214
+ },
215
+ },
216
+ };
@@ -4,8 +4,8 @@ export const floatingUiMixin = {
4
4
  props: {
5
5
  placement: {
6
6
  type: String,
7
- default: 'bottom-start'
8
- }
7
+ default: 'bottom-start',
8
+ },
9
9
  },
10
10
 
11
11
  data() {
@@ -14,7 +14,7 @@ export const floatingUiMixin = {
14
14
  componentTrigger: null,
15
15
  componentContent: null,
16
16
  active: false,
17
- }
17
+ };
18
18
  },
19
19
 
20
20
  watch: {
@@ -40,6 +40,8 @@ export const floatingUiMixin = {
40
40
  },
41
41
 
42
42
  positionContentToTrigger() {
43
+ // console.log(window.innerWidth)
44
+
43
45
  const trigger = document.getElementById(this.componentTrigger.id);
44
46
  const content = document.getElementById(this.componentContent.id);
45
47
 
@@ -51,8 +53,14 @@ export const floatingUiMixin = {
51
53
  shift({ padding: 5 }),
52
54
  size({
53
55
  apply({ rects }) {
56
+ // the min width to floating uis should be 200
57
+ // since less than that can look not as nice
58
+ const minWidthLimit = 200;
59
+ const width = rects.reference.width;
60
+ const minWidth = width < minWidthLimit ? minWidthLimit : width;
61
+
54
62
  Object.assign(content.style, {
55
- width: `${rects.reference.width}px`,
63
+ minWidth: `${minWidth}px`,
56
64
  });
57
65
  },
58
66
  }),
@@ -64,5 +72,5 @@ export const floatingUiMixin = {
64
72
  });
65
73
  });
66
74
  },
67
- }
68
- }
75
+ },
76
+ };
@@ -0,0 +1,72 @@
1
+ export const formControlMixin = {
2
+ model: {
3
+ prop: 'value',
4
+ event: 'input',
5
+ },
6
+
7
+ props: {
8
+ disabled: {
9
+ type: Boolean,
10
+ default: false,
11
+ },
12
+ headless: {
13
+ type: Boolean,
14
+ default: false,
15
+ },
16
+ value: {
17
+ type: [String, Number, Object, Array],
18
+ default: null,
19
+ },
20
+ variant: {
21
+ type: [String, Object, Function],
22
+ default: '',
23
+ },
24
+ },
25
+
26
+ computed: {
27
+ listeners() {
28
+ // `Object.assign` merges objects together to form a new object
29
+ return Object.assign(
30
+ {},
31
+ // We add all the listeners from the parent
32
+ this.$listeners,
33
+ // Then we can add custom listeners or override the
34
+ // behavior of some listeners.
35
+ {
36
+ // This ensures that the component works with v-model
37
+ input: (event) => {
38
+ // if (this.lazy) return;
39
+ this.$emit('input', event.target.value);
40
+ },
41
+ blur: (event) => {
42
+ this.$emit('blur', event);
43
+ },
44
+ }
45
+ );
46
+ },
47
+
48
+ classComputed() {
49
+ return [
50
+ this.headless
51
+ ? `${this.name}`
52
+ : 'leading-0 flex w-full max-w-full appearance-none items-center justify-between gap-3 rounded border border-solid px-3 py-2 font-inherit text-base text-inherit file:hidden focus:border-secondary-200',
53
+ // variant styles
54
+ this.headless
55
+ ? `${this.name}--${this.variant}`
56
+ : this.isError
57
+ ? 'border-error-300'
58
+ : 'border-gray-300',
59
+ // height styles
60
+ this.headless
61
+ ? null
62
+ : this.name === 'textarea'
63
+ ? 'min-h-10' // limit it because input type number height can be different from other input types
64
+ : 'h-10',
65
+ ];
66
+ },
67
+
68
+ isError() {
69
+ return this.variant === 'error';
70
+ },
71
+ },
72
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veritree/ui",
3
- "version": "0.21.1-0",
3
+ "version": "0.21.1-10",
4
4
  "description": "veritree ui library",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "@floating-ui/dom": "^1.0.4",
15
15
  "@linusborg/vue-simple-portal": "^0.1.5",
16
- "@veritree/icons": "^0.19.0"
16
+ "@veritree/icons": "^0.39.0"
17
17
  },
18
18
  "devDependencies": {}
19
19
  }
@@ -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
  };
@@ -15,9 +15,7 @@ export default {
15
15
 
16
16
  provide() {
17
17
  return {
18
- api: () => {
19
- const { dark: isDark, headless: isHeadless } = this;
20
-
18
+ apiDropdownMenu: () => {
21
19
  const registerTrigger = (trigger) => {
22
20
  if (!trigger) return;
23
21
  this.componentTrigger = trigger;
@@ -33,14 +31,13 @@ export default {
33
31
  this.items.push(item);
34
32
  };
35
33
 
36
- const unregisterItems = () => {
37
- this.items = [];
34
+ const unregisterItem = (id) => {
35
+ const index = this.items.findIndex((item) => item.id === id);
36
+ this.items.splice(index, 1);
38
37
  };
39
38
 
40
39
  return {
41
40
  id: this.componentId,
42
- isDark,
43
- isHeadless,
44
41
  component: this.component,
45
42
  componentTrigger: this.componentTrigger,
46
43
  componentContent: this.componentContent,
@@ -48,7 +45,7 @@ export default {
48
45
  registerTrigger,
49
46
  registerContent,
50
47
  registerItem,
51
- unregisterItems,
48
+ unregisterItem,
52
49
  };
53
50
  },
54
51
  };
@@ -59,14 +56,6 @@ export default {
59
56
  type: Boolean,
60
57
  default: false,
61
58
  },
62
- dark: {
63
- type: Boolean,
64
- default: false,
65
- },
66
- right: {
67
- type: Boolean,
68
- default: false,
69
- },
70
59
  },
71
60
 
72
61
  data() {
@@ -4,96 +4,48 @@
4
4
  :id="id"
5
5
  :headless="headless"
6
6
  :class="{ 'dropdown-menu-content': headless }"
7
+ :floating-ui-class="floatingUiClass"
7
8
  >
8
9
  <slot></slot>
9
10
  </FloatingUi>
10
11
  </template>
11
12
 
12
13
  <script>
13
- import FloatingUi from '../Utils/FloatingUi.vue';
14
+ import { floatingUiContentMixin } from '../../../mixins/floating-ui-content';
14
15
 
15
16
  export default {
16
17
  name: 'VTDropdownMenuContent',
17
18
 
18
- components: {
19
- FloatingUi,
20
- },
21
-
22
- inject: ['api'],
19
+ mixins: [floatingUiContentMixin],
23
20
 
24
- data() {
25
- return {
26
- visible: false,
27
- };
28
- },
21
+ inject: ['apiDropdownMenu'],
29
22
 
30
23
  computed: {
31
24
  id() {
32
- return `dropdown-menu-content-${this.api().id}`;
33
- },
34
-
35
- dark() {
36
- return this.api().isDark;
37
- },
38
-
39
- headless() {
40
- return this.api().isHeadless;
25
+ return `dropdown-menu-content-${this.apiDropdownMenu().id}`;
41
26
  },
42
27
 
43
28
  component() {
44
- return this.api().component;
29
+ return this.apiDropdownMenu().component;
45
30
  },
46
31
 
47
32
  componentTrigger() {
48
- return this.api().componentTrigger;
33
+ return this.apiDropdownMenu().componentTrigger;
49
34
  },
50
35
  },
51
36
 
52
37
  mounted() {
53
- this.api().registerContent(this);
54
-
55
- // T-218 Create a directive or mixin for this
56
- document.addEventListener('click', (e) => {
57
- if (!e) {
58
- return;
59
- }
60
-
61
- e.stopPropagation();
62
-
63
- if (this.visible && !this.$el.contains(e.target)) {
64
- this.componentTrigger.onClick();
65
- }
66
- });
67
- },
68
-
69
- destroyed() {
70
- document.removeEventListener('click', this.componentTrigger.onClick);
71
- },
72
-
73
- methods: {
74
- show() {
75
- if (this.visible) return;
76
-
77
- this.visible = true;
78
-
79
- this.$nextTick(() => {
80
- this.component.setActive();
81
- this.$emit('shown');
82
- });
83
- },
84
-
85
- hide() {
86
- if (!this.visible) return;
87
-
88
- this.visible = false;
38
+ const content = {
39
+ id: this.id,
40
+ hide: this.hide,
41
+ show: this.show,
42
+ getMousemove: this.getMousemove,
43
+ setMousemove: this.setMousemove,
44
+ unsetMousemove: this.unsetMousemove,
45
+ setActiveDescedant: this.setActiveDescedant,
46
+ };
89
47
 
90
- this.$nextTick(() => {
91
- this.componentTrigger.focus();
92
- this.componentTrigger.hideExpanded();
93
- this.component.clearActive();
94
- this.$emit('hidden');
95
- });
96
- },
48
+ this.apiDropdownMenu().registerContent(content);
97
49
  },
98
50
  };
99
51
  </script>