Commit cf137da3 authored by Phil Hughes's avatar Phil Hughes

Merge branch '52366-improved-group-lists-ui' into 'master'

Resolve "Improved group lists UI"

Closes #52366

See merge request gitlab-org/gitlab-ce!26542
parents 77b2e8a2 7a4b1521
<script> <script>
import { GlLoadingIcon } from '@gitlab/ui';
import { visitUrl } from '../../lib/utils/url_utility'; import { visitUrl } from '../../lib/utils/url_utility';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import identicon from '../../vue_shared/components/identicon.vue'; import identicon from '../../vue_shared/components/identicon.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '../constants';
import itemCaret from './item_caret.vue'; import itemCaret from './item_caret.vue';
import itemTypeIcon from './item_type_icon.vue'; import itemTypeIcon from './item_type_icon.vue';
import itemStats from './item_stats.vue'; import itemStats from './item_stats.vue';
import itemStatsValue from './item_stats_value.vue';
import itemActions from './item_actions.vue'; import itemActions from './item_actions.vue';
export default { export default {
...@@ -14,10 +17,12 @@ export default { ...@@ -14,10 +17,12 @@ export default {
tooltip, tooltip,
}, },
components: { components: {
GlLoadingIcon,
identicon, identicon,
itemCaret, itemCaret,
itemTypeIcon, itemTypeIcon,
itemStats, itemStats,
itemStatsValue,
itemActions, itemActions,
}, },
props: { props: {
...@@ -57,6 +62,12 @@ export default { ...@@ -57,6 +62,12 @@ export default {
isGroup() { isGroup() {
return this.group.type === 'group'; return this.group.type === 'group';
}, },
visibilityIcon() {
return VISIBILITY_TYPE_ICON[this.group.visibility];
},
visibilityTooltip() {
return GROUP_VISIBILITY_TYPE[this.group.visibility];
},
}, },
methods: { methods: {
onClickRowGroup(e) { onClickRowGroup(e) {
...@@ -80,43 +91,61 @@ export default { ...@@ -80,43 +91,61 @@ export default {
<li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup"> <li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup">
<div <div
:class="{ 'project-row-contents': !isGroup }" :class="{ 'project-row-contents': !isGroup }"
class="group-row-contents d-flex justify-content-end align-items-center" class="group-row-contents d-flex align-items-center"
> >
<div class="folder-toggle-wrap append-right-4 d-flex align-items-center"> <div class="folder-toggle-wrap append-right-4 d-flex align-items-center">
<item-caret :is-group-open="group.isOpen" /> <item-caret :is-group-open="group.isOpen" />
<item-type-icon :item-type="group.type" :is-group-open="group.isOpen" /> <item-type-icon :item-type="group.type" :is-group-open="group.isOpen" />
</div> </div>
<gl-loading-icon
v-if="group.isChildrenLoading"
size="md"
class="d-none d-sm-inline-flex flex-shrink-0 append-right-10"
/>
<div <div
:class="{ 'content-loading': group.isChildrenLoading }" :class="{ 'd-sm-flex': !group.isChildrenLoading }"
class="avatar-container rect-avatar s24 d-none d-sm-flex" class="avatar-container rect-avatar s32 d-none flex-grow-0 flex-shrink-0 "
> >
<a :href="group.relativePath" class="no-expand"> <a :href="group.relativePath" class="no-expand">
<img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s24" /> <img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s32" />
<identicon v-else :entity-id="group.id" :entity-name="group.name" size-class="s24" /> <identicon v-else :entity-id="group.id" :entity-name="group.name" size-class="s32" />
</a> </a>
</div> </div>
<div class="group-text flex-grow"> <div class="group-text-container d-flex flex-fill align-items-center">
<div class="title namespace-title append-right-8"> <div class="group-text flex-grow-1 flex-shrink-1">
<a <div class="d-flex align-items-center flex-wrap title namespace-title append-right-8">
v-tooltip <a
:href="group.relativePath" v-tooltip
:title="group.fullName" :href="group.relativePath"
class="no-expand" :title="group.fullName"
data-placement="bottom" class="no-expand prepend-top-8 append-right-8"
>{{ data-placement="bottom"
// ending bracket must be by closing tag to prevent >{{
// link hover text-decoration from over-extending // ending bracket must be by closing tag to prevent
group.name // link hover text-decoration from over-extending
}}</a group.name
> }}</a
<span v-if="group.permission" class="user-access-role"> {{ group.permission }} </span> >
<item-stats-value
:icon-name="visibilityIcon"
:title="visibilityTooltip"
css-class="item-visibility d-inline-flex align-items-center prepend-top-8 append-right-4"
/>
<span v-if="group.permission" class="user-access-role prepend-top-8">
{{ group.permission }}
</span>
</div>
<div v-if="group.description" class="description">
<span v-html="group.description"> </span>
</div>
</div> </div>
<div v-if="group.description" class="description"> <div
<span v-html="group.description"> </span> class="metadata align-items-md-center d-flex flex-grow-1 flex-shrink-0 flex-wrap justify-content-md-between"
>
<item-actions v-if="isGroup" :group="group" :parent-group="parentGroup" />
<item-stats :item="group" class="group-stats prepend-top-2 d-none d-md-flex" />
</div> </div>
</div> </div>
<item-stats :item="group" class="group-stats prepend-top-2" />
<item-actions v-if="isGroup" :group="group" :parent-group="parentGroup" />
</div> </div>
<group-folder <group-folder
v-if="group.isOpen && hasChildren" v-if="group.isOpen && hasChildren"
......
...@@ -44,31 +44,31 @@ export default { ...@@ -44,31 +44,31 @@ export default {
</script> </script>
<template> <template>
<div class="controls"> <div class="controls d-flex justify-content-end">
<a <a
v-if="group.canEdit" v-if="group.canLeave"
v-tooltip v-tooltip
:href="group.editPath" :href="group.leavePath"
:title="editBtnTitle" :title="leaveBtnTitle"
:aria-label="editBtnTitle" :aria-label="leaveBtnTitle"
data-container="body" data-container="body"
data-placement="bottom" data-placement="bottom"
class="edit-group btn no-expand" class="leave-group btn btn-xs no-expand"
@click.prevent="onLeaveGroup"
> >
<icon name="settings" /> <icon name="leave" css-classes="position-top-0" />
</a> </a>
<a <a
v-if="group.canLeave" v-if="group.canEdit"
v-tooltip v-tooltip
:href="group.leavePath" :href="group.editPath"
:title="leaveBtnTitle" :title="editBtnTitle"
:aria-label="leaveBtnTitle" :aria-label="editBtnTitle"
data-container="body" data-container="body"
data-placement="bottom" data-placement="bottom"
class="leave-group btn no-expand" class="edit-group btn btn-xs no-expand"
@click.prevent="onLeaveGroup"
> >
<icon name="leave" /> <icon name="settings" css-classes="position-top-0" />
</a> </a>
</div> </div>
</template> </template>
...@@ -21,5 +21,5 @@ export default { ...@@ -21,5 +21,5 @@ export default {
</script> </script>
<template> <template>
<span class="folder-caret"> <icon :size="12" :name="iconClass" /> </span> <span class="folder-caret append-right-4"> <icon :size="10" :name="iconClass" /> </span>
</template> </template>
...@@ -48,7 +48,7 @@ export default { ...@@ -48,7 +48,7 @@ export default {
:title="__('Subgroups')" :title="__('Subgroups')"
:value="item.subgroupCount" :value="item.subgroupCount"
css-class="number-subgroups" css-class="number-subgroups"
icon-name="folder" icon-name="folder-o"
/> />
<item-stats-value <item-stats-value
v-if="isGroup" v-if="isGroup"
...@@ -70,12 +70,6 @@ export default { ...@@ -70,12 +70,6 @@ export default {
css-class="project-stars" css-class="project-stars"
icon-name="star" icon-name="star"
/> />
<item-stats-value
:icon-name="visibilityIcon"
:title="visibilityTooltip"
css-class="item-visibility"
tooltip-placement="left"
/>
<div v-if="isProject" class="last-updated"> <div v-if="isProject" class="last-updated">
<time-ago-tooltip :time="item.updatedAt" tooltip-placement="bottom" /> <time-ago-tooltip :time="item.updatedAt" tooltip-placement="bottom" />
</div> </div>
......
...@@ -20,7 +20,7 @@ export default { ...@@ -20,7 +20,7 @@ export default {
computed: { computed: {
iconClass() { iconClass() {
if (this.itemType === ITEM_TYPE.GROUP) { if (this.itemType === ITEM_TYPE.GROUP) {
return this.isGroupOpen ? 'folder-open' : 'folder'; return this.isGroupOpen ? 'folder-open' : 'folder-o';
} }
return 'bookmark'; return 'bookmark';
}, },
......
...@@ -133,7 +133,6 @@ ul.content-list { ...@@ -133,7 +133,6 @@ ul.content-list {
.description { .description {
@include str-truncated; @include str-truncated;
color: $gl-text-color-secondary;
} }
.controls { .controls {
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
.stats { .stats {
float: right; float: right;
line-height: $list-text-height; line-height: $list-text-height;
color: $gl-text-color; color: $gl-text-color-secondary;
span { span {
margin-right: 15px; margin-right: 15px;
......
...@@ -168,12 +168,6 @@ ...@@ -168,12 +168,6 @@
} }
} }
.groups-listing {
.group-list-tree .group-row:first-child {
border-top: 0;
}
}
.card { .card {
.shared_runners_limit_under_quota { .shared_runners_limit_under_quota {
color: $green-500; color: $green-500;
...@@ -260,7 +254,6 @@ table.pipeline-project-metrics tr td { ...@@ -260,7 +254,6 @@ table.pipeline-project-metrics tr td {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 20px;
margin: -5px 3px;
padding: 0 $label-padding; padding: 0 $label-padding;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: $label-border-radius; border-radius: $label-border-radius;
...@@ -294,39 +287,6 @@ table.pipeline-project-metrics tr td { ...@@ -294,39 +287,6 @@ table.pipeline-project-metrics tr td {
} }
.group-list-tree { .group-list-tree {
.avatar-container.content-loading {
position: relative;
> a,
> a .avatar {
height: 100%;
border-radius: 50%;
}
> a {
padding: 2px;
.avatar {
border: 2px solid $white-normal;
&.identicon {
line-height: 15px;
}
}
}
&::after {
content: '';
position: absolute;
height: 100%;
width: 100%;
background-color: transparent;
border: 2px outset $gl-gray-200;
border-radius: 50%;
animation: spin-avatar 3s infinite linear;
}
}
.folder-toggle-wrap { .folder-toggle-wrap {
font-size: 0; font-size: 0;
flex-shrink: 0; flex-shrink: 0;
...@@ -339,13 +299,14 @@ table.pipeline-project-metrics tr td { ...@@ -339,13 +299,14 @@ table.pipeline-project-metrics tr td {
.folder-caret, .folder-caret,
.item-type-icon { .item-type-icon {
display: inline-block; display: inline-block;
color: $gl-text-color-secondary;
} }
.folder-caret { .folder-caret {
width: 15px; width: $gl-font-size-large;
svg { svg {
margin-bottom: 1px; margin-bottom: 2px;
} }
} }
...@@ -420,7 +381,7 @@ table.pipeline-project-metrics tr td { ...@@ -420,7 +381,7 @@ table.pipeline-project-metrics tr td {
} }
.group-row-contents { .group-row-contents {
padding: $gl-padding-top; padding: $gl-padding;
&:hover { &:hover {
border-color: $blue-200; border-color: $blue-200;
...@@ -428,10 +389,15 @@ table.pipeline-project-metrics tr td { ...@@ -428,10 +389,15 @@ table.pipeline-project-metrics tr td {
cursor: pointer; cursor: pointer;
} }
.group-text-container,
.group-text { .group-text {
min-width: 0; // allows for truncated text within flex children min-width: 0; // allows for truncated text within flex children
} }
.group-text {
flex-basis: 100%;
}
.avatar-container { .avatar-container {
flex-shrink: 0; flex-shrink: 0;
...@@ -441,6 +407,21 @@ table.pipeline-project-metrics tr td { ...@@ -441,6 +407,21 @@ table.pipeline-project-metrics tr td {
} }
} }
.title {
margin-top: -$gl-padding-8; // negative margin required for flex-wrap
font-size: $gl-font-size-large;
}
.item-visibility {
color: $gl-text-color-secondary;
}
@include media-breakpoint-down(md) {
.title {
font-size: $gl-font-size;
}
}
&.has-more-items { &.has-more-items {
display: block; display: block;
padding: 20px 10px; padding: 20px 10px;
...@@ -477,17 +458,18 @@ table.pipeline-project-metrics tr td { ...@@ -477,17 +458,18 @@ table.pipeline-project-metrics tr td {
} }
.controls { .controls {
flex-shrink: 0; flex-basis: 90px;
> .btn { > .btn {
margin: 0 0 0 $btn-margin-5; margin: 0 $btn-side-margin 0 0;
color: $gl-text-color-secondary;
} }
} }
}
@include media-breakpoint-down(xs) { .metadata {
.group-stats { @include media-breakpoint-up(md) {
display: none; flex-basis: 240px;
}
} }
} }
......
...@@ -889,7 +889,6 @@ pre.light-well { ...@@ -889,7 +889,6 @@ pre.light-well {
@include basic-list-stats; @include basic-list-stats;
display: flex; display: flex;
align-items: center; align-items: center;
color: $gl-text-color-secondary;
padding: $gl-padding 0; padding: $gl-padding 0;
@include media-breakpoint-up(lg) { @include media-breakpoint-up(lg) {
...@@ -952,10 +951,6 @@ pre.light-well { ...@@ -952,10 +951,6 @@ pre.light-well {
.description { .description {
line-height: 1.5; line-height: 1.5;
@include media-breakpoint-up(md) {
color: $gl-text-color;
}
} }
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
......
---
title: Improve group list UI
merge_request: 26542
author:
type: changed
...@@ -156,6 +156,8 @@ describe('GroupItemComponent', () => { ...@@ -156,6 +156,8 @@ describe('GroupItemComponent', () => {
describe('template', () => { describe('template', () => {
it('should render component template correctly', () => { it('should render component template correctly', () => {
const visibilityIconEl = vm.$el.querySelector('.item-visibility');
expect(vm.$el.getAttribute('id')).toBe('group-55'); expect(vm.$el.getAttribute('id')).toBe('group-55');
expect(vm.$el.classList.contains('group-row')).toBeTruthy(); expect(vm.$el.classList.contains('group-row')).toBeTruthy();
...@@ -173,6 +175,11 @@ describe('GroupItemComponent', () => { ...@@ -173,6 +175,11 @@ describe('GroupItemComponent', () => {
expect(vm.$el.querySelector('.title')).toBeDefined(); expect(vm.$el.querySelector('.title')).toBeDefined();
expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined(); expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined();
expect(visibilityIconEl).not.toBe(null);
expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip);
expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
expect(vm.$el.querySelector('.access-type')).toBeDefined(); expect(vm.$el.querySelector('.access-type')).toBeDefined();
expect(vm.$el.querySelector('.description')).toBeDefined(); expect(vm.$el.querySelector('.description')).toBeDefined();
......
...@@ -108,18 +108,6 @@ describe('ItemStatsComponent', () => { ...@@ -108,18 +108,6 @@ describe('ItemStatsComponent', () => {
vm.$destroy(); vm.$destroy();
}); });
it('renders item visibility icon and tooltip correctly', () => {
const vm = createComponent();
const visibilityIconEl = vm.$el.querySelector('.item-visibility');
expect(visibilityIconEl).not.toBe(null);
expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip);
expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
vm.$destroy();
});
it('renders start count and last updated information for project item correctly', () => { it('renders start count and last updated information for project item correctly', () => {
const item = Object.assign({}, mockParentGroupItem, { const item = Object.assign({}, mockParentGroupItem, {
type: ITEM_TYPE.PROJECT, type: ITEM_TYPE.PROJECT,
......
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