Commit 665741cb authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Jose Ivan Vargas

Update list_item component with details slots

- update source
- adjust tests
parent 2507dfd2
......@@ -63,7 +63,8 @@ export default {
v-for="(tag, index) in tags"
:key="tag.path"
:tag="tag"
:index="index"
:first="index === 0"
:last="index === tags.length - 1"
:selected="selectedItems[tag.name]"
:is-desktop="isDesktop"
@select="updateSelectedItems(tag.name)"
......
......@@ -30,16 +30,12 @@ export default {
type: Object,
required: true,
},
index: {
type: Number,
required: true,
},
selected: {
isDesktop: {
type: Boolean,
default: false,
required: false,
},
isDesktop: {
selected: {
type: Boolean,
default: false,
required: false,
......@@ -66,7 +62,7 @@ export default {
</script>
<template>
<list-item :index="index" :selected="selected">
<list-item v-bind="$attrs" :selected="selected">
<template #left-action>
<gl-form-checkbox class="gl-m-0" :checked="selected" @change="$emit('select')" />
</template>
......
<script>
import { GlButton } from '@gitlab/ui';
export default {
name: 'ListItem',
components: { GlButton },
props: {
index: {
type: Number,
default: 0,
first: {
type: Boolean,
default: false,
required: false,
},
last: {
type: Boolean,
default: false,
required: false,
},
disabled: {
......@@ -18,51 +26,103 @@ export default {
required: false,
},
},
data() {
return {
isDetailsShown: false,
detailsSlots: [],
};
},
computed: {
optionalClasses() {
return {
'gl-border-t-solid gl-border-t-1': this.index === 0,
'gl-border-t-1': !this.first,
'gl-border-t-2': this.first,
'gl-border-b-1': !this.last,
'gl-border-b-2': this.last,
'disabled-content': this.disabled,
'gl-border-gray-200': !this.selected,
'gl-bg-blue-50 gl-border-blue-200': this.selected,
};
},
},
mounted() {
this.detailsSlots = Object.keys(this.$slots).filter(k => k.startsWith('details_'));
},
methods: {
toggleDetails() {
this.isDetailsShown = !this.isDetailsShown;
},
},
};
</script>
<template>
<div
class="gl-display-flex gl-align-items-center gl-border-b-solid gl-border-b-1 gl-py-4 gl-px-2"
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid"
:class="optionalClasses"
>
<div v-if="$slots['left-action']" class="gl-mr-5 gl-display-none gl-display-sm-block">
<slot name="left-action"></slot>
</div>
<div class="gl-display-flex gl-flex-direction-column gl-flex-fill-1">
<div class="gl-display-flex gl-align-items-center gl-py-4 gl-px-2">
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-text-black-normal gl-font-weight-bold"
v-if="$slots['left-action']"
class="gl-w-7 gl-display-none gl-display-sm-flex gl-justify-content-start gl-pl-2"
>
<div>
<slot name="left-primary"></slot>
<slot name="left-action"></slot>
</div>
<div class="gl-display-flex gl-flex-direction-column gl-flex-fill-1">
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-text-black-normal gl-font-weight-bold"
>
<div class="gl-display-flex gl-align-items-center">
<slot name="left-primary"></slot>
<gl-button
v-if="detailsSlots.length > 0"
:selected="isDetailsShown"
icon="ellipsis_h"
size="small"
class="gl-ml-2"
@click="toggleDetails"
/>
</div>
<div>
<slot name="right-primary"></slot>
</div>
</div>
<div>
<slot name="right-primary"></slot>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-font-sm gl-text-gray-500"
>
<div>
<slot name="left-secondary"></slot>
</div>
<div>
<slot name="right-secondary"></slot>
</div>
</div>
</div>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-font-sm gl-text-gray-500"
v-if="$slots['right-action']"
class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-2"
>
<div>
<slot name="left-secondary"></slot>
</div>
<div>
<slot name="right-secondary"></slot>
</div>
<slot name="right-action"></slot>
</div>
</div>
<div v-if="$slots['right-action']" class="gl-ml-5 gl-display-none gl-display-sm-block">
<slot name="right-action"></slot>
<div class="gl-display-flex">
<div class="gl-w-7"></div>
<div
v-if="isDetailsShown"
class="gl-display-flex gl-flex-direction-column gl-flex-fill-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-200 gl-mb-3"
>
<div
v-for="(row, detailIndex) in detailsSlots"
:key="detailIndex"
class="gl-px-5 gl-py-2"
:class="{
'gl-border-gray-200 gl-border-t-solid gl-border-t-1': detailIndex !== 0,
}"
>
<slot :name="row"></slot>
</div>
</div>
<div class="gl-w-9"></div>
</div>
</div>
</template>
......@@ -37,7 +37,8 @@ export default {
v-for="(listItem, index) in images"
:key="index"
:item="listItem"
:index="index"
:first="index === 0"
:last="index === images.length - 1"
@delete="$emit('delete', $event)"
/>
......
......@@ -29,11 +29,6 @@ export default {
type: Object,
required: true,
},
index: {
type: Number,
default: 0,
required: false,
},
},
i18n: {
LIST_DELETE_BUTTON_DISABLED,
......@@ -71,7 +66,7 @@ export default {
disabled: !item.deleting,
title: $options.i18n.ROW_SCHEDULED_FOR_DELETION,
}"
:index="index"
v-bind="$attrs"
:disabled="item.deleting"
>
<template #left-primary>
......
......@@ -108,3 +108,12 @@
.gl-transition-property-stroke {
transition-property: stroke;
}
// temporary class till giltab-ui one is merged
.gl-border-t-2 {
border-top-width: $gl-border-size-2;
}
.gl-border-b-2 {
border-bottom-width: $gl-border-size-2;
}
......@@ -102,12 +102,16 @@ describe('Tags List', () => {
it('the correct props are bound to it', () => {
mountComponent();
expect(
findTagsListRow()
.at(0)
.attributes(),
).toMatchObject({
index: '0',
const rows = findTagsListRow();
expect(rows.at(0).attributes()).toMatchObject({
first: 'true',
isdesktop: 'true',
});
// The list has only two tags and for some reasons .at(-1) does not work
expect(rows.at(1).attributes()).toMatchObject({
last: 'true',
isdesktop: 'true',
});
});
......
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import component from '~/registry/explorer/components/list_item.vue';
......@@ -10,8 +11,10 @@ describe('list item', () => {
const findRightPrimarySlot = () => wrapper.find('[data-testid="right-primary"]');
const findRightSecondarySlot = () => wrapper.find('[data-testid="right-secondary"]');
const findRightActionSlot = () => wrapper.find('[data-testid="right-action"]');
const findDetailsSlot = name => wrapper.find(`[data-testid="${name}"]`);
const findToggleDetailsButton = () => wrapper.find(GlButton);
const mountComponent = propsData => {
const mountComponent = (propsData, slots) => {
wrapper = shallowMount(component, {
propsData,
slots: {
......@@ -21,6 +24,7 @@ describe('list item', () => {
'right-primary': '<div data-testid="right-primary" />',
'right-secondary': '<div data-testid="right-secondary" />',
'right-action': '<div data-testid="right-action" />',
...slots,
},
});
};
......@@ -44,6 +48,50 @@ describe('list item', () => {
expect(finderFunction().exists()).toBe(true);
});
describe.each`
slotNames
${['details_foo']}
${['details_foo', 'details_bar']}
${['details_foo', 'details_bar', 'details_baz']}
`('$slotNames details slots', ({ slotNames }) => {
const slotMocks = slotNames.reduce((acc, current) => {
acc[current] = `<div data-testid="${current}" />`;
return acc;
}, {});
it('are visible when details is shown', async () => {
mountComponent({}, slotMocks);
await wrapper.vm.$nextTick();
findToggleDetailsButton().vm.$emit('click');
await wrapper.vm.$nextTick();
slotNames.forEach(name => {
expect(findDetailsSlot(name).exists()).toBe(true);
});
});
it('are not visible when details are not shown', () => {
mountComponent({}, slotMocks);
slotNames.forEach(name => {
expect(findDetailsSlot(name).exists()).toBe(false);
});
});
});
describe('details toggle button', () => {
it('is visible when at least one details slot exists', async () => {
mountComponent({}, { details_foo: '<span></span>' });
await wrapper.vm.$nextTick();
expect(findToggleDetailsButton().exists()).toBe(true);
});
it('is hidden without details slot', () => {
mountComponent();
expect(findToggleDetailsButton().exists()).toBe(false);
});
});
describe('disabled prop', () => {
it('when true applies disabled-content class', () => {
mountComponent({ disabled: true });
......@@ -58,21 +106,31 @@ describe('list item', () => {
});
});
describe('index prop', () => {
it('when index is 0 displays a top border', () => {
mountComponent({ index: 0 });
describe('first prop', () => {
it('when is true displays a double top border', () => {
mountComponent({ first: true });
expect(wrapper.classes()).toEqual(
expect.arrayContaining(['gl-border-t-solid', 'gl-border-t-1']),
);
expect(wrapper.classes('gl-border-t-2')).toBe(true);
});
it('when index is not 0 hides top border', () => {
mountComponent({ index: 1 });
it('when is false display a single top border', () => {
mountComponent({ first: false });
expect(wrapper.classes()).toEqual(
expect.not.arrayContaining(['gl-border-t-solid', 'gl-border-t-1']),
);
expect(wrapper.classes('gl-border-t-1')).toBe(true);
});
});
describe('last prop', () => {
it('when is true displays a double bottom border', () => {
mountComponent({ last: true });
expect(wrapper.classes('gl-border-b-2')).toBe(true);
});
it('when is false display a single bottom border', () => {
mountComponent({ last: false });
expect(wrapper.classes('gl-border-b-1')).toBe(true);
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment