@gitlab/ui 42.20.0 → 42.22.1

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 (39) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/components/base/alert/alert.js +1 -0
  3. package/dist/components/base/badge/badge.js +1 -0
  4. package/dist/components/base/button/button.js +2 -0
  5. package/dist/components/base/drawer/drawer.js +1 -0
  6. package/dist/components/base/dropdown/dropdown.js +1 -0
  7. package/dist/components/base/filtered_search/filtered_search_token_segment.js +1 -0
  8. package/dist/components/base/form/form_combobox/form_combobox.js +31 -35
  9. package/dist/components/base/form/form_group/form_group.js +1 -0
  10. package/dist/components/base/modal/modal.js +4 -1
  11. package/dist/components/base/toggle/toggle.js +1 -0
  12. package/dist/index.css +1 -1
  13. package/dist/index.css.map +1 -1
  14. package/dist/utility_classes.css +1 -1
  15. package/dist/utility_classes.css.map +1 -1
  16. package/package.json +3 -3
  17. package/src/components/base/alert/alert.vue +1 -0
  18. package/src/components/base/badge/badge.vue +1 -0
  19. package/src/components/base/button/button.vue +2 -0
  20. package/src/components/base/carousel/carousel.vue +1 -0
  21. package/src/components/base/carousel/carousel_slide.vue +1 -0
  22. package/src/components/base/drawer/drawer.vue +1 -0
  23. package/src/components/base/dropdown/dropdown.vue +1 -0
  24. package/src/components/base/filtered_search/filtered_search_token_segment.vue +1 -0
  25. package/src/components/base/form/form_combobox/form_combobox.scss +1 -5
  26. package/src/components/base/form/form_combobox/form_combobox.spec.js +26 -14
  27. package/src/components/base/form/form_combobox/form_combobox.vue +38 -32
  28. package/src/components/base/form/form_group/form_group.vue +2 -0
  29. package/src/components/base/form/form_select/form_select.scss +1 -0
  30. package/src/components/base/form/form_select/form_select.vue +1 -0
  31. package/src/components/base/modal/modal.vue +3 -0
  32. package/src/components/base/table/table.scss +4 -0
  33. package/src/components/base/table/table.stories.js +12 -2
  34. package/src/components/base/tabs/tab/tab.vue +1 -0
  35. package/src/components/base/tabs/tabs/scrollable_tabs.vue +1 -0
  36. package/src/components/base/tabs/tabs/tabs.vue +1 -0
  37. package/src/components/base/toggle/toggle.vue +1 -0
  38. package/src/scss/utilities.scss +8 -0
  39. package/src/scss/utility-mixins/flex.scss +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "42.20.0",
3
+ "version": "42.22.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -81,9 +81,9 @@
81
81
  "@arkweid/lefthook": "0.7.7",
82
82
  "@babel/core": "^7.10.2",
83
83
  "@babel/preset-env": "^7.10.2",
84
- "@gitlab/eslint-plugin": "13.1.0",
84
+ "@gitlab/eslint-plugin": "14.0.0",
85
85
  "@gitlab/stylelint-config": "4.1.0",
86
- "@gitlab/svgs": "2.26.0",
86
+ "@gitlab/svgs": "2.28.0",
87
87
  "@rollup/plugin-commonjs": "^11.1.0",
88
88
  "@rollup/plugin-node-resolve": "^7.1.3",
89
89
  "@rollup/plugin-replace": "^2.3.2",
@@ -94,6 +94,7 @@ export default {
94
94
  return alertVariantIconMap[this.variant];
95
95
  },
96
96
  shouldRenderActions() {
97
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
97
98
  return Boolean(this.$slots.actions || this.actionButtons.length);
98
99
  },
99
100
  actionButtons() {
@@ -44,6 +44,7 @@ export default {
44
44
  },
45
45
  computed: {
46
46
  hasIconOnly() {
47
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
47
48
  return Boolean(this.icon && Object.keys(this.$slots).length === 0);
48
49
  },
49
50
  role() {
@@ -74,6 +74,7 @@ export default {
74
74
  return this.icon !== '';
75
75
  },
76
76
  hasIconOnly() {
77
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
77
78
  return Object.keys(this.$slots).length === 0 && this.hasIcon;
78
79
  },
79
80
  isButtonDisabled() {
@@ -111,6 +112,7 @@ export default {
111
112
  },
112
113
  },
113
114
  mounted() {
115
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
114
116
  if (!this.$slots.default && !this.$attrs['aria-label'] && !this.$props.label) {
115
117
  logWarning('[gl-button]: Accessible name missing. Please add inner text or aria-label.');
116
118
  }
@@ -10,6 +10,7 @@ export default {
10
10
  </script>
11
11
  <template>
12
12
  <b-carousel v-bind="$attrs" v-on="$listeners">
13
+ <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
13
14
  <template v-for="slot in Object.keys($slots)" #[slot]>
14
15
  <slot :name="slot"></slot>
15
16
  </template>
@@ -9,6 +9,7 @@ export default {
9
9
  </script>
10
10
  <template>
11
11
  <b-carousel-slide v-bind="$attrs" v-on="$listeners">
12
+ <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
12
13
  <template v-for="slot in Object.keys($slots)" #[slot]>
13
14
  <slot :name="slot"></slot>
14
15
  </template>
@@ -57,6 +57,7 @@ export default {
57
57
  };
58
58
  },
59
59
  shouldRenderFooter() {
60
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
60
61
  return Boolean(this.$slots.footer);
61
62
  },
62
63
  variantClass() {
@@ -202,6 +202,7 @@ export default {
202
202
  },
203
203
  methods: {
204
204
  hasSlotContents(slotName) {
205
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
205
206
  return Boolean(this.$slots[slotName]);
206
207
  },
207
208
  show(...args) {
@@ -118,6 +118,7 @@ export default {
118
118
  },
119
119
 
120
120
  hasOptionsOrSuggestions() {
121
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
121
122
  return this.options?.length || this.$slots.suggestions;
122
123
  },
123
124
 
@@ -1,9 +1,5 @@
1
1
  .gl-form-combobox {
2
- .dropdown-full-width {
3
- @include gl-w-full;
4
- }
5
-
6
- .show-dropdown {
2
+ .gl-form-combobox-inner {
7
3
  max-height: $gl-max-dropdown-max-height;
8
4
  }
9
5
  }
@@ -1,3 +1,4 @@
1
+ import { nextTick } from 'vue';
1
2
  import { mount } from '@vue/test-utils';
2
3
  import GlDropdownItem from '../../dropdown/dropdown_item.vue';
3
4
  import GlFormInput from '../form_input/form_input.vue';
@@ -65,7 +66,9 @@ describe('GlFormCombobox', () => {
65
66
  const findInput = () => wrapper.findComponent(GlFormInput);
66
67
  const findInputValue = () => findInput().element.value;
67
68
  const setInput = (val) => findInput().setValue(val);
68
- const arrowDown = () => findInput().trigger('keydown.down');
69
+ const arrowDown = () => findDropdown().trigger('keydown.down');
70
+ const arrowUp = () => findDropdown().trigger('keydown.up');
71
+ const enter = () => wrapper.find('[aria-selected="true"]').trigger('keydown.enter');
69
72
  const findFirstAction = () => wrapper.find('[data-testid="combobox-action"]');
70
73
 
71
74
  beforeAll(() => {
@@ -128,8 +131,10 @@ describe('GlFormCombobox', () => {
128
131
  describe('on down arrow + enter', () => {
129
132
  it('selects the next item in the list and closes the dropdown', async () => {
130
133
  await setInput(partialToken);
131
- findInput().trigger('keydown.down');
132
- await findInput().trigger('keydown.enter');
134
+ arrowDown();
135
+ await nextTick();
136
+ enter();
137
+ await nextTick();
133
138
 
134
139
  if (valueType === 'string') {
135
140
  expect(findInputValue()).toBe(partialTokenMatch[0]);
@@ -141,7 +146,9 @@ describe('GlFormCombobox', () => {
141
146
  it('loops to the top when it reaches the bottom', async () => {
142
147
  await setInput(partialToken);
143
148
  doTimes(findDropdownOptions().length + 1, arrowDown);
144
- await findInput().trigger('keydown.enter');
149
+ await nextTick();
150
+ enter();
151
+ await nextTick();
145
152
 
146
153
  if (valueType === 'string') {
147
154
  expect(findInputValue()).toBe(partialTokenMatch[0]);
@@ -157,8 +164,10 @@ describe('GlFormCombobox', () => {
157
164
 
158
165
  await wrapper.vm.$nextTick();
159
166
  doTimes(3, arrowDown);
160
- findInput().trigger('keydown.up');
161
- findInput().trigger('keydown.enter');
167
+ arrowUp();
168
+ await nextTick();
169
+ enter();
170
+ await nextTick();
162
171
 
163
172
  await wrapper.vm.$nextTick();
164
173
 
@@ -172,9 +181,11 @@ describe('GlFormCombobox', () => {
172
181
 
173
182
  it('loops to the bottom when it reaches the top', async () => {
174
183
  await setInput(partialToken);
175
- findInput().trigger('keydown.down');
176
- findInput().trigger('keydown.up');
177
- await findInput().trigger('keydown.enter');
184
+ arrowDown();
185
+ arrowUp();
186
+ await nextTick();
187
+ enter();
188
+ await nextTick();
178
189
 
179
190
  if (valueType === 'string') {
180
191
  expect(findInputValue()).toBe(partialTokenMatch[partialTokenMatch.length - 1]);
@@ -187,11 +198,11 @@ describe('GlFormCombobox', () => {
187
198
  });
188
199
 
189
200
  describe('on enter with no item highlighted', () => {
190
- it('does not select any item and closes the dropdown', async () => {
201
+ it('does nothing', async () => {
191
202
  await setInput(partialToken);
192
203
  await findInput().trigger('keydown.enter');
193
204
  expect(findInputValue()).toBe(partialToken);
194
- expect(findDropdown().isVisible()).toBe(false);
205
+ expect(findDropdown().isVisible()).toBe(true);
195
206
  });
196
207
  });
197
208
 
@@ -268,9 +279,10 @@ describe('GlFormCombobox', () => {
268
279
 
269
280
  it('keyboard navigation and executes function on enter', async () => {
270
281
  await setInput('dog');
271
- findInput().trigger('keydown.down');
272
- findInput().trigger('keydown.down');
273
- await findInput().trigger('keydown.enter');
282
+ doTimes(2, arrowDown);
283
+ await nextTick();
284
+ enter();
285
+ await nextTick();
274
286
 
275
287
  expect(actionSpy).toHaveBeenCalled();
276
288
  expect(findDropdown().isVisible()).toBe(false);
@@ -54,6 +54,14 @@ export default {
54
54
  required: false,
55
55
  default: false,
56
56
  },
57
+ /**
58
+ * Placeholder text for input field
59
+ */
60
+ placeholder: {
61
+ type: String,
62
+ required: false,
63
+ default: undefined,
64
+ },
57
65
  },
58
66
  data() {
59
67
  return {
@@ -88,16 +96,8 @@ export default {
88
96
  },
89
97
  watch: {
90
98
  tokenList(newList) {
91
- const filteredTokens = newList.filter((token) => {
92
- if (this.matchValueToAttr) {
93
- // For API driven tokens, we don't need extra filtering
94
- return token;
95
- }
96
- return token.toLowerCase().includes(this.value.toLowerCase());
97
- });
98
-
99
- if (filteredTokens.length) {
100
- this.openSuggestions(filteredTokens);
99
+ if (newList.length) {
100
+ this.openSuggestions(newList);
101
101
  } else {
102
102
  this.results = [];
103
103
  this.arrowCounter = -1;
@@ -121,35 +121,33 @@ export default {
121
121
  this.closeSuggestions();
122
122
  }
123
123
  },
124
- onArrowDown() {
125
- const newCount = this.arrowCounter + 1;
124
+ focusItem(index) {
125
+ this.$refs.suggestionsMenu
126
+ .querySelectorAll('.gl-new-dropdown-item')
127
+ [index]?.querySelector('button')
128
+ .focus();
129
+ },
130
+ onArrowDown(e) {
131
+ e.preventDefault();
132
+ let newCount = this.arrowCounter + 1;
126
133
 
127
134
  if (newCount >= this.allItems.length) {
128
- this.arrowCounter = 0;
129
- return;
135
+ newCount = 0;
130
136
  }
131
137
 
132
138
  this.arrowCounter = newCount;
133
- this.$refs.results[newCount]?.$el.scrollIntoView(false);
139
+ this.focusItem(newCount);
134
140
  },
135
- onArrowUp() {
136
- const newCount = this.arrowCounter - 1;
141
+ onArrowUp(e) {
142
+ e.preventDefault();
143
+ let newCount = this.arrowCounter - 1;
137
144
 
138
145
  if (newCount < 0) {
139
- this.arrowCounter = this.allItems.length - 1;
140
- return;
146
+ newCount = this.allItems.length - 1;
141
147
  }
142
148
 
143
149
  this.arrowCounter = newCount;
144
- this.$refs.results[newCount]?.$el.scrollIntoView(true);
145
- },
146
- onEnter() {
147
- const focusedItem = this.allItems[this.arrowCounter] || this.value;
148
- if (focusedItem.fn) {
149
- this.selectAction(focusedItem);
150
- } else {
151
- this.selectToken(focusedItem);
152
- }
150
+ this.focusItem(newCount);
153
151
  },
154
152
  onEsc() {
155
153
  if (!this.showSuggestions) {
@@ -198,6 +196,9 @@ export default {
198
196
  this.$emit('input', this.value);
199
197
  this.closeSuggestions();
200
198
  },
199
+ resetCounter() {
200
+ this.arrowCounter = -1;
201
+ },
201
202
  },
202
203
  };
203
204
  </script>
@@ -219,10 +220,11 @@ export default {
219
220
  :aria-controls="suggestionsId"
220
221
  aria-haspopup="listbox"
221
222
  :autofocus="autofocus"
223
+ :placeholder="placeholder"
222
224
  @input="onEntry"
225
+ @focus="resetCounter"
223
226
  @keydown.down="onArrowDown"
224
227
  @keydown.up="onArrowUp"
225
- @keydown.enter.prevent="onEnter"
226
228
  @keydown.esc.stop="onEsc"
227
229
  @keydown.tab="closeSuggestions"
228
230
  />
@@ -231,8 +233,12 @@ export default {
231
233
  <ul
232
234
  v-show="showSuggestions && !userDismissedResults"
233
235
  :id="suggestionsId"
236
+ ref="suggestionsMenu"
234
237
  data-testid="combobox-dropdown"
235
- class="dropdown-menu dropdown-full-width show-dropdown gl-list-style-none gl-pl-0 gl-mb-0 gl-display-flex gl-flex-direction-column"
238
+ class="dropdown-menu gl-w-full gl-form-combobox-inner gl-list-style-none gl-pl-0 gl-mb-0 gl-display-flex gl-flex-direction-column"
239
+ @keydown.down="onArrowDown"
240
+ @keydown.up="onArrowUp"
241
+ @keydown.esc.stop="onEsc"
236
242
  >
237
243
  <li class="gl-overflow-y-auto show-dropdown">
238
244
  <ul class="gl-list-style-none gl-pl-0 gl-mb-0">
@@ -241,10 +247,10 @@ export default {
241
247
  ref="results"
242
248
  :key="i"
243
249
  role="option"
244
- :class="{ 'gl-bg-gray-50': i === arrowCounter }"
245
250
  :aria-selected="i === arrowCounter"
246
251
  tabindex="-1"
247
252
  @click="selectToken(result)"
253
+ @keydown.enter.native="selectToken(result)"
248
254
  >
249
255
  <!-- @slot The suggestion result item to display. -->
250
256
  <slot name="result" :item="result">{{ result }}</slot>
@@ -258,11 +264,11 @@ export default {
258
264
  v-for="(action, i) in actionList"
259
265
  :key="i + resultsLength"
260
266
  role="option"
261
- :class="{ 'gl-bg-gray-50': i + resultsLength === arrowCounter }"
262
267
  :aria-selected="i + resultsLength === arrowCounter"
263
268
  tabindex="-1"
264
269
  data-testid="combobox-action"
265
270
  @click="selectAction(action)"
271
+ @keydown.enter.native="selectAction(action)"
266
272
  >
267
273
  <!-- @slot The action item to display. -->
268
274
  <slot name="action" :item="action">{{ action.label }}</slot>
@@ -48,6 +48,7 @@ export default {
48
48
  return defaultClass;
49
49
  },
50
50
  hasLabelDescription() {
51
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
51
52
  return Boolean(this.labelDescription || this.$slots['label-description']);
52
53
  },
53
54
  },
@@ -67,6 +68,7 @@ export default {
67
68
  </gl-form-text>
68
69
  </template>
69
70
 
71
+ <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
70
72
  <template v-for="slot in Object.keys($slots)" #[slot]>
71
73
  <slot :name="slot"></slot>
72
74
  </template>
@@ -21,6 +21,7 @@ See: https://gitlab.com/gitlab-org/gitlab/issues/30055
21
21
  @include gl-border-none;
22
22
  @include gl-appearance-none;
23
23
  @include gl-text-truncate;
24
+ @include gl-bg-no-repeat;
24
25
  background-image: url($gl-icon-select-chevron-down);
25
26
 
26
27
  &:focus,
@@ -10,6 +10,7 @@ export default {
10
10
  </script>
11
11
  <template>
12
12
  <b-form-select class="gl-form-select" v-bind="$attrs" v-on="$listeners">
13
+ <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
13
14
  <template v-for="slot in Object.keys($slots)" #[slot]>
14
15
  <slot :name="slot"></slot>
15
16
  </template>
@@ -88,9 +88,11 @@ export default {
88
88
  },
89
89
  computed: {
90
90
  shouldRenderModalOk() {
91
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
91
92
  return Boolean(this.$slots['modal-ok']);
92
93
  },
93
94
  shouldRenderModalCancel() {
95
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
94
96
  return Boolean(this.$slots['modal-cancel']);
95
97
  },
96
98
  shouldRenderModalFooter() {
@@ -98,6 +100,7 @@ export default {
98
100
  this.actionCancel ||
99
101
  this.actionSecondary ||
100
102
  this.actionPrimary ||
103
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
101
104
  this.$slots['modal-footer']
102
105
  );
103
106
  },
@@ -37,6 +37,10 @@ table.gl-table {
37
37
  }
38
38
  }
39
39
 
40
+ caption {
41
+ @include gl-pt-2;
42
+ }
43
+
40
44
  @mixin gl-tmp-stacked-override {
41
45
  tbody > tr {
42
46
  &::after {
@@ -18,10 +18,16 @@ const tableItems = [
18
18
  },
19
19
  ];
20
20
 
21
- const generateProps = ({ fixed = false, footClone = false, stacked = false } = {}) => ({
21
+ const generateProps = ({
22
+ fixed = false,
23
+ footClone = false,
24
+ stacked = false,
25
+ caption = '',
26
+ } = {}) => ({
22
27
  fixed,
23
28
  footClone,
24
29
  stacked,
30
+ caption,
25
31
  });
26
32
 
27
33
  export const Default = (args, { argTypes }) => ({
@@ -37,7 +43,11 @@ export const Default = (args, { argTypes }) => ({
37
43
  hover
38
44
  selectable
39
45
  selected-variant="primary"
40
- />
46
+ >
47
+ <template v-if="caption" #table-caption>
48
+ {{ caption }}
49
+ </template>
50
+ </gl-table>
41
51
  `,
42
52
  fields: [
43
53
  {
@@ -48,6 +48,7 @@ export default {
48
48
  v-bind="$attrs"
49
49
  v-on="$listeners"
50
50
  >
51
+ <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
51
52
  <template v-for="slot in Object.keys($slots)" #[slot]>
52
53
  <slot :name="slot"></slot>
53
54
  </template>
@@ -107,6 +107,7 @@ export default {
107
107
  v-bind="passthroughAttrs"
108
108
  v-on="$listeners"
109
109
  >
110
+ <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
110
111
  <template v-for="slot in Object.keys($slots)" #[slot]>
111
112
  <slot :name="slot"></slot>
112
113
  </template>
@@ -221,6 +221,7 @@ export default {
221
221
  v-bind="$attrs"
222
222
  v-on="listeners"
223
223
  >
224
+ <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
224
225
  <template v-for="slot in Object.keys($slots)" #[slot]>
225
226
  <slot :name="slot"></slot>
226
227
  </template>
@@ -82,6 +82,7 @@ export default {
82
82
  },
83
83
  computed: {
84
84
  shouldRenderHelp() {
85
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
85
86
  return Boolean(this.$slots.help || this.help);
86
87
  },
87
88
  icon() {
@@ -3404,6 +3404,14 @@
3404
3404
  flex-basis: 0 !important;
3405
3405
  }
3406
3406
 
3407
+ .gl-flex-basis-third {
3408
+ flex-basis: 33%;
3409
+ }
3410
+
3411
+ .gl-flex-basis-third\! {
3412
+ flex-basis: 33% !important;
3413
+ }
3414
+
3407
3415
  .gl-flex-basis-half {
3408
3416
  flex-basis: 50%;
3409
3417
  }
@@ -179,6 +179,10 @@
179
179
  flex-basis: 0;
180
180
  }
181
181
 
182
+ @mixin gl-flex-basis-third {
183
+ flex-basis: 33%;
184
+ }
185
+
182
186
  @mixin gl-flex-basis-half {
183
187
  flex-basis: 50%;
184
188
  }