@gitlab/ui 52.2.1 → 52.3.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.
- package/CHANGELOG.md +15 -0
- package/dist/components/base/new_dropdowns/listbox/listbox.js +13 -3
- package/dist/components/base/new_dropdowns/listbox/listbox_item.js +1 -1
- package/dist/components/base/new_dropdowns/listbox/listbox_search_input.js +93 -0
- package/dist/components/charts/chart/chart.js +18 -2
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/utils/charts/config.js +2 -1
- package/dist/utils/charts/theme.js +6 -1
- package/package.json +9 -9
- package/src/components/base/new_dropdowns/listbox/listbox.scss +48 -0
- package/src/components/base/new_dropdowns/listbox/listbox.spec.js +6 -6
- package/src/components/base/new_dropdowns/listbox/listbox.stories.js +8 -1
- package/src/components/base/new_dropdowns/listbox/listbox.vue +15 -4
- package/src/components/base/new_dropdowns/listbox/listbox_item.vue +1 -1
- package/src/components/base/new_dropdowns/listbox/listbox_search_input.spec.js +64 -0
- package/src/components/base/new_dropdowns/listbox/listbox_search_input.vue +76 -0
- package/src/components/charts/chart/chart.spec.js +17 -0
- package/src/components/charts/chart/chart.vue +24 -2
- package/src/scss/components.scss +1 -0
- package/src/utils/charts/config.js +1 -0
- package/src/utils/charts/theme.js +5 -0
|
@@ -15,6 +15,7 @@ const defaultFontSize = 12;
|
|
|
15
15
|
const defaultHeight = 400;
|
|
16
16
|
const defaultWidth = 300;
|
|
17
17
|
const validRenderers = ['canvas', 'svg'];
|
|
18
|
+
const toolboxHeight = 14;
|
|
18
19
|
const axes = {
|
|
19
20
|
name: 'Value',
|
|
20
21
|
type: 'value',
|
|
@@ -504,4 +505,4 @@ const getDefaultTooltipContent = function (params) {
|
|
|
504
505
|
};
|
|
505
506
|
};
|
|
506
507
|
|
|
507
|
-
export { annotationsYAxisCoords, axes, dataZoomAdjustments, defaultAreaOpacity, defaultChartOptions, defaultFontSize, defaultHeight, defaultWidth, generateAnnotationSeries, generateBarSeries, generateLineSeries, getAnnotationsConfig, getDataZoomConfig, getDefaultTooltipContent, getThresholdConfig, grid, gridWithSecondaryYAxis, lineStyle, mergeAnnotationAxisToOptions, mergeSeriesToOptions, parseAnnotations, symbolSize, validRenderers, xAxis, yAxis };
|
|
508
|
+
export { annotationsYAxisCoords, axes, dataZoomAdjustments, defaultAreaOpacity, defaultChartOptions, defaultFontSize, defaultHeight, defaultWidth, generateAnnotationSeries, generateBarSeries, generateLineSeries, getAnnotationsConfig, getDataZoomConfig, getDefaultTooltipContent, getThresholdConfig, grid, gridWithSecondaryYAxis, lineStyle, mergeAnnotationAxisToOptions, mergeSeriesToOptions, parseAnnotations, symbolSize, toolboxHeight, validRenderers, xAxis, yAxis };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { scrollHandleSvgPath } from '../svgs/svg_paths';
|
|
2
2
|
import { hexToRgba } from '../utils';
|
|
3
3
|
|
|
4
|
+
const white = '#fff';
|
|
4
5
|
const whiteNormal = '#f0f0f0';
|
|
5
6
|
const red500 = '#dd2b0e';
|
|
6
7
|
const gray50 = '#ececef';
|
|
@@ -42,6 +43,7 @@ const dataVizOrange700 = '#92430a';
|
|
|
42
43
|
const dataVizOrange800 = '#6f3500';
|
|
43
44
|
const dataVizOrange900 = '#5e2f05';
|
|
44
45
|
const dataVizOrange950 = '#4b2707';
|
|
46
|
+
const glBorderRadiusBase = '0.25rem';
|
|
45
47
|
|
|
46
48
|
const themeName = 'gitlab';
|
|
47
49
|
const heatmapHues = [gray100, dataVizBlue200, dataVizBlue400, dataVizBlue600, dataVizBlue800];
|
|
@@ -163,7 +165,10 @@ const createTheme = function () {
|
|
|
163
165
|
emphasis: {
|
|
164
166
|
iconStyle: {
|
|
165
167
|
borderWidth: 0,
|
|
166
|
-
color: gray900
|
|
168
|
+
color: gray900,
|
|
169
|
+
textBackgroundColor: white,
|
|
170
|
+
textBorderRadius: glBorderRadiusBase,
|
|
171
|
+
textPadding: [8, 12]
|
|
167
172
|
}
|
|
168
173
|
},
|
|
169
174
|
iconStyle: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "52.
|
|
3
|
+
"version": "52.3.1",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -91,14 +91,14 @@
|
|
|
91
91
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
92
92
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
93
93
|
"@rollup/plugin-replace": "^2.3.2",
|
|
94
|
-
"@storybook/addon-a11y": "6.5.
|
|
95
|
-
"@storybook/addon-docs": "6.5.
|
|
96
|
-
"@storybook/addon-essentials": "6.5.
|
|
97
|
-
"@storybook/addon-storyshots": "6.5.
|
|
98
|
-
"@storybook/addon-storyshots-puppeteer": "6.5.
|
|
99
|
-
"@storybook/addon-viewport": "6.5.
|
|
100
|
-
"@storybook/theming": "6.5.
|
|
101
|
-
"@storybook/vue": "6.5.
|
|
94
|
+
"@storybook/addon-a11y": "6.5.14",
|
|
95
|
+
"@storybook/addon-docs": "6.5.14",
|
|
96
|
+
"@storybook/addon-essentials": "6.5.14",
|
|
97
|
+
"@storybook/addon-storyshots": "6.5.14",
|
|
98
|
+
"@storybook/addon-storyshots-puppeteer": "6.5.14",
|
|
99
|
+
"@storybook/addon-viewport": "6.5.14",
|
|
100
|
+
"@storybook/theming": "6.5.14",
|
|
101
|
+
"@storybook/vue": "6.5.14",
|
|
102
102
|
"@vue/compat": "^3.2.40",
|
|
103
103
|
"@vue/compiler-sfc": "^3.2.40",
|
|
104
104
|
"@vue/test-utils": "1.3.0",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
$search-icon-size: 12px;
|
|
2
|
+
$clear-button-size: 24px;
|
|
3
|
+
|
|
4
|
+
.gl-listbox-search {
|
|
5
|
+
@include gl-relative;
|
|
6
|
+
|
|
7
|
+
.gl-listbox-search-input {
|
|
8
|
+
@include gl-w-full;
|
|
9
|
+
@include gl-line-height-normal;
|
|
10
|
+
@include gl-h-auto;
|
|
11
|
+
@include gl-border-none;
|
|
12
|
+
@include gl-pl-7;
|
|
13
|
+
padding-right: calc(#{$gl-spacing-scale-6} + #{$gl-spacing-scale-2});
|
|
14
|
+
@include gl-py-4;
|
|
15
|
+
@include gl-font-base;
|
|
16
|
+
|
|
17
|
+
&:focus {
|
|
18
|
+
@include gl-focus($inset: true);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&::placeholder {
|
|
22
|
+
@include gl-text-gray-400;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&::-webkit-search-cancel-button {
|
|
26
|
+
@include gl-display-none;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.gl-listbox-search-icon {
|
|
31
|
+
@include gl-absolute;
|
|
32
|
+
top: calc(50% - #{$search-icon-size} / 2);
|
|
33
|
+
left: $gl-spacing-scale-4;
|
|
34
|
+
@include gl-text-gray-500;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.gl-listbox-search-clear-button {
|
|
38
|
+
@include gl-absolute;
|
|
39
|
+
top: calc(50% - #{$clear-button-size} / 2);
|
|
40
|
+
right: $gl-spacing-scale-2;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.gl-listbox-item {
|
|
45
|
+
&:focus {
|
|
46
|
+
@include gl-focus($inset: true);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -248,25 +248,25 @@ describe('GlListbox', () => {
|
|
|
248
248
|
});
|
|
249
249
|
|
|
250
250
|
describe('when `searchable` is enabled', () => {
|
|
251
|
-
let
|
|
251
|
+
let searchboxInput;
|
|
252
252
|
|
|
253
253
|
beforeEach(() => {
|
|
254
254
|
buildWrapper({ items: mockOptions, searchable: true });
|
|
255
255
|
findBaseDropdown().vm.$emit(GL_DROPDOWN_SHOWN);
|
|
256
256
|
firstItem = findListItem(0);
|
|
257
|
-
|
|
257
|
+
searchboxInput = findSearchBox().find('input');
|
|
258
258
|
});
|
|
259
259
|
|
|
260
260
|
it('should move focus to the first item on search input `ARROW_DOWN`', async () => {
|
|
261
|
-
expect(
|
|
262
|
-
|
|
261
|
+
expect(searchboxInput.element).toHaveFocus();
|
|
262
|
+
searchboxInput.trigger('keydown', { code: ARROW_DOWN });
|
|
263
263
|
expect(firstItem.element).toHaveFocus();
|
|
264
264
|
});
|
|
265
265
|
|
|
266
266
|
it('should move focus to the search input on first item `ARROW_UP', async () => {
|
|
267
|
-
|
|
267
|
+
searchboxInput.trigger('keydown', { code: ARROW_DOWN });
|
|
268
268
|
firstItem.trigger('keydown', { code: ARROW_UP });
|
|
269
|
-
expect(
|
|
269
|
+
expect(searchboxInput.element).toHaveFocus();
|
|
270
270
|
});
|
|
271
271
|
});
|
|
272
272
|
});
|
|
@@ -29,6 +29,7 @@ const generateProps = ({
|
|
|
29
29
|
searchable = defaultValue('searchable'),
|
|
30
30
|
searching = defaultValue('searching'),
|
|
31
31
|
noResultsText = defaultValue('noResultsText'),
|
|
32
|
+
searchPlaceholder = defaultValue('searchPlaceholder'),
|
|
32
33
|
noCaret = defaultValue('noCaret'),
|
|
33
34
|
right = defaultValue('right'),
|
|
34
35
|
toggleText,
|
|
@@ -51,6 +52,7 @@ const generateProps = ({
|
|
|
51
52
|
searchable,
|
|
52
53
|
searching,
|
|
53
54
|
noResultsText,
|
|
55
|
+
searchPlaceholder,
|
|
54
56
|
noCaret,
|
|
55
57
|
right,
|
|
56
58
|
toggleText,
|
|
@@ -76,6 +78,7 @@ const makeBindings = (overrides = {}) =>
|
|
|
76
78
|
':searchable': 'searchable',
|
|
77
79
|
':searching': 'searching',
|
|
78
80
|
':no-results-text': 'noResultsText',
|
|
81
|
+
':search-placeholder': 'searchPlaceholder',
|
|
79
82
|
':no-caret': 'noCaret',
|
|
80
83
|
':right': 'right',
|
|
81
84
|
':toggle-text': 'toggleText',
|
|
@@ -437,7 +440,11 @@ export const Searchable = (args, { argTypes }) => ({
|
|
|
437
440
|
}
|
|
438
441
|
),
|
|
439
442
|
});
|
|
440
|
-
Searchable.args = generateProps({
|
|
443
|
+
Searchable.args = generateProps({
|
|
444
|
+
headerText: 'Assign to department',
|
|
445
|
+
searchable: true,
|
|
446
|
+
searchPlaceholder: 'Find department',
|
|
447
|
+
});
|
|
441
448
|
Searchable.decorators = [makeContainer({ height: '370px' })];
|
|
442
449
|
|
|
443
450
|
export const SearchableGroups = (args, { argTypes }) => ({
|
|
@@ -20,13 +20,14 @@ import GlLoadingIcon from '../../loading_icon/loading_icon.vue';
|
|
|
20
20
|
import GlSearchBoxByType from '../../search_box_by_type/search_box_by_type.vue';
|
|
21
21
|
import GlBaseDropdown from '../base_dropdown/base_dropdown.vue';
|
|
22
22
|
import GlListboxItem from './listbox_item.vue';
|
|
23
|
+
import GlListboxSearchInput from './listbox_search_input.vue';
|
|
23
24
|
import GlListboxGroup from './listbox_group.vue';
|
|
24
25
|
import { isOption, itemsValidator, flattenedOptions } from './utils';
|
|
25
26
|
|
|
26
27
|
export const ITEM_SELECTOR = '[role="option"]';
|
|
27
28
|
const HEADER_ITEMS_BORDER_CLASSES = ['gl-border-b-1', 'gl-border-b-solid', 'gl-border-b-gray-200'];
|
|
28
29
|
const GROUP_TOP_BORDER_CLASSES = ['gl-border-t', 'gl-pt-3', 'gl-mt-3'];
|
|
29
|
-
export const SEARCH_INPUT_SELECTOR = '.gl-search-
|
|
30
|
+
export const SEARCH_INPUT_SELECTOR = '.gl-listbox-search-input';
|
|
30
31
|
|
|
31
32
|
export default {
|
|
32
33
|
HEADER_ITEMS_BORDER_CLASSES,
|
|
@@ -40,6 +41,7 @@ export default {
|
|
|
40
41
|
GlListboxGroup,
|
|
41
42
|
GlButton,
|
|
42
43
|
GlSearchBoxByType,
|
|
44
|
+
GlListboxSearchInput,
|
|
43
45
|
GlLoadingIcon,
|
|
44
46
|
},
|
|
45
47
|
model: {
|
|
@@ -57,7 +59,7 @@ export default {
|
|
|
57
59
|
validator: itemsValidator,
|
|
58
60
|
},
|
|
59
61
|
/**
|
|
60
|
-
*
|
|
62
|
+
* Array of selected items values for multi-select and selected item value for single-select
|
|
61
63
|
*/
|
|
62
64
|
selected: {
|
|
63
65
|
type: [Array, String, Number],
|
|
@@ -221,6 +223,14 @@ export default {
|
|
|
221
223
|
required: false,
|
|
222
224
|
default: 'No results found',
|
|
223
225
|
},
|
|
226
|
+
/**
|
|
227
|
+
* Search input placeholder text and aria-label
|
|
228
|
+
*/
|
|
229
|
+
searchPlaceholder: {
|
|
230
|
+
type: String,
|
|
231
|
+
required: false,
|
|
232
|
+
default: 'Search',
|
|
233
|
+
},
|
|
224
234
|
/**
|
|
225
235
|
* The reset button's label, to be rendered in the header. If this is omitted, the button is not
|
|
226
236
|
* rendered.
|
|
@@ -484,7 +494,7 @@ export default {
|
|
|
484
494
|
>
|
|
485
495
|
<div
|
|
486
496
|
v-if="headerText"
|
|
487
|
-
class="gl-display-flex gl-align-items-center gl-
|
|
497
|
+
class="gl-display-flex gl-align-items-center gl-p-4! gl-min-h-8"
|
|
488
498
|
:class="$options.HEADER_ITEMS_BORDER_CLASSES"
|
|
489
499
|
>
|
|
490
500
|
<div
|
|
@@ -506,11 +516,12 @@ export default {
|
|
|
506
516
|
</div>
|
|
507
517
|
|
|
508
518
|
<div v-if="searchable" :class="$options.HEADER_ITEMS_BORDER_CLASSES">
|
|
509
|
-
<gl-search-
|
|
519
|
+
<gl-listbox-search-input
|
|
510
520
|
ref="searchBox"
|
|
511
521
|
v-model="searchStr"
|
|
512
522
|
:aria-owns="listboxId"
|
|
513
523
|
data-testid="listbox-search-input"
|
|
524
|
+
:placeholder="searchPlaceholder"
|
|
514
525
|
@input="search"
|
|
515
526
|
@keydown="onKeydown"
|
|
516
527
|
/>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { mount, shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
import ClearIcon from '~/components/shared_components/clear_icon_button/clear_icon_button.vue';
|
|
4
|
+
import ListboxSearchInput from './listbox_search_input.vue';
|
|
5
|
+
|
|
6
|
+
const modelEvent = ListboxSearchInput.model.event;
|
|
7
|
+
const newSearchValue = 'new value';
|
|
8
|
+
|
|
9
|
+
describe('listbox search input component', () => {
|
|
10
|
+
let wrapper;
|
|
11
|
+
const searchValue = 'some value';
|
|
12
|
+
|
|
13
|
+
const createComponent = ({ listeners, ...propsData }, mountFn = shallowMount) => {
|
|
14
|
+
wrapper = mountFn(ListboxSearchInput, { propsData, listeners });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const findClearIcon = () => wrapper.findComponent(ClearIcon);
|
|
18
|
+
const findInput = () => wrapper.findComponent({ ref: 'input' });
|
|
19
|
+
|
|
20
|
+
describe('clear icon component', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
createComponent({ value: searchValue });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('is not rendered when value is empty', () => {
|
|
26
|
+
createComponent({ value: '' });
|
|
27
|
+
|
|
28
|
+
expect(findClearIcon().exists()).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('is rendered when value is provided', () => {
|
|
32
|
+
expect(findClearIcon().exists()).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('emits empty value when clicked', () => {
|
|
36
|
+
findClearIcon().vm.$emit('click', { stopPropagation: jest.fn() });
|
|
37
|
+
|
|
38
|
+
expect(wrapper.emitted('input')).toEqual([['']]);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('v-model', () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
jest.useFakeTimers();
|
|
45
|
+
createComponent({ value: searchValue }, mount);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('syncs value prop to input value', async () => {
|
|
49
|
+
expect(findInput().element.value).toEqual(searchValue);
|
|
50
|
+
wrapper.setProps({ value: newSearchValue });
|
|
51
|
+
await nextTick();
|
|
52
|
+
|
|
53
|
+
expect(findInput().element.value).toEqual(newSearchValue);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it(`emits ${modelEvent} event when input value changes`, () => {
|
|
57
|
+
findInput().setValue(newSearchValue);
|
|
58
|
+
jest.advanceTimersByTime(199);
|
|
59
|
+
expect(wrapper.emitted('input')).toEqual(undefined);
|
|
60
|
+
jest.advanceTimersByTime(1);
|
|
61
|
+
expect(wrapper.emitted('input')).toEqual([[newSearchValue]]);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import debounce from 'lodash/debounce';
|
|
3
|
+
import GlClearIconButton from '../../../shared_components/clear_icon_button/clear_icon_button.vue';
|
|
4
|
+
import GlIcon from '../../icon/icon.vue';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
components: {
|
|
8
|
+
GlClearIconButton,
|
|
9
|
+
GlIcon,
|
|
10
|
+
},
|
|
11
|
+
model: {
|
|
12
|
+
prop: 'value',
|
|
13
|
+
event: 'input',
|
|
14
|
+
},
|
|
15
|
+
props: {
|
|
16
|
+
/**
|
|
17
|
+
* If provided, used as value of search input
|
|
18
|
+
*/
|
|
19
|
+
value: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: false,
|
|
22
|
+
default: '',
|
|
23
|
+
},
|
|
24
|
+
/**
|
|
25
|
+
* Search input placeholder text and aria-label
|
|
26
|
+
*/
|
|
27
|
+
placeholder: {
|
|
28
|
+
type: String,
|
|
29
|
+
required: false,
|
|
30
|
+
default: 'Search',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
computed: {
|
|
34
|
+
hasValue() {
|
|
35
|
+
return Boolean(this.value.length);
|
|
36
|
+
},
|
|
37
|
+
inputListeners() {
|
|
38
|
+
return {
|
|
39
|
+
...this.$listeners,
|
|
40
|
+
input: debounce((event) => {
|
|
41
|
+
this.$emit('input', event.target.value);
|
|
42
|
+
}, 200),
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
methods: {
|
|
47
|
+
clearInput() {
|
|
48
|
+
this.$emit('input', '');
|
|
49
|
+
this.focusInput();
|
|
50
|
+
},
|
|
51
|
+
focusInput() {
|
|
52
|
+
this.$refs.input.focus();
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div class="gl-listbox-search">
|
|
60
|
+
<gl-icon name="search-sm" :size="12" class="gl-listbox-search-icon" />
|
|
61
|
+
<input
|
|
62
|
+
ref="input"
|
|
63
|
+
type="search"
|
|
64
|
+
:value="value"
|
|
65
|
+
class="gl-listbox-search-input"
|
|
66
|
+
:aria-label="placeholder"
|
|
67
|
+
:placeholder="placeholder"
|
|
68
|
+
v-on="inputListeners"
|
|
69
|
+
/>
|
|
70
|
+
<gl-clear-icon-button
|
|
71
|
+
v-if="hasValue"
|
|
72
|
+
class="gl-listbox-search-clear-button"
|
|
73
|
+
@click.stop="clearInput"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { shallowMount } from '@vue/test-utils';
|
|
2
2
|
import * as echarts from 'echarts';
|
|
3
|
+
import { toolboxHeight } from '~/utils/charts/config';
|
|
3
4
|
import { createTheme } from '~/utils/charts/theme';
|
|
4
5
|
import { useMockResizeObserver } from '~helpers/mock_dom_observer';
|
|
5
6
|
import Chart from './chart.vue';
|
|
@@ -118,8 +119,11 @@ describe('chart component', () => {
|
|
|
118
119
|
|
|
119
120
|
describe('draw method', () => {
|
|
120
121
|
it('sets chart options', () => {
|
|
122
|
+
expect(wrapper.vm.chart.setOption).toHaveBeenCalledTimes(1);
|
|
123
|
+
|
|
121
124
|
wrapper.vm.draw();
|
|
122
125
|
|
|
126
|
+
expect(wrapper.vm.chart.setOption).toHaveBeenCalledTimes(2);
|
|
123
127
|
expect(wrapper.vm.chart.setOption).toHaveBeenCalledWith(options);
|
|
124
128
|
});
|
|
125
129
|
|
|
@@ -142,4 +146,17 @@ describe('chart component', () => {
|
|
|
142
146
|
});
|
|
143
147
|
});
|
|
144
148
|
});
|
|
149
|
+
|
|
150
|
+
describe('with toolbox in options', () => {
|
|
151
|
+
it('increases grid top by `toolboxHeight`', async () => {
|
|
152
|
+
const optionsWithToolbox = { toolbox: {} };
|
|
153
|
+
wrapper = shallowMount(Chart, { propsData: { options: optionsWithToolbox } });
|
|
154
|
+
await wrapper.vm.$nextTick();
|
|
155
|
+
|
|
156
|
+
expect(wrapper.vm.chart.setOption).toHaveBeenCalledWith({
|
|
157
|
+
...optionsWithToolbox,
|
|
158
|
+
grid: { top: toolboxHeight },
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
145
162
|
});
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<!-- eslint-disable vue/multi-word-component-names -->
|
|
2
2
|
<script>
|
|
3
3
|
import * as echarts from 'echarts';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
defaultHeight,
|
|
6
|
+
defaultWidth,
|
|
7
|
+
validRenderers,
|
|
8
|
+
toolboxHeight,
|
|
9
|
+
} from '../../../utils/charts/config';
|
|
5
10
|
import { createTheme, themeName } from '../../../utils/charts/theme';
|
|
6
11
|
import { GlResizeObserverDirective } from '../../../directives/resize_observer/resize_observer';
|
|
7
12
|
|
|
@@ -11,6 +16,16 @@ import { GlResizeObserverDirective } from '../../../directives/resize_observer/r
|
|
|
11
16
|
*/
|
|
12
17
|
const sizeValidator = (size) => Number.isFinite(size) || size === 'auto' || size == null;
|
|
13
18
|
|
|
19
|
+
const isChartWithToolbox = (options) => options?.toolbox !== undefined;
|
|
20
|
+
|
|
21
|
+
const increaseChartGridTop = (options, increaseBy) => ({
|
|
22
|
+
...options,
|
|
23
|
+
grid: {
|
|
24
|
+
...options.grid,
|
|
25
|
+
top: (options?.grid?.top || 0) + increaseBy,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
14
29
|
export default {
|
|
15
30
|
directives: {
|
|
16
31
|
resizeObserver: GlResizeObserverDirective,
|
|
@@ -78,6 +93,13 @@ export default {
|
|
|
78
93
|
chart: null,
|
|
79
94
|
};
|
|
80
95
|
},
|
|
96
|
+
computed: {
|
|
97
|
+
normalizedOptions() {
|
|
98
|
+
return isChartWithToolbox(this.options)
|
|
99
|
+
? increaseChartGridTop(this.options, toolboxHeight)
|
|
100
|
+
: this.options;
|
|
101
|
+
},
|
|
102
|
+
},
|
|
81
103
|
watch: {
|
|
82
104
|
options() {
|
|
83
105
|
if (this.chart) {
|
|
@@ -123,7 +145,7 @@ export default {
|
|
|
123
145
|
},
|
|
124
146
|
methods: {
|
|
125
147
|
draw() {
|
|
126
|
-
this.chart.setOption(this.
|
|
148
|
+
this.chart.setOption(this.normalizedOptions);
|
|
127
149
|
/**
|
|
128
150
|
* Emitted after calling `echarts.setOption`
|
|
129
151
|
*/
|
package/src/scss/components.scss
CHANGED
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
dataVizOrange800,
|
|
41
41
|
dataVizOrange950,
|
|
42
42
|
dataVizOrange900,
|
|
43
|
+
glBorderRadiusBase,
|
|
44
|
+
white,
|
|
43
45
|
} from '../../../scss_to_js/scss_variables';
|
|
44
46
|
import { scrollHandleSvgPath } from '../svgs/svg_paths';
|
|
45
47
|
import { hexToRgba } from '../utils';
|
|
@@ -209,6 +211,9 @@ export const createTheme = (options = {}) => ({
|
|
|
209
211
|
iconStyle: {
|
|
210
212
|
borderWidth: 0,
|
|
211
213
|
color: gray900,
|
|
214
|
+
textBackgroundColor: white,
|
|
215
|
+
textBorderRadius: glBorderRadiusBase,
|
|
216
|
+
textPadding: [8, 12],
|
|
212
217
|
},
|
|
213
218
|
},
|
|
214
219
|
iconStyle: {
|