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:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-03 03:11:20 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-03 03:11:20 +0300
commit498ba9dc41fcf2b4be30a8f3721543953efb3c3b (patch)
treeed33fbf37c0c2ae3a71042455f9b51800907a984 /app/assets/javascripts/header_search
parent515f39456fce82eb2ab811fa366167ad084a3b12 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/header_search')
-rw-r--r--app/assets/javascripts/header_search/components/app.vue80
-rw-r--r--app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue5
-rw-r--r--app/assets/javascripts/header_search/components/header_search_default_items.vue5
-rw-r--r--app/assets/javascripts/header_search/components/header_search_scoped_items.vue19
-rw-r--r--app/assets/javascripts/header_search/constants.js22
5 files changed, 111 insertions, 20 deletions
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index a575b80facc..67e3998bc97 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -2,9 +2,14 @@
import { GlSearchBoxByType, GlOutsideDirective as Outside } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import { visitUrl } from '~/lib/utils/url_utility';
-import { __ } from '~/locale';
+import { s__, sprintf } from '~/locale';
import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
-import { FIRST_DROPDOWN_INDEX, SEARCH_BOX_INDEX } from '../constants';
+import {
+ FIRST_DROPDOWN_INDEX,
+ SEARCH_BOX_INDEX,
+ SEARCH_INPUT_DESCRIPTION,
+ SEARCH_RESULTS_DESCRIPTION,
+} from '../constants';
import HeaderSearchAutocompleteItems from './header_search_autocomplete_items.vue';
import HeaderSearchDefaultItems from './header_search_default_items.vue';
import HeaderSearchScopedItems from './header_search_scoped_items.vue';
@@ -12,7 +17,21 @@ import HeaderSearchScopedItems from './header_search_scoped_items.vue';
export default {
name: 'HeaderSearchApp',
i18n: {
- searchPlaceholder: __('Search or jump to...'),
+ searchPlaceholder: s__('GlobalSearch|Search or jump to...'),
+ searchAria: s__('GlobalSearch|Search GitLab'),
+ searchInputDescribeByNoDropdown: s__(
+ 'GlobalSearch|Type and press the enter key to submit search.',
+ ),
+ searchInputDescribeByWithDropdown: s__(
+ 'GlobalSearch|Type for new suggestions to appear below.',
+ ),
+ searchDescribedByDefault: s__(
+ 'GlobalSearch|%{count} default results provided. Use the up and down arrow keys to navigate search results list.',
+ ),
+ searchDescribedByUpdated: s__(
+ 'GlobalSearch|Results updated. %{count} results available. Use the up and down arrow keys to navigate search results list, or ENTER to submit.',
+ ),
+ searchResultsLoading: s__('GlobalSearch|Search results are loading'),
},
directives: { Outside },
components: {
@@ -29,7 +48,7 @@ export default {
};
},
computed: {
- ...mapState(['search']),
+ ...mapState(['search', 'loading']),
...mapGetters(['searchQuery', 'searchOptions']),
searchText: {
get() {
@@ -42,6 +61,9 @@ export default {
currentFocusedOption() {
return this.searchOptions[this.currentFocusIndex];
},
+ currentFocusedId() {
+ return this.currentFocusedOption?.html_id;
+ },
isLoggedIn() {
return gon?.current_username;
},
@@ -58,6 +80,30 @@ export default {
return FIRST_DROPDOWN_INDEX;
},
+ searchInputDescribeBy() {
+ if (this.isLoggedIn) {
+ return this.$options.i18n.searchInputDescribeByWithDropdown;
+ }
+
+ return this.$options.i18n.searchInputDescribeByNoDropdown;
+ },
+ dropdownResultsDescription() {
+ if (!this.showSearchDropdown) {
+ return ''; // This allows aria-live to see register an update when the dropdown is shown
+ }
+
+ if (this.showDefaultItems) {
+ return sprintf(this.$options.i18n.searchDescribedByDefault, {
+ count: this.searchOptions.length,
+ });
+ }
+
+ return this.loading
+ ? this.$options.i18n.searchResultsLoading
+ : sprintf(this.$options.i18n.searchDescribedByUpdated, {
+ count: this.searchOptions.length,
+ });
+ },
},
methods: {
...mapActions(['setSearch', 'fetchAutocompleteOptions']),
@@ -79,22 +125,44 @@ export default {
},
},
SEARCH_BOX_INDEX,
+ SEARCH_INPUT_DESCRIPTION,
+ SEARCH_RESULTS_DESCRIPTION,
};
</script>
<template>
- <section v-outside="closeDropdown" class="header-search gl-relative">
+ <form
+ v-outside="closeDropdown"
+ role="search"
+ :aria-label="$options.i18n.searchAria"
+ class="header-search gl-relative"
+ >
<gl-search-box-by-type
v-model="searchText"
+ role="searchbox"
class="gl-z-index-1"
:debounce="500"
autocomplete="off"
:placeholder="$options.i18n.searchPlaceholder"
+ :aria-activedescendant="currentFocusedId"
+ :aria-describedby="$options.SEARCH_INPUT_DESCRIPTION"
@focus="openDropdown"
@click="openDropdown"
@input="getAutocompleteOptions"
@keydown.enter.stop.prevent="submitSearch"
/>
+ <span :id="$options.SEARCH_INPUT_DESCRIPTION" role="region" class="gl-sr-only">{{
+ searchInputDescribeBy
+ }}</span>
+ <span
+ role="region"
+ :data-testid="$options.SEARCH_RESULTS_DESCRIPTION"
+ class="gl-sr-only"
+ aria-live="polite"
+ aria-atomic="true"
+ >
+ {{ dropdownResultsDescription }}
+ </span>
<div
v-if="showSearchDropdown"
data-testid="header-search-dropdown-menu"
@@ -118,5 +186,5 @@ export default {
</template>
</div>
</div>
- </section>
+ </form>
</template>
diff --git a/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue
index cf1f7c030e2..9f4f4768247 100644
--- a/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue
+++ b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue
@@ -69,13 +69,16 @@ export default {
<gl-dropdown-section-header>{{ option.category }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="data in option.data"
+ :id="data.html_id"
:ref="data.html_id"
:key="data.html_id"
:class="{ 'gl-bg-gray-50': isOptionFocused(data) }"
+ :aria-selected="isOptionFocused(data)"
+ :aria-label="data.label"
tabindex="-1"
:href="data.url"
>
- <div class="gl-display-flex gl-align-items-center">
+ <div class="gl-display-flex gl-align-items-center" aria-hidden="true">
<gl-avatar
v-if="data.avatar_url !== undefined"
:src="data.avatar_url"
diff --git a/app/assets/javascripts/header_search/components/header_search_default_items.vue b/app/assets/javascripts/header_search/components/header_search_default_items.vue
index 2228bc0c2a2..53e63bc6cca 100644
--- a/app/assets/javascripts/header_search/components/header_search_default_items.vue
+++ b/app/assets/javascripts/header_search/components/header_search_default_items.vue
@@ -43,13 +43,16 @@ export default {
<gl-dropdown-section-header>{{ sectionHeader }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="option in defaultSearchOptions"
+ :id="option.html_id"
:ref="option.html_id"
:key="option.html_id"
:class="{ 'gl-bg-gray-50': isOptionFocused(option) }"
+ :aria-selected="isOptionFocused(option)"
+ :aria-label="option.title"
tabindex="-1"
:href="option.url"
>
- {{ option.title }}
+ <span aria-hidden="true">{{ option.title }}</span>
</gl-dropdown-item>
</div>
</template>
diff --git a/app/assets/javascripts/header_search/components/header_search_scoped_items.vue b/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
index d3def929752..3aebee71509 100644
--- a/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
+++ b/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
@@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
+import { __, sprintf } from '~/locale';
export default {
name: 'HeaderSearchScopedItems',
@@ -22,6 +23,13 @@ export default {
isOptionFocused(option) {
return this.currentFocusedOption?.html_id === option.html_id;
},
+ ariaLabel(option) {
+ return sprintf(__('%{search} %{description} %{scope}'), {
+ search: this.search,
+ description: option.description,
+ scope: option.scope || '',
+ });
+ },
},
};
</script>
@@ -30,15 +38,20 @@ export default {
<div>
<gl-dropdown-item
v-for="option in scopedSearchOptions"
+ :id="option.html_id"
:ref="option.html_id"
:key="option.html_id"
:class="{ 'gl-bg-gray-50': isOptionFocused(option) }"
+ :aria-selected="isOptionFocused(option)"
+ :aria-label="ariaLabel(option)"
tabindex="-1"
:href="option.url"
>
- "<span class="gl-font-weight-bold">{{ search }}</span
- >" {{ option.description }}
- <span v-if="option.scope" class="gl-font-style-italic">{{ option.scope }}</span>
+ <span aria-hidden="true">
+ "<span class="gl-font-weight-bold">{{ search }}</span
+ >" {{ option.description }}
+ <span v-if="option.scope" class="gl-font-style-italic">{{ option.scope }}</span>
+ </span>
</gl-dropdown-item>
</div>
</template>
diff --git a/app/assets/javascripts/header_search/constants.js b/app/assets/javascripts/header_search/constants.js
index 34777697863..b2e45fcd648 100644
--- a/app/assets/javascripts/header_search/constants.js
+++ b/app/assets/javascripts/header_search/constants.js
@@ -1,20 +1,20 @@
-import { __ } from '~/locale';
+import { s__ } from '~/locale';
-export const MSG_ISSUES_ASSIGNED_TO_ME = __('Issues assigned to me');
+export const MSG_ISSUES_ASSIGNED_TO_ME = s__('GlobalSearch|Issues assigned to me');
-export const MSG_ISSUES_IVE_CREATED = __("Issues I've created");
+export const MSG_ISSUES_IVE_CREATED = s__("GlobalSearch|Issues I've created");
-export const MSG_MR_ASSIGNED_TO_ME = __('Merge requests assigned to me');
+export const MSG_MR_ASSIGNED_TO_ME = s__('GlobalSearch|Merge requests assigned to me');
-export const MSG_MR_IM_REVIEWER = __("Merge requests that I'm a reviewer");
+export const MSG_MR_IM_REVIEWER = s__("GlobalSearch|Merge requests that I'm a reviewer");
-export const MSG_MR_IVE_CREATED = __("Merge requests I've created");
+export const MSG_MR_IVE_CREATED = s__("GlobalSearch|Merge requests I've created");
-export const MSG_IN_ALL_GITLAB = __('in all GitLab');
+export const MSG_IN_ALL_GITLAB = s__('GlobalSearch|in all GitLab');
-export const MSG_IN_GROUP = __('in group');
+export const MSG_IN_GROUP = s__('GlobalSearch|in group');
-export const MSG_IN_PROJECT = __('in project');
+export const MSG_IN_PROJECT = s__('GlobalSearch|in project');
export const GROUPS_CATEGORY = 'Groups';
@@ -27,3 +27,7 @@ export const SMALL_AVATAR_PX = 16;
export const FIRST_DROPDOWN_INDEX = 0;
export const SEARCH_BOX_INDEX = -1;
+
+export const SEARCH_INPUT_DESCRIPTION = 'search-input-description';
+
+export const SEARCH_RESULTS_DESCRIPTION = 'search-results-description';