@gitlab/ui 66.11.0 → 66.13.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 (33) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/base/datepicker/datepicker.js +29 -6
  3. package/dist/components/base/form/form_input/form_input.js +24 -4
  4. package/dist/components/base/form/form_select/form_select.js +24 -4
  5. package/dist/components/base/table/table.js +9 -1
  6. package/dist/components/base/table_lite/table_lite.js +9 -1
  7. package/dist/index.css +1 -1
  8. package/dist/index.css.map +1 -1
  9. package/dist/tokens/css/tokens.css +1 -1
  10. package/dist/tokens/css/tokens.dark.css +1 -1
  11. package/dist/tokens/js/tokens.dark.js +1 -1
  12. package/dist/tokens/js/tokens.js +1 -1
  13. package/dist/tokens/scss/_tokens.dark.scss +1 -1
  14. package/dist/tokens/scss/_tokens.scss +1 -1
  15. package/dist/utils/constants.js +7 -1
  16. package/package.json +1 -1
  17. package/src/components/base/datepicker/datepicker.spec.js +17 -0
  18. package/src/components/base/datepicker/datepicker.stories.js +20 -4
  19. package/src/components/base/datepicker/datepicker.vue +33 -5
  20. package/src/components/base/form/form_input/form_input.spec.js +52 -0
  21. package/src/components/base/form/form_input/form_input.stories.js +10 -0
  22. package/src/components/base/form/form_input/form_input.vue +25 -4
  23. package/src/components/base/form/form_select/form_select.spec.js +25 -0
  24. package/src/components/base/form/form_select/form_select.stories.js +7 -0
  25. package/src/components/base/form/form_select/form_select.vue +25 -4
  26. package/src/components/base/table/table.scss +13 -0
  27. package/src/components/base/table/table.spec.js +11 -1
  28. package/src/components/base/table/table.stories.js +47 -0
  29. package/src/components/base/table/table.vue +9 -1
  30. package/src/components/base/table_lite/table_lite.spec.js +11 -1
  31. package/src/components/base/table_lite/table_lite.stories.js +14 -3
  32. package/src/components/base/table_lite/table_lite.vue +9 -1
  33. package/src/utils/constants.js +7 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 20 Sep 2023 19:33:35 GMT
3
+ * Generated on Thu, 21 Sep 2023 07:11: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, 20 Sep 2023 19:33:35 GMT
3
+ * Generated on Thu, 21 Sep 2023 07:11:32 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Wed, 20 Sep 2023 19:33:35 GMT
3
+ * Generated on Thu, 21 Sep 2023 07:11:32 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, 20 Sep 2023 19:33:35 GMT
3
+ * Generated on Thu, 21 Sep 2023 07:11: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, 20 Sep 2023 19:33:35 GMT
3
+ // Generated on Thu, 21 Sep 2023 07:11:32 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, 20 Sep 2023 19:33:35 GMT
3
+ // Generated on Thu, 21 Sep 2023 07:11:32 GMT
4
4
 
5
5
  $gl-line-height-52: 3.25rem;
6
6
  $gl-line-height-44: 2.75rem;
@@ -123,6 +123,12 @@ const datepickerSizeOptionsMap = {
123
123
  small: 'sm',
124
124
  medium: 'md'
125
125
  };
126
+ const datepickerWidthOptionsMap = {
127
+ sm: 'sm',
128
+ md: 'md',
129
+ lg: 'lg',
130
+ xl: 'xl'
131
+ };
126
132
 
127
133
  // size options all have corresponding styles (e.g. .s12 defined in icon.scss)
128
134
  const iconSizeOptions = glIconSizes.split(' ').map(Number);
@@ -267,4 +273,4 @@ const loadingIconVariants = {
267
273
  dots: 'dots'
268
274
  };
269
275
 
270
- export { COMMA, LEFT_MOUSE_BUTTON, alertVariantIconMap, alertVariantOptions, alignOptions, avatarShapeOptions, avatarSizeOptions, avatarsInlineSizeOptions, badgeForButtonOptions, badgeIconSizeOptions, badgeSizeOptions, badgeVariantOptions, bannerVariants, buttonCategoryOptions, buttonSizeOptions, buttonVariantOptions, colorThemes, columnOptions, datepickerSizeOptionsMap, defaultDateFormat, drawerVariants, dropdownAllowedAutoPlacements, dropdownPlacements, dropdownVariantOptions, focusableTags, formInputSizes, formStateOptions, glThemes, iconSizeOptions, keyboard, labelColorOptions, labelSizeOptions, loadingIconSizes, loadingIconVariants, maxZIndex, modalButtonDefaults, modalSizeOptions, popoverPlacements, resizeDebounceTime, tabsButtonDefaults, targetOptions, toggleLabelPosition, tokenVariants, tooltipActionEvents, tooltipDelay, tooltipPlacements, triggerVariantOptions, truncateOptions, variantCssColorMap, variantOptions, variantOptionsWithNoDefault, viewModeOptions };
276
+ export { COMMA, LEFT_MOUSE_BUTTON, alertVariantIconMap, alertVariantOptions, alignOptions, avatarShapeOptions, avatarSizeOptions, avatarsInlineSizeOptions, badgeForButtonOptions, badgeIconSizeOptions, badgeSizeOptions, badgeVariantOptions, bannerVariants, buttonCategoryOptions, buttonSizeOptions, buttonVariantOptions, colorThemes, columnOptions, datepickerSizeOptionsMap, datepickerWidthOptionsMap, defaultDateFormat, drawerVariants, dropdownAllowedAutoPlacements, dropdownPlacements, dropdownVariantOptions, focusableTags, formInputSizes, formStateOptions, glThemes, iconSizeOptions, keyboard, labelColorOptions, labelSizeOptions, loadingIconSizes, loadingIconVariants, maxZIndex, modalButtonDefaults, modalSizeOptions, popoverPlacements, resizeDebounceTime, tabsButtonDefaults, targetOptions, toggleLabelPosition, tokenVariants, tooltipActionEvents, tooltipDelay, tooltipPlacements, triggerVariantOptions, truncateOptions, variantCssColorMap, variantOptions, variantOptionsWithNoDefault, viewModeOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "66.11.0",
3
+ "version": "66.13.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -426,4 +426,21 @@ describe('datepicker component', () => {
426
426
 
427
427
  expect(wrapper.classes()).toContain(expectedClass);
428
428
  });
429
+
430
+ it.each`
431
+ width | expectedClass
432
+ ${undefined} | ${'gl-form-input-md'}
433
+ ${'sm'} | ${'gl-form-input-sm'}
434
+ ${'md'} | ${'gl-form-input-md'}
435
+ ${'lg'} | ${'gl-form-input-lg'}
436
+ ${'xl'} | ${'gl-form-input-xl'}
437
+ `('applies $expectedClass class when width is $width', ({ width, expectedClass }) => {
438
+ const wrapper = mountWithOptions({
439
+ propsData: {
440
+ width,
441
+ },
442
+ });
443
+
444
+ expect(wrapper.classes()).toContain(expectedClass);
445
+ });
429
446
  });
@@ -1,4 +1,5 @@
1
1
  import GlFormGroup from '../form/form_group/form_group.vue';
2
+ import { datepickerWidthOptionsMap, datepickerSizeOptionsMap } from '../../../utils/constants';
2
3
  import { disableControls } from '../../../utils/stories_utils';
3
4
  import { useFakeDate } from '../../../utils/use_fake_date';
4
5
  import { makeContainer } from '../../../utils/story_decorators/container';
@@ -69,16 +70,23 @@ export const DifferentSizes = (_args, { argTypes }) => ({
69
70
  props: Object.keys(argTypes),
70
71
  data() {
71
72
  return {
73
+ datepickerWidthOptionsMap,
72
74
  pickerValue: defaultDate,
73
75
  };
74
76
  },
75
77
  template: `
76
78
  <div class="gl-display-flex gl-flex-direction-column gl-gap-3">
77
- <gl-form-group label="Size: sm">
78
- <gl-datepicker showClearButton :max-date="maxDate" :min-date="minDate" v-model="pickerValue" size="small" />
79
+ <gl-form-group label="Width: sm">
80
+ <gl-datepicker showClearButton :max-date="maxDate" :min-date="minDate" v-model="pickerValue" width="sm" />
79
81
  </gl-form-group>
80
- <gl-form-group label="Size: md (default)">
81
- <gl-datepicker showClearButton :max-date="maxDate" :min-date="minDate" v-model="pickerValue" size="medium" />
82
+ <gl-form-group label="Width: md (default)">
83
+ <gl-datepicker showClearButton :max-date="maxDate" :min-date="minDate" v-model="pickerValue" width="md" />
84
+ </gl-form-group>
85
+ <gl-form-group label="Width: lg">
86
+ <gl-datepicker showClearButton :max-date="maxDate" :min-date="minDate" v-model="pickerValue" width="lg" />
87
+ </gl-form-group>
88
+ <gl-form-group label="Width: xl">
89
+ <gl-datepicker showClearButton :max-date="maxDate" :min-date="minDate" v-model="pickerValue" width="xl" />
82
90
  </gl-form-group>
83
91
  </div>
84
92
  `,
@@ -125,5 +133,13 @@ export default {
125
133
  maxDate: {
126
134
  control: 'date',
127
135
  },
136
+ width: {
137
+ options: datepickerWidthOptionsMap,
138
+ control: 'select',
139
+ },
140
+ size: {
141
+ options: datepickerSizeOptionsMap,
142
+ control: 'select',
143
+ },
128
144
  },
129
145
  };
@@ -2,7 +2,11 @@
2
2
  <script>
3
3
  import isString from 'lodash/isString';
4
4
  import Pikaday from 'pikaday';
5
- import { defaultDateFormat, datepickerSizeOptionsMap } from '../../../utils/constants';
5
+ import {
6
+ defaultDateFormat,
7
+ datepickerWidthOptionsMap,
8
+ datepickerSizeOptionsMap,
9
+ } from '../../../utils/constants';
6
10
  import { areDatesEqual } from '../../../utils/datetime_utility';
7
11
  import GlButton from '../button/button.vue';
8
12
  import GlFormInput from '../form/form_input/form_input.vue';
@@ -172,10 +176,27 @@ export default {
172
176
  required: false,
173
177
  default: null,
174
178
  },
179
+ /**
180
+ * Maximum width of the Datepicker
181
+ */
182
+ width: {
183
+ type: String,
184
+ required: false,
185
+ default: null,
186
+ validator: (value) => Object.keys(datepickerWidthOptionsMap).includes(value),
187
+ },
188
+ /**
189
+ * ⚠️ DEPRECATED:
190
+ *
191
+ * Will be replaced by the
192
+ * property width
193
+ *
194
+ * Maximum width of the Datepicker
195
+ */
175
196
  size: {
176
197
  type: String,
177
198
  required: false,
178
- default: 'medium',
199
+ default: null,
179
200
  validator: (value) => Object.keys(datepickerSizeOptionsMap).includes(value),
180
201
  },
181
202
  },
@@ -216,11 +237,18 @@ export default {
216
237
  'gl-datepicker',
217
238
  'd-inline-block',
218
239
  'gl-w-full',
219
- `gl-form-input-${this.datepickerSize}`,
240
+ `gl-form-input-${this.computedWidth}`,
220
241
  ];
221
242
  },
222
- datepickerSize() {
223
- return datepickerSizeOptionsMap[this.size];
243
+ computedWidth() {
244
+ if (this.width) {
245
+ return this.width;
246
+ // eslint-disable-next-line no-else-return
247
+ } else if (this.size) {
248
+ return datepickerSizeOptionsMap[this.size];
249
+ }
250
+
251
+ return 'md';
224
252
  },
225
253
  },
226
254
  watch: {
@@ -66,6 +66,58 @@ describe('GlFormInput', () => {
66
66
  });
67
67
  });
68
68
 
69
+ describe('width prop', () => {
70
+ describe('when number is passed', () => {
71
+ // Exclude the default null value
72
+ const widths = Object.values(formInputSizes).filter(Boolean);
73
+
74
+ it.each(widths)('adds correct class for width %s', (width) => {
75
+ createComponent({ width });
76
+
77
+ expect(wrapper.classes()).toEqual(['gl-form-input', `gl-form-input-${width}`]);
78
+ });
79
+
80
+ it('does not add a width class if not given the width prop', () => {
81
+ createComponent();
82
+
83
+ expect(wrapper.classes()).toEqual(['gl-form-input']);
84
+ });
85
+
86
+ it('does not add a width class if passed null', () => {
87
+ createComponent({ width: null });
88
+
89
+ expect(wrapper.classes()).toEqual(['gl-form-input']);
90
+ });
91
+ });
92
+
93
+ describe('when object is passed', () => {
94
+ describe('when `default` key is provided', () => {
95
+ it('adds responsive CSS classes and base class', () => {
96
+ createComponent({ width: { default: 'md', md: 'lg', lg: 'xl' } });
97
+
98
+ expect(wrapper.classes()).toEqual([
99
+ 'gl-form-input',
100
+ 'gl-form-input-md',
101
+ 'gl-md-form-input-lg',
102
+ 'gl-lg-form-input-xl',
103
+ ]);
104
+ });
105
+ });
106
+
107
+ describe('when `default` key is not provided', () => {
108
+ it('adds responsive CSS classes', () => {
109
+ createComponent({ width: { md: 'lg', lg: 'xl' } });
110
+
111
+ expect(wrapper.classes()).toEqual([
112
+ 'gl-form-input',
113
+ 'gl-md-form-input-lg',
114
+ 'gl-lg-form-input-xl',
115
+ ]);
116
+ });
117
+ });
118
+ });
119
+ });
120
+
69
121
  describe('v-model', () => {
70
122
  beforeEach(() => {
71
123
  createComponent({}, mount);
@@ -8,15 +8,18 @@ const template = `
8
8
  :readonly="readonly"
9
9
  :disabled="disabled"
10
10
  :value="value"
11
+ :width="width"
11
12
  :size="size"
12
13
  />`;
13
14
 
14
15
  const generateProps = ({
16
+ width = GlFormInput.props.size.default,
15
17
  size = GlFormInput.props.size.default,
16
18
  value = '',
17
19
  disabled = false,
18
20
  readonly = false,
19
21
  } = {}) => ({
22
+ width,
20
23
  size,
21
24
  value,
22
25
  disabled,
@@ -49,6 +52,7 @@ export const Sizes = (args, { argTypes }) => ({
49
52
  <gl-form-input
50
53
  v-for="(size, name) in formInputSizes"
51
54
  :key="size"
55
+ :width="width"
52
56
  :size="size"
53
57
  :value="name"
54
58
  />
@@ -63,11 +67,13 @@ export const ResponsiveSizes = (args, { argTypes }) => ({
63
67
  template: `
64
68
  <div>
65
69
  <gl-form-input
70
+ :width="{ default: 'md', md: 'lg', lg: 'xl' }"
66
71
  :size="{ default: 'md', md: 'lg', lg: 'xl' }"
67
72
  value="With \`default\` key"
68
73
  />
69
74
  <gl-form-input
70
75
  class="gl-mt-4"
76
+ :width="{ md: 'lg', lg: 'xl' }"
71
77
  :size="{ md: 'lg', lg: 'xl' }"
72
78
  value="Without \`default\` key"
73
79
  />
@@ -88,6 +94,10 @@ export default {
88
94
  },
89
95
  },
90
96
  argTypes: {
97
+ width: {
98
+ options: formInputSizes,
99
+ control: 'select',
100
+ },
91
101
  size: {
92
102
  options: formInputSizes,
93
103
  control: 'select',
@@ -21,6 +21,24 @@ export default {
21
21
  /**
22
22
  * Maximum width of the input
23
23
  */
24
+ width: {
25
+ type: [String, Object],
26
+ required: false,
27
+ default: null,
28
+ validator: (value) => {
29
+ const widths = isObject(value) ? Object.values(value) : [value];
30
+
31
+ return widths.every((width) => Object.values(formInputSizes).includes(width));
32
+ },
33
+ },
34
+ /**
35
+ * ⚠️ DEPRECATED:
36
+ *
37
+ * Will be replaced by the
38
+ * property width
39
+ *
40
+ * Maximum width of the input
41
+ */
24
42
  size: {
25
43
  type: [String, Object],
26
44
  required: false,
@@ -33,13 +51,16 @@ export default {
33
51
  },
34
52
  },
35
53
  computed: {
54
+ computedWidth() {
55
+ return this.width ? this.width : this.size;
56
+ },
36
57
  cssClasses() {
37
- if (this.size === null) {
58
+ if (this.computedWidth === null) {
38
59
  return [];
39
60
  }
40
61
 
41
- if (isObject(this.size)) {
42
- const { default: defaultSize, ...nonDefaultSizes } = this.size;
62
+ if (isObject(this.computedWidth)) {
63
+ const { default: defaultSize, ...nonDefaultSizes } = this.computedWidth;
43
64
 
44
65
  return [
45
66
  ...(defaultSize ? [`gl-form-input-${defaultSize}`] : []),
@@ -49,7 +70,7 @@ export default {
49
70
  ];
50
71
  }
51
72
 
52
- return [`gl-form-input-${this.size}`];
73
+ return [`gl-form-input-${this.computedWidth}`];
53
74
  },
54
75
  listeners() {
55
76
  return {
@@ -56,6 +56,31 @@ describe('GlFormSelect', () => {
56
56
  });
57
57
  });
58
58
 
59
+ describe('width prop', () => {
60
+ // Exclude the default null value
61
+ const nonNullSizes = excludeDefaultNull(formInputSizes);
62
+
63
+ it.each(nonNullSizes)('adds correct class for width %s', (width) => {
64
+ createComponent({ width });
65
+
66
+ expect(wrapper.classes().sort()).toEqual(
67
+ [...DEFAULT_SELECT_CLASSES, `gl-form-select-${width}`].sort()
68
+ );
69
+ });
70
+
71
+ it('does not add a width class if not given the width prop', () => {
72
+ createComponent();
73
+
74
+ expect(wrapper.classes().sort()).toEqual([...DEFAULT_SELECT_CLASSES].sort());
75
+ });
76
+
77
+ it('does not add a width class if passed null', () => {
78
+ createComponent({ width: null });
79
+
80
+ expect(wrapper.classes().sort()).toEqual([...DEFAULT_SELECT_CLASSES].sort());
81
+ });
82
+ });
83
+
59
84
  describe('v-model', () => {
60
85
  it('should select an option element and update the v-model bound data', async () => {
61
86
  createComponent({ options: formSelectOptions });
@@ -10,6 +10,7 @@ const data = () => ({
10
10
  const template = `
11
11
  <gl-form-select
12
12
  v-model="selected"
13
+ :width="width"
13
14
  :size="size"
14
15
  :disabled="disabled"
15
16
  :state="state"
@@ -20,6 +21,7 @@ const template = `
20
21
  `;
21
22
 
22
23
  const generateProps = ({
24
+ width = null,
23
25
  size = null,
24
26
  state = null,
25
27
  disabled = false,
@@ -27,6 +29,7 @@ const generateProps = ({
27
29
  selectSize = 1,
28
30
  options = formSelectOptions,
29
31
  } = {}) => ({
32
+ width,
30
33
  size,
31
34
  disabled,
32
35
  state,
@@ -89,6 +92,10 @@ export default {
89
92
  },
90
93
  },
91
94
  argTypes: {
95
+ width: {
96
+ options: formInputSizes,
97
+ control: 'select',
98
+ },
92
99
  size: {
93
100
  options: formInputSizes,
94
101
  control: 'select',
@@ -13,6 +13,24 @@ export default {
13
13
  /**
14
14
  * Maximum width of the Select
15
15
  */
16
+ width: {
17
+ type: [String, Object],
18
+ required: false,
19
+ default: null,
20
+ validator: (value) => {
21
+ const widths = isObject(value) ? Object.values(value) : [value];
22
+
23
+ return widths.every((width) => Object.values(formInputSizes).includes(width));
24
+ },
25
+ },
26
+ /**
27
+ * ⚠️ DEPRECATED:
28
+ *
29
+ * Will be replaced by the
30
+ * property width
31
+ *
32
+ * Maximum width of the Select
33
+ */
16
34
  size: {
17
35
  type: [String, Object],
18
36
  required: false,
@@ -25,13 +43,16 @@ export default {
25
43
  },
26
44
  },
27
45
  computed: {
46
+ computedWidth() {
47
+ return this.width ? this.width : this.size;
48
+ },
28
49
  cssClasses() {
29
- if (this.size === null) {
50
+ if (this.computedWidth === null) {
30
51
  return [];
31
52
  }
32
53
 
33
- if (isObject(this.size)) {
34
- const { default: defaultSize, ...nonDefaultSizes } = this.size;
54
+ if (isObject(this.computedWidth)) {
55
+ const { default: defaultSize, ...nonDefaultSizes } = this.computedWidth;
35
56
 
36
57
  return [
37
58
  ...(defaultSize ? [`gl-form-select-${defaultSize}`] : []),
@@ -41,7 +62,7 @@ export default {
41
62
  ];
42
63
  }
43
64
 
44
- return [`gl-form-select-${this.size}`];
65
+ return [`gl-form-select-${this.computedWidth}`];
45
66
  },
46
67
  },
47
68
  };
@@ -27,9 +27,22 @@ table.gl-table {
27
27
  @include gl-focus;
28
28
  position: relative;
29
29
  z-index: 1;
30
+ background: $white;
31
+
32
+ &:hover {
33
+ background: $gray-50;
34
+ }
30
35
  }
31
36
  }
32
37
 
38
+ // Sticky header
39
+ &--sticky-header thead tr {
40
+ position: sticky;
41
+ top: -1px;
42
+ background: $white;
43
+ box-shadow: inset 0 -1px 0 $gray-100;
44
+ }
45
+
33
46
  .table-primary,
34
47
  .table-primary:hover {
35
48
  > td {
@@ -55,7 +55,17 @@ describe('GlTable', () => {
55
55
  it('adds gl-table class to tableClass prop', () => {
56
56
  factory({ props: { tableClass: 'test-class' } });
57
57
 
58
- expect(findBTable().props().tableClass).toEqual(['gl-table', 'test-class']);
58
+ expect(findBTable().props().tableClass).toEqual(['gl-table', 'test-class', null]);
59
+ });
60
+
61
+ it('adds sticky header class to tableClass prop', () => {
62
+ factory({ props: { stickyHeader: true } });
63
+
64
+ expect(findBTable().props().tableClass).toEqual([
65
+ 'gl-table',
66
+ undefined,
67
+ 'gl-table--sticky-header',
68
+ ]);
59
69
  });
60
70
 
61
71
  it('adds gl-table fields to table prop', () => {
@@ -20,11 +20,13 @@ const tableItems = [
20
20
  ];
21
21
 
22
22
  const generateProps = ({
23
+ stickyHeader = false,
23
24
  fixed = false,
24
25
  footClone = false,
25
26
  stacked = false,
26
27
  caption = '',
27
28
  } = {}) => ({
29
+ stickyHeader,
28
30
  fixed,
29
31
  footClone,
30
32
  stacked,
@@ -36,6 +38,7 @@ export const Default = (args, { argTypes }) => ({
36
38
  props: Object.keys(argTypes),
37
39
  template: `
38
40
  <gl-table
41
+ :sticky-header="stickyHeader"
39
42
  :items="$options.items"
40
43
  :fields="$options.fields"
41
44
  :fixed="fixed"
@@ -84,6 +87,7 @@ export const WithFilter = (args, { argTypes }) => ({
84
87
  <gl-form-input v-model="filter" placeholder="Type to search" />
85
88
  <br />
86
89
  <gl-table
90
+ :sticky-header="stickyHeader"
87
91
  :items="$options.items"
88
92
  :fields="$options.fields"
89
93
  :filter=filter
@@ -104,6 +108,45 @@ export const WithFilter = (args, { argTypes }) => ({
104
108
  });
105
109
  WithFilter.args = generateProps();
106
110
 
111
+ export const WithStickyHeader = (args, { argTypes }) => ({
112
+ components: { ...components, GlFormInput },
113
+ props: Object.keys(argTypes),
114
+ template: `<div class="gl-line-height-normal">
115
+ <gl-form-input v-model="filter" placeholder="Type to search" />
116
+ <br />
117
+ <gl-table
118
+ :sticky-header="stickyHeader"
119
+ :items="$options.items"
120
+ :fields="$options.fields"
121
+ :filter=filter
122
+ :fixed="fixed"
123
+ :stacked="stacked"
124
+ :foot-clone="footClone"
125
+ hover
126
+ selectable
127
+ selected-variant="primary"
128
+ />
129
+ </div>`,
130
+ items: [
131
+ ...tableItems,
132
+ ...tableItems,
133
+ ...tableItems,
134
+ ...tableItems,
135
+ ...tableItems,
136
+ ...tableItems,
137
+ ...tableItems,
138
+ ...tableItems,
139
+ ...tableItems,
140
+ ...tableItems,
141
+ ],
142
+ data() {
143
+ return {
144
+ filter: null,
145
+ };
146
+ },
147
+ });
148
+ WithStickyHeader.args = generateProps({ stickyHeader: true });
149
+
107
150
  export default {
108
151
  title: 'base/table/table',
109
152
  component: GlTable,
@@ -120,5 +163,9 @@ export default {
120
163
  options: ['sm', 'md', 'lg', 'xl', true, false],
121
164
  control: 'select',
122
165
  },
166
+ stickyHeader: {
167
+ options: [false, true],
168
+ control: 'boolean',
169
+ },
123
170
  },
124
171
  };
@@ -26,10 +26,18 @@ export default {
26
26
  required: false,
27
27
  default: null,
28
28
  },
29
+ stickyHeader: {
30
+ type: Boolean,
31
+ default: false,
32
+ required: false,
33
+ },
29
34
  },
30
35
  computed: {
36
+ stickyHeaderClass() {
37
+ return this.stickyHeader ? 'gl-table--sticky-header' : null;
38
+ },
31
39
  localTableClass() {
32
- return ['gl-table', this.tableClass];
40
+ return ['gl-table', this.tableClass, this.stickyHeaderClass];
33
41
  },
34
42
  },
35
43
  mounted() {
@@ -16,7 +16,17 @@ describe('GlTableLite', () => {
16
16
  it('adds gl-table class to tableClass prop', () => {
17
17
  factory({ tableClass: 'test-class' });
18
18
 
19
- expect(findBTableLite().props().tableClass).toEqual(['gl-table', 'test-class']);
19
+ expect(findBTableLite().props().tableClass).toEqual(['gl-table', 'test-class', null]);
20
+ });
21
+
22
+ it('adds sticky header class to tableClass prop', () => {
23
+ factory({ stickyHeader: true });
24
+
25
+ expect(findBTableLite().props().tableClass).toEqual([
26
+ 'gl-table',
27
+ undefined,
28
+ 'gl-table--sticky-header',
29
+ ]);
20
30
  });
21
31
 
22
32
  it('adds gl-table fields to table prop', () => {