Skip to content
Snippets Groups Projects
Commit 7a4b1521 authored by Dennis Tang's avatar Dennis Tang :art:
Browse files

Improve group list UI

This updates the groups list UI to match the style of the project list:

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