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
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_note.vue14
-rw-r--r--app/assets/javascripts/issues/show/components/header_actions.vue259
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/app.vue2
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue11
-rw-r--r--app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue210
-rw-r--r--app/assets/javascripts/work_items/components/work_item_milestone.vue133
-rw-r--r--app/assets/javascripts/work_items/components/work_item_parent.vue32
-rw-r--r--app/assets/stylesheets/page_bundles/issuable.scss23
-rw-r--r--app/assets/stylesheets/page_bundles/work_items.scss22
10 files changed, 321 insertions, 389 deletions
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
index 54ffd986588..b247f17fd97 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
@@ -13,6 +13,7 @@ import SafeHtml from '~/vue_shared/directives/safe_html';
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPENAME_USER } from '~/graphql_shared/constants';
import { __ } from '~/locale';
+import { setUrlFragment } from '~/lib/utils/url_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import EmojiPicker from '~/emoji/components/picker.vue';
@@ -29,6 +30,7 @@ export default {
editCommentLabel: __('Edit comment'),
moreActionsLabel: __('More actions'),
deleteCommentText: __('Delete comment'),
+ copyCommentLink: __('Copy link'),
},
components: {
DesignNoteAwardsList,
@@ -133,6 +135,18 @@ export default {
},
},
{
+ text: this.$options.i18n.copyCommentLink,
+ action: () => {
+ this.$toast.show(__('Link copied to clipboard.'));
+ },
+ extraAttrs: {
+ 'data-clipboard-text': setUrlFragment(
+ window.location.href,
+ `note_${this.noteAnchorId}`,
+ ),
+ },
+ },
+ {
text: this.$options.i18n.deleteCommentText,
action: () => {
this.$emit('delete-note', this.note);
diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue
index 1c1b15e2026..c50b8009284 100644
--- a/app/assets/javascripts/issues/show/components/header_actions.vue
+++ b/app/assets/javascripts/issues/show/components/header_actions.vue
@@ -1,9 +1,9 @@
<script>
import {
GlButton,
- GlDropdown,
+ GlDisclosureDropdown,
GlDropdownDivider,
- GlDropdownItem,
+ GlDisclosureDropdownItem,
GlLink,
GlModal,
GlModalDirective,
@@ -59,9 +59,9 @@ export default {
components: {
DeleteIssueModal,
GlButton,
- GlDropdown,
+ GlDisclosureDropdown,
GlDropdownDivider,
- GlDropdownItem,
+ GlDisclosureDropdownItem,
GlLink,
GlModal,
AbuseCategorySelector,
@@ -184,6 +184,18 @@ export default {
showMovedSidebarOptions() {
return this.isMrSidebarMoved && this.isUserSignedIn;
},
+ newIssueItem() {
+ return {
+ text: this.newIssueTypeText,
+ href: this.newIssuePath,
+ };
+ },
+ submitSpamItem() {
+ return {
+ text: __('Submit as spam'),
+ href: this.submitAsSpamPath,
+ };
+ },
},
created() {
eventHub.$on('toggle.issuable.state', this.toggleIssueState);
@@ -197,6 +209,7 @@ export default {
toggleIssueState() {
if (!this.isClosed && this.getBlockedByIssues?.length) {
this.$refs.blockedByIssuesModal.show();
+ this.closeActionsDropdown();
return;
}
@@ -204,6 +217,7 @@ export default {
},
toggleReportAbuseDrawer(isOpen) {
this.isReportAbuseDrawerOpen = isOpen;
+ this.closeActionsDropdown();
},
invokeUpdateIssueMutation() {
this.toggleStateButtonLoading(true);
@@ -237,6 +251,7 @@ export default {
.catch(() => createAlert({ message: __('Error occurred while updating the issue status') }))
.finally(() => {
this.toggleStateButtonLoading(false);
+ this.closeActionsDropdown();
});
},
promoteToEpic() {
@@ -267,16 +282,24 @@ export default {
.catch(() => createAlert({ message: this.$options.i18n.promoteErrorMessage }))
.finally(() => {
this.toggleStateButtonLoading(false);
+ this.closeActionsDropdown();
});
},
edit() {
issuesEventHub.$emit('open.form');
+ this.closeActionsDropdown();
},
copyReference() {
toast(__('Reference copied'));
+ this.closeActionsDropdown();
},
copyEmailAddress() {
toast(__('Email address copied'));
+ this.closeActionsDropdown();
+ },
+ closeActionsDropdown() {
+ this.$refs.issuableActionsDropdownMobile?.close();
+ this.$refs.issuableActionsDropdownDesktop?.close();
},
},
TYPE_ISSUE,
@@ -285,87 +308,90 @@ export default {
<template>
<div class="detail-page-header-actions gl-display-flex gl-align-self-start gl-sm-gap-3">
- <gl-dropdown
- v-if="hasMobileDropdown"
- class="gl-sm-display-none! w-100"
- block
- :text="dropdownText"
- data-testid="mobile-dropdown"
- :loading="isToggleStateButtonLoading"
- >
- <template v-if="showMovedSidebarOptions">
- <sidebar-subscriptions-widget
- :iid="String(iid)"
- :full-path="fullPath"
- :issuable-type="$options.TYPE_ISSUE"
- data-testid="notification-toggle"
- />
+ <div class="gl-sm-display-none! w-100">
+ <gl-disclosure-dropdown
+ v-if="hasMobileDropdown"
+ ref="issuableActionsDropdownMobile"
+ toggle-class="gl-w-full"
+ block
+ :toggle-text="dropdownText"
+ :auto-close="false"
+ data-testid="mobile-dropdown"
+ :loading="isToggleStateButtonLoading"
+ placement="right"
+ >
+ <template v-if="showMovedSidebarOptions">
+ <sidebar-subscriptions-widget
+ :iid="String(iid)"
+ :full-path="fullPath"
+ :issuable-type="$options.TYPE_ISSUE"
+ data-testid="notification-toggle"
+ />
- <gl-dropdown-divider />
- </template>
+ <gl-dropdown-divider />
+ </template>
- <template v-if="showLockIssueOption">
- <issuable-lock-form :is-editable="false" data-testid="lock-issue-toggle" />
- </template>
+ <template v-if="showLockIssueOption">
+ <issuable-lock-form :is-editable="false" data-testid="lock-issue-toggle" />
+ </template>
- <gl-dropdown-item v-if="canUpdateIssue" @click="edit">
- {{ $options.i18n.edit }}
- </gl-dropdown-item>
- <gl-dropdown-item
- v-if="showToggleIssueStateButton"
- :data-testid="`mobile_${qaSelector}`"
- @click="toggleIssueState"
- >
- {{ buttonText }}
- </gl-dropdown-item>
- <gl-dropdown-item v-if="canCreateIssue" :href="newIssuePath">
- {{ newIssueTypeText }}
- </gl-dropdown-item>
- <gl-dropdown-item v-if="canPromoteToEpic" @click="promoteToEpic">
- {{ __('Promote to epic') }}
- </gl-dropdown-item>
- <template v-if="isMrSidebarMoved">
- <gl-dropdown-item
- :data-clipboard-text="issuableReference"
- button-class="js-copy-reference"
- data-testid="copy-reference"
- @click="copyReference"
- >{{ $options.i18n.copyReferenceText }}</gl-dropdown-item
- >
- <gl-dropdown-item
- v-if="issuableEmailAddress && showMovedSidebarOptions"
- :data-clipboard-text="issuableEmailAddress"
- data-testid="copy-email"
- @click="copyEmailAddress"
- >{{ copyMailAddressText }}</gl-dropdown-item
+ <gl-disclosure-dropdown-item v-if="canUpdateIssue" @action="edit">
+ <template #list-item>{{ $options.i18n.edit }}</template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item
+ v-if="showToggleIssueStateButton"
+ :data-testid="`mobile_${qaSelector}`"
+ @action="toggleIssueState"
>
- </template>
- <gl-dropdown-item
- v-if="canReportSpam"
- :href="submitAsSpamPath"
- data-method="post"
- rel="nofollow"
- >
- {{ __('Submit as spam') }}
- </gl-dropdown-item>
- <template v-if="canDestroyIssue">
- <gl-dropdown-divider />
- <gl-dropdown-item
- v-gl-modal="$options.deleteModalId"
- variant="danger"
- @click="track('click_dropdown')"
+ <template #list-item>{{ buttonText }}</template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item v-if="canCreateIssue" :item="newIssueItem" />
+ <gl-disclosure-dropdown-item v-if="canPromoteToEpic" @action="promoteToEpic">
+ <template #list-item>{{ __('Promote to epic') }}</template>
+ </gl-disclosure-dropdown-item>
+ <template v-if="isMrSidebarMoved">
+ <gl-disclosure-dropdown-item
+ :data-clipboard-text="issuableReference"
+ button-class="js-copy-reference"
+ data-testid="copy-reference"
+ @action="copyReference"
+ ><template #list-item>{{
+ $options.i18n.copyReferenceText
+ }}</template></gl-disclosure-dropdown-item
+ >
+ <gl-disclosure-dropdown-item
+ v-if="issuableEmailAddress && showMovedSidebarOptions"
+ :data-clipboard-text="issuableEmailAddress"
+ data-testid="copy-email"
+ @action="copyEmailAddress"
+ >{{ copyMailAddressText }}</gl-disclosure-dropdown-item
+ >
+ </template>
+ <gl-disclosure-dropdown-item
+ v-if="canReportSpam"
+ :item="submitSpamItem"
+ data-method="post"
+ rel="nofollow"
+ />
+ <template v-if="canDestroyIssue">
+ <gl-dropdown-divider />
+ <gl-disclosure-dropdown-item
+ v-gl-modal="$options.deleteModalId"
+ variant="danger"
+ @action="track('click_dropdown')"
+ >
+ <template #list-item>{{ deleteButtonText }}</template>
+ </gl-disclosure-dropdown-item>
+ </template>
+ <gl-disclosure-dropdown-item
+ v-if="!isIssueAuthor && isUserSignedIn"
+ data-testid="report-abuse-item"
+ @action="toggleReportAbuseDrawer(true)"
>
- {{ deleteButtonText }}
- </gl-dropdown-item>
- </template>
- <gl-dropdown-item
- v-if="!isIssueAuthor && isUserSignedIn"
- data-testid="report-abuse-item"
- @click="toggleReportAbuseDrawer(true)"
- >
- {{ $options.i18n.reportAbuse }}
- </gl-dropdown-item>
- </gl-dropdown>
+ <template #list-item>{{ $options.i18n.reportAbuse }}</template>
+ </gl-disclosure-dropdown-item>
+ </gl-disclosure-dropdown>
+ </div>
<gl-button
v-if="canUpdateIssue"
@@ -379,20 +405,22 @@ export default {
{{ $options.i18n.edit }}
</gl-button>
- <gl-dropdown
+ <gl-disclosure-dropdown
v-if="hasDesktopDropdown"
id="new-actions-header-dropdown"
+ ref="issuableActionsDropdownDesktop"
v-gl-tooltip.hover
class="gl-display-none gl-sm-display-inline-flex!"
icon="ellipsis_v"
category="tertiary"
- :text="dropdownText"
- :text-sr-only="true"
+ placement="left"
+ :toggle-text="dropdownText"
+ text-sr-only
:title="dropdownText"
:aria-label="dropdownText"
+ :auto-close="false"
data-testid="desktop-dropdown"
no-caret
- right
>
<template v-if="showMovedSidebarOptions && !glFeatures.notificationsTodosButtons">
<sidebar-subscriptions-widget
@@ -401,73 +429,70 @@ export default {
:issuable-type="$options.TYPE_ISSUE"
data-testid="notification-toggle"
/>
-
<gl-dropdown-divider />
</template>
- <gl-dropdown-item
+ <gl-disclosure-dropdown-item
v-if="showToggleIssueStateButton"
data-testid="toggle-issue-state-button"
- @click="toggleIssueState"
+ @action="toggleIssueState"
>
- {{ buttonText }}
- </gl-dropdown-item>
- <gl-dropdown-item v-if="canCreateIssue && isUserSignedIn" :href="newIssuePath">
- {{ newIssueTypeText }}
- </gl-dropdown-item>
- <gl-dropdown-item
+ <template #list-item>{{ buttonText }}</template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item v-if="canCreateIssue && isUserSignedIn" :item="newIssueItem" />
+ <gl-disclosure-dropdown-item
v-if="canPromoteToEpic"
:disabled="isToggleStateButtonLoading"
data-testid="promote-button"
- @click="promoteToEpic"
+ @action="promoteToEpic"
>
- {{ __('Promote to epic') }}
- </gl-dropdown-item>
+ <template #list-item>{{ __('Promote to epic') }}</template>
+ </gl-disclosure-dropdown-item>
<template v-if="showLockIssueOption">
<issuable-lock-form :is-editable="false" data-testid="lock-issue-toggle" />
</template>
<template v-if="isMrSidebarMoved">
- <gl-dropdown-item
+ <gl-disclosure-dropdown-item
:data-clipboard-text="issuableReference"
button-class="js-copy-reference"
data-testid="copy-reference"
- @click="copyReference"
- >{{ $options.i18n.copyReferenceText }}</gl-dropdown-item
+ @action="copyReference"
+ ><template #list-item>{{
+ $options.i18n.copyReferenceText
+ }}</template></gl-disclosure-dropdown-item
>
- <gl-dropdown-item
+ <gl-disclosure-dropdown-item
v-if="issuableEmailAddress && showMovedSidebarOptions"
:data-clipboard-text="issuableEmailAddress"
data-testid="copy-email"
- @click="copyEmailAddress"
- >{{ copyMailAddressText }}</gl-dropdown-item
+ @action="copyEmailAddress"
+ ><template #list-item>{{ copyMailAddressText }}</template></gl-disclosure-dropdown-item
>
</template>
- <gl-dropdown-divider v-if="canDestroyIssue || canReportSpam || !isIssueAuthor" />
- <gl-dropdown-item
+ <gl-dropdown-divider v-if="showToggleIssueStateButton || canDestroyIssue || canReportSpam" />
+ <gl-disclosure-dropdown-item
v-if="canReportSpam"
- :href="submitAsSpamPath"
+ :item="submitSpamItem"
data-method="post"
rel="nofollow"
- >
- {{ __('Submit as spam') }}
- </gl-dropdown-item>
- <gl-dropdown-item
+ />
+ <gl-disclosure-dropdown-item
v-if="!isIssueAuthor && isUserSignedIn"
data-testid="report-abuse-item"
- @click="toggleReportAbuseDrawer(true)"
+ @action="toggleReportAbuseDrawer(true)"
>
- {{ $options.i18n.reportAbuse }}
- </gl-dropdown-item>
+ <template #list-item>{{ $options.i18n.reportAbuse }}</template>
+ </gl-disclosure-dropdown-item>
<template v-if="canDestroyIssue">
- <gl-dropdown-item
+ <gl-disclosure-dropdown-item
v-gl-modal="$options.deleteModalId"
variant="danger"
data-testid="delete-issue-button"
- @click="track('click_dropdown')"
+ @action="track('click_dropdown')"
>
- {{ deleteButtonText }}
- </gl-dropdown-item>
+ <template #list-item>{{ deleteButtonText }}</template>
+ </gl-disclosure-dropdown-item>
</template>
- </gl-dropdown>
+ </gl-disclosure-dropdown>
<gl-modal
ref="blockedByIssuesModal"
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
index 7753b850744..7d9ad83a1c6 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
@@ -76,7 +76,7 @@ export default {
v-gl-modal="$options.modalId"
size="small"
class="gl-ml-3"
- data-qa-selector="add_branch_rule_button"
+ data-testid="add-branch-rule-button"
>{{ $options.i18n.addBranchRule }}</gl-button
>
</template>
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
index f45a5b12db6..0a5fa288828 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
@@ -156,7 +156,7 @@ export default {
<li>
<div
class="gl-display-flex gl-justify-content-space-between"
- data-qa-selector="branch_content"
+ data-testid="branch-content"
:data-qa-branch-name="name"
>
<div>
@@ -178,7 +178,7 @@ export default {
class="gl-align-self-start"
category="tertiary"
size="small"
- data-qa-selector="details_button"
+ data-testid="details-button"
:href="detailsPath"
>
{{ $options.i18n.detailsButtonLabel }}</gl-button
diff --git a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
index 165499696de..16235275a54 100644
--- a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
+++ b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
@@ -170,9 +170,14 @@ export default {
</script>
<template>
- <li v-if="isMovedMrSidebar && isIssuable" class="gl-dropdown-item">
- <button type="button" class="dropdown-item" data-testid="issuable-lock" @click="toggleLocked">
- <span class="gl-dropdown-item-text-wrapper">
+ <li v-if="isMovedMrSidebar && isIssuable" class="gl-new-dropdown-item">
+ <button
+ type="button"
+ class="gl-new-dropdown-item-content"
+ data-testid="issuable-lock"
+ @click="toggleLocked"
+ >
+ <span class="gl-new-dropdown-item-text-wrapper">
<template v-if="isLoading">
<gl-loading-icon inline size="sm" /> {{ lockToggleInProgressText }}
</template>
diff --git a/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
index 34a4da946d6..ea8e0c4b950 100644
--- a/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
+++ b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
@@ -1,26 +1,20 @@
<script>
import {
GlIcon,
- GlLoadingIcon,
- GlDropdown,
- GlDropdownForm,
- GlDropdownItem,
- GlSearchBoxByType,
GlButton,
+ GlCollapsibleListbox,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
-
+import { debounce } from 'lodash';
+import { __ } from '~/locale';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import axios from '~/lib/utils/axios_utils';
export default {
components: {
GlIcon,
- GlLoadingIcon,
- GlDropdown,
- GlDropdownForm,
- GlDropdownItem,
- GlSearchBoxByType,
GlButton,
+ GlCollapsibleListbox,
},
directives: {
GlTooltip,
@@ -51,82 +45,58 @@ export default {
},
data() {
return {
- projectsListLoading: false,
- projectsListLoadFailed: false,
- searchKey: '',
projects: [],
- selectedProject: null,
- projectItemClick: false,
+ projectsList: [],
+ selectedProjects: [],
+ noResultsText: '',
+ isSearching: false,
};
},
- computed: {
- hasNoSearchResults() {
- return Boolean(
- !this.projectsListLoading &&
- !this.projectsListLoadFailed &&
- this.searchKey &&
- !this.projects.length,
- );
- },
- failedToLoadResults() {
- return !this.projectsListLoading && this.projectsListLoadFailed;
- },
- },
- watch: {
- searchKey(value = '') {
- this.fetchProjects(value);
- },
+ mounted() {
+ this.fetchProjects = debounce(this.fetchProjects, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
},
methods: {
- fetchProjects(search = '') {
- this.projectsListLoading = true;
- this.projectsListLoadFailed = false;
- return axios
- .get(this.projectsFetchPath, {
+ triggerSearch() {
+ this.$refs.dropdown.search();
+ },
+ async fetchProjects(search = '') {
+ this.isSearching = true;
+
+ try {
+ const { data } = await axios.get(this.projectsFetchPath, {
params: {
search,
},
- })
- .then(({ data }) => {
- this.projects = data;
- this.$refs.searchInput.focusInput();
- })
- .catch(() => {
- this.projectsListLoadFailed = true;
- })
- .finally(() => {
- this.projectsListLoading = false;
});
- },
- isSelectedProject(project) {
- if (this.selectedProject) {
- return this.selectedProject.id === project.id;
- }
- return false;
- },
- /**
- * This handler is to prevent dropdown
- * from closing when an item is selected
- * and emit an event only when dropdown closes.
- */
- handleDropdownHide(e) {
- if (this.projectItemClick) {
- e.preventDefault();
- this.projectItemClick = false;
- } else {
- this.$emit('dropdown-close');
+ this.projects = data;
+ this.projectsList = data.map((item) => ({
+ value: item.id,
+ text: item.name_with_namespace,
+ }));
+
+ if (!this.projectsList.length) {
+ this.noResultsText = __('No matching results');
+ }
+ } catch (e) {
+ this.noResultsText = __('Failed to load projects');
+ } finally {
+ this.isSearching = false;
}
},
- handleDropdownCloseClick() {
- this.$refs.dropdown.hide();
- },
- handleProjectSelect(project) {
- this.selectedProject = project.id === this.selectedProject?.id ? null : project;
- this.projectItemClick = true;
+ handleProjectSelect(items) {
+ // hack: simulate a single select to prevent the dropdown from closing
+ // todo: switch back to single select when https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2363 is fixed
+ this.selectedProjects = [items[items.length - 1]];
},
handleMoveClick() {
- this.$refs.dropdown.hide();
- this.$emit('move-issuable', this.selectedProject);
+ this.$refs.dropdown.close();
+ this.$emit(
+ 'move-issuable',
+ this.projects.find((item) => item.id === this.selectedProjects[0]),
+ );
+ },
+ handleDropdownHide() {
+ this.$emit('dropdown-close');
},
},
};
@@ -143,79 +113,45 @@ export default {
>
<gl-icon name="arrow-right" />
</div>
- <gl-dropdown
+ <gl-collapsible-listbox
ref="dropdown"
+ v-model="selectedProjects"
+ :items="projectsList"
:block="true"
- :disabled="moveInProgress || disabled"
- class="hide-collapsed"
- toggle-class="js-sidebar-dropdown-toggle"
- @shown="fetchProjects"
- @hide="handleDropdownHide"
+ :multiple="true"
+ :searchable="true"
+ :searching="isSearching"
+ :search-placeholder="__('Search project')"
+ :no-results-text="noResultsText"
+ :header-text="dropdownButtonTitle"
+ @hidden="handleDropdownHide"
+ @shown="triggerSearch"
+ @search="fetchProjects"
+ @select="handleProjectSelect"
>
- <template #button-content
- ><gl-loading-icon v-if="moveInProgress" size="sm" class="gl-mr-3" />{{
- dropdownButtonTitle
- }}</template
- >
- <gl-dropdown-form class="gl-pt-0">
- <div
- data-testid="header"
- class="gl-display-flex gl-pb-3 gl-border-1 gl-border-b-solid gl-border-gray-100"
- >
- <span class="gl-flex-grow-1 gl-text-center gl-font-weight-bold gl-py-1">{{
- dropdownHeaderTitle
- }}</span>
- <gl-button
- variant="link"
- icon="close"
- class="gl-mr-2 gl-w-auto! gl-p-2!"
- :aria-label="__('Close')"
- @click.prevent="handleDropdownCloseClick"
- />
- </div>
- <gl-search-box-by-type
- ref="searchInput"
- v-model.trim="searchKey"
- :placeholder="__('Search project')"
- :debounce="300"
- />
- <div data-testid="content" class="dropdown-content">
- <gl-loading-icon v-if="projectsListLoading" size="lg" class="gl-p-5" />
- <ul v-else>
- <gl-dropdown-item
- v-for="project in projects"
- :key="project.id"
- is-check-item
- :is-checked="isSelectedProject(project)"
- @click.stop.prevent="handleProjectSelect(project)"
- >{{ project.name_with_namespace }}</gl-dropdown-item
- >
- </ul>
- <div v-if="hasNoSearchResults" class="gl-text-center gl-p-3">
- {{ __('No matching results') }}
- </div>
- <div
- v-if="failedToLoadResults"
- data-testid="failed-load-results"
- class="gl-text-center gl-p-3"
- >
- {{ __('Failed to load projects') }}
- </div>
- </div>
- <div
- data-testid="footer"
- class="gl-pt-3 gl-px-3 gl-border-1 gl-border-t-solid gl-border-gray-100"
+ <template #toggle>
+ <gl-button
+ :loading="moveInProgress"
+ size="medium"
+ class="gl-w-full js-sidebar-dropdown-toggle hide-collapsed"
+ data-testid="dropdown-button"
+ :disabled="moveInProgress || disabled"
+ >{{ dropdownButtonTitle }}</gl-button
>
+ </template>
+ <template #footer>
+ <div data-testid="footer" class="gl-p-3">
<gl-button
category="primary"
variant="confirm"
- :disabled="!Boolean(selectedProject)"
- class="gl-w-full issuable-move-button"
+ :disabled="!Boolean(selectedProjects.length)"
+ class="gl-w-full"
+ data-testid="dropdown-move-button"
@click="handleMoveClick"
>{{ __('Move') }}</gl-button
>
</div>
- </gl-dropdown-form>
- </gl-dropdown>
+ </template>
+ </gl-collapsible-listbox>
</div>
</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_milestone.vue b/app/assets/javascripts/work_items/components/work_item_milestone.vue
index fe09105e173..9c6fa158169 100644
--- a/app/assets/javascripts/work_items/components/work_item_milestone.vue
+++ b/app/assets/javascripts/work_items/components/work_item_milestone.vue
@@ -1,13 +1,5 @@
<script>
-import {
- GlFormGroup,
- GlDropdown,
- GlDropdownItem,
- GlDropdownDivider,
- GlSkeletonLoader,
- GlSearchBoxByType,
- GlDropdownText,
-} from '@gitlab/ui';
+import { GlCollapsibleListbox, GlFormGroup, GlSkeletonLoader } from '@gitlab/ui';
import { debounce } from 'lodash';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import Tracking from '~/tracking';
@@ -22,7 +14,8 @@ import {
TRACKING_CATEGORY_SHOW,
} from '../constants';
-const noMilestoneId = 'no-milestone-id';
+export const noMilestoneId = 'no-milestone-id';
+const noMilestoneItem = { text: s__('WorkItem|No milestone'), value: noMilestoneId };
export default {
i18n: {
@@ -37,13 +30,9 @@ export default {
EXPIRED_TEXT: __('(expired)'),
},
components: {
+ GlCollapsibleListbox,
GlFormGroup,
- GlDropdown,
- GlDropdownItem,
- GlDropdownDivider,
GlSkeletonLoader,
- GlSearchBoxByType,
- GlDropdownText,
},
mixins: [Tracking.mixin()],
props: {
@@ -74,11 +63,23 @@ export default {
data() {
return {
localMilestone: this.workItemMilestone,
+ localMilestoneId: this.workItemMilestone?.id,
searchTerm: '',
shouldFetch: false,
updateInProgress: false,
- isFocused: false,
milestones: [],
+ dropdownGroups: [
+ {
+ text: this.$options.i18n.NO_MILESTONE,
+ textSrOnly: true,
+ options: [noMilestoneItem],
+ },
+ {
+ text: __('Milestones'),
+ textSrOnly: true,
+ options: [],
+ },
+ ],
};
},
computed: {
@@ -103,23 +104,29 @@ export default {
isLoadingMilestones() {
return this.$apollo.queries.milestones.loading;
},
- isNoMilestone() {
- return this.localMilestone?.id === noMilestoneId || !this.localMilestone?.id;
+ milestonesList() {
+ return (
+ this.milestones.map(({ id, title, expired }) => {
+ return {
+ value: id,
+ text: title,
+ expired,
+ };
+ }) ?? []
+ );
},
- dropdownClasses() {
- return {
- 'gl-text-gray-500!': this.canUpdate && this.isNoMilestone,
- 'is-not-focused': !this.isFocused,
- 'gl-min-w-20': true,
- };
+ toggleClasses() {
+ const toggleClasses = ['gl-max-w-full'];
+
+ if (this.localMilestoneId === noMilestoneId) {
+ toggleClasses.push('gl-text-gray-500!');
+ }
+ return toggleClasses;
},
},
watch: {
- workItemMilestone: {
- handler(newVal) {
- this.localMilestone = newVal;
- },
- deep: true,
+ milestones() {
+ this.dropdownGroups[1].options = this.milestonesList;
},
},
created() {
@@ -152,15 +159,11 @@ export default {
this.localMilestone = milestone;
},
onDropdownShown() {
- this.$refs.search.focusInput();
this.shouldFetch = true;
- this.isFocused = true;
},
onDropdownHide() {
- this.isFocused = false;
this.searchTerm = '';
this.shouldFetch = false;
- this.updateMilestone();
},
setSearchKey(value) {
this.searchTerm = value;
@@ -169,6 +172,9 @@ export default {
return this.localMilestone?.id === milestone?.id;
},
updateMilestone() {
+ this.localMilestone =
+ this.milestones.find(({ id }) => id === this.localMilestoneId) ?? noMilestoneItem;
+
if (this.workItemMilestone?.id === this.localMilestone?.id) {
return;
}
@@ -182,8 +188,7 @@ export default {
input: {
id: this.workItemId,
milestoneWidget: {
- milestoneId:
- this.localMilestone?.id === 'no-milestone-id' ? null : this.localMilestone?.id,
+ milestoneId: this.localMilestoneId === noMilestoneId ? null : this.localMilestoneId,
},
},
},
@@ -222,49 +227,45 @@ export default {
>
{{ dropdownText }}
</span>
- <gl-dropdown
+
+ <gl-collapsible-listbox
v-else
id="milestone-value"
- class="gl-pl-0 gl-max-w-full work-item-field-value"
- :toggle-class="dropdownClasses"
- :text="dropdownText"
+ v-model="localMilestoneId"
+ :items="dropdownGroups"
+ category="tertiary"
+ data-testid="work-item-milestone-dropdown"
+ class="gl-max-w-full"
+ :toggle-text="dropdownText"
:loading="updateInProgress"
+ :toggle-class="toggleClasses"
+ searchable
+ @select="updateMilestone"
@shown="onDropdownShown"
- @hide="onDropdownHide"
+ @hidden="onDropdownHide"
+ @search="debouncedSearchKeyUpdate"
>
- <template #header>
- <gl-search-box-by-type ref="search" :value="searchTerm" @input="debouncedSearchKeyUpdate" />
+ <template #list-item="{ item }">
+ {{ item.text }}
+ <span v-if="item.expired">{{ $options.i18n.EXPIRED_TEXT }}</span>
</template>
- <gl-dropdown-item
- data-testid="no-milestone"
- is-check-item
- :is-checked="isNoMilestone"
- @click="handleMilestoneClick({ id: 'no-milestone-id' })"
- >
- {{ $options.i18n.NO_MILESTONE }}
- </gl-dropdown-item>
- <gl-dropdown-divider />
- <gl-dropdown-text v-if="isLoadingMilestones">
- <gl-skeleton-loader :height="90">
+ <template #footer>
+ <gl-skeleton-loader v-if="isLoadingMilestones" :height="90">
<rect width="380" height="10" x="10" y="15" rx="4" />
<rect width="280" height="10" x="10" y="30" rx="4" />
<rect width="380" height="10" x="10" y="50" rx="4" />
<rect width="280" height="10" x="10" y="65" rx="4" />
</gl-skeleton-loader>
- </gl-dropdown-text>
- <template v-else-if="milestones.length">
- <gl-dropdown-item
- v-for="milestone in milestones"
- :key="milestone.id"
- is-check-item
- :is-checked="isMilestoneChecked(milestone)"
- @click="handleMilestoneClick(milestone)"
+
+ <div
+ v-else-if="!milestones.length"
+ aria-live="assertive"
+ class="gl-pl-7 gl-pr-5 gl-py-3 gl-font-base gl-text-gray-600"
+ data-testid="no-results-text"
>
- {{ milestone.title }}
- <template v-if="milestone.expired">{{ $options.i18n.EXPIRED_TEXT }}</template>
- </gl-dropdown-item>
+ {{ $options.i18n.NO_MATCHING_RESULTS }}
+ </div>
</template>
- <gl-dropdown-text v-else>{{ $options.i18n.NO_MATCHING_RESULTS }}</gl-dropdown-text>
- </gl-dropdown>
+ </gl-collapsible-listbox>
</gl-form-group>
</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_parent.vue b/app/assets/javascripts/work_items/components/work_item_parent.vue
index 23660f24783..74f0ec42905 100644
--- a/app/assets/javascripts/work_items/components/work_item_parent.vue
+++ b/app/assets/javascripts/work_items/components/work_item_parent.vue
@@ -61,7 +61,6 @@ export default {
searchStarted: false,
availableWorkItems: [],
localSelectedItem: this.parent?.id,
- isNotFocused: true,
oldParent: this.parent,
};
},
@@ -82,14 +81,6 @@ export default {
workItems() {
return this.availableWorkItems.map(({ id, title }) => ({ text: title, value: id }));
},
- listboxCategory() {
- return this.searchStarted ? 'secondary' : 'tertiary';
- },
- listboxClasses() {
- return {
- 'is-not-focused': this.isNotFocused && !this.searchStarted,
- };
- },
parentType() {
return SUPPORTED_PARENT_TYPE_MAP[this.workItemType];
},
@@ -184,19 +175,10 @@ export default {
},
onListboxShown() {
this.searchStarted = true;
- this.isNotFocused = false;
},
onListboxHide() {
this.searchStarted = false;
this.search = '';
- this.isNotFocused = true;
- },
- setListboxFocused() {
- // This is to match the caret behaviour of parent listbox
- // to the other dropdown fields of work items
- if (document.activeElement.parentElement.id !== 'work-item-parent-listbox-value') {
- this.isNotFocused = true;
- }
},
},
};
@@ -219,30 +201,20 @@ export default {
>
{{ listboxText }}
</span>
- <div
- v-else
- :class="{ 'gl-max-w-max-content': !workItemsMvc2Enabled }"
- @mouseover="isNotFocused = false"
- @mouseleave="setListboxFocused"
- @focusout="isNotFocused = true"
- @focusin="isNotFocused = false"
- >
+ <div v-else :class="{ 'gl-max-w-max-content': !workItemsMvc2Enabled }">
<gl-collapsible-listbox
id="work-item-parent-listbox-value"
class="gl-max-w-max-content"
data-testid="work-item-parent-listbox"
- block
searchable
- :no-caret="isNotFocused && !searchStarted"
is-check-centered
- :category="listboxCategory"
+ category="tertiary"
:searching="isLoading"
:header-text="$options.i18n.assignParentLabel"
:no-results-text="$options.i18n.noMatchingResults"
:loading="updateInProgress"
:items="workItems"
:toggle-text="listboxText"
- :toggle-class="listboxClasses"
:selected="localSelectedItem"
:reset-button-label="$options.i18n.unAssign"
@reset="unAssignParent"
diff --git a/app/assets/stylesheets/page_bundles/issuable.scss b/app/assets/stylesheets/page_bundles/issuable.scss
index 07614c5271a..d52cd45e575 100644
--- a/app/assets/stylesheets/page_bundles/issuable.scss
+++ b/app/assets/stylesheets/page_bundles/issuable.scss
@@ -114,29 +114,6 @@
}
}
-/*
- * Following overrides are done to prevent
- * legacy dropdown styles from influencing
- * GitLab UI components used within GlDropdown
- */
-.issuable-move-dropdown {
- .b-dropdown-form {
- @include gl-p-0;
- }
-
- .gl-search-box-by-type button.gl-clear-icon-button:hover {
- @include gl-bg-transparent;
-
- &:focus {
- @include gl-focus($inset: true);
- }
- }
-
- .issuable-move-button:not(.disabled):hover {
- @include gl-text-white;
- }
-}
-
.suggestion-footer {
font-size: 12px;
line-height: 15px;
diff --git a/app/assets/stylesheets/page_bundles/work_items.scss b/app/assets/stylesheets/page_bundles/work_items.scss
index f41a1f540e3..154803c7d88 100644
--- a/app/assets/stylesheets/page_bundles/work_items.scss
+++ b/app/assets/stylesheets/page_bundles/work_items.scss
@@ -67,6 +67,7 @@ $work-item-sticky-header-height: 52px;
}
}
+//TODO: remove all the styles related to `gl-dropdown` when all `.work-item-dropdown`s are migrated
.work-item-dropdown {
// duplicate classname because we are fighting with gl-button styles
.gl-dropdown-toggle.gl-dropdown-toggle {
@@ -95,24 +96,25 @@ $work-item-sticky-header-height: 52px;
// need to override the listbox styles to match with dropdown
// till the dropdown are converted to listbox
- .gl-new-dropdown-toggle {
+ .gl-new-dropdown-toggle.gl-new-dropdown-toggle {
&:hover,
&:focus {
- background: none !important;
box-shadow: $work-item-field-inset-shadow;
background-color: $input-bg;
- }
- .is-not-focused {
- &.gl-new-dropdown-button-text {
- margin: 0 0.25rem;
+ .gl-dark & {
+ // $input-bg is overridden in dark mode but that does not
+ // work in page bundles currently, manually override here
+ background-color: var(--gray-50, $input-bg);
}
}
- }
- .gl-new-dropdown-toggle.is-not-focused {
- .gl-new-dropdown-button-text {
- margin: 0 0.25rem;
+ &:not(:hover, :focus) {
+ box-shadow: none;
+
+ .gl-new-dropdown-chevron {
+ visibility: hidden;
+ }
}
}