@gitlab/ui 66.2.0 → 66.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 CHANGED
@@ -1,3 +1,17 @@
1
+ ## [66.3.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.3.0...v66.3.1) (2023-09-01)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **GlCollapsibleListbox:** show/hide select all and reset buttons for groups ([867e9c5](https://gitlab.com/gitlab-org/gitlab-ui/commit/867e9c5948f21a1bfb94502ac56f6248c6ab0b4a))
7
+
8
+ # [66.3.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.2.0...v66.3.0) (2023-08-30)
9
+
10
+
11
+ ### Features
12
+
13
+ * accept generic translations at configuration time ([c3e04c7](https://gitlab.com/gitlab-org/gitlab-ui/commit/c3e04c7b72db211bcf3b34c9c47dc42214d1b3db))
14
+
1
15
  # [66.2.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.1.0...v66.2.0) (2023-08-30)
2
16
 
3
17
 
package/dist/charts.js CHANGED
@@ -1,4 +1,3 @@
1
- import setConfigs from './config';
2
1
  export { default as GlChart } from './components/charts/chart/chart';
3
2
  export { default as GlAreaChart } from './components/charts/area/area';
4
3
  export { default as GlBarChart } from './components/charts/bar/bar';
@@ -13,6 +12,3 @@ export { default as GlStackedColumnChart } from './components/charts/stacked_col
13
12
  export { default as GlDiscreteScatterChart } from './components/charts/discrete_scatter/discrete_scatter';
14
13
  export { default as GlSingleStat } from './components/charts/single_stat/single_stat';
15
14
  export { default as GlSparklineChart } from './components/charts/sparkline/sparkline';
16
-
17
- // Add config files
18
- setConfigs();
@@ -401,7 +401,7 @@ var script = {
401
401
  return false;
402
402
  }
403
403
  if (this.multiple) {
404
- return this.selected.length === this.items.length;
404
+ return this.selected.length === this.flattenedOptions.length;
405
405
  }
406
406
  return Boolean(this.selected);
407
407
  },
@@ -420,7 +420,7 @@ var script = {
420
420
  if (!this.hasItems) {
421
421
  return false;
422
422
  }
423
- return this.selected.length !== this.items.length;
423
+ return this.selected.length !== this.flattenedOptions.length;
424
424
  },
425
425
  showIntersectionObserver() {
426
426
  return this.infiniteScroll && !this.infiniteScrollLoading && !this.loading && !this.searching;
@@ -63,6 +63,12 @@ const mockGroups = [{
63
63
  value: 'v2.1'
64
64
  }]
65
65
  }];
66
+ const mockGroupOptionsValues = mockGroups.map(group => group.options).flat().map(_ref2 => {
67
+ let {
68
+ value
69
+ } = _ref2;
70
+ return value;
71
+ });
66
72
  const mockGroupsWithTextSrOnly = [{
67
73
  text: 'Default',
68
74
  options: [{
@@ -109,4 +115,4 @@ const mockUsers = [{
109
115
  icon: 'bin'
110
116
  }];
111
117
 
112
- export { mockGroups, mockGroupsWithTextSrOnly, mockOptions, mockOptionsValues, mockUsers };
118
+ export { mockGroupOptionsValues, mockGroups, mockGroupsWithTextSrOnly, mockOptions, mockOptionsValues, mockUsers };
@@ -2,6 +2,7 @@ import GlClearIconButton from '../../shared_components/clear_icon_button/clear_i
2
2
  import GlFormInput from '../form/form_input/form_input';
3
3
  import GlIcon from '../icon/icon';
4
4
  import GlLoadingIcon from '../loading_icon/loading_icon';
5
+ import { translate } from '../../../utils/i18n';
5
6
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
6
7
 
7
8
  var script = {
@@ -34,7 +35,7 @@ var script = {
34
35
  clearButtonTitle: {
35
36
  type: String,
36
37
  required: false,
37
- default: 'Clear'
38
+ default: () => translate('GlSearchBoxByType.clearButtonTitle', 'Clear')
38
39
  },
39
40
  /**
40
41
  * If provided and true, disables the input and controls
@@ -65,7 +66,7 @@ var script = {
65
66
  inputAttributes() {
66
67
  const attributes = {
67
68
  type: 'search',
68
- placeholder: 'Search',
69
+ placeholder: translate('GlSearchBoxByType.input.placeholder', 'Search'),
69
70
  ...this.$attrs
70
71
  };
71
72
  if (!attributes['aria-label']) {
@@ -1,5 +1,6 @@
1
1
  import { GlTooltipDirective } from '../../../directives/tooltip';
2
2
  import GlButton from '../../base/button/button';
3
+ import { translate } from '../../../utils/i18n';
3
4
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
4
5
 
5
6
  var script = {
@@ -14,7 +15,7 @@ var script = {
14
15
  title: {
15
16
  type: String,
16
17
  required: false,
17
- default: 'Clear'
18
+ default: () => translate('ClearIconButton.title', 'Clear')
18
19
  },
19
20
  tooltipContainer: {
20
21
  required: false,
package/dist/config.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { BVConfigPlugin } from 'bootstrap-vue/esm/index.js';
2
2
  import Vue from 'vue';
3
+ import translationKeys from '../translations.json';
3
4
  import { tooltipDelay } from './utils/constants';
4
5
 
5
6
  const bFormTextGlobalConfig = {
@@ -28,11 +29,51 @@ try {
28
29
  } catch (e) {
29
30
  // localStorage doesn't exist (or the value is not properly formatted)
30
31
  }
31
- const setConfigs = () => {
32
+ const i18n = translationKeys;
33
+ let configured = false;
34
+
35
+ /**
36
+ * Set GitLab UI configuration.
37
+ *
38
+ * @typedef {object} GitLabUIConfiguration
39
+ * @template TValue=string
40
+ * @property {undefined | Object} translations Generic translations for component labels to fall back to.
41
+ * @property {boolean} disableTranslations Whether translation capabilities should be disabled. Suppresses the warning about missing translations.
42
+ */
43
+ const setConfigs = function () {
44
+ let {
45
+ translations
46
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
47
+ if (configured) {
48
+ if (process.env.NODE_ENV === 'development') {
49
+ throw new Error('GitLab UI can only be configured once!');
50
+ }
51
+ return;
52
+ }
53
+ configured = true;
32
54
  Vue.use(BVConfigPlugin, {
33
55
  BFormText: bFormTextGlobalConfig,
34
56
  BTooltip: tooltipGlobalConfig
35
57
  });
58
+ if (typeof translations === 'object') {
59
+ if (process.env.NODE_ENV === 'development') {
60
+ const undefinedTranslationKeys = Object.keys(i18n).reduce((acc, current) => {
61
+ if (!(current in translations)) {
62
+ acc.push(current);
63
+ }
64
+ return acc;
65
+ }, []);
66
+ if (undefinedTranslationKeys.length) {
67
+ /* eslint-disable no-console */
68
+ console.warn('[@gitlab/ui] The following translations have not been given, so will fall back to their default US English strings:');
69
+ console.table(undefinedTranslationKeys);
70
+ /* eslint-enable no-console */
71
+ }
72
+ }
73
+
74
+ Object.assign(i18n, translations);
75
+ }
36
76
  };
37
77
 
38
78
  export default setConfigs;
79
+ export { i18n };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 30 Aug 2023 05:58:01 GMT
3
+ * Generated on Fri, 01 Sep 2023 14:28:31 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 30 Aug 2023 05:58:01 GMT
3
+ * Generated on Fri, 01 Sep 2023 14:28:31 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 30 Aug 2023 05:58:01 GMT
3
+ * Generated on Fri, 01 Sep 2023 14:28:31 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#fff";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 30 Aug 2023 05:58:01 GMT
3
+ * Generated on Fri, 01 Sep 2023 14:28:31 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#000";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Wed, 30 Aug 2023 05:58:01 GMT
3
+ // Generated on Fri, 01 Sep 2023 14:28:31 GMT
4
4
 
5
5
  $red-950: #fff4f3;
6
6
  $red-900: #fcf1ef;
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Wed, 30 Aug 2023 05:58:01 GMT
3
+ // Generated on Fri, 01 Sep 2023 14:28:31 GMT
4
4
 
5
5
  $brand-gray-05: #2b2838 !default;
6
6
  $brand-gray-04: #45424d !default;
@@ -0,0 +1,15 @@
1
+ import { i18n } from '../config';
2
+
3
+ /**
4
+ * Mark a label as translatable.
5
+ *
6
+ * @param {string} key Translation key to be leveraged by the consumer to provide a generic translation at configuration time.
7
+ * @param {string} defaultValue A fallback value to be relied on if the consumer doesn't have translation capabilities.
8
+ * @returns {string} The translated label.
9
+ */
10
+ const translate = (key, defaultValue) => {
11
+ var _i18n$key;
12
+ return (_i18n$key = i18n[key]) !== null && _i18n$key !== void 0 ? _i18n$key : defaultValue;
13
+ };
14
+
15
+ export { translate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "66.2.0",
3
+ "version": "66.3.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,8 @@
17
17
  "files": [
18
18
  "src",
19
19
  "dist",
20
- "scss_to_js"
20
+ "scss_to_js",
21
+ "translations.json"
21
22
  ],
22
23
  "scripts": {
23
24
  "build": "NODE_ENV=production rollup -c",
@@ -60,7 +61,8 @@
60
61
  "markdownlint:fix": "yarn markdownlint --fix",
61
62
  "lint": "run-p prettier eslint stylelint markdownlint",
62
63
  "lint:fix": "run-s prettier:fix eslint:fix stylelint:fix markdownlint:fix",
63
- "generate:component": "plop"
64
+ "generate:component": "plop",
65
+ "translations:collect": "make translations.json"
64
66
  },
65
67
  "dependencies": {
66
68
  "@floating-ui/dom": "1.2.9",
@@ -92,22 +94,22 @@
92
94
  "@gitlab/eslint-plugin": "19.0.0",
93
95
  "@gitlab/fonts": "^1.2.0",
94
96
  "@gitlab/stylelint-config": "5.0.0",
95
- "@gitlab/svgs": "3.60.0",
97
+ "@gitlab/svgs": "3.61.0",
96
98
  "@rollup/plugin-commonjs": "^11.1.0",
97
99
  "@rollup/plugin-node-resolve": "^7.1.3",
98
100
  "@rollup/plugin-replace": "^2.3.2",
99
- "@storybook/addon-a11y": "7.3.2",
100
- "@storybook/addon-docs": "7.3.2",
101
- "@storybook/addon-essentials": "7.3.2",
102
- "@storybook/addon-storyshots": "7.3.2",
103
- "@storybook/addon-storyshots-puppeteer": "7.3.2",
104
- "@storybook/addon-viewport": "7.3.2",
105
- "@storybook/builder-webpack5": "7.3.2",
106
- "@storybook/theming": "7.3.2",
107
- "@storybook/vue": "7.3.2",
108
- "@storybook/vue-webpack5": "7.3.2",
109
- "@storybook/vue3": "7.3.2",
110
- "@storybook/vue3-webpack5": "7.3.2",
101
+ "@storybook/addon-a11y": "7.4.0",
102
+ "@storybook/addon-docs": "7.4.0",
103
+ "@storybook/addon-essentials": "7.4.0",
104
+ "@storybook/addon-storyshots": "7.4.0",
105
+ "@storybook/addon-storyshots-puppeteer": "7.4.0",
106
+ "@storybook/addon-viewport": "7.4.0",
107
+ "@storybook/builder-webpack5": "7.4.0",
108
+ "@storybook/theming": "7.4.0",
109
+ "@storybook/vue": "7.4.0",
110
+ "@storybook/vue-webpack5": "7.4.0",
111
+ "@storybook/vue3": "7.4.0",
112
+ "@storybook/vue3-webpack5": "7.4.0",
111
113
  "@vue/compat": "^3.2.40",
112
114
  "@vue/compiler-sfc": "^3.2.40",
113
115
  "@vue/test-utils": "1.3.0",
@@ -126,7 +128,7 @@
126
128
  "eslint-import-resolver-jest": "3.0.2",
127
129
  "eslint-plugin-cypress": "2.14.0",
128
130
  "eslint-plugin-storybook": "0.6.13",
129
- "glob": "^7.2.0",
131
+ "glob": "10.3.3",
130
132
  "identity-obj-proxy": "^3.0.0",
131
133
  "inquirer-select-directory": "^1.2.0",
132
134
  "jest": "^29.6.4",
@@ -156,7 +158,7 @@
156
158
  "sass-loader": "^10.2.0",
157
159
  "sass-true": "^6.1.0",
158
160
  "start-server-and-test": "^1.10.6",
159
- "storybook": "7.3.2",
161
+ "storybook": "7.4.0",
160
162
  "storybook-dark-mode": "3.0.1",
161
163
  "style-dictionary": "^3.8.0",
162
164
  "stylelint": "15.10.2",
package/src/charts.js CHANGED
@@ -1,8 +1,3 @@
1
- // Add config files
2
- import setConfigs from './config';
3
-
4
- setConfigs();
5
-
6
1
  export { default as GlChart } from './components/charts/chart/chart.vue';
7
2
  export { default as GlAreaChart } from './components/charts/area/area.vue';
8
3
  export { default as GlBarChart } from './components/charts/bar/bar.vue';
@@ -18,7 +18,13 @@ import GlIntersectionObserver from '../../../utilities/intersection_observer/int
18
18
  import GlCollapsibleListbox, { ITEM_SELECTOR } from './listbox.vue';
19
19
  import GlListboxItem from './listbox_item.vue';
20
20
  import GlListboxGroup from './listbox_group.vue';
21
- import { mockOptions, mockOptionsValues, mockGroups, mockGroupsWithTextSrOnly } from './mock_data';
21
+ import {
22
+ mockOptions,
23
+ mockOptionsValues,
24
+ mockGroups,
25
+ mockGroupOptionsValues,
26
+ mockGroupsWithTextSrOnly,
27
+ } from './mock_data';
22
28
 
23
29
  jest.mock('@floating-ui/dom');
24
30
  autoUpdate.mockImplementation(() => {
@@ -556,6 +562,26 @@ describe('GlCollapsibleListbox', () => {
556
562
  expect(wrapper.emitted('reset')).toHaveLength(1);
557
563
  expect(wrapper.vm.closeAndFocus).not.toHaveBeenCalled();
558
564
  });
565
+
566
+ describe('with groups', () => {
567
+ it.each`
568
+ description | props
569
+ ${'multi-select'} | ${{ multiple: true, selected: mockGroupOptionsValues }}
570
+ ${'single-select'} | ${{ multiple: false, selected: mockGroups[0].options[0].value }}
571
+ `(
572
+ 'shows the button if the label is provided and the selection is complete in $description mode',
573
+ ({ props }) => {
574
+ buildWrapper({
575
+ headerText: 'Select assignee',
576
+ resetButtonLabel: 'Unassign',
577
+ items: mockGroups,
578
+ ...props,
579
+ });
580
+
581
+ expect(findResetButton().exists()).toBe(true);
582
+ }
583
+ );
584
+ });
559
585
  });
560
586
 
561
587
  describe('with select all action', () => {
@@ -663,6 +689,21 @@ describe('GlCollapsibleListbox', () => {
663
689
 
664
690
  expect(wrapper.emitted('select-all')).toHaveLength(1);
665
691
  });
692
+
693
+ describe('with groups', () => {
694
+ it('hides select all button if all items are selected', () => {
695
+ buildWrapper({
696
+ headerText: 'Select assignee',
697
+ resetButtonLabel: 'Unassign',
698
+ showSelectAllButtonLabel: 'Select All',
699
+ selected: mockGroupOptionsValues,
700
+ items: mockGroups,
701
+ multiple: true,
702
+ });
703
+
704
+ expect(findSelectAllButton().exists()).toBe(false);
705
+ });
706
+ });
666
707
  });
667
708
 
668
709
  describe('when `infiniteScroll` prop is `true`', () => {
@@ -412,7 +412,7 @@ export default {
412
412
  }
413
413
 
414
414
  if (this.multiple) {
415
- return this.selected.length === this.items.length;
415
+ return this.selected.length === this.flattenedOptions.length;
416
416
  }
417
417
  return Boolean(this.selected);
418
418
  },
@@ -433,7 +433,7 @@ export default {
433
433
  return false;
434
434
  }
435
435
 
436
- return this.selected.length !== this.items.length;
436
+ return this.selected.length !== this.flattenedOptions.length;
437
437
  },
438
438
  showIntersectionObserver() {
439
439
  return this.infiniteScroll && !this.infiniteScrollLoading && !this.loading && !this.searching;
@@ -69,6 +69,11 @@ export const mockGroups = [
69
69
  },
70
70
  ];
71
71
 
72
+ export const mockGroupOptionsValues = mockGroups
73
+ .map((group) => group.options)
74
+ .flat()
75
+ .map(({ value }) => value);
76
+
72
77
  export const mockGroupsWithTextSrOnly = [
73
78
  {
74
79
  text: 'Default',
@@ -3,6 +3,7 @@ import GlClearIconButton from '../../shared_components/clear_icon_button/clear_i
3
3
  import GlFormInput from '../form/form_input/form_input.vue';
4
4
  import GlIcon from '../icon/icon.vue';
5
5
  import GlLoadingIcon from '../loading_icon/loading_icon.vue';
6
+ import { translate } from '../../../utils/i18n';
6
7
 
7
8
  export default {
8
9
  name: 'GlSearchboxByType',
@@ -34,7 +35,7 @@ export default {
34
35
  clearButtonTitle: {
35
36
  type: String,
36
37
  required: false,
37
- default: 'Clear',
38
+ default: () => translate('GlSearchBoxByType.clearButtonTitle', 'Clear'),
38
39
  },
39
40
  /**
40
41
  * If provided and true, disables the input and controls
@@ -66,7 +67,7 @@ export default {
66
67
  inputAttributes() {
67
68
  const attributes = {
68
69
  type: 'search',
69
- placeholder: 'Search',
70
+ placeholder: translate('GlSearchBoxByType.input.placeholder', 'Search'),
70
71
  ...this.$attrs,
71
72
  };
72
73
 
@@ -1,6 +1,7 @@
1
1
  <script>
2
2
  import { GlTooltipDirective } from '../../../directives/tooltip';
3
3
  import GlButton from '../../base/button/button.vue';
4
+ import { translate } from '../../../utils/i18n';
4
5
 
5
6
  export default {
6
7
  name: 'ClearIconButton',
@@ -14,7 +15,7 @@ export default {
14
15
  title: {
15
16
  type: String,
16
17
  required: false,
17
- default: 'Clear',
18
+ default: () => translate('ClearIconButton.title', 'Clear'),
18
19
  },
19
20
  tooltipContainer: {
20
21
  required: false,
package/src/config.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { BVConfigPlugin } from 'bootstrap-vue';
2
2
  import Vue from 'vue';
3
+ import translationKeys from '../translations.json';
3
4
  import { tooltipDelay } from './utils/constants';
4
5
 
5
6
  const bFormTextGlobalConfig = {
@@ -31,11 +32,53 @@ try {
31
32
  // localStorage doesn't exist (or the value is not properly formatted)
32
33
  }
33
34
 
34
- const setConfigs = () => {
35
+ export const i18n = translationKeys;
36
+
37
+ let configured = false;
38
+
39
+ /**
40
+ * Set GitLab UI configuration.
41
+ *
42
+ * @typedef {object} GitLabUIConfiguration
43
+ * @template TValue=string
44
+ * @property {undefined | Object} translations Generic translations for component labels to fall back to.
45
+ * @property {boolean} disableTranslations Whether translation capabilities should be disabled. Suppresses the warning about missing translations.
46
+ */
47
+ const setConfigs = ({ translations } = {}) => {
48
+ if (configured) {
49
+ if (process.env.NODE_ENV === 'development') {
50
+ throw new Error('GitLab UI can only be configured once!');
51
+ }
52
+
53
+ return;
54
+ }
55
+ configured = true;
56
+
35
57
  Vue.use(BVConfigPlugin, {
36
58
  BFormText: bFormTextGlobalConfig,
37
59
  BTooltip: tooltipGlobalConfig,
38
60
  });
61
+
62
+ if (typeof translations === 'object') {
63
+ if (process.env.NODE_ENV === 'development') {
64
+ const undefinedTranslationKeys = Object.keys(i18n).reduce((acc, current) => {
65
+ if (!(current in translations)) {
66
+ acc.push(current);
67
+ }
68
+ return acc;
69
+ }, []);
70
+ if (undefinedTranslationKeys.length) {
71
+ /* eslint-disable no-console */
72
+ console.warn(
73
+ '[@gitlab/ui] The following translations have not been given, so will fall back to their default US English strings:'
74
+ );
75
+ console.table(undefinedTranslationKeys);
76
+ /* eslint-enable no-console */
77
+ }
78
+ }
79
+
80
+ Object.assign(i18n, translations);
81
+ }
39
82
  };
40
83
 
41
84
  export default setConfigs;
@@ -0,0 +1,10 @@
1
+ import { i18n } from '../config';
2
+
3
+ /**
4
+ * Mark a label as translatable.
5
+ *
6
+ * @param {string} key Translation key to be leveraged by the consumer to provide a generic translation at configuration time.
7
+ * @param {string} defaultValue A fallback value to be relied on if the consumer doesn't have translation capabilities.
8
+ * @returns {string} The translated label.
9
+ */
10
+ export const translate = (key, defaultValue) => i18n[key] ?? defaultValue;
@@ -0,0 +1,5 @@
1
+ {
2
+ "ClearIconButton.title": "Clear",
3
+ "GlSearchBoxByType.clearButtonTitle": "Clear",
4
+ "GlSearchBoxByType.input.placeholder": "Search"
5
+ }