@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.
- package/CHANGELOG.md +11 -0
- package/dist/components/base/table/table.js +40 -2
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/tokens/css/tokens.css +1 -1
- package/dist/tokens/css/tokens.dark.css +1 -1
- package/dist/tokens/js/tokens.dark.js +1 -1
- package/dist/tokens/js/tokens.js +1 -1
- package/dist/tokens/scss/_tokens.dark.scss +1 -1
- package/dist/tokens/scss/_tokens.scss +1 -1
- package/package.json +1 -1
- package/src/components/base/table/table.scss +10 -0
- package/src/components/base/table/table.spec.js +71 -3
- package/src/components/base/table/table.stories.js +4 -1
- package/src/components/base/table/table.vue +58 -3
package/dist/tokens/js/tokens.js
CHANGED
package/package.json
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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:
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
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>
|