@gitlab/ui 74.9.0 → 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 +18 -0
- package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +1 -1
- 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/new_dropdowns/base_dropdown/base_dropdown.spec.js +3 -3
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +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
|
@@ -307,7 +307,7 @@ describe('base dropdown', () => {
|
|
|
307
307
|
// close menu clicking toggle btn
|
|
308
308
|
await toggle.trigger('click');
|
|
309
309
|
expect(menu.classes('gl-display-block!')).toBe(false);
|
|
310
|
-
expect(toggle.attributes('aria-expanded')).
|
|
310
|
+
expect(toggle.attributes('aria-expanded')).toBe('false');
|
|
311
311
|
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
|
|
312
312
|
expect(toggle.element).toHaveFocus();
|
|
313
313
|
});
|
|
@@ -332,7 +332,7 @@ describe('base dropdown', () => {
|
|
|
332
332
|
await menu.trigger('keydown.esc');
|
|
333
333
|
|
|
334
334
|
expect(menu.classes('gl-display-block!')).toBe(false);
|
|
335
|
-
expect(toggle.attributes('aria-expanded')).
|
|
335
|
+
expect(toggle.attributes('aria-expanded')).toBe('false');
|
|
336
336
|
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
|
|
337
337
|
expect(toggle.element).toHaveFocus();
|
|
338
338
|
});
|
|
@@ -391,7 +391,7 @@ describe('base dropdown', () => {
|
|
|
391
391
|
moveFocusWithinDropdown();
|
|
392
392
|
await menu.trigger('keydown.esc');
|
|
393
393
|
expect(menu.classes('gl-display-block!')).toBe(true);
|
|
394
|
-
expect(toggle.attributes('aria-expanded')).
|
|
394
|
+
expect(toggle.attributes('aria-expanded')).toBe('true');
|
|
395
395
|
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toBeUndefined();
|
|
396
396
|
expect(toggle.element).not.toHaveFocus();
|
|
397
397
|
});
|
|
@@ -166,7 +166,7 @@ export default {
|
|
|
166
166
|
ariaAttributes() {
|
|
167
167
|
return {
|
|
168
168
|
'aria-haspopup': this.ariaHaspopup,
|
|
169
|
-
'aria-expanded': this.visible,
|
|
169
|
+
'aria-expanded': String(this.visible),
|
|
170
170
|
'aria-controls': this.baseDropdownId,
|
|
171
171
|
'aria-labelledby': this.toggleLabelledBy,
|
|
172
172
|
};
|
|
@@ -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>
|