Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorConstance Okoghenun <cokoghenun@gitlab.com>2018-11-07 20:20:17 +0300
committerPhil Hughes <me@iamphill.com>2018-11-07 20:20:17 +0300
commitbaa37edd93d47e836835617ef08d6fc85ad3a689 (patch)
tree9241261a47917e76dc845eea5f47939d475d6685 /app
parent06e8cf58558cccc5a8556e94c93aa4bf25dc083e (diff)
Resolve "Issue board card design"
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue142
-rw-r--r--app/assets/javascripts/boards/components/issue_due_date.vue90
-rw-r--r--app/assets/javascripts/boards/components/issue_time_estimate.vue48
-rw-r--r--app/assets/javascripts/boards/models/issue.js1
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js10
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue53
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue12
-rw-r--r--app/assets/stylesheets/framework/common.scss7
-rw-r--r--app/assets/stylesheets/framework/variables.scss3
-rw-r--r--app/assets/stylesheets/pages/boards.scss197
-rw-r--r--app/serializers/issue_board_entity.rb1
11 files changed, 404 insertions, 160 deletions
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index d956777a86b..2315a48a306 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -1,18 +1,24 @@
<script>
-import $ from 'jquery';
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
+import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
-import tooltip from '../../vue_shared/directives/tooltip';
+import IssueDueDate from './issue_due_date.vue';
+import IssueTimeEstimate from './issue_time_estimate.vue';
import boardsStore from '../stores/boards_store';
export default {
components: {
- UserAvatarLink,
Icon,
+ UserAvatarLink,
+ TooltipOnTruncate,
+ IssueDueDate,
+ IssueTimeEstimate,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
issue: {
@@ -45,8 +51,8 @@ export default {
},
data() {
return {
- limitBeforeCounter: 3,
- maxRender: 4,
+ limitBeforeCounter: 2,
+ maxRender: 3,
maxCounter: 99,
};
},
@@ -55,7 +61,9 @@ export default {
return this.issue.assignees.length - this.limitBeforeCounter;
},
assigneeCounterTooltip() {
- return `${this.assigneeCounterLabel} more`;
+ const { numberOverLimit, maxCounter } = this;
+ const count = numberOverLimit > maxCounter ? maxCounter : numberOverLimit;
+ return sprintf(__('%{count} more assignees'), { count });
},
assigneeCounterLabel() {
if (this.numberOverLimit > this.maxCounter) {
@@ -80,6 +88,10 @@ export default {
showLabelFooter() {
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
},
+ issueReferencePath() {
+ const { referencePath, groupId } = this.issue;
+ return !groupId ? referencePath.split('#')[0] : null;
+ },
},
methods: {
isIndexLessThanlimit(index) {
@@ -96,11 +108,9 @@ export default {
return index < this.limitBeforeCounter;
},
assigneeUrl(assignee) {
+ if (!assignee) return '';
return `${this.rootPath}${assignee.username}`;
},
- assigneeUrlTitle(assignee) {
- return `Assigned to ${assignee.name}`;
- },
avatarUrlTitle(assignee) {
return `Avatar for ${assignee.name}`;
},
@@ -108,19 +118,29 @@ export default {
if (!label.id) return false;
return true;
},
- filterByLabel(label, e) {
+ filterByLabel(label) {
+ if (!this.updateFilters) return;
+ const labelTitle = encodeURIComponent(label.title);
+ const filter = `label_name[]=${labelTitle}`;
+
+ this.applyFilter(filter);
+ },
+ filterByWeight(weight) {
if (!this.updateFilters) return;
+ const issueWeight = encodeURIComponent(weight);
+ const filter = `weight=${issueWeight}`;
+
+ this.applyFilter(filter);
+ },
+ applyFilter(filter) {
const filterPath = boardsStore.filter.path.split('&');
- const labelTitle = encodeURIComponent(label.title);
- const param = `label_name[]=${labelTitle}`;
- const labelIndex = filterPath.indexOf(param);
- $(e.currentTarget).tooltip('hide');
+ const filterIndex = filterPath.indexOf(filter);
- if (labelIndex === -1) {
- filterPath.push(param);
+ if (filterIndex === -1) {
+ filterPath.push(filter);
} else {
- filterPath.splice(labelIndex, 1);
+ filterPath.splice(filterIndex, 1);
}
boardsStore.filter.path = filterPath.join('&');
@@ -141,24 +161,62 @@ export default {
<template>
<div>
<div class="board-card-header">
- <h4 class="board-card-title">
+ <h4 class="board-card-title append-bottom-0 prepend-top-0">
<icon
v-if="issue.confidential"
+ v-gl-tooltip
name="eye-slash"
- class="confidential-icon"
- />
- <a
+ :title="__('Confidential')"
+ class="confidential-icon append-right-4"
+ :aria-label="__('Confidential')"
+ /><a
:href="issue.path"
:title="issue.title"
class="js-no-trigger"
@mousemove.stop>{{ issue.title }}</a>
+ </h4>
+ </div>
+ <div
+ v-if="showLabelFooter"
+ class="board-card-labels prepend-top-4 d-flex flex-wrap"
+ >
+ <button
+ v-for="label in issue.labels"
+ v-if="showLabel(label)"
+ :key="label.id"
+ v-gl-tooltip
+ :style="labelStyle(label)"
+ :title="label.description"
+ class="badge color-label append-right-4 prepend-top-4"
+ type="button"
+ @click="filterByLabel(label)"
+ >
+ {{ label.title }}
+ </button>
+ </div>
+ <div class="board-card-footer d-flex justify-content-between align-items-end">
+ <div class="d-flex align-items-start flex-wrap-reverse board-card-number-container js-board-card-number-container">
<span
- v-if="issueId"
- class="board-card-number append-right-5"
+ v-if="issue.referencePath"
+ class="board-card-number d-flex append-right-8 prepend-top-8"
>
- {{ issue.referencePath }}
+ <tooltip-on-truncate
+ v-if="issueReferencePath"
+ :title="issueReferencePath"
+ placement="bottom"
+ class="board-issue-path block-truncated bold"
+ >{{ issueReferencePath }}</tooltip-on-truncate>#{{ issue.iid }}
</span>
- </h4>
+ <span class="board-info-items prepend-top-8 d-inline-block">
+ <issue-due-date
+ v-if="issue.dueDate"
+ :date="issue.dueDate"
+ /><issue-time-estimate
+ v-if="issue.timeEstimate"
+ :estimate="issue.timeEstimate"
+ />
+ </span>
+ </div>
<div class="board-card-assignee">
<user-avatar-link
v-for="(assignee, index) in issue.assignees"
@@ -167,38 +225,26 @@ export default {
:link-href="assigneeUrl(assignee)"
:img-alt="avatarUrlTitle(assignee)"
:img-src="assignee.avatar"
- :tooltip-text="assigneeUrlTitle(assignee)"
+ :img-size="24"
class="js-no-trigger"
tooltip-placement="bottom"
- />
+ >
+ <span class="js-assignee-tooltip">
+ <span class="bold d-block">Assignee</span>
+ {{ assignee.name }}
+ <span class="text-white-50">@{{ assignee.username }}</span>
+ </span>
+ </user-avatar-link>
<span
v-if="shouldRenderCounter"
- v-tooltip
+ v-gl-tooltip
:title="assigneeCounterTooltip"
class="avatar-counter"
+ data-placement="bottom"
>
{{ assigneeCounterLabel }}
</span>
</div>
</div>
- <div
- v-if="showLabelFooter"
- class="board-card-footer"
- >
- <button
- v-for="label in issue.labels"
- v-if="showLabel(label)"
- :key="label.id"
- v-tooltip
- :style="labelStyle(label)"
- :title="label.description"
- class="badge color-label"
- type="button"
- data-container="body"
- @click="filterByLabel(label, $event)"
- >
- {{ label.title }}
- </button>
- </div>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue
new file mode 100644
index 00000000000..025ef7e9743
--- /dev/null
+++ b/app/assets/javascripts/boards/components/issue_due_date.vue
@@ -0,0 +1,90 @@
+<script>
+import dateFormat from 'dateformat';
+import { GlTooltip } from '@gitlab-org/gitlab-ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { __ } from '~/locale';
+import { getDayDifference, getTimeago, dateInWords } from '~/lib/utils/datetime_utility';
+
+export default {
+ components: {
+ Icon,
+ GlTooltip,
+ },
+ props: {
+ date: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ title() {
+ const timeago = getTimeago();
+ const { timeDifference, standardDateFormat } = this;
+ const formatedDate = standardDateFormat;
+
+ if (timeDifference >= -1 && timeDifference < 7) {
+ return `${timeago.format(this.issueDueDate)} (${formatedDate})`;
+ }
+
+ return timeago.format(this.issueDueDate);
+ },
+ body() {
+ const { timeDifference, issueDueDate, standardDateFormat } = this;
+
+ if (timeDifference === 0) {
+ return __('Today');
+ } else if (timeDifference === 1) {
+ return __('Tomorrow');
+ } else if (timeDifference === -1) {
+ return __('Yesterday');
+ } else if (timeDifference > 0 && timeDifference < 7) {
+ return dateFormat(issueDueDate, 'dddd', true);
+ }
+
+ return standardDateFormat;
+ },
+ issueDueDate() {
+ return new Date(this.date);
+ },
+ timeDifference() {
+ const today = new Date();
+ return getDayDifference(today, this.issueDueDate);
+ },
+ isPastDue() {
+ if (this.timeDifference >= 0) return false;
+ return true;
+ },
+ standardDateFormat() {
+ const today = new Date();
+ const isDueInCurrentYear = today.getFullYear() === this.issueDueDate.getFullYear();
+
+ return dateInWords(this.issueDueDate, true, isDueInCurrentYear);
+ },
+ },
+};
+</script>
+
+<template>
+ <span>
+ <span
+ ref="issueDueDate"
+ class="board-card-info card-number"
+ >
+ <icon
+ :class="{'text-danger': isPastDue, 'board-card-info-icon': true}"
+ name="calendar"
+ /><time
+ :class="{'text-danger': isPastDue}"
+ datetime="date"
+ class="board-card-info-text">{{ body }}</time>
+ </span>
+ <gl-tooltip
+ :target="() => $refs.issueDueDate"
+ placement="bottom"
+ >
+ <span class="bold">{{ __('Due date') }}</span>
+ <br />
+ <span :class="{'text-danger-muted': isPastDue}">{{ title }}</span>
+ </gl-tooltip>
+ </span>
+</template>
diff --git a/app/assets/javascripts/boards/components/issue_time_estimate.vue b/app/assets/javascripts/boards/components/issue_time_estimate.vue
new file mode 100644
index 00000000000..efc7daf7812
--- /dev/null
+++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue
@@ -0,0 +1,48 @@
+<script>
+import { GlTooltip } from '@gitlab-org/gitlab-ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
+
+export default {
+ components: {
+ Icon,
+ GlTooltip,
+ },
+ props: {
+ estimate: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ title() {
+ return stringifyTime(parseSeconds(this.estimate), true);
+ },
+ timeEstimate() {
+ return stringifyTime(parseSeconds(this.estimate));
+ },
+ },
+};
+</script>
+
+<template>
+ <span>
+ <span
+ ref="issueTimeEstimate"
+ class="board-card-info card-number"
+ >
+ <icon
+ name="hourglass"
+ css-classes="board-card-info-icon"
+ /><time class="board-card-info-text">{{ timeEstimate }}</time>
+ </span>
+ <gl-tooltip
+ :target="() => $refs.issueTimeEstimate"
+ placement="bottom"
+ class="js-issue-time-estimate"
+ >
+ <span class="bold d-block">{{ __('Time estimate') }}</span>
+ {{ title }}
+ </gl-tooltip>
+ </span>
+</template>
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 669630edcab..5e0f0b07247 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -30,6 +30,7 @@ class ListIssue {
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
+ this.timeEstimate = obj.time_estimate;
this.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
if (obj.project) {
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 46740308f17..e69e56c85be 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -454,12 +454,20 @@ export const parseSeconds = (seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {})
/**
* Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it
* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
+ * If the 'fullNameFormat' param is passed it returns a non condensed string eg '1 week 3 days'
*/
-export const stringifyTime = timeObject => {
+export const stringifyTime = (timeObject, fullNameFormat = false) => {
const reducedTime = _.reduce(
timeObject,
(memo, unitValue, unitName) => {
const isNonZero = !!unitValue;
+
+ if (fullNameFormat && isNonZero) {
+ // Remove traling 's' if unit value is singular
+ const formatedUnitName = unitValue > 1 ? unitName : unitName.replace(/s$/, '');
+ return `${memo} ${unitValue} ${formatedUnitName}`;
+ }
+
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
},
'',
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
index 7737b9f2697..4cfb1ded0a9 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -15,14 +15,14 @@
*/
+import { GlTooltip } from '@gitlab-org/gitlab-ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '../../../lazy_loader';
-import tooltip from '../../directives/tooltip';
export default {
name: 'UserAvatarImage',
- directives: {
- tooltip,
+ components: {
+ GlTooltip,
},
props: {
lazy: {
@@ -73,9 +73,6 @@ export default {
resultantSrcAttribute() {
return this.lazy ? placeholderImage : this.sanitizedSource;
},
- tooltipContainer() {
- return this.tooltipText ? 'body' : null;
- },
avatarSizeClass() {
return `s${this.size}`;
},
@@ -84,22 +81,30 @@ export default {
</script>
<template>
- <img
- v-tooltip
- :class="{
- lazy: lazy,
- [avatarSizeClass]: true,
- [cssClasses]: true
- }"
- :src="resultantSrcAttribute"
- :width="size"
- :height="size"
- :alt="imgAlt"
- :data-src="sanitizedSource"
- :data-container="tooltipContainer"
- :data-placement="tooltipPlacement"
- :title="tooltipText"
- class="avatar"
- data-boundary="window"
- />
+ <span>
+ <img
+ ref="userAvatarImage"
+ :class="{
+ lazy: lazy,
+ [avatarSizeClass]: true,
+ [cssClasses]: true
+ }"
+ :src="resultantSrcAttribute"
+ :width="size"
+ :height="size"
+ :alt="imgAlt"
+ :data-src="sanitizedSource"
+ class="avatar"
+ />
+ <gl-tooltip
+ :target="() => $refs.userAvatarImage"
+ :placement="tooltipPlacement"
+ boundary="window"
+ class="js-user-avatar-image-toolip"
+ >
+ <slot>
+ {{ tooltipText }}
+ </slot>
+ </gl-tooltip>
+ </span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
index dd6f96e2609..351a639c6e8 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
@@ -17,9 +17,8 @@
*/
-import { GlLink } from '@gitlab-org/gitlab-ui';
+import { GlLink, GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import userAvatarImage from './user_avatar_image.vue';
-import tooltip from '../../directives/tooltip';
export default {
name: 'UserAvatarLink',
@@ -28,7 +27,7 @@ export default {
userAvatarImage,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
linkHref: {
@@ -94,11 +93,14 @@ export default {
:size="imgSize"
:tooltip-text="avatarTooltipText"
:tooltip-placement="tooltipPlacement"
- /><span
+ >
+ <slot></slot>
+ </user-avatar-image><span
v-if="shouldShowUsername"
- v-tooltip
+ v-gl-tooltip
:title="tooltipText"
:tooltip-placement="tooltipPlacement"
+ class="js-user-avatar-link-username"
>{{ username }}</span><slot name="avatar-badge"></slot>
</gl-link>
</template>
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index fa753b13e5f..626c8f92d1d 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -33,6 +33,11 @@
color: $brand-danger;
}
+.text-danger-muted,
+.text-danger-muted:hover {
+ color: $red-300;
+}
+
.text-warning,
.text-warning:hover {
color: $brand-warning;
@@ -345,6 +350,7 @@ img.emoji {
/** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; }
.prepend-top-2 { margin-top: 2px; }
+.prepend-top-4 { margin-top: $gl-padding-4; }
.prepend-top-5 { margin-top: 5px; }
.prepend-top-8 { margin-top: $grid-size; }
.prepend-top-10 { margin-top: 10px; }
@@ -365,6 +371,7 @@ img.emoji {
.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px; }
.append-bottom-0 { margin-bottom: 0; }
+.append-bottom-4 { margin-bottom: $gl-padding-4; }
.append-bottom-5 { margin-bottom: 5px; }
.append-bottom-8 { margin-bottom: $grid-size; }
.append-bottom-10 { margin-bottom: 10px; }
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index bfcac3f1c3f..016fee862e8 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -195,6 +195,7 @@ $well-light-text-color: #5b6169;
* Text
*/
$gl-font-size: 14px;
+$gl-font-size-xs: 11px;
$gl-font-size-small: 12px;
$gl-font-weight-normal: 400;
$gl-font-weight-bold: 600;
@@ -440,7 +441,7 @@ $ci-skipped-color: #888;
* Boards
*/
$issue-boards-font-size: 14px;
-$issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
+$issue-boards-card-shadow: rgba(0, 0, 0, 0.1);
/*
The following heights are used in boards.scss and are used for calculation of the board height.
They probably should be derived in a smarter way.
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 54fbd40cece..c6074eb9df4 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -90,20 +90,14 @@
}
.with-performance-bar & {
- height: calc(
- 100vh - #{$issue-board-list-difference-xs} - #{$performance-bar-height}
- );
+ height: calc(100vh - #{$issue-board-list-difference-xs} - #{$performance-bar-height});
@include media-breakpoint-only(sm) {
- height: calc(
- 100vh - #{$issue-board-list-difference-sm} - #{$performance-bar-height}
- );
+ height: calc(100vh - #{$issue-board-list-difference-sm} - #{$performance-bar-height});
}
@include media-breakpoint-up(md) {
- height: calc(
- 100vh - #{$issue-board-list-difference-md} - #{$performance-bar-height}
- );
+ height: calc(100vh - #{$issue-board-list-difference-md} - #{$performance-bar-height});
}
}
}
@@ -271,7 +265,7 @@
height: 100%;
width: 100%;
margin-bottom: 0;
- padding: 5px;
+ padding: $gl-padding-4;
list-style: none;
overflow-y: auto;
overflow-x: hidden;
@@ -284,14 +278,16 @@
.board-card {
position: relative;
- padding: 11px 10px 11px $gl-padding;
+ padding: $gl-padding;
background: $white-light;
border-radius: $border-radius-default;
+ border: 1px solid $theme-gray-200;
box-shadow: 0 1px 2px $issue-boards-card-shadow;
list-style: none;
+ line-height: $gl-padding;
&:not(:last-child) {
- margin-bottom: 5px;
+ margin-bottom: $gl-padding-8;
}
&.is-active,
@@ -302,113 +298,120 @@
.badge {
border: 0;
outline: 0;
+
+ &:hover {
+ text-decoration: underline;
+ }
+
+ @include media-breakpoint-down(lg) {
+ font-size: $gl-font-size-xs;
+ padding-left: $gl-padding-4;
+ padding-right: $gl-padding-4;
+ font-weight: $gl-font-weight-bold;
+ }
+ }
+
+ svg {
+ vertical-align: top;
}
.confidential-icon {
- vertical-align: text-top;
- margin-right: 5px;
+ color: $orange-600;
+ cursor: help;
+ }
+
+ @include media-breakpoint-down(md) {
+ padding: $gl-padding-8;
}
}
.board-card-title {
@include overflow-break-word();
- margin: 0 30px 0 0;
font-size: 1em;
- line-height: inherit;
a {
color: $gl-text-color;
- margin-right: 2px;
+ }
+
+ @include media-breakpoint-down(md) {
+ font-size: $label-font-size;
}
}
.board-card-header {
display: flex;
- min-height: 20px;
-
- .board-card-assignee {
- display: flex;
- justify-content: flex-end;
- position: absolute;
- right: 15px;
- height: 20px;
- width: 20px;
+}
- .avatar-counter {
- display: none;
- vertical-align: middle;
- min-width: 20px;
- line-height: 19px;
- height: 20px;
- padding-left: 2px;
- padding-right: 2px;
- border-radius: 2em;
- }
+.board-card-assignee {
+ display: flex;
+ margin-top: -$gl-padding-4;
+ margin-bottom: -$gl-padding-4;
+
+ .avatar-counter {
+ vertical-align: middle;
+ line-height: $gl-padding-24;
+ min-width: $gl-padding-24;
+ height: $gl-padding-24;
+ border-radius: $gl-padding-24;
+ background-color: $gl-text-color-tertiary;
+ font-size: $gl-font-size-xs;
+ cursor: help;
+ font-weight: $gl-font-weight-bold;
+ margin-left: -$gl-padding-4;
+ border: 0;
+ padding: 0 $gl-padding-4;
- img {
- vertical-align: top;
+ @include media-breakpoint-down(md) {
+ min-width: auto;
+ height: $gl-padding;
+ border-radius: $gl-padding;
+ line-height: $gl-padding;
}
+ }
- a {
- position: relative;
- margin-left: -15px;
- }
+ img {
+ vertical-align: top;
+ }
- a:nth-child(1) {
- z-index: 3;
- }
+ .user-avatar-link:not(:only-child) {
+ margin-left: -$gl-padding-4;
- a:nth-child(2) {
+ &:nth-of-type(1) {
z-index: 2;
}
- a:nth-child(3) {
+ &:nth-of-type(2) {
z-index: 1;
}
+ }
- a:nth-child(4) {
- display: none;
- }
-
- &:hover {
- .avatar-counter {
- display: inline-block;
- }
-
- a {
- position: static;
- background-color: $white-light;
- transition: background-color 0s;
- margin-left: auto;
-
- &:nth-child(4) {
- display: block;
- }
+ .avatar {
+ margin: 0;
- &:first-child:not(:only-child) {
- box-shadow: -10px 0 10px 1px $white-light;
- }
- }
+ @include media-breakpoint-down(md) {
+ width: $gl-padding;
+ height: $gl-padding;
}
}
- .avatar {
- margin: 0;
+ @include media-breakpoint-down(md) {
+ margin-top: 0;
+ margin-bottom: 0;
}
}
-.board-card-footer {
- margin: 0 0 5px;
+.board-card-number {
+ font-size: $gl-font-size-xs;
+ color: $gl-text-color-secondary;
+ overflow: hidden;
- .badge {
- margin-top: 5px;
- margin-right: 6px;
+ @include media-breakpoint-up(md) {
+ font-size: $label-font-size;
}
}
-.board-card-number {
- font-size: 12px;
- color: $gl-text-color-secondary;
+.board-card-number-container {
+ overflow: hidden;
}
.issue-boards-search {
@@ -474,8 +477,7 @@
.right-sidebar.right-sidebar-expanded {
&.boards-sidebar-slide-enter-active,
&.boards-sidebar-slide-leave-active {
- transition: width $sidebar-transition-duration,
- padding $sidebar-transition-duration;
+ transition: width $sidebar-transition-duration, padding $sidebar-transition-duration;
}
&.boards-sidebar-slide-enter,
@@ -650,3 +652,36 @@
}
}
}
+
+.board-card-info {
+ color: $gl-text-color-secondary;
+ white-space: nowrap;
+ margin-right: $gl-padding-8;
+
+ &:not(.board-card-weight) {
+ cursor: help;
+ }
+
+ &.board-card-weight {
+ color: $gl-text-color;
+ cursor: pointer;
+
+ &:hover {
+ color: initial;
+ text-decoration: underline;
+ }
+ }
+
+ .board-card-info-icon {
+ color: $theme-gray-600;
+ margin-right: $gl-padding-4;
+ }
+
+ @include media-breakpoint-down(md) {
+ font-size: $label-font-size;
+ }
+}
+
+.board-issue-path.js-show-tooltip {
+ cursor: help;
+}
diff --git a/app/serializers/issue_board_entity.rb b/app/serializers/issue_board_entity.rb
index 6a9e9638e70..4e3d03b236b 100644
--- a/app/serializers/issue_board_entity.rb
+++ b/app/serializers/issue_board_entity.rb
@@ -12,6 +12,7 @@ class IssueBoardEntity < Grape::Entity
expose :project_id
expose :relative_position
expose :weight, if: -> (*) { respond_to?(:weight) }
+ expose :time_estimate
expose :project do |issue|
API::Entities::Project.represent issue.project, only: [:id, :path]