@gitlab/ui 59.1.1 → 59.3.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 (25) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/components/base/filtered_search/filtered_search.js +8 -5
  3. package/dist/components/base/filtered_search/filtered_search_suggestion.js +2 -1
  4. package/dist/components/base/filtered_search/filtered_search_suggestion_list.js +2 -2
  5. package/dist/components/base/filtered_search/filtered_search_term.js +1 -1
  6. package/dist/components/base/filtered_search/filtered_search_token_segment.js +2 -2
  7. package/dist/index.css +1 -1
  8. package/dist/index.css.map +1 -1
  9. package/dist/utility_classes.css +1 -1
  10. package/dist/utility_classes.css.map +1 -1
  11. package/package.json +5 -5
  12. package/src/components/base/button/button.scss +4 -0
  13. package/src/components/base/filtered_search/filtered_search.md +4 -3
  14. package/src/components/base/filtered_search/filtered_search.spec.js +13 -0
  15. package/src/components/base/filtered_search/filtered_search.stories.js +6 -1
  16. package/src/components/base/filtered_search/filtered_search.vue +8 -10
  17. package/src/components/base/filtered_search/filtered_search_suggestion.vue +1 -1
  18. package/src/components/base/filtered_search/filtered_search_suggestion_list.spec.js +11 -1
  19. package/src/components/base/filtered_search/filtered_search_suggestion_list.vue +2 -2
  20. package/src/components/base/filtered_search/filtered_search_term.spec.js +16 -1
  21. package/src/components/base/filtered_search/filtered_search_term.vue +2 -1
  22. package/src/components/base/filtered_search/filtered_search_token_segment.vue +2 -2
  23. package/src/components/charts/sparkline/sparkline.stories.js +2 -0
  24. package/src/scss/utilities.scss +36 -0
  25. package/src/scss/utility-mixins/sizing.scss +18 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "59.1.1",
3
+ "version": "59.3.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -85,12 +85,12 @@
85
85
  },
86
86
  "devDependencies": {
87
87
  "@arkweid/lefthook": "0.7.7",
88
- "@babel/core": "^7.21.3",
89
- "@babel/preset-env": "^7.20.2",
90
- "@gitlab/eslint-plugin": "18.2.0",
88
+ "@babel/core": "^7.21.4",
89
+ "@babel/preset-env": "^7.21.4",
90
+ "@gitlab/eslint-plugin": "18.3.0",
91
91
  "@gitlab/fonts": "^1.2.0",
92
92
  "@gitlab/stylelint-config": "4.1.0",
93
- "@gitlab/svgs": "3.31.0",
93
+ "@gitlab/svgs": "3.36.0",
94
94
  "@rollup/plugin-commonjs": "^11.1.0",
95
95
  "@rollup/plugin-node-resolve": "^7.1.3",
96
96
  "@rollup/plugin-replace": "^2.3.2",
@@ -337,6 +337,10 @@
337
337
  }
338
338
  }
339
339
 
340
+ &.btn-default-tertiary.selected {
341
+ box-shadow: inset 0 0 0 $gl-border-size-2 $gray-300;
342
+ }
343
+
340
344
  &.btn-sm {
341
345
  @include gl-py-2;
342
346
  @include gl-px-3;
@@ -20,9 +20,10 @@ Prepare array of available token configurations with the following fields:
20
20
  (e.g. `{ value: '=', description: 'is', default: 'true' }`)
21
21
  - `multiSelect`: (optional) when `true`, the suggestions list becomes multi-select instead of single-select.
22
22
  It is discouraged to use this together with `unique`, as `unique` is intended for single-select.
23
- - `options`: (optional) an array of options which the user can pick after the operator has been selected.
24
- The option object can have the following properties defined: `value`, `icon`, `text`, and `default`
25
- all of which are expected be of type `string`. If the `default` is omitted, the `value` of the first
23
+ - `options`: (optional) an array of options which the user can pick after the
24
+ operator has been selected. The option object can have the following
25
+ properties defined: `value: string`, `icon: string`, `title: string` and
26
+ `default: boolean`. If the `default` is omitted, the `value` of the first
26
27
  option will be displayed as a suggestion
27
28
  - any additional fields required to configure your component
28
29
 
@@ -709,6 +709,19 @@ describe('Filtered search integration tests', () => {
709
709
  });
710
710
  });
711
711
 
712
+ it('sets the gl-filtered-search-last-item class only when no token is active', async () => {
713
+ mountComponent({ value: ['one', 'two'] });
714
+ await nextTick();
715
+
716
+ expect(wrapper.findAll('.gl-filtered-search-last-item')).toHaveLength(1);
717
+
718
+ activate(0);
719
+
720
+ await nextTick();
721
+
722
+ expect(wrapper.findAll('.gl-filtered-search-last-item')).toHaveLength(0);
723
+ });
724
+
712
725
  it('does not render unique token in suggestions list if it is already present', async () => {
713
726
  mountComponent({ value: ['token', { type: 'unique', value: { data: 'something' } }] });
714
727
  activate(0);
@@ -118,7 +118,12 @@ const UserToken = {
118
118
  const MilestoneToken = {
119
119
  name: 'MilestoneToken',
120
120
  __v_skip: true /* temporary workaround for @vue/compat */,
121
- components: { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon },
121
+ components: {
122
+ GlFilteredSearchToken,
123
+ GlFilteredSearchSuggestion,
124
+ GlLoadingIcon,
125
+ GlDropdownDivider,
126
+ },
122
127
  props: ['value', 'active'],
123
128
  inheritAttrs: false,
124
129
  data() {
@@ -220,7 +220,7 @@ export default {
220
220
  },
221
221
 
222
222
  isLastToken(idx) {
223
- return !this.activeTokenIdx && idx === this.lastTokenIdx;
223
+ return this.activeTokenIdx === null && idx === this.lastTokenIdx;
224
224
  },
225
225
 
226
226
  isLastTokenEmpty() {
@@ -235,8 +235,11 @@ export default {
235
235
  return this.getTokenEntry(type)?.token || GlFilteredSearchTerm;
236
236
  },
237
237
 
238
- getLastTokenClassList(idx) {
239
- return this.isLastToken(idx) && !this.viewOnly ? 'gl-filtered-search-last-item' : '';
238
+ getTokenClassList(idx) {
239
+ return {
240
+ 'gl-filtered-search-item': true,
241
+ 'gl-filtered-search-last-item': this.isLastToken(idx) && !this.viewOnly,
242
+ };
240
243
  },
241
244
 
242
245
  activate(idx) {
@@ -315,11 +318,7 @@ export default {
315
318
  },
316
319
 
317
320
  createTokens(idx, newStrings = ['']) {
318
- if (
319
- this.activeTokenIdx !== this.lastTokenIdx &&
320
- newStrings.length === 1 &&
321
- newStrings[0] === ''
322
- ) {
321
+ if (!this.isLastTokenActive && newStrings.length === 1 && newStrings[0] === '') {
323
322
  this.activeTokenIdx = this.lastTokenIdx;
324
323
  return;
325
324
  }
@@ -394,8 +393,7 @@ export default {
394
393
  :search-input-attributes="searchInputAttributes"
395
394
  :view-only="viewOnly"
396
395
  :is-last-token="isLastToken(idx)"
397
- class="gl-filtered-search-item"
398
- :class="{ 'gl-filtered-search-last-item': isLastToken(idx) && !viewOnly }"
396
+ :class="getTokenClassList(idx)"
399
397
  @activate="activate(idx)"
400
398
  @deactivate="deactivate(token)"
401
399
  @destroy="destroyToken(idx, $event)"
@@ -26,7 +26,7 @@ export default {
26
26
  isActive(newValue) {
27
27
  if (newValue) {
28
28
  window.requestAnimationFrame(() => {
29
- this.$refs.item.$el.scrollIntoView({ block: 'nearest', inline: 'end' });
29
+ this.$refs.item?.$el?.scrollIntoView({ block: 'nearest', inline: 'end' });
30
30
  });
31
31
  }
32
32
  },
@@ -79,6 +79,7 @@ describe('Filtered search suggestion list component', () => {
79
79
  <div>
80
80
  <gl-filtered-search-suggestion value="One">One</gl-filtered-search-suggestion>
81
81
  <gl-filtered-search-suggestion value="Two">Two</gl-filtered-search-suggestion>
82
+ <gl-filtered-search-suggestion :value="false">Three</gl-filtered-search-suggestion>
82
83
  </div>
83
84
  `,
84
85
  };
@@ -123,6 +124,7 @@ describe('Filtered search suggestion list component', () => {
123
124
  wrapper.vm.nextItem();
124
125
  wrapper.vm.nextItem();
125
126
  wrapper.vm.nextItem();
127
+ wrapper.vm.nextItem();
126
128
  return wrapper.vm.$nextTick().then(() => {
127
129
  expect(wrapper.vm.getValue()).toBe(null);
128
130
  });
@@ -141,7 +143,7 @@ describe('Filtered search suggestion list component', () => {
141
143
  wrapper.vm.prevItem();
142
144
  wrapper.vm.prevItem();
143
145
  return wrapper.vm.$nextTick().then(() => {
144
- expect(wrapper.vm.getValue()).toBe('Two');
146
+ expect(wrapper.vm.getValue()).toBe(false);
145
147
  });
146
148
  });
147
149
 
@@ -150,6 +152,7 @@ describe('Filtered search suggestion list component', () => {
150
152
  wrapper.vm.nextItem();
151
153
  wrapper.vm.nextItem();
152
154
  wrapper.vm.nextItem();
155
+ wrapper.vm.nextItem();
153
156
  return wrapper.vm.$nextTick().then(() => {
154
157
  expect(wrapper.vm.getValue()).toBe('One');
155
158
  });
@@ -169,6 +172,13 @@ describe('Filtered search suggestion list component', () => {
169
172
  });
170
173
  });
171
174
 
175
+ it('highlights suggestion if initial-value is provided, regardless of falsiness', async () => {
176
+ wrapper.setProps({ initialValue: false });
177
+ return wrapper.vm.$nextTick().then(() => {
178
+ expect(wrapper.find('.gl-filtered-search-suggestion-active').text()).toBe('Three');
179
+ });
180
+ });
181
+
172
182
  it('does not highlight anything if initial-value matches nothing', () => {
173
183
  wrapper.setProps({ initialValue: 'missing' });
174
184
  return wrapper.vm.$nextTick().then(() => {
@@ -45,9 +45,9 @@ export default {
45
45
 
46
46
  methods: {
47
47
  valuesMatch(firstValue, secondValue) {
48
- if (!firstValue || !secondValue) return false;
48
+ if (firstValue == null || secondValue == null) return false;
49
49
 
50
- return typeof firstValue === 'string'
50
+ return typeof firstValue === 'string' && typeof secondValue === 'string'
51
51
  ? firstValue.toLowerCase() === secondValue.toLowerCase()
52
52
  : firstValue === secondValue;
53
53
  },
@@ -68,7 +68,7 @@ describe('Filtered search term', () => {
68
68
 
69
69
  it('renders input with placeholder if placeholder prop is provided', () => {
70
70
  createComponent({ placeholder: 'placeholder-stub' });
71
- expect(wrapper.find('input').attributes('placeholder')).toBe('placeholder-stub');
71
+ expect(findSearchInput().attributes('placeholder')).toBe('placeholder-stub');
72
72
  });
73
73
 
74
74
  it('filters suggestions by input', async () => {
@@ -145,6 +145,21 @@ describe('Filtered search term', () => {
145
145
  );
146
146
  });
147
147
 
148
+ it('activates and deactivates when the input is focused/blurred', async () => {
149
+ createComponent({ placeholder: 'placeholder-stub' });
150
+ await nextTick();
151
+
152
+ expect(wrapper.emitted()).toEqual({});
153
+
154
+ await findSearchInput().trigger('focusin');
155
+
156
+ expect(wrapper.emitted()).toEqual({ activate: [[]] });
157
+
158
+ await findSearchInput().trigger('focusout');
159
+
160
+ expect(wrapper.emitted()).toEqual({ activate: [[]], deactivate: [[]] });
161
+ });
162
+
148
163
  describe.each([true, false])('when `viewOnly` is %s', (viewOnly) => {
149
164
  beforeEach(() => {
150
165
  createComponent({ viewOnly, searchInputAttributes, placeholder: 'placeholder-stub' });
@@ -146,7 +146,6 @@ export default {
146
146
  class="gl-filtered-search-term-token"
147
147
  :active="active"
148
148
  :cursor-position="cursorPosition"
149
- :class="{ 'gl-w-full': placeholder }"
150
149
  :search-input-attributes="searchInputAttributes"
151
150
  :is-last-token="isLastToken"
152
151
  :current-value="currentValue"
@@ -181,6 +180,8 @@ export default {
181
180
  :aria-label="placeholder"
182
181
  :readonly="viewOnly"
183
182
  data-testid="filtered-search-term-input"
183
+ @focusin="$emit('activate')"
184
+ @focusout="$emit('deactivate')"
184
185
  />
185
186
 
186
187
  <template v-else>{{ value.data }}</template>
@@ -170,10 +170,10 @@ export default {
170
170
  return this.nonMultipleValue;
171
171
  }
172
172
  if (this.value) {
173
- const match =
173
+ const option =
174
174
  this.getMatchingOptionForInputValue(this.inputValue) ||
175
175
  this.getMatchingOptionForInputValue(this.inputValue, { loose: true });
176
- return match?.value;
176
+ return option?.value;
177
177
  }
178
178
 
179
179
  const defaultSuggestion = this.options.find((op) => op.default);
@@ -55,12 +55,14 @@ Default.args = generateProps();
55
55
 
56
56
  export const WithoutLastYValue = Template.bind({});
57
57
  WithoutLastYValue.args = generateProps({ showLastYValue: false });
58
+ WithoutLastYValue.parameters = { storyshots: { disable: true } };
58
59
 
59
60
  export const WithChartColorGradient = Template.bind({});
60
61
  WithChartColorGradient.args = generateProps({ gradient: customGradient });
61
62
 
62
63
  export const WithSmoothing = Template.bind({});
63
64
  WithSmoothing.args = generateProps({ smooth: 0.5 });
65
+ WithSmoothing.parameters = { storyshots: { disable: true } };
64
66
 
65
67
  export const AutoHeight = Template.bind({});
66
68
  Object.assign(AutoHeight, {
@@ -4984,6 +4984,42 @@
4984
4984
  }
4985
4985
  }
4986
4986
 
4987
+ .gl-md-w-15 {
4988
+ @include gl-media-breakpoint-up(md) {
4989
+ width: $gl-spacing-scale-15;
4990
+ }
4991
+ }
4992
+
4993
+ .gl-md-w-15\! {
4994
+ @include gl-media-breakpoint-up(md) {
4995
+ width: $gl-spacing-scale-15 !important;
4996
+ }
4997
+ }
4998
+
4999
+ .gl-md-w-20 {
5000
+ @include gl-media-breakpoint-up(md) {
5001
+ width: $gl-spacing-scale-20;
5002
+ }
5003
+ }
5004
+
5005
+ .gl-md-w-20\! {
5006
+ @include gl-media-breakpoint-up(md) {
5007
+ width: $gl-spacing-scale-20 !important;
5008
+ }
5009
+ }
5010
+
5011
+ .gl-md-w-30 {
5012
+ @include gl-media-breakpoint-up(md) {
5013
+ width: $gl-spacing-scale-30;
5014
+ }
5015
+ }
5016
+
5017
+ .gl-md-w-30\! {
5018
+ @include gl-media-breakpoint-up(md) {
5019
+ width: $gl-spacing-scale-30 !important;
5020
+ }
5021
+ }
5022
+
4987
5023
  .gl-md-w-half {
4988
5024
  @include gl-media-breakpoint-up(md) {
4989
5025
  width: 50%;
@@ -251,6 +251,24 @@
251
251
  }
252
252
  }
253
253
 
254
+ @mixin gl-md-w-15 {
255
+ @include gl-media-breakpoint-up(md) {
256
+ @include gl-w-15;
257
+ }
258
+ }
259
+
260
+ @mixin gl-md-w-20 {
261
+ @include gl-media-breakpoint-up(md) {
262
+ @include gl-w-20;
263
+ }
264
+ }
265
+
266
+ @mixin gl-md-w-30 {
267
+ @include gl-media-breakpoint-up(md) {
268
+ @include gl-w-30;
269
+ }
270
+ }
271
+
254
272
  @mixin gl-md-w-half {
255
273
  @include gl-media-breakpoint-up(md) {
256
274
  width: 50%;