diff options
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/sidebar')
8 files changed, 142 insertions, 10 deletions
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue b/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue new file mode 100644 index 00000000000..bbc7e6e7a6e --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/copyable_field.vue @@ -0,0 +1,88 @@ +<script> +import { GlLoadingIcon } from '@gitlab/ui'; +import { s__, __, sprintf } from '~/locale'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; + +/** + * Renders an inline field, whose value can be copied to the clipboard, + * for use in the GitLab sidebar (issues, MRs, etc.). + */ +export default { + name: 'CopyableField', + components: { + GlLoadingIcon, + ClipboardButton, + }, + props: { + value: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + clipboardTooltipText: { + type: String, + required: false, + default: undefined, + }, + }, + computed: { + clipboardProps() { + return { + category: 'tertiary', + tooltipBoundary: 'viewport', + tooltipPlacement: 'left', + text: this.value, + title: + this.clipboardTooltipText || + sprintf(this.$options.i18n.clipboardTooltip, { name: this.name }), + }; + }, + loadingIconLabel() { + return sprintf(this.$options.i18n.loadingIconLabel, { name: this.name }); + }, + templateText() { + return sprintf(this.$options.i18n.templateText, { + name: this.name, + value: this.value, + }); + }, + }, + i18n: { + loadingIconLabel: __('Loading %{name}'), + clipboardTooltip: __('Copy %{name}'), + templateText: s__('Sidebar|%{name}: %{value}'), + }, +}; +</script> + +<template> + <div> + <clipboard-button + v-if="!isLoading" + css-class="sidebar-collapsed-icon dont-change-state gl-rounded-0! gl-hover-bg-transparent" + v-bind="clipboardProps" + /> + + <div + class="gl-display-flex gl-align-items-center gl-justify-content-space-between hide-collapsed" + > + <span + class="gl-overflow-hidden gl-text-overflow-ellipsis gl-white-space-nowrap" + :title="value" + > + {{ templateText }} + </span> + + <gl-loading-icon v-if="isLoading" inline :label="loadingIconLabel" /> + <clipboard-button v-else size="small" v-bind="clipboardProps" /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue index 1d3bd312b09..320e2048f1c 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue @@ -164,6 +164,7 @@ export default { variant="link" icon="close" class="gl-mr-2 gl-w-auto! gl-p-2!" + :aria-label="__('Close')" @click.prevent="handleDropdownCloseClick" /> </div> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue index 426ae430ce7..f547433f322 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue @@ -172,9 +172,11 @@ export default { after: this.handleVuexActionDispatch, }); + document.addEventListener('mousedown', this.handleDocumentMousedown); document.addEventListener('click', this.handleDocumentClick); }, beforeDestroy() { + document.removeEventListener('mousedown', this.handleDocumentMousedown); document.removeEventListener('click', this.handleDocumentClick); }, methods: { @@ -197,11 +199,36 @@ export default { } }, /** + * This method stores a mousedown event's target. + * Required by the click listener because the click + * event itself has no reference to this element. + */ + handleDocumentMousedown({ target }) { + this.mousedownTarget = target; + }, + /** * This method listens for document-wide click event * and toggle dropdown if user clicks anywhere outside * the dropdown while dropdown is visible. */ handleDocumentClick({ target }) { + // We also perform the toggle exception check for the + // last mousedown event's target to avoid hiding the + // box when the mousedown happened inside the box and + // only the mouseup did not. + if ( + this.showDropdownContents && + !this.preventDropdownToggleOnClick(target) && + !this.preventDropdownToggleOnClick(this.mousedownTarget) + ) { + this.toggleDropdownContents(); + } + }, + /** + * This method checks whether a given click target + * should prevent the dropdown from being toggled. + */ + preventDropdownToggleOnClick(target) { // This approach of element detection is needed // as the dropdown wrapper is not using `GlDropdown` as // it will also require us to use `BDropdownForm` @@ -216,19 +243,20 @@ export default { target?.parentElement?.classList.contains(className), ); - const hadExceptionParent = ['.js-btn-back', '.js-labels-list'].some( + const hasExceptionParent = ['.js-btn-back', '.js-labels-list'].some( (className) => $(target).parents(className).length, ); - if ( - this.showDropdownContents && - !hadExceptionParent && - !hasExceptionClass && - !this.$refs.dropdownButtonCollapsed?.$el.contains(target) && - !this.$refs.dropdownContents?.$el.contains(target) - ) { - this.toggleDropdownContents(); - } + const isInDropdownButtonCollapsed = this.$refs.dropdownButtonCollapsed?.$el.contains(target); + + const isInDropdownContents = this.$refs.dropdownContents?.$el.contains(target); + + return ( + hasExceptionClass || + hasExceptionParent || + isInDropdownButtonCollapsed || + isInDropdownContents + ); }, handleDropdownClose(labels) { // Only emit label updates if there are any labels to update diff --git a/app/assets/javascripts/vue_shared/components/sidebar/multiselect_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/multiselect_dropdown.vue index ef5f052527b..17904f20341 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/multiselect_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/multiselect_dropdown.vue @@ -30,5 +30,8 @@ export default { <gl-dropdown-form> <slot name="items"></slot> </gl-dropdown-form> + <template #footer> + <slot name="footer"></slot> + </template> </gl-dropdown> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql index 459ea27e9cd..3885127fa8e 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql +++ b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql @@ -1,4 +1,5 @@ #import "~/graphql_shared/fragments/user.fragment.graphql" +#import "~/graphql_shared/fragments/user_availability.fragment.graphql" query issueParticipants($fullPath: ID!, $iid: String!) { workspace: project(fullPath: $fullPath) { @@ -9,11 +10,13 @@ query issueParticipants($fullPath: ID!, $iid: String!) { participants { nodes { ...User + ...UserAvailability } } assignees { nodes { ...User + ...UserAvailability } } } diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_participants.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_participants.query.graphql index 43bd9f17e9a..63482873b69 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_participants.query.graphql +++ b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_mr_participants.query.graphql @@ -1,4 +1,5 @@ #import "~/graphql_shared/fragments/user.fragment.graphql" +#import "~/graphql_shared/fragments/user_availability.fragment.graphql" query getMrParticipants($fullPath: ID!, $iid: String!) { workspace: project(fullPath: $fullPath) { @@ -7,11 +8,13 @@ query getMrParticipants($fullPath: ID!, $iid: String!) { participants { nodes { ...User + ...UserAvailability } } assignees { nodes { ...User + ...UserAvailability } } } diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql index 8ee8de2cb5c..3f40c0368d7 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql +++ b/app/assets/javascripts/vue_shared/components/sidebar/queries/update_issue_assignees.mutation.graphql @@ -1,4 +1,5 @@ #import "~/graphql_shared/fragments/user.fragment.graphql" +#import "~/graphql_shared/fragments/user_availability.fragment.graphql" mutation issueSetAssignees($iid: String!, $assigneeUsernames: [String!]!, $fullPath: ID!) { issuableSetAssignees: issueSetAssignees( @@ -9,11 +10,13 @@ mutation issueSetAssignees($iid: String!, $assigneeUsernames: [String!]!, $fullP assignees { nodes { ...User + ...UserAvailability } } participants { nodes { ...User + ...UserAvailability } } } diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql index a0f15a07692..77140ea36d8 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql +++ b/app/assets/javascripts/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql @@ -1,4 +1,5 @@ #import "~/graphql_shared/fragments/user.fragment.graphql" +#import "~/graphql_shared/fragments/user_availability.fragment.graphql" mutation mergeRequestSetAssignees($iid: String!, $assigneeUsernames: [String!]!, $fullPath: ID!) { mergeRequestSetAssignees( @@ -9,11 +10,13 @@ mutation mergeRequestSetAssignees($iid: String!, $assigneeUsernames: [String!]!, assignees { nodes { ...User + ...UserAvailability } } participants { nodes { ...User + ...UserAvailability } } } |