diff options
author | Paul Slaughter <pslaughter@gitlab.com> | 2019-01-02 13:09:54 -0600 |
---|---|---|
committer | Paul Slaughter <pslaughter@gitlab.com> | 2019-01-04 16:50:47 -0600 |
commit | ed7f44aabab081fb1544d7160dd732645bd10261 (patch) | |
tree | 2fec55f6c213e4816503ac72cd0f0f4a197cf2db | |
parent | c50b0e58feb208fd26129548ad086c3ef50df604 (diff) | |
download | gitlab-ce-ee1979-user-avatar-list-component.tar.gz |
Create shared user-avatar-list componentee1979-user-avatar-list-component
**Why?**
We need a component that can expand / collapse a list of avatars for
approval rules table.
See issue https://gitlab.com/gitlab-org/gitlab-ee/issues/1979
3 files changed, 219 insertions, 0 deletions
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue new file mode 100644 index 00000000000..7361867edc5 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue @@ -0,0 +1,83 @@ +<script> +import { GlButton } from '@gitlab/ui'; +import { sprintf, __ } from '~/locale'; +import UserAvatarLink from './user_avatar_link.vue'; + +export default { + components: { + UserAvatarLink, + GlButton, + }, + props: { + items: { + type: Array, + required: true, + }, + breakpoint: { + type: Number, + required: false, + default: 10, + }, + imgSize: { + type: Number, + required: false, + default: 20, + }, + }, + data() { + return { + isExpanded: false, + }; + }, + computed: { + visibleItems() { + if (!this.hasHiddenItems) { + return this.items; + } + + return this.items.slice(0, this.breakpoint); + }, + hasHiddenItems() { + return this.hasBreakpoint && !this.isExpanded && this.items.length > this.breakpoint; + }, + hasBreakpoint() { + return this.breakpoint > 0 && this.items.length > this.breakpoint; + }, + expandText() { + if (!this.hasHiddenItems) { + return ''; + } + + const count = this.items.length - this.breakpoint; + + return sprintf(__('%{count} more'), { count }); + }, + }, + methods: { + expand() { + this.isExpanded = true; + }, + collapse() { + this.isExpanded = false; + }, + }, +}; +</script> + +<template> + <div> + <user-avatar-link + v-for="item in visibleItems" + :key="item.id" + :link-href="item.web_url" + :img-src="item.avatar_url" + :img-alt="item.name" + :tooltip-text="item.name" + :img-size="imgSize" + /> + <template v-if="hasBreakpoint"> + <gl-button v-if="hasHiddenItems" variant="link" @click="expand"> {{ expandText }} </gl-button> + <gl-button v-else variant="link" @click="collapse"> {{ __('show less') }} </gl-button> + </template> + </div> +</template> diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ddfd5599883..5a7df199bb3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -106,6 +106,9 @@ msgstr "" msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)" msgstr "" +msgid "%{count} more" +msgstr "" + msgid "%{count} more assignees" msgstr "" @@ -8175,6 +8178,9 @@ msgstr[1] "" msgid "should be higher than %{access} inherited membership from group %{group_name}" msgstr "" +msgid "show less" +msgstr "" + msgid "source" msgstr "" diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js new file mode 100644 index 00000000000..64aa7e29718 --- /dev/null +++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js @@ -0,0 +1,130 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { GlButton } from '@gitlab/ui'; +import { TEST_HOST } from 'spec/test_constants'; +import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; + +const TEST_IMAGE_SIZE = 7; +const TEST_BREAKPOINT = 5; + +const createUser = id => ({ + id, + name: 'Lorem', + web_url: `${TEST_HOST}/${id}`, + avatar_url: `${TEST_HOST}/${id}/avatar`, +}); +const createList = n => + Array(n) + .fill(1) + .map((x, id) => createUser(id)); + +const localVue = createLocalVue(); + +describe('UserAvatarList', () => { + let propsData; + let wrapper; + + const factory = options => { + wrapper = shallowMount(localVue.extend(UserAvatarList), { + localVue, + propsData, + ...options, + }); + }; + + const clickButton = () => { + const button = wrapper.find(GlButton); + button.vm.$emit('click'); + }; + + beforeEach(() => { + propsData = { imgSize: TEST_IMAGE_SIZE }; + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('with no breakpoint', () => { + beforeEach(() => { + propsData.breakpoint = 0; + }); + + it('renders avatars', () => { + const items = createList(20); + propsData.items = items; + factory(); + + const links = wrapper.findAll(UserAvatarLink); + const linkProps = links.wrappers.map(x => x.props()); + + expect(linkProps).toEqual( + propsData.items.map(x => + jasmine.objectContaining({ + linkHref: x.web_url, + imgSrc: x.avatar_url, + imgAlt: x.name, + tooltipText: x.name, + imgSize: TEST_IMAGE_SIZE, + }), + ), + ); + }); + }); + + describe('with breakpoint and length equal to breakpoint', () => { + beforeEach(() => { + propsData.breakpoint = TEST_BREAKPOINT; + propsData.items = createList(TEST_BREAKPOINT); + }); + + it('renders all avatars if length is <= breakpoint', () => { + factory(); + + const links = wrapper.findAll(UserAvatarLink); + + expect(links.length).toEqual(propsData.items.length); + }); + + it('does not show button', () => { + factory(); + + expect(wrapper.find(GlButton).exists()).toBe(false); + }); + }); + + describe('with breakpoint and length greater than breakpoint', () => { + beforeEach(() => { + propsData.breakpoint = TEST_BREAKPOINT; + propsData.items = createList(TEST_BREAKPOINT + 1); + }); + + it('renders avatars up to breakpoint', () => { + factory(); + + const links = wrapper.findAll(UserAvatarLink); + + expect(links.length).toEqual(TEST_BREAKPOINT); + }); + + describe('with expand clicked', () => { + beforeEach(() => { + factory(); + clickButton(); + }); + + it('renders all avatars', () => { + const links = wrapper.findAll(UserAvatarLink); + + expect(links.length).toEqual(propsData.items.length); + }); + + it('with collapse clicked, it renders avatars up to breakpoint', () => { + clickButton(); + const links = wrapper.findAll(UserAvatarLink); + + expect(links.length).toEqual(TEST_BREAKPOINT); + }); + }); + }); +}); |