@gitlab/ui 37.0.0 → 37.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 (95) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/components/base/drawer/drawer.documentation.js +1 -1
  3. package/dist/components/base/form/form_input_group/form_input_group.documentation.js +2 -26
  4. package/dist/components/base/form/form_input_group/form_input_group.js +7 -0
  5. package/dist/components/base/loading_icon/loading_icon.js +1 -1
  6. package/dist/components/base/pagination/pagination.js +23 -2
  7. package/dist/components/base/path/data.js +2 -1
  8. package/dist/components/base/path/path.js +14 -7
  9. package/dist/components/base/tabs/tab/tab.js +4 -0
  10. package/dist/components/base/tabs/tabs/tabs.documentation.js +2 -14
  11. package/dist/components/base/tabs/tabs/tabs.js +8 -0
  12. package/dist/components/charts/legend/legend.js +12 -0
  13. package/dist/directives/safe_html/safe_html.documentation.js +1 -1
  14. package/dist/directives/safe_link/safe_link.documentation.js +2 -3
  15. package/dist/index.css +1 -1
  16. package/dist/index.css.map +1 -1
  17. package/dist/utility_classes.css +1 -1
  18. package/dist/utility_classes.css.map +1 -1
  19. package/documentation/components_documentation.js +0 -2
  20. package/documentation/documented_stories.js +6 -0
  21. package/package.json +1 -1
  22. package/src/components/base/drawer/drawer.documentation.js +1 -1
  23. package/src/components/base/form/form_input_group/form_input_group.documentation.js +0 -28
  24. package/src/components/base/form/form_input_group/form_input_group.md +0 -4
  25. package/src/components/base/form/form_input_group/form_input_group.stories.js +84 -62
  26. package/src/components/base/form/form_input_group/form_input_group.vue +9 -0
  27. package/src/components/base/loading_icon/loading_icon.vue +1 -1
  28. package/src/components/base/pagination/pagination.spec.js +12 -2
  29. package/src/components/base/pagination/pagination.vue +23 -6
  30. package/src/components/base/path/__snapshots__/path.spec.js.snap +6 -0
  31. package/src/components/base/path/data.js +1 -0
  32. package/src/components/base/path/path.scss +6 -1
  33. package/src/components/base/path/path.spec.js +20 -0
  34. package/src/components/base/path/path.vue +18 -7
  35. package/src/components/base/tabs/tab/tab.vue +4 -0
  36. package/src/components/base/tabs/tabs/tabs.documentation.js +0 -12
  37. package/src/components/base/tabs/tabs/tabs.md +2 -7
  38. package/src/components/base/tabs/tabs/tabs.stories.js +219 -161
  39. package/src/components/base/tabs/tabs/tabs.vue +6 -0
  40. package/src/components/charts/legend/legend.stories.js +22 -15
  41. package/src/components/charts/legend/legend.vue +9 -0
  42. package/src/directives/safe_html/safe_html.md +0 -4
  43. package/src/directives/safe_html/safe_html.stories.js +53 -44
  44. package/src/directives/safe_link/safe_link.documentation.js +0 -1
  45. package/src/directives/safe_link/safe_link.md +0 -4
  46. package/src/directives/safe_link/safe_link.stories.js +31 -30
  47. package/src/scss/utilities.scss +8 -0
  48. package/src/scss/utility-mixins/background.scss +4 -0
  49. package/dist/components/base/form/form_input_group/examples/form_input_group.basic.example.js +0 -38
  50. package/dist/components/base/form/form_input_group/examples/form_input_group.predefined_options.example.js +0 -54
  51. package/dist/components/base/form/form_input_group/examples/form_input_group.predefined_reactive.example.js +0 -55
  52. package/dist/components/base/form/form_input_group/examples/form_input_group.reactive.example.js +0 -48
  53. package/dist/components/base/form/form_input_group/examples/index.js +0 -27
  54. package/dist/components/base/tabs/tab/examples/index.js +0 -13
  55. package/dist/components/base/tabs/tab/examples/tab.basic.example.js +0 -38
  56. package/dist/components/base/tabs/tab/tab.documentation.js +0 -18
  57. package/dist/components/base/tabs/tabs/examples/index.js +0 -60
  58. package/dist/components/base/tabs/tabs/examples/tabs.basic.example.js +0 -38
  59. package/dist/components/base/tabs/tabs/examples/tabs.contentless_tab.example.js +0 -38
  60. package/dist/components/base/tabs/tabs/examples/tabs.counterbadges.example.js +0 -38
  61. package/dist/components/base/tabs/tabs/examples/tabs.custom_title.example.js +0 -38
  62. package/dist/components/base/tabs/tabs/examples/tabs.disabled.example.js +0 -38
  63. package/dist/components/base/tabs/tabs/examples/tabs.justified.example.js +0 -38
  64. package/dist/components/base/tabs/tabs/examples/tabs.no_tabs.example.js +0 -38
  65. package/dist/components/base/tabs/tabs/examples/tabs.scrollable.example.js +0 -47
  66. package/dist/components/base/tabs/tabs/examples/tabs.styles_only.example.js +0 -38
  67. package/dist/components/charts/legend/examples/index.js +0 -22
  68. package/dist/components/charts/legend/examples/legend.basic.example.js +0 -93
  69. package/dist/components/charts/legend/examples/legend.toggled.example.js +0 -100
  70. package/dist/components/charts/legend/legend.documentation.js +0 -21
  71. package/src/components/base/form/form_input_group/examples/form_input_group.basic.example.vue +0 -10
  72. package/src/components/base/form/form_input_group/examples/form_input_group.predefined_options.example.vue +0 -25
  73. package/src/components/base/form/form_input_group/examples/form_input_group.predefined_reactive.example.vue +0 -32
  74. package/src/components/base/form/form_input_group/examples/form_input_group.reactive.example.vue +0 -25
  75. package/src/components/base/form/form_input_group/examples/index.js +0 -32
  76. package/src/components/base/tabs/tab/examples/index.js +0 -15
  77. package/src/components/base/tabs/tab/examples/tab.basic.example.vue +0 -5
  78. package/src/components/base/tabs/tab/tab.documentation.js +0 -16
  79. package/src/components/base/tabs/tab/tab.md +0 -3
  80. package/src/components/base/tabs/tab/tab.stories.js +0 -12
  81. package/src/components/base/tabs/tabs/examples/index.js +0 -72
  82. package/src/components/base/tabs/tabs/examples/tabs.basic.example.vue +0 -6
  83. package/src/components/base/tabs/tabs/examples/tabs.contentless_tab.example.vue +0 -17
  84. package/src/components/base/tabs/tabs/examples/tabs.counterbadges.example.vue +0 -28
  85. package/src/components/base/tabs/tabs/examples/tabs.custom_title.example.vue +0 -9
  86. package/src/components/base/tabs/tabs/examples/tabs.disabled.example.vue +0 -7
  87. package/src/components/base/tabs/tabs/examples/tabs.justified.example.vue +0 -6
  88. package/src/components/base/tabs/tabs/examples/tabs.no_tabs.example.vue +0 -8
  89. package/src/components/base/tabs/tabs/examples/tabs.scrollable.example.vue +0 -20
  90. package/src/components/base/tabs/tabs/examples/tabs.styles_only.example.vue +0 -22
  91. package/src/components/charts/legend/examples/index.js +0 -27
  92. package/src/components/charts/legend/examples/legend.basic.example.vue +0 -55
  93. package/src/components/charts/legend/examples/legend.toggled.example.vue +0 -60
  94. package/src/components/charts/legend/legend.documentation.js +0 -19
  95. package/src/components/charts/legend/legend.md +0 -0
@@ -16,7 +16,6 @@ export { default as GlNavbarDocumentation } from '../src/components/base/navbar/
16
16
  export { default as GlIntersectionObserverDocumentation } from '../src/components/utilities/intersection_observer/intersection_observer.documentation';
17
17
  export { default as GlChartDocumentation } from '../src/components/charts/chart/chart.documentation';
18
18
  export { default as GlAreaChartDocumentation } from '../src/components/charts/area/area.documentation';
19
- export { default as GlChartLegendDocumentation } from '../src/components/charts/legend/legend.documentation';
20
19
  export { default as GlLineChartDocumentation } from '../src/components/charts/line/line.documentation';
21
20
  export { default as GlSparklineChartDocumentation } from '../src/components/charts/sparkline/sparkline.documentation';
22
21
  export { default as GlChartSeriesLabelDocumentation } from '../src/components/charts/series_label/series_label.documentation';
@@ -64,7 +63,6 @@ export { default as GlTableDocumentation } from '../src/components/base/table/ta
64
63
  export { default as GlBreadcrumbDocumentation } from '../src/components/base/breadcrumb/breadcrumb.documentation';
65
64
  export { default as GlHeatmapDocumentation } from '../src/components/charts/heatmap/heatmap.documentation';
66
65
  export { default as GlTabsDocumentation } from '../src/components/base/tabs/tabs/tabs.documentation';
67
- export { default as GlTabDocumentation } from '../src/components/base/tabs/tab/tab.documentation';
68
66
  export { default as GlButtonGroupDocumentation } from '../src/components/base/button_group/button_group.documentation';
69
67
  export { default as GlFormCheckboxDocumentation } from '../src/components/base/form/form_checkbox/form_checkbox.documentation';
70
68
  export { default as GlLabelDocumentation } from '../src/components/base/label/label.documentation';
@@ -77,6 +77,7 @@ export const setupStorybookReadme = () =>
77
77
  'GlSorting',
78
78
  'GlSortingItem',
79
79
  'GlIcon',
80
+ 'GlSafeLinkDirective',
80
81
  'GlDashboardSkeleton',
81
82
  'GlToggle',
82
83
  'GlNavbar',
@@ -102,6 +103,7 @@ export const setupStorybookReadme = () =>
102
103
  'GlFormInput',
103
104
  'GlSegmentedControl',
104
105
  'GlAvatar',
106
+ 'GlChartLegend',
105
107
  'GlAvatarLink',
106
108
  'GlPagination',
107
109
  'GlSkeletonLoader',
@@ -112,11 +114,14 @@ export const setupStorybookReadme = () =>
112
114
  'GlChartTooltip',
113
115
  'GlInputGroupText',
114
116
  'GlGaugeChart',
117
+ 'GlSafeHtmlDirective',
115
118
  'GlFormRadio',
116
119
  'GlModal',
117
120
  'GlKeysetPagination',
118
121
  'GlForm',
119
122
  'GlTable',
123
+ 'GlTab',
124
+ 'GlTabs',
120
125
  'GlToast',
121
126
  'GlPaginatedList',
122
127
  'GlIntersectionObserver',
@@ -134,6 +139,7 @@ export const setupStorybookReadme = () =>
134
139
  'GlChartSeriesLabel',
135
140
  'GlAreaChart',
136
141
  'GlLineChart',
142
+ 'GlFormInputGroup',
137
143
  ],
138
144
  components: {
139
145
  GlComponentDocumentation,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "37.0.0",
3
+ "version": "37.3.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  import description from './drawer.md';
2
2
 
3
3
  export default {
4
- followsDesignSystem: false,
4
+ followsDesignSystem: true,
5
5
  description,
6
6
  };
@@ -1,34 +1,6 @@
1
- import examples from './examples';
2
1
  import description from './form_input_group.md';
3
2
 
4
3
  export default {
5
4
  followsDesignSystem: false,
6
5
  description,
7
- examples,
8
- bootstrapComponent: 'b-form-input',
9
- propsInfo: {
10
- 'select-on-click': {
11
- type: Boolean,
12
- additionalInfo: 'Automatically selects the content of the input field on click',
13
- },
14
- 'predefined-options': {
15
- type: Array,
16
- additionalInfo:
17
- 'Array of options. Each option should have `name` and `value` information: {name: "Foo", value: "Bar"})',
18
- },
19
- },
20
- slots: [
21
- {
22
- name: 'default',
23
- description: 'Allows replacement of default input field.',
24
- },
25
- {
26
- name: 'prepend',
27
- description: 'Is rendered in front of the input field.',
28
- },
29
- {
30
- name: 'append',
31
- description: 'Is rendered after the input field.',
32
- },
33
- ],
34
6
  };
@@ -1,7 +1,3 @@
1
- # Form Group Input
2
-
3
- <!-- STORY -->
4
-
5
1
  The `GlFormInputGroup` component allows one to build more advanced text
6
2
  input fields than generic `GlFormInput` when one needs that flexibility.
7
3
  Basic usage of the component:
@@ -1,73 +1,95 @@
1
- import { withKnobs, boolean, object, text } from '@storybook/addon-knobs';
2
- import { documentedStoriesOf } from '../../../../../documentation/documented_stories';
3
1
  import { GlFormInputGroup, GlInputGroupText } from '../../../../index';
2
+ import { disableControls } from '../../../../utils/stories_utils';
4
3
  import readme from './form_input_group.md';
5
4
 
6
- const components = {
7
- GlFormInputGroup,
8
- GlInputGroupText,
9
- };
10
- const predefinedOptions = [
11
- { name: 'Embed', value: 'https://embed.com' },
12
- { name: 'Share', value: 'https://share.org' },
13
- ];
5
+ const template = `
6
+ <gl-form-input-group
7
+ :readonly="readonly"
8
+ :select-on-click="selectOnClick"
9
+ :predefined-options="predefinedOptions"
10
+ :label="label"
11
+ :inputClass="inputClass">
12
+ <template #prepend v-if="prepend">
13
+ <gl-input-group-text>{{prepend}}</gl-input-group-text>
14
+ </template>
15
+ <template #append v-if="append">
16
+ <gl-input-group-text>{{append}}</gl-input-group-text>
17
+ </template>
18
+ </gl-form-input-group>
19
+ `;
14
20
 
15
- documentedStoriesOf('base/form/form-input-group', readme)
16
- .addDecorator(withKnobs)
17
- .add('default', () => ({
18
- components,
19
- props: {
20
- prepend: {
21
- default: text('Prepend text', 'Username'),
22
- },
23
- append: {
24
- default: text('Append text', 'Add'),
25
- },
26
- readonly: {
27
- default: boolean('Readonly', false),
28
- },
29
- select: {
30
- default: boolean('Select text on click', false),
21
+ const defaultValue = (prop) => GlFormInputGroup.props[prop].default;
22
+
23
+ const generateProps = ({
24
+ prepend = 'Username',
25
+ append = 'Add',
26
+ readonly = false,
27
+ selectOnClick = false,
28
+ predefinedOptions = defaultValue('predefinedOptions')(),
29
+ label = '',
30
+ inputClass = '',
31
+ } = {}) => ({
32
+ prepend,
33
+ append,
34
+ readonly,
35
+ selectOnClick,
36
+ predefinedOptions,
37
+ label,
38
+ inputClass,
39
+ });
40
+
41
+ const Template = (args, { argTypes }) => ({
42
+ components: { GlFormInputGroup, GlInputGroupText },
43
+ props: Object.keys(argTypes),
44
+ template,
45
+ });
46
+
47
+ export const Default = Template.bind({});
48
+ Default.args = generateProps();
49
+
50
+ export const PredefinedOptions = Template.bind({});
51
+ PredefinedOptions.args = generateProps({
52
+ prepend: '',
53
+ predefinedOptions: [
54
+ { name: 'Embed', value: 'https://embed.com' },
55
+ { name: 'Share', value: 'https://share.org' },
56
+ ],
57
+ });
58
+
59
+ export default {
60
+ title: 'base/form/form-input-group',
61
+ component: GlFormInputGroup,
62
+ knobs: { disabled: true },
63
+ parameters: {
64
+ bootstrapComponent: 'b-form-input',
65
+ docs: {
66
+ description: {
67
+ component: readme,
31
68
  },
32
69
  },
33
- template: `
34
- <gl-form-input-group :readonly="readonly" :select-on-click="select">
35
- <template #prepend v-if="prepend">
36
- <gl-input-group-text>{{prepend}}</gl-input-group-text>
37
- </template>
38
- <template #append v-if="append">
39
- <gl-input-group-text>{{append}}</gl-input-group-text>
40
- </template>
41
- </gl-form-input-group>
42
- `,
43
- }))
44
- .add('with predefined options', () => ({
45
- components,
46
- props: {
47
- prepend: {
48
- default: text('Prepend text', ''),
70
+ knobs: { disabled: true },
71
+ },
72
+ argTypes: {
73
+ ...disableControls(['value']),
74
+ prepend: {
75
+ control: {
76
+ type: 'text',
49
77
  },
50
- append: {
51
- default: text('Append text', 'Add'),
52
- },
53
- readonly: {
54
- default: boolean('Readonly', false),
78
+ },
79
+ append: {
80
+ control: {
81
+ type: 'text',
55
82
  },
56
- select: {
57
- default: boolean('Select text on click', false),
83
+ },
84
+ inputClass: {
85
+ control: {
86
+ type: 'text',
58
87
  },
59
- options: {
60
- default: object('Predefined options', predefinedOptions),
88
+ },
89
+ label: {
90
+ control: {
91
+ type: 'text',
61
92
  },
62
93
  },
63
- template: `
64
- <gl-form-input-group :readonly="readonly" :select-on-click="select" :predefined-options="options">
65
- <template #prepend v-if="prepend">
66
- <gl-input-group-text>{{prepend}}</gl-input-group-text>
67
- </template>
68
- <template #append v-if="append">
69
- <gl-input-group-text>{{append}}</gl-input-group-text>
70
- </template>
71
- </gl-form-input-group>
72
- `,
73
- }));
94
+ },
95
+ };
@@ -16,11 +16,17 @@ export default {
16
16
  },
17
17
  mixins: [InputGroupMixin],
18
18
  props: {
19
+ /**
20
+ * Automatically selects the content of the input field on click.
21
+ */
19
22
  selectOnClick: {
20
23
  type: Boolean,
21
24
  required: false,
22
25
  default: false,
23
26
  },
27
+ /**
28
+ * Array of options. Each option should have `name` and `value` information: {name: "Foo", value: "Bar"})
29
+ */
24
30
  predefinedOptions: {
25
31
  type: Array,
26
32
  required: false,
@@ -61,6 +67,7 @@ export default {
61
67
  <div>
62
68
  <b-input-group>
63
69
  <b-input-group-prepend v-if="activeOption || $scopedSlots.prepend">
70
+ <!-- @slot Is rendered in front of the input field. -->
64
71
  <slot name="prepend"></slot>
65
72
  <gl-dropdown v-if="activeOption" :text="activeOption">
66
73
  <gl-dropdown-item
@@ -74,6 +81,7 @@ export default {
74
81
  </gl-dropdown-item>
75
82
  </gl-dropdown>
76
83
  </b-input-group-prepend>
84
+ <!-- @slot Allows replacement of default input field. -->
77
85
  <slot>
78
86
  <b-form-input
79
87
  ref="input"
@@ -86,6 +94,7 @@ export default {
86
94
  />
87
95
  </slot>
88
96
  <b-input-group-append v-if="$scopedSlots.append">
97
+ <!-- @slot Is rendered after the input field. -->
89
98
  <slot name="append"></slot>
90
99
  </b-input-group-append>
91
100
  </b-input-group>
@@ -64,6 +64,6 @@ export default {
64
64
  </script>
65
65
  <template>
66
66
  <component :is="rootElementType" class="gl-spinner-container" role="status">
67
- <span :class="cssClasses" class="align-text-bottom" :aria-label="label"></span>
67
+ <span :class="cssClasses" class="gl-vertical-align-text-bottom!" :aria-label="label"></span>
68
68
  </component>
69
69
  </template>
@@ -16,6 +16,7 @@ const mockResizeWidth = (width) => {
16
16
  describe('pagination component', () => {
17
17
  let wrapper;
18
18
  const findButtons = () => wrapper.findAll('.page-link');
19
+ const findItems = () => wrapper.findAll('.page-item');
19
20
  const propsData = {
20
21
  value: 3,
21
22
  perPage: 5,
@@ -179,6 +180,7 @@ describe('pagination component', () => {
179
180
  it('shows 3rd page as active and enables all buttons', () => {
180
181
  const buttons = findButtons();
181
182
  expect(buttons.at(3).classes()).toEqual(expectClassActive);
183
+ expect(buttons.at(3).attributes('aria-current')).toEqual('page');
182
184
  buttons.wrappers.forEach((button) => {
183
185
  expect(button.element.tagName).not.toBe('SPAN');
184
186
  });
@@ -222,6 +224,7 @@ describe('pagination component', () => {
222
224
  const buttons = findButtons();
223
225
  expect(buttons.at(0).element.tagName).toBe('SPAN');
224
226
  expect(buttons.at(1).classes()).toEqual(expectClassActive);
227
+ expect(buttons.at(1).attributes('aria-current')).toEqual('page');
225
228
  expect(buttons.at(buttons.length - 1).element.tagName).not.toBe('SPAN');
226
229
  });
227
230
 
@@ -310,6 +313,7 @@ describe('pagination component', () => {
310
313
  const buttons = findButtons();
311
314
  expect(buttons.at(0).element.tagName).not.toBe('SPAN');
312
315
  expect(buttons.at(7).classes()).toEqual(expectClassActive);
316
+ expect(buttons.at(7).attributes('aria-current')).toEqual('page');
313
317
  expect(buttons.at(buttons.length - 1).element.tagName).toBe('SPAN');
314
318
  });
315
319
 
@@ -372,9 +376,12 @@ describe('pagination component', () => {
372
376
  value: 1,
373
377
  nextPage: 2,
374
378
  });
379
+ const prevItem = findItems().at(0);
380
+ expect(prevItem.attributes('aria-hidden')).toBe('true');
375
381
  const prevButton = findButtons().at(0);
376
382
  expect(prevButton.element.tagName).toBe('SPAN');
377
- expect(prevButton.attributes('aria-disabled')).toBe('true');
383
+ expect(prevButton.attributes('href')).toBeUndefined();
384
+ expect(prevButton.attributes('aria-label')).toBeUndefined();
378
385
  });
379
386
 
380
387
  it('disables next button when on last page', () => {
@@ -383,9 +390,12 @@ describe('pagination component', () => {
383
390
  value: 2,
384
391
  prevPage: 1,
385
392
  });
393
+ const nextItem = findItems().at(1);
394
+ expect(nextItem.attributes('aria-hidden')).toBe('true');
386
395
  const nextButton = findButtons().at(1);
387
396
  expect(nextButton.element.tagName).toBe('SPAN');
388
- expect(nextButton.attributes('aria-disabled')).toBe('true');
397
+ expect(nextButton.attributes('href')).toBeUndefined();
398
+ expect(nextButton.attributes('aria-label')).toBeUndefined();
389
399
  });
390
400
  });
391
401
  });
@@ -261,6 +261,22 @@ export default {
261
261
  nextPageIsDisabled() {
262
262
  return this.pageIsDisabled(this.value + 1);
263
263
  },
264
+ prevPageAriaLabel() {
265
+ return this.prevPageIsDisabled ? false : this.labelPrevPage || this.labelPage(this.value - 1);
266
+ },
267
+ nextPageAriaLabel() {
268
+ return this.nextPageIsDisabled ? false : this.labelNextPage || this.labelPage(this.value + 1);
269
+ },
270
+ prevPageHref() {
271
+ if (this.prevPageIsDisabled) return false;
272
+ if (this.isLinkBased) return this.linkGen(this.value - 1);
273
+ return '#';
274
+ },
275
+ nextPageHref() {
276
+ if (this.nextPageIsDisabled) return false;
277
+ if (this.isLinkBased) return this.linkGen(this.value + 1);
278
+ return '#';
279
+ },
264
280
  },
265
281
  created() {
266
282
  window.addEventListener('resize', debounce(this.setBreakpoint, resizeDebounceTime));
@@ -293,6 +309,7 @@ export default {
293
309
  const listeners = {};
294
310
  if (isActivePage) {
295
311
  attrs.class.push('active');
312
+ attrs['aria-current'] = 'page';
296
313
  }
297
314
  // Disable previous and/or next buttons if needed
298
315
  if (this.isLinkBased) {
@@ -368,13 +385,13 @@ export default {
368
385
  disabled: prevPageIsDisabled,
369
386
  'flex-fill': isFillAlign,
370
387
  }"
388
+ :aria-hidden="prevPageIsDisabled"
371
389
  >
372
390
  <component
373
391
  :is="prevPageIsDisabled ? 'span' : 'a'"
374
392
  class="gl-link page-link prev-page-item gl-display-flex"
375
- :aria-disabled="prevPageIsDisabled"
376
- :aria-label="labelPrevPage || labelPage(value - 1)"
377
- :href="isLinkBased ? linkGen(value - 1) : '#'"
393
+ :aria-label="prevPageAriaLabel"
394
+ :href="prevPageHref"
378
395
  @click="handlePrevious($event, value - 1)"
379
396
  >
380
397
  <!--
@@ -431,13 +448,13 @@ export default {
431
448
  disabled: nextPageIsDisabled,
432
449
  'flex-fill': isFillAlign,
433
450
  }"
451
+ :aria-hidden="nextPageIsDisabled"
434
452
  >
435
453
  <component
436
454
  :is="nextPageIsDisabled ? 'span' : 'a'"
437
455
  class="gl-link page-link next-page-item gl-display-flex"
438
- :aria-disabled="nextPageIsDisabled"
439
- :aria-label="labelNextPage || labelPage(value + 1)"
440
- :href="isLinkBased ? linkGen(value + 1) : '#'"
456
+ :aria-label="nextPageAriaLabel"
457
+ :href="nextPageHref"
441
458
  @click="handleNext($event, value + 1)"
442
459
  >
443
460
  <!--
@@ -155,7 +155,9 @@ exports[`Path matches the snapshot 1`] = `
155
155
  id="path-1-item-7"
156
156
  >
157
157
  <button
158
+ category="tertiary"
158
159
  class="gl-path-button"
160
+ disabled="disabled"
159
161
  >
160
162
  <!---->
161
163
 
@@ -383,7 +385,9 @@ exports[`Path renders the list of items with icons matches the snapshot 1`] = `
383
385
  id="path-15-item-7"
384
386
  >
385
387
  <button
388
+ category="tertiary"
386
389
  class="gl-path-button"
390
+ disabled="disabled"
387
391
  >
388
392
  <!---->
389
393
 
@@ -606,7 +610,9 @@ exports[`Path renders the list of items with metrics matches the snapshot 1`] =
606
610
  id="path-11-item-7"
607
611
  >
608
612
  <button
613
+ category="tertiary"
609
614
  class="gl-path-button"
615
+ disabled="disabled"
610
616
  >
611
617
  <!---->
612
618
 
@@ -30,6 +30,7 @@ export const mockPathItems = [
30
30
  {
31
31
  title: 'Eighth',
32
32
  metric: '8d',
33
+ disabled: true,
33
34
  },
34
35
  {
35
36
  title: 'Ninth',
@@ -78,7 +78,12 @@ $path-chevron-right-margin: px-to-rem(14px);
78
78
  @include gl-path-chevron;
79
79
  }
80
80
 
81
- &:hover {
81
+ &[disabled] {
82
+ @include gl-text-gray-400;
83
+ @include gl-cursor-not-allowed;
84
+ }
85
+
86
+ &:not([disabled]):hover {
82
87
  @include gl-path-active-item-color($gray-100);
83
88
  @include gl-text-gray-900;
84
89
  }
@@ -1,3 +1,4 @@
1
+ import { nextTick } from 'vue';
1
2
  import { shallowMount } from '@vue/test-utils';
2
3
  import { mockPathItems } from './data';
3
4
  import GlPath from './path.vue';
@@ -143,6 +144,17 @@ describe('Path', () => {
143
144
  it('selects the first item', () => {
144
145
  expect(pathItemAt(0).classList).toContain(SELECTED_CLASS_INDIGO);
145
146
  });
147
+
148
+ it('updates the selected item when props change', async () => {
149
+ const items = JSON.parse(JSON.stringify(mockPathItems));
150
+ items[3].selected = true;
151
+
152
+ wrapper.setProps({ items });
153
+ await nextTick();
154
+
155
+ expect(pathItemAt(0).classList).not.toContain(SELECTED_CLASS_INDIGO);
156
+ expect(pathItemAt(3).classList).toContain(SELECTED_CLASS_INDIGO);
157
+ });
146
158
  });
147
159
 
148
160
  describe('with a specifically selected item passed in', () => {
@@ -188,6 +200,14 @@ describe('Path', () => {
188
200
  ]);
189
201
  });
190
202
  });
203
+
204
+ describe('when a disabled item is clicked', () => {
205
+ it('does not emit the selected event', () => {
206
+ clickItemAt(7);
207
+
208
+ expect(wrapper.emitted('selected')).toBeUndefined();
209
+ });
210
+ });
191
211
  });
192
212
 
193
213
  describe('slots', () => {
@@ -21,9 +21,10 @@ export default {
21
21
  * A list of path items in the form:
22
22
  * ```
23
23
  * {
24
- * title: String, required
25
- * metric: Any, optional
26
- * icon: String, optional
24
+ * title: String, required
25
+ * metric: Any, optional
26
+ * icon: String, optional
27
+ * disabled: Boolean, optional
27
28
  * }
28
29
  * ```
29
30
  */
@@ -78,9 +79,14 @@ export default {
78
79
  return this.scrollLeft + BOUNDARY_WIDTH;
79
80
  },
80
81
  },
81
- mounted() {
82
- const selectedIndex = this.items.findIndex((item) => item.selected);
83
- this.selectedIndex = selectedIndex > 0 ? selectedIndex : 0;
82
+ watch: {
83
+ items: {
84
+ immediate: true,
85
+ handler(items) {
86
+ const selectedIndex = items.findIndex((item) => item.selected);
87
+ this.selectedIndex = selectedIndex > 0 ? selectedIndex : 0;
88
+ },
89
+ },
84
90
  },
85
91
  beforeCreate() {
86
92
  this.pathUuid = uniqueId('path-');
@@ -164,7 +170,12 @@ export default {
164
170
  :key="index"
165
171
  class="gl-path-nav-list-item"
166
172
  >
167
- <button :class="pathItemClass(index)" @click="onItemClicked(index)">
173
+ <button
174
+ :class="pathItemClass(index)"
175
+ :category="item.disabled ? 'tertiary' : undefined"
176
+ :disabled="item.disabled"
177
+ @click="onItemClicked(index)"
178
+ >
168
179
  <gl-icon
169
180
  v-if="shouldDisplayIcon(item.icon)"
170
181
  :name="item.icon"
@@ -15,6 +15,9 @@ export default {
15
15
  required: false,
16
16
  default: '',
17
17
  },
18
+ /**
19
+ * Query string parameter value to use when `gl-tabs` `sync-active-tab-with-query-params` prop is set to `true`.
20
+ */
18
21
  queryParamValue: {
19
22
  type: String,
20
23
  required: false,
@@ -36,6 +39,7 @@ export default {
36
39
  },
37
40
  };
38
41
  </script>
42
+
39
43
  <template>
40
44
  <b-tab
41
45
  :title-link-class="linkClass"
@@ -1,18 +1,6 @@
1
- import examples from './examples';
2
1
  import description from './tabs.md';
3
2
 
4
3
  export default {
5
4
  description,
6
- examples,
7
- bootstrapComponent: 'b-tabs',
8
5
  followsDesignSystem: true,
9
- propsInfo: {
10
- syncActiveTabWithQueryParams: {
11
- additionalInfo:
12
- 'Sync active tab with query string parameters. Allows for deep linking into specific tabs.',
13
- },
14
- queryParamName: {
15
- additionalInfo: 'Name to use for query string parameter.',
16
- },
17
- },
18
6
  };
@@ -1,15 +1,10 @@
1
- # Tabs
2
-
3
- <!-- STORY -->
4
- ## Usage
5
-
6
1
  Tabs are used to divide content into meaningful, related sections. Tabs allow users to focus on one
7
2
  specific view at a time while maintaining sight of all the relevant content options available. Each
8
3
  tab, when active, will reveal it’s own unique content.
9
4
 
10
5
  ## Using the component Vue
11
6
 
12
- ~~~js
7
+ ~~~html
13
8
  <gl-tabs>
14
9
  <gl-tab title="Tab 1">
15
10
  Tab panel 1
@@ -22,7 +17,7 @@ tab, when active, will reveal it’s own unique content.
22
17
 
23
18
  ## Using the component HTML
24
19
 
25
- ~~~js
20
+ ~~~html
26
21
  <div class="tabs gl-tabs">
27
22
  <ul role="tablist" class="nav gl-tabs-nav">
28
23
  <li role="presentation" class="nav-item">