@gitlab/ui 74.9.1 → 74.9.2

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Tue, 20 Feb 2024 00:41:01 GMT
3
+ * Generated on Tue, 20 Feb 2024 13:40:26 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Tue, 20 Feb 2024 00:41:01 GMT
3
+ * Generated on Tue, 20 Feb 2024 13:40:26 GMT
4
4
  */
5
5
 
6
6
  :root.gl-dark {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Tue, 20 Feb 2024 00:41:01 GMT
3
+ * Generated on Tue, 20 Feb 2024 13:40:26 GMT
4
4
  */
5
5
 
6
6
  export const DATA_VIZ_GREEN_50 = "#133a03";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Tue, 20 Feb 2024 00:41:01 GMT
3
+ * Generated on Tue, 20 Feb 2024 13:40:26 GMT
4
4
  */
5
5
 
6
6
  export const DATA_VIZ_GREEN_50 = "#ddfab7";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Tue, 20 Feb 2024 00:41:01 GMT
3
+ // Generated on Tue, 20 Feb 2024 13:40:26 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 Tue, 20 Feb 2024 00:41:01 GMT
3
+ // Generated on Tue, 20 Feb 2024 13:40:26 GMT
4
4
 
5
5
  $gl-line-height-52: 3.25rem;
6
6
  $gl-line-height-44: 2.75rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "74.9.1",
3
+ "version": "74.9.2",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -110,4 +110,14 @@ table.gl-table {
110
110
  }
111
111
  }
112
112
  }
113
+
114
+ &.table-hover tbody tr:hover,
115
+ &.table-hover td.table-secondary:hover {
116
+ background-color: $gray-10;
117
+ }
118
+ }
119
+
120
+ .table.b-table > thead > tr > th,
121
+ .table.b-table > tfoot > tr > th {
122
+ background-image: none !important;
113
123
  }
@@ -1,7 +1,9 @@
1
- import { shallowMount } from '@vue/test-utils';
1
+ import { nextTick } from 'vue';
2
+ import { shallowMount, mount } from '@vue/test-utils';
2
3
  import { BTable } from 'bootstrap-vue';
3
4
  import { logWarning } from '../../../utils/utils';
4
5
  import { waitForAnimationFrame } from '../../../utils/test_utils';
6
+ import Icon from '../icon/icon.vue';
5
7
  import { glTableLiteWarning } from './constants';
6
8
  import Table from './table.vue';
7
9
 
@@ -18,14 +20,15 @@ describe('GlTable', () => {
18
20
  <p>Placeholder empty text</p>`,
19
21
  };
20
22
 
21
- const factory = ({ props = {}, scopedSlots = {} } = {}) => {
22
- wrapper = shallowMount(Table, {
23
+ const factory = ({ mountFn = shallowMount, props = {}, scopedSlots = {} } = {}) => {
24
+ wrapper = mountFn(Table, {
23
25
  scopedSlots,
24
26
  propsData: props,
25
27
  });
26
28
  };
27
29
 
28
30
  const findBTable = () => wrapper.findComponent(BTable);
31
+ const findFirstColHeader = () => findBTable().find('thead').findAll('th').at(0);
29
32
 
30
33
  afterEach(() => {
31
34
  logWarning.mockClear();
@@ -76,4 +79,69 @@ describe('GlTable', () => {
76
79
  expect(wrapper.props('fields')).toEqual(fields);
77
80
  expect(findBTable().props('fields')).toEqual(fields);
78
81
  });
82
+
83
+ describe('sortable columns', () => {
84
+ const field = {
85
+ key: 'name',
86
+ label: 'Name column',
87
+ sortable: true,
88
+ };
89
+
90
+ describe('without custom slots', () => {
91
+ beforeEach(() => {
92
+ factory({ mountFn: mount, props: { fields: [field] } });
93
+ });
94
+
95
+ it('sets the correct column label', () => {
96
+ expect(findFirstColHeader().text()).toMatch(field.label);
97
+ });
98
+
99
+ it('renders the ascending sort icon', async () => {
100
+ findFirstColHeader().trigger('click');
101
+ await nextTick();
102
+
103
+ const icon = findFirstColHeader().findComponent(Icon);
104
+
105
+ expect(icon.exists()).toBe(true);
106
+ expect(icon.props('name')).toBe('arrow-up');
107
+ });
108
+
109
+ it('renders the descending sort icon', async () => {
110
+ findFirstColHeader().trigger('click');
111
+ findFirstColHeader().trigger('click');
112
+ await nextTick();
113
+ const icon = findFirstColHeader().findComponent(Icon);
114
+
115
+ expect(icon.exists()).toBe(true);
116
+ expect(icon.props('name')).toBe('arrow-down');
117
+ });
118
+ });
119
+
120
+ describe('when headers are customized via slots', () => {
121
+ const customSlotContent = 'customSlotContent';
122
+
123
+ beforeEach(() => {
124
+ factory({
125
+ mountFn: mount,
126
+ props: {
127
+ fields: [field],
128
+ },
129
+ scopedSlots: {
130
+ 'head(name)': `<div>${customSlotContent}</div>`,
131
+ },
132
+ });
133
+ });
134
+
135
+ it('renders the ascending sort icon alongside the custom slot content', async () => {
136
+ findFirstColHeader().trigger('click');
137
+ await nextTick();
138
+
139
+ const icon = findFirstColHeader().findComponent(Icon);
140
+
141
+ expect(icon.exists()).toBe(true);
142
+ expect(icon.props('name')).toBe('arrow-up');
143
+ expect(findFirstColHeader().text()).toContain(customSlotContent);
144
+ });
145
+ });
146
+ });
79
147
  });
@@ -44,6 +44,8 @@ export const Default = (args, { argTypes }) => ({
44
44
  :fixed="fixed"
45
45
  :stacked="stacked"
46
46
  :foot-clone="footClone"
47
+ sort-by="col_2"
48
+ sort-desc
47
49
  hover
48
50
  selectable
49
51
  selected-variant="primary"
@@ -58,11 +60,12 @@ export const Default = (args, { argTypes }) => ({
58
60
  key: 'column_one',
59
61
  label: 'Column One',
60
62
  variant: 'secondary',
61
- sortable: false,
63
+ sortable: true,
62
64
  isRowHeader: false,
63
65
  },
64
66
  {
65
67
  key: 'col_2',
68
+ sortable: true,
66
69
  label: 'Column 2',
67
70
  formatter: (value) => value,
68
71
  },
@@ -1,6 +1,7 @@
1
1
  <!-- eslint-disable vue/multi-word-component-names -->
2
2
  <script>
3
3
  import { BTable } from 'bootstrap-vue';
4
+ import GlIcon from '../icon/icon.vue';
4
5
  import { logWarning, isDev } from '../../../utils/utils';
5
6
  import { tableFullSlots, tableFullProps, glTableLiteWarning } from './constants';
6
7
 
@@ -17,6 +18,7 @@ export default {
17
18
  name: 'GlTable',
18
19
  components: {
19
20
  BTable,
21
+ GlIcon,
20
22
  },
21
23
  inheritAttrs: false,
22
24
  props: {
@@ -31,6 +33,22 @@ export default {
31
33
  default: false,
32
34
  required: false,
33
35
  },
36
+ sortBy: {
37
+ type: String,
38
+ required: false,
39
+ default: undefined,
40
+ },
41
+ sortDesc: {
42
+ type: Boolean,
43
+ required: false,
44
+ default: false,
45
+ },
46
+ },
47
+ data() {
48
+ return {
49
+ localSortBy: this.sortBy,
50
+ localSortDesc: this.sortDesc,
51
+ };
34
52
  },
35
53
  computed: {
36
54
  stickyHeaderClass() {
@@ -39,6 +57,12 @@ export default {
39
57
  localTableClass() {
40
58
  return ['gl-table', this.tableClass, this.stickyHeaderClass];
41
59
  },
60
+ headSlots() {
61
+ return [
62
+ 'head()',
63
+ ...Object.keys(this.$scopedSlots).filter((slotName) => slotName.startsWith('head(')),
64
+ ];
65
+ },
42
66
  },
43
67
  mounted() {
44
68
  // logWarning will call isDev before logging any message
@@ -47,13 +71,44 @@ export default {
47
71
  logWarning(glTableLiteWarning, this.$el);
48
72
  }
49
73
  },
74
+ methods: {
75
+ isSortable({ field }) {
76
+ return field?.sortable;
77
+ },
78
+ getSortingIcon({ field }) {
79
+ if (this.localSortBy !== field?.key) {
80
+ return null;
81
+ }
82
+ return this.localSortDesc ? 'arrow-down' : 'arrow-up';
83
+ },
84
+ },
50
85
  };
51
86
  </script>
52
87
 
53
88
  <template>
54
- <b-table :table-class="localTableClass" :fields="fields" v-bind="$attrs" v-on="$listeners">
55
- <template v-for="slot in Object.keys($scopedSlots)" #[slot]="scope">
56
- <slot :name="slot" v-bind="scope"></slot>
89
+ <b-table
90
+ :table-class="localTableClass"
91
+ :fields="fields"
92
+ :sort-by.sync="localSortBy"
93
+ :sort-desc.sync="localSortDesc"
94
+ v-bind="$attrs"
95
+ v-on="$listeners"
96
+ >
97
+ <template v-for="slotName in Object.keys($scopedSlots)" #[slotName]="scope">
98
+ <slot :name="slotName" v-bind="scope"></slot>
99
+ </template>
100
+ <template v-for="headSlotName in headSlots" #[headSlotName]="scope">
101
+ <div :key="headSlotName" class="gl-display-flex gl-align-items-center">
102
+ <slot :name="headSlotName" v-bind="scope">{{ scope.label }}</slot
103
+ ><template v-if="isSortable(scope)">
104
+ <gl-icon
105
+ v-if="getSortingIcon(scope)"
106
+ :name="getSortingIcon(scope)"
107
+ class="gl-ml-3 gl-min-w-5 gl-text-gray-900"
108
+ />
109
+ <div v-else class="gl-display-inline-block gl-w-5 gl-h-5 gl-ml-3"></div>
110
+ </template>
111
+ </div>
57
112
  </template>
58
113
  </b-table>
59
114
  </template>