Skip to content
Snippets Groups Projects
Verified Commit 5bdef015 authored by Peter Hegman's avatar Peter Hegman :two: Committed by GitLab
Browse files

Adjust styling in group and project lists

To align with UX preferences
parent 51f01d85
No related branches found
No related tags found
1 merge request!139175Adjust styling in group and project lists
Showing
with 200 additions and 101 deletions
......@@ -172,6 +172,6 @@ export default {
</div>
</div>
</div>
<component :is="routerView" />
<component :is="routerView" list-item-class="gl-px-5" />
</div>
</template>
......@@ -39,14 +39,14 @@ export const organizationProjects = {
id: 'gid://gitlab/Project/8',
nameWithNamespace: 'Twitter / Typeahead.Js',
webUrl: 'http://127.0.0.1:3000/twitter/Typeahead.Js',
topics: ['JavaScript', 'Vue.js'],
topics: ['JavaScript', 'Vue.js', 'GraphQL', 'Jest', 'CSS', 'HTML'],
forksCount: 4,
avatarUrl: null,
starCount: 0,
visibility: 'public',
openIssuesCount: 48,
descriptionHtml:
'<p data-sourcepos="1:1-1:59" dir="auto">Optio et reprehenderit enim doloremque deserunt et commodi.</p>',
'<p data-sourcepos="1:1-1:59" dir="auto">Optio et reprehenderit enim doloremque deserunt et commodi. Sed sit amet iaculis neque. Morbi vel convallis elit. Aliquam vitae arcu orci. Aenean sem velit, dapibus eget enim id, tempor lobortis orci. Pellentesque dignissim nec velit eget sagittis. Maecenas lectus sapien, tincidunt ac cursus a, aliquam eu ipsum. Aliquam posuere maximus augue, ut vehicula elit vulputate condimentum. In libero leo, vehicula nec risus in, ullamcorper convallis risus. Phasellus sit amet lectus sit amet sem volutpat cursus. Nullam facilisis nulla nec lacus pretium, in pretium ex aliquam.</p>',
issuesAccessLevel: 'enabled',
forkingAccessLevel: 'enabled',
isForked: true,
......@@ -141,7 +141,7 @@ export const organizationGroups = {
parent: null,
webUrl: 'http://127.0.0.1:3000/groups/Commit451',
descriptionHtml:
'<p data-sourcepos="1:1-1:52" dir="auto">Autem praesentium vel ut ratione itaque ullam culpa.</p>',
'<p data-sourcepos="1:1-1:52" dir="auto">Autem praesentium vel ut ratione itaque ullam culpa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse libero sem, congue ut sem id, semper pharetra ante. Sed at dui ac nunc pellentesque congue. Phasellus posuere, nisl non pellentesque dignissim, lacus nisi molestie ex, vel placerat neque ligula non libero. Curabitur ipsum enim, pretium eu dignissim vitae, euismod nec nibh. Praesent eget ipsum eleifend, pellentesque tortor vel, consequat neque. Etiam suscipit dolor massa, sed consectetur nunc tincidunt ut. Suspendisse posuere malesuada finibus. Maecenas iaculis et diam eu iaculis. Proin at nulla sit amet erat sollicitudin suscipit sit amet a libero.</p>',
avatarUrl: null,
descendantGroupsCount: 0,
projectsCount: 3,
......
......@@ -32,6 +32,11 @@ export default {
required: false,
default: false,
},
listItemClass: {
type: [String, Array, Object],
required: false,
default: '',
},
},
data() {
return {
......@@ -77,6 +82,11 @@ export default {
<template>
<gl-loading-icon v-if="isLoading" class="gl-mt-5" size="md" />
<groups-list v-else-if="groups.length" :groups="groups" show-group-icon />
<groups-list
v-else-if="groups.length"
:groups="groups"
show-group-icon
:list-item-class="listItemClass"
/>
<gl-empty-state v-else v-bind="emptyStateProps" />
</template>
......@@ -36,6 +36,11 @@ export default {
required: false,
default: false,
},
listItemClass: {
type: [String, Array, Object],
required: false,
default: '',
},
},
data() {
return {
......@@ -81,6 +86,11 @@ export default {
<template>
<gl-loading-icon v-if="isLoading" class="gl-mt-5" size="md" />
<projects-list v-else-if="projects.length" :projects="projects" show-project-icon />
<projects-list
v-else-if="projects.length"
:projects="projects"
show-project-icon
:list-item-class="listItemClass"
/>
<gl-empty-state v-else v-bind="emptyStateProps" />
</template>
......@@ -13,6 +13,11 @@ export default {
required: false,
default: false,
},
listItemClass: {
type: [String, Array, Object],
required: false,
default: '',
},
},
};
</script>
......@@ -24,6 +29,7 @@ export default {
:key="group.id"
:group="group"
:show-group-icon="showGroupIcon"
:class="listItemClass"
@delete="$emit('delete', $event)"
/>
</ul>
......
<script>
import { GlAvatarLabeled, GlIcon, GlTooltipDirective, GlTruncateText } from '@gitlab/ui';
import { GlAvatarLabeled, GlIcon, GlTooltipDirective, GlTruncateText, GlBadge } from '@gitlab/ui';
import uniqueId from 'lodash/uniqueId';
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/visibility_level/constants';
import { ACCESS_LEVEL_LABELS } from '~/access_level/constants';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
import { __ } from '~/locale';
import { numberToMetricPrefix } from '~/lib/utils/number_utils';
import SafeHtml from '~/vue_shared/directives/safe_html';
......@@ -20,15 +19,12 @@ export default {
showMore: __('Show more'),
showLess: __('Show less'),
},
avatarSize: { default: 32, md: 48 },
safeHtmlConfig: {
ADD_TAGS: ['gl-emoji'],
},
truncateTextToggleButtonProps: { class: 'gl-font-sm!' },
components: {
GlAvatarLabeled,
GlIcon,
UserAccessRoleBadge,
GlTruncateText,
GlBadge,
ListActions,
DangerConfirmModal,
},
......@@ -76,7 +72,7 @@ export default {
return this.group.parent ? 'subgroup' : 'group';
},
statsPadding() {
return this.showGroupIcon ? 'gl-pl-11' : 'gl-pl-8';
return this.showGroupIcon ? 'gl-pl-12' : 'gl-pl-10';
},
descendantGroupsCount() {
return numberToMetricPrefix(this.group.descendantGroupsCount);
......@@ -113,21 +109,22 @@ export default {
</script>
<template>
<li class="groups-list-item gl-py-5 gl-border-b gl-display-flex gl-align-items-flex-start">
<div class="gl-md-display-flex gl-align-items-center gl-flex-grow-1">
<li class="groups-list-item gl-py-5 gl-border-b gl-display-flex">
<div class="gl-md-display-flex gl-flex-grow-1">
<div class="gl-display-flex gl-flex-grow-1">
<gl-icon
<div
v-if="showGroupIcon"
class="gl-mr-3 gl-mt-3 gl-md-mt-5 gl-flex-shrink-0 gl-text-secondary"
:name="groupIconName"
/>
class="gl-display-flex gl-align-items-center gl-flex-shrink-0 gl-h-9 gl-mr-3"
>
<gl-icon class="gl-text-secondary" :name="groupIconName" />
</div>
<gl-avatar-labeled
:entity-id="group.id"
:entity-name="group.fullName"
:label="group.fullName"
:label-link="group.webUrl"
shape="rect"
:size="$options.avatarSize"
:size="48"
>
<template #meta>
<div class="gl-px-2">
......@@ -141,9 +138,9 @@ export default {
/>
</div>
<div class="gl-px-2">
<user-access-role-badge v-if="shouldShowAccessLevel">{{
<gl-badge v-if="shouldShowAccessLevel" size="sm" class="gl-display-block">{{
accessLevelLabel
}}</user-access-role-badge>
}}</gl-badge>
</div>
</div>
</div>
......@@ -154,57 +151,57 @@ export default {
:mobile-lines="2"
:show-more-text="$options.i18n.showMore"
:show-less-text="$options.i18n.showLess"
class="gl-mt-2"
:toggle-button-props="$options.truncateTextToggleButtonProps"
class="gl-mt-2 gl-max-w-88"
>
<div
v-safe-html:[$options.safeHtmlConfig]="group.descriptionHtml"
class="gl-font-sm md"
v-safe-html="group.descriptionHtml"
class="gl-font-sm gl-text-secondary md"
data-testid="group-description"
></div>
</gl-truncate-text>
</gl-avatar-labeled>
</div>
<div
class="gl-md-display-flex gl-flex-direction-column gl-align-items-flex-end gl-flex-shrink-0 gl-mt-3 gl-md-pl-0 gl-md-mt-0 gl-md-ml-3"
class="gl-display-flex gl-align-items-center gl-gap-x-3 gl-flex-shrink-0 gl-mt-3 gl-md-pl-0 gl-md-mt-0 gl-md-ml-3 gl-md-h-9"
:class="statsPadding"
>
<div class="gl-display-flex gl-align-items-center gl-gap-x-3">
<div
v-gl-tooltip="$options.i18n.subgroups"
:aria-label="$options.i18n.subgroups"
class="gl-text-secondary"
data-testid="subgroups-count"
>
<gl-icon name="subgroup" />
<span>{{ descendantGroupsCount }}</span>
</div>
<div
v-gl-tooltip="$options.i18n.projects"
:aria-label="$options.i18n.projects"
class="gl-text-secondary"
data-testid="projects-count"
>
<gl-icon name="project" />
<span>{{ projectsCount }}</span>
</div>
<div
v-gl-tooltip="$options.i18n.directMembers"
:aria-label="$options.i18n.directMembers"
class="gl-text-secondary"
data-testid="members-count"
>
<gl-icon name="users" />
<span>{{ groupMembersCount }}</span>
</div>
<div
v-gl-tooltip="$options.i18n.subgroups"
:aria-label="$options.i18n.subgroups"
class="gl-text-secondary"
data-testid="subgroups-count"
>
<gl-icon name="subgroup" />
<span>{{ descendantGroupsCount }}</span>
</div>
<div
v-gl-tooltip="$options.i18n.projects"
:aria-label="$options.i18n.projects"
class="gl-text-secondary"
data-testid="projects-count"
>
<gl-icon name="project" />
<span>{{ projectsCount }}</span>
</div>
<div
v-gl-tooltip="$options.i18n.directMembers"
:aria-label="$options.i18n.directMembers"
class="gl-text-secondary"
data-testid="members-count"
>
<gl-icon name="users" />
<span>{{ groupMembersCount }}</span>
</div>
</div>
</div>
<list-actions
v-if="hasActions"
class="gl-ml-3 gl-md-align-self-center"
:actions="actions"
:available-actions="group.availableActions"
/>
<div class="gl-display-flex gl-align-items-center gl-h-9 gl-ml-3">
<list-actions
v-if="hasActions"
:actions="actions"
:available-actions="group.availableActions"
/>
</div>
<danger-confirm-modal
v-if="hasActionDelete"
......
......@@ -35,6 +35,11 @@ export default {
required: false,
default: false,
},
listItemClass: {
type: [String, Array, Object],
required: false,
default: '',
},
},
};
</script>
......@@ -46,6 +51,7 @@ export default {
:key="project.id"
:project="project"
:show-project-icon="showProjectIcon"
:class="listItemClass"
@delete="$emit('delete', $event)"
/>
</ul>
......
......@@ -7,13 +7,13 @@ import {
GlTooltipDirective,
GlPopover,
GlSprintf,
GlTruncateText,
} from '@gitlab/ui';
import uniqueId from 'lodash/uniqueId';
import { VISIBILITY_TYPE_ICON, PROJECT_VISIBILITY_TYPE } from '~/visibility_level/constants';
import { ACCESS_LEVEL_LABELS } from '~/access_level/constants';
import { FEATURABLE_ENABLED } from '~/featurable/constants';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
import { __ } from '~/locale';
import { numberToMetricPrefix } from '~/lib/utils/number_utils';
import { truncate } from '~/lib/utils/text_utility';
......@@ -37,19 +37,18 @@ export default {
moreTopics: __('More topics'),
updated: __('Updated'),
actions: __('Actions'),
showMore: __('Show more'),
showLess: __('Show less'),
},
avatarSize: { default: 32, md: 48 },
safeHtmlConfig: {
ADD_TAGS: ['gl-emoji'],
},
truncateTextToggleButtonProps: { class: 'gl-font-sm!' },
components: {
GlAvatarLabeled,
GlIcon,
UserAccessRoleBadge,
GlLink,
GlBadge,
GlPopover,
GlSprintf,
GlTruncateText,
TimeAgoTooltip,
DeleteModal,
ListActions,
......@@ -203,21 +202,22 @@ export default {
</script>
<template>
<li class="projects-list-item gl-py-5 gl-border-b gl-display-flex gl-align-items-flex-start">
<div class="gl-md-display-flex gl-align-items-center gl-flex-grow-1">
<li class="projects-list-item gl-py-5 gl-border-b gl-display-flex">
<div class="gl-md-display-flex gl-flex-grow-1">
<div class="gl-display-flex gl-flex-grow-1">
<gl-icon
<div
v-if="showProjectIcon"
class="gl-mr-3 gl-mt-3 gl-md-mt-5 gl-flex-shrink-0 gl-text-secondary"
name="project"
/>
class="gl-display-flex gl-align-items-center gl-flex-shrink-0 gl-h-9 gl-mr-3"
>
<gl-icon class="gl-text-secondary" name="project" />
</div>
<gl-avatar-labeled
:entity-id="project.id"
:entity-name="project.name"
:label="project.name"
:label-link="project.webUrl"
shape="rect"
:size="$options.avatarSize"
:size="48"
>
<template #meta>
<div class="gl-px-2">
......@@ -231,33 +231,46 @@ export default {
/>
</div>
<div class="gl-px-2">
<user-access-role-badge v-if="shouldShowAccessLevel">{{
<gl-badge v-if="shouldShowAccessLevel" size="sm" class="gl-display-block">{{
accessLevelLabel
}}</user-access-role-badge>
}}</gl-badge>
</div>
</div>
</div>
</template>
<div
<gl-truncate-text
v-if="project.descriptionHtml"
v-safe-html:[$options.safeHtmlConfig]="project.descriptionHtml"
class="gl-font-sm gl-overflow-hidden gl-line-height-20 description md"
data-testid="project-description"
></div>
:lines="2"
:mobile-lines="2"
:show-more-text="$options.i18n.showMore"
:show-less-text="$options.i18n.showLess"
:toggle-button-props="$options.truncateTextToggleButtonProps"
class="gl-mt-2 gl-max-w-88"
>
<div
v-safe-html="project.descriptionHtml"
class="gl-font-sm gl-text-secondary md"
data-testid="project-description"
></div>
</gl-truncate-text>
<div v-if="hasTopics" class="gl-mt-3" data-testid="project-topics">
<div
class="gl-w-full gl-display-inline-flex gl-flex-wrap gl-font-base gl-font-weight-normal gl-align-items-center gl-mx-n2 gl-my-n2"
>
<span class="gl-p-2 gl-text-secondary">{{ $options.i18n.topics }}:</span>
<span class="gl-p-2 gl-font-sm gl-text-secondary">{{ $options.i18n.topics }}:</span>
<div v-for="topic in visibleTopics" :key="topic" class="gl-p-2">
<gl-badge v-gl-tooltip="topicTooltipTitle(topic)" :href="topicPath(topic)">
<gl-badge
v-gl-tooltip="topicTooltipTitle(topic)"
size="sm"
:href="topicPath(topic)"
>
{{ topicTitle(topic) }}
</gl-badge>
</div>
<template v-if="popoverTopics.length">
<div
:id="topicsPopoverTarget"
class="gl-p-2 gl-text-secondary"
class="gl-p-2 gl-font-sm gl-text-secondary"
role="button"
tabindex="0"
>
......@@ -272,7 +285,11 @@ export default {
:key="topic"
class="gl-p-2 gl-display-inline-block"
>
<gl-badge v-gl-tooltip="topicTooltipTitle(topic)" :href="topicPath(topic)">
<gl-badge
v-gl-tooltip="topicTooltipTitle(topic)"
size="sm"
:href="topicPath(topic)"
>
{{ topicTitle(topic) }}
</gl-badge>
</div>
......@@ -285,9 +302,9 @@ export default {
</div>
<div
class="gl-md-display-flex gl-flex-direction-column gl-align-items-flex-end gl-flex-shrink-0 gl-mt-3 gl-md-pl-0 gl-md-mt-0"
:class="showProjectIcon ? 'gl-pl-11' : 'gl-pl-8'"
:class="showProjectIcon ? 'gl-pl-12' : 'gl-pl-10'"
>
<div class="gl-display-flex gl-align-items-center gl-gap-x-3">
<div class="gl-display-flex gl-align-items-center gl-gap-x-3 gl-md-h-9">
<gl-badge v-if="project.archived" variant="warning">{{
$options.i18n.archived
}}</gl-badge>
......@@ -323,19 +340,20 @@ export default {
</div>
<div
v-if="project.updatedAt"
class="gl-font-sm gl-white-space-nowrap gl-text-secondary gl-mt-3"
class="gl-font-sm gl-white-space-nowrap gl-text-secondary gl-mt-3 gl-md-mt-0"
>
<span>{{ $options.i18n.updated }}</span>
<time-ago-tooltip :time="project.updatedAt" />
</div>
</div>
</div>
<list-actions
v-if="hasActions"
class="gl-ml-3 gl-md-align-self-center"
:actions="actions"
:available-actions="project.availableActions"
/>
<div class="gl-display-flex gl-align-items-center gl-h-9 gl-ml-3">
<list-actions
v-if="hasActions"
:actions="actions"
:available-actions="project.availableActions"
/>
</div>
<delete-modal
v-if="hasActionDelete"
......
......@@ -602,6 +602,20 @@
}
@include email-code-block;
&.gl-text-secondary {
color: $gl-text-color-secondary;
p,
h1,
h2,
h3,
h4,
h5,
table:not(.code) {
color: $gl-text-color-secondary;
}
}
}
/**
......
......@@ -136,3 +136,13 @@
.gl-last-of-type-border-b-0:last-of-type {
@include gl-border-b-0;
}
.gl-md-h-9 {
@include gl-media-breakpoint-up(md) {
height: $gl-spacing-scale-9;
}
}
.gl-pl-12 {
padding-left: $gl-spacing-scale-12;
}
......@@ -25,13 +25,20 @@ describe('GroupsView', () => {
newGroupPath: '/groups/new',
};
const defaultPropsData = {
listItemClass: 'gl-px-5',
};
const createComponent = ({ mockResolvers = resolvers, propsData = {} } = {}) => {
mockApollo = createMockApollo([], mockResolvers);
wrapper = shallowMountExtended(GroupsView, {
apolloProvider: mockApollo,
provide: defaultProvide,
propsData,
propsData: {
...defaultPropsData,
...propsData,
},
});
};
......@@ -115,6 +122,7 @@ describe('GroupsView', () => {
expect(wrapper.findComponent(GroupsList).props()).toEqual({
groups: formatGroups(organizationGroups.nodes),
showGroupIcon: true,
listItemClass: defaultPropsData.listItemClass,
});
});
});
......
......@@ -25,13 +25,20 @@ describe('ProjectsView', () => {
newProjectPath: '/projects/new',
};
const defaultPropsData = {
listItemClass: 'gl-px-5',
};
const createComponent = ({ mockResolvers = resolvers, propsData = {} } = {}) => {
mockApollo = createMockApollo([], mockResolvers);
wrapper = shallowMountExtended(ProjectsView, {
apolloProvider: mockApollo,
provide: defaultProvide,
propsData,
propsData: {
...defaultPropsData,
...propsData,
},
});
};
......@@ -115,6 +122,7 @@ describe('ProjectsView', () => {
expect(wrapper.findComponent(ProjectsList).props()).toEqual({
projects: formatProjects(organizationProjects.nodes),
showProjectIcon: true,
listItemClass: defaultPropsData.listItemClass,
});
});
});
......
import { GlAvatarLabeled, GlIcon } from '@gitlab/ui';
import { GlAvatarLabeled, GlIcon, GlBadge } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import GroupsListItem from '~/vue_shared/components/groups_list/groups_list_item.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
......@@ -7,7 +7,6 @@ import {
VISIBILITY_LEVEL_INTERNAL_STRING,
GROUP_VISIBILITY_TYPE,
} from '~/visibility_level/constants';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
import { ACCESS_LEVEL_LABELS } from '~/access_level/constants';
import ListActions from '~/vue_shared/components/list_actions/list_actions.vue';
import { ACTION_EDIT, ACTION_DELETE } from '~/vue_shared/components/list_actions/constants';
......@@ -112,7 +111,7 @@ describe('GroupsListItem', () => {
it('renders access role badge', () => {
createComponent();
expect(findAvatarLabeled().findComponent(UserAccessRoleBadge).text()).toBe(
expect(findAvatarLabeled().findComponent(GlBadge).text()).toBe(
ACCESS_LEVEL_LABELS[group.accessLevel.integerValue],
);
});
......
......@@ -8,6 +8,7 @@ describe('GroupsList', () => {
const defaultPropsData = {
groups,
listItemClass: 'gl-px-5',
};
const createComponent = () => {
......@@ -23,6 +24,9 @@ describe('GroupsList', () => {
const expectedProps = groupsListItemWrappers.map((groupsListItemWrapper) =>
groupsListItemWrapper.props(),
);
const expectedClasses = groupsListItemWrappers.map((groupsListItemWrapper) =>
groupsListItemWrapper.classes(),
);
expect(expectedProps).toEqual(
defaultPropsData.groups.map((group) => ({
......@@ -30,6 +34,9 @@ describe('GroupsList', () => {
showGroupIcon: false,
})),
);
expect(expectedClasses).toEqual(
defaultPropsData.groups.map(() => [defaultPropsData.listItemClass]),
);
});
describe('when `GroupsListItem` emits `delete` event', () => {
......
......@@ -12,7 +12,6 @@ import {
VISIBILITY_LEVEL_PRIVATE_STRING,
PROJECT_VISIBILITY_TYPE,
} from '~/visibility_level/constants';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
import { ACCESS_LEVEL_LABELS } from '~/access_level/constants';
import { FEATURABLE_DISABLED, FEATURABLE_ENABLED } from '~/featurable/constants';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
......@@ -92,7 +91,7 @@ describe('ProjectsListItem', () => {
it('renders access role badge', () => {
createComponent();
expect(findAvatarLabeled().findComponent(UserAccessRoleBadge).text()).toBe(
expect(findAvatarLabeled().findComponent(GlBadge).text()).toBe(
ACCESS_LEVEL_LABELS[project.permissions.projectAccess.accessLevel],
);
});
......
......@@ -9,6 +9,7 @@ describe('ProjectsList', () => {
const defaultPropsData = {
projects: convertObjectPropsToCamelCase(projects, { deep: true }),
listItemClass: 'gl-px-5',
};
const createComponent = () => {
......@@ -24,6 +25,9 @@ describe('ProjectsList', () => {
const expectedProps = projectsListItemWrappers.map((projectsListItemWrapper) =>
projectsListItemWrapper.props(),
);
const expectedClasses = projectsListItemWrappers.map((projectsListItemWrapper) =>
projectsListItemWrapper.classes(),
);
expect(expectedProps).toEqual(
defaultPropsData.projects.map((project) => ({
......@@ -31,6 +35,9 @@ describe('ProjectsList', () => {
showProjectIcon: false,
})),
);
expect(expectedClasses).toEqual(
defaultPropsData.projects.map(() => [defaultPropsData.listItemClass]),
);
});
describe('when `ProjectListItem` emits `delete` event', () => {
......
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