Skip to content
Snippets Groups Projects
Commit 28f25991 authored by Marcel van Remmerden's avatar Marcel van Remmerden Committed by Marcel van Remmerden
Browse files

fix: Move icon sort position in table

Moves sort icon right of label
parent 33e61436
No related branches found
No related tags found
1 merge request!3855fix: Move sort icon after column title
......@@ -110,4 +110,18 @@ table.gl-table {
}
}
}
&.table-hover tbody tr:hover,
&.table-hover td.table-secondary:hover {
background-color: $gray-10;
}
&.table-hover thead th.table-secondary:hover svg.gl-text-gray-500 {
color: $gray-900;
}
}
.table.b-table > thead > tr > th,
.table.b-table > tfoot > tr > th {
background-image: none !important;
}
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { shallowMount, mount } from '@vue/test-utils';
import { BTable } from 'bootstrap-vue';
import { logWarning } from '../../../utils/utils';
import { waitForAnimationFrame } from '../../../utils/test_utils';
import Icon from '../icon/icon.vue';
import { glTableLiteWarning } from './constants';
import Table from './table.vue';
......@@ -18,14 +20,15 @@ describe('GlTable', () => {
<p>Placeholder empty text</p>`,
};
const factory = ({ props = {}, scopedSlots = {} } = {}) => {
wrapper = shallowMount(Table, {
const factory = ({ mountFn = shallowMount, props = {}, scopedSlots = {} } = {}) => {
wrapper = mountFn(Table, {
scopedSlots,
propsData: props,
});
};
const findBTable = () => wrapper.findComponent(BTable);
const findFirstColHeader = () => findBTable().find('thead').findAll('th').at(0);
afterEach(() => {
logWarning.mockClear();
......@@ -76,4 +79,69 @@ describe('GlTable', () => {
expect(wrapper.props('fields')).toEqual(fields);
expect(findBTable().props('fields')).toEqual(fields);
});
describe('sortable columns', () => {
const field = {
key: 'name',
label: 'Name column',
sortable: true,
};
describe('without custom slots', () => {
beforeEach(() => {
factory({ mountFn: mount, props: { fields: [field] } });
});
it('sets the correct column label', () => {
expect(findFirstColHeader().text()).toMatch(field.label);
});
it('renders the ascending sort icon', async () => {
findFirstColHeader().trigger('click');
await nextTick();
const icon = findFirstColHeader().findComponent(Icon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('arrow-up');
});
it('renders the descending sort icon', async () => {
findFirstColHeader().trigger('click');
findFirstColHeader().trigger('click');
await nextTick();
const icon = findFirstColHeader().findComponent(Icon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('arrow-down');
});
});
describe('when headers are customized via slots', () => {
const customSlotContent = 'customSlotContent';
beforeEach(() => {
factory({
mountFn: mount,
props: {
fields: [field],
},
scopedSlots: {
'head(name)': `<div>${customSlotContent}</div>`,
},
});
});
it('renders the ascending sort icon alongside the custom slot content', async () => {
findFirstColHeader().trigger('click');
await nextTick();
const icon = findFirstColHeader().findComponent(Icon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('arrow-up');
expect(findFirstColHeader().text()).toContain(customSlotContent);
});
});
});
});
......@@ -44,6 +44,8 @@ export const Default = (args, { argTypes }) => ({
:fixed="fixed"
:stacked="stacked"
:foot-clone="footClone"
sort-by="col_2"
sort-desc
hover
selectable
selected-variant="primary"
......@@ -58,11 +60,12 @@ export const Default = (args, { argTypes }) => ({
key: 'column_one',
label: 'Column One',
variant: 'secondary',
sortable: false,
sortable: true,
isRowHeader: false,
},
{
key: 'col_2',
sortable: true,
label: 'Column 2',
formatter: (value) => value,
},
......
<!-- eslint-disable vue/multi-word-component-names -->
<script>
import { BTable } from 'bootstrap-vue';
import GlIcon from '../icon/icon.vue';
import { logWarning, isDev } from '../../../utils/utils';
import { tableFullSlots, tableFullProps, glTableLiteWarning } from './constants';
......@@ -17,6 +18,7 @@ export default {
name: 'GlTable',
components: {
BTable,
GlIcon,
},
inheritAttrs: false,
props: {
......@@ -31,6 +33,22 @@ export default {
default: false,
required: false,
},
sortBy: {
type: String,
required: false,
default: undefined,
},
sortDesc: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
localSortBy: this.sortBy,
localSortDesc: this.sortDesc,
};
},
computed: {
stickyHeaderClass() {
......@@ -39,6 +57,12 @@ export default {
localTableClass() {
return ['gl-table', this.tableClass, this.stickyHeaderClass];
},
headSlots() {
return [
'head()',
...Object.keys(this.$scopedSlots).filter((slotName) => slotName.startsWith('head(')),
];
},
},
mounted() {
// logWarning will call isDev before logging any message
......@@ -47,13 +71,45 @@ export default {
logWarning(glTableLiteWarning, this.$el);
}
},
methods: {
isSortable({ field }) {
return field?.sortable;
},
getSortingIcon({ field }) {
if (this.localSortBy !== field?.key) {
return null;
}
return this.localSortDesc ? 'arrow-down' : 'arrow-up';
},
},
};
</script>
<template>
<b-table :table-class="localTableClass" :fields="fields" v-bind="$attrs" v-on="$listeners">
<template v-for="slot in Object.keys($scopedSlots)" #[slot]="scope">
<slot :name="slot" v-bind="scope"></slot>
<b-table
:table-class="localTableClass"
:fields="fields"
:sort-by.sync="localSortBy"
:sort-desc.sync="localSortDesc"
v-bind="$attrs"
v-on="$listeners"
>
<template v-for="slotName in Object.keys($scopedSlots)" #[slotName]="scope">
<slot :name="slotName" v-bind="scope"></slot>
</template>
<template v-for="headSlotName in headSlots" #[headSlotName]="scope">
<div :key="headSlotName" class="gl-display-flex gl-align-items-center">
<slot :name="headSlotName" v-bind="scope">{{ scope.label }}</slot
><gl-icon
v-if="isSortable(scope) && getSortingIcon(scope)"
:name="getSortingIcon(scope)"
class="gl-ml-3 gl-min-w-5 gl-text-gray-500"
/>
<div
v-if="isSortable(scope) && !getSortingIcon(scope)"
class="gl-display-inline-block gl-w-5 gl-h-5 gl-ml-3 gl-min-w-5"
></div>
</div>
</template>
</b-table>
</template>
tests/__image_snapshots__/storyshots-spec-js-image-storyshots-base-table-table-default-1-snap.png

17.8 KiB | W: 800px | H: 600px

tests/__image_snapshots__/storyshots-spec-js-image-storyshots-base-table-table-default-1-snap.png

17.7 KiB | W: 800px | H: 600px

tests/__image_snapshots__/storyshots-spec-js-image-storyshots-base-table-table-default-1-snap.png
tests/__image_snapshots__/storyshots-spec-js-image-storyshots-base-table-table-default-1-snap.png
tests/__image_snapshots__/storyshots-spec-js-image-storyshots-base-table-table-default-1-snap.png
tests/__image_snapshots__/storyshots-spec-js-image-storyshots-base-table-table-default-1-snap.png
  • 2-up
  • Swipe
  • Onion skin
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment