1
1
<script >
2
- import {
3
- GlIcon ,
4
- GlLoadingIcon ,
5
- GlAvatar ,
6
- GlDropdown ,
7
- GlDropdownSectionHeader ,
8
- GlDropdownItem ,
9
- GlSearchBoxByType ,
10
- GlTruncate ,
11
- } from ' @gitlab/ui' ;
2
+ import { GlButton , GlIcon , GlAvatar , GlCollapsibleListbox , GlTruncate } from ' @gitlab/ui' ;
12
3
import { debounce } from ' lodash' ;
13
4
import { filterBySearchTerm } from ' ~/analytics/shared/utils' ;
14
5
import { getIdFromGraphQLId } from ' ~/graphql_shared/utils' ;
@@ -18,17 +9,15 @@ import { n__, s__, __ } from '~/locale';
18
9
import getProjects from ' ../graphql/projects.query.graphql' ;
19
10
20
11
const sortByProjectName = (projects = []) => projects .sort ((a , b ) => a .name .localeCompare (b .name ));
12
+ const mapItemToListboxFormat = (item ) => ({ ... item, value: item .id , text: item .name });
21
13
22
14
export default {
23
15
name: ' ProjectsDropdownFilter' ,
24
16
components: {
17
+ GlButton,
25
18
GlIcon,
26
- GlLoadingIcon,
27
19
GlAvatar,
28
- GlDropdown,
29
- GlDropdownSectionHeader,
30
- GlDropdownItem,
31
- GlSearchBoxByType,
20
+ GlCollapsibleListbox,
32
21
GlTruncate,
33
22
},
34
23
props: {
@@ -94,6 +83,9 @@ export default {
94
83
selectedProjectIds () {
95
84
return this .selectedProjects .map ((p ) => p .id );
96
85
},
86
+ selectedListBoxItems () {
87
+ return this .multiSelect ? this .selectedProjectIds : this .selectedProjectIds [0 ];
88
+ },
97
89
hasSelectedProjects () {
98
90
return Boolean (this .selectedProjects .length );
99
91
},
@@ -110,6 +102,28 @@ export default {
110
102
unselectedItems () {
111
103
return this .availableProjects .filter (({ id }) => ! this .selectedProjectIds .includes (id));
112
104
},
105
+ selectedGroupOptions () {
106
+ return this .selectedItems .map (mapItemToListboxFormat);
107
+ },
108
+ unSelectedGroupOptions () {
109
+ return this .unselectedItems .map (mapItemToListboxFormat);
110
+ },
111
+ listBoxItems () {
112
+ if (this .selectedGroupOptions .length === 0 ) {
113
+ return this .unSelectedGroupOptions ;
114
+ }
115
+
116
+ return [
117
+ {
118
+ text: __ (' Selected' ),
119
+ options: this .selectedGroupOptions ,
120
+ },
121
+ {
122
+ text: __ (' Unselected' ),
123
+ options: this .unSelectedGroupOptions ,
124
+ },
125
+ ];
126
+ },
113
127
},
114
128
watch: {
115
129
searchTerm () {
@@ -129,32 +143,29 @@ export default {
129
143
search: debounce (function debouncedSearch () {
130
144
this .fetchData ();
131
145
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS ),
132
- getSelectedProjects (selectedProject , isSelected ) {
133
- return isSelected
134
- ? this .selectedProjects .concat ([selectedProject])
135
- : this .selectedProjects .filter ((project ) => project .id !== selectedProject .id );
136
- },
137
146
singleSelectedProject (selectedObj , isMarking ) {
138
147
return isMarking ? [selectedObj] : [];
139
148
},
140
- setSelectedProjects (project ) {
149
+ setSelectedProjects (payload ) {
141
150
this .selectedProjects = this .multiSelect
142
- ? this . getSelectedProjects (project, ! this . isProjectSelected (project))
143
- : this .singleSelectedProject (project , ! this .isProjectSelected (project ));
151
+ ? payload
152
+ : this .singleSelectedProject (payload , ! this .isProjectSelected (payload ));
144
153
},
145
- onClick (project ) {
154
+ onClick (projectId ) {
155
+ const project = this .availableProjects .find (({ id }) => id === projectId);
146
156
this .setSelectedProjects (project);
147
157
this .handleUpdatedSelectedProjects ();
148
158
},
149
- onMultiSelectClick (project ) {
150
- this .setSelectedProjects (project);
159
+ onMultiSelectClick (projectIds ) {
160
+ const projects = this .availableProjects .filter (({ id }) => projectIds .includes (id));
161
+ this .setSelectedProjects (projects);
151
162
this .isDirty = true ;
152
163
},
153
- onSelected (project ) {
164
+ onSelected (payload ) {
154
165
if (this .multiSelect ) {
155
- this .onMultiSelectClick (project );
166
+ this .onMultiSelectClick (payload );
156
167
} else {
157
- this .onClick (project );
168
+ this .onClick (payload );
158
169
}
159
170
},
160
171
onHide () {
@@ -201,97 +212,65 @@ export default {
201
212
getEntityId (project ) {
202
213
return getIdFromGraphQLId (project .id );
203
214
},
215
+ setSearchTerm (val ) {
216
+ this .searchTerm = val;
217
+ },
204
218
},
205
219
AVATAR_SHAPE_OPTION_RECT ,
206
220
};
207
221
</script >
208
222
<template >
209
- <gl-dropdown
223
+ <gl-collapsible-listbox
210
224
ref =" projectsDropdown"
211
- class =" dropdown dropdown-projects"
212
225
toggle-class =" gl-shadow-none gl-mb-0"
226
+ :header-text =" __('Projects')"
227
+ :items =" listBoxItems"
228
+ :reset-button-label =" __('Clear All')"
213
229
:loading =" loadingDefaultProjects"
214
- :show-clear-all =" hasSelectedProjects"
215
- show-highlighted-items-title
216
- highlighted-items-title-class =" gl-p-3"
217
- block
218
- @clear-all.stop =" onClearAll"
219
- @hide =" onHide"
230
+ :multiple =" multiSelect"
231
+ :no-results-text =" __('No matching results')"
232
+ :selected =" selectedListBoxItems"
233
+ :searching =" loading"
234
+ searchable
235
+ @hidden =" onHide"
236
+ @reset =" onClearAll"
237
+ @search =" setSearchTerm"
238
+ @select =" onSelected"
220
239
>
221
- <template #button-content >
222
- <gl-loading-icon v-if =" loadingDefaultProjects" class =" gl-mr-2 gl-flex-shrink-0" />
223
- <gl-avatar
224
- v-if =" isOnlyOneProjectSelected"
225
- :src =" selectedProjects[0].avatarUrl"
226
- :entity-id =" getEntityId(selectedProjects[0])"
227
- :entity-name =" selectedProjects[0].name"
228
- :size =" 16"
229
- :shape =" $options.AVATAR_SHAPE_OPTION_RECT"
230
- :alt =" selectedProjects[0].name"
231
- class =" gl-display-inline-flex gl-vertical-align-middle gl-mr-2 gl-flex-shrink-0"
232
- />
233
- <gl-truncate :text =" selectedProjectsLabel" class =" gl-min-w-0 gl-flex-grow-1" />
234
- <gl-icon class =" gl-ml-2 gl-flex-shrink-0" name =" chevron-down" />
235
- </template >
236
- <template #header >
237
- <gl-dropdown-section-header >{{ __('Projects') }}</gl-dropdown-section-header >
238
- <gl-search-box-by-type v-model.trim =" searchTerm" :placeholder =" __('Search')" />
239
- </template >
240
- <template #highlighted-items >
241
- <gl-dropdown-item
242
- v-for =" project in selectedItems"
243
- :key =" project.id"
244
- is-check-item
245
- :is-checked =" isProjectSelected(project)"
246
- @click.native.capture.stop =" onSelected(project)"
247
- >
248
- <div class =" gl-display-flex" >
249
- <gl-avatar
250
- class =" gl-mr-2 gl-vertical-align-middle"
251
- :alt =" project.name"
252
- :size =" 16"
253
- :entity-id =" getEntityId(project)"
254
- :entity-name =" project.name"
255
- :src =" project.avatarUrl"
256
- :shape =" $options.AVATAR_SHAPE_OPTION_RECT"
257
- />
258
- <div >
259
- <div data-testid =" project-name" >{{ project.name }}</div >
260
- <div class =" gl-text-gray-500" data-testid =" project-full-path" >
261
- {{ project.fullPath }}
262
- </div >
263
- </div >
264
- </div >
265
- </gl-dropdown-item >
240
+ <template #toggle >
241
+ <gl-button class =" dropdown-projects" >
242
+ <gl-avatar
243
+ v-if =" isOnlyOneProjectSelected"
244
+ :src =" selectedProjects[0].avatarUrl"
245
+ :entity-id =" getEntityId(selectedProjects[0])"
246
+ :entity-name =" selectedProjects[0].name"
247
+ :size =" 16"
248
+ :shape =" $options.AVATAR_SHAPE_OPTION_RECT"
249
+ :alt =" selectedProjects[0].name"
250
+ class =" gl-display-inline-flex gl-vertical-align-middle gl-mr-2 gl-flex-shrink-0"
251
+ />
252
+ <gl-truncate :text =" selectedProjectsLabel" class =" gl-min-w-0 gl-flex-grow-1" />
253
+ <gl-icon class =" gl-ml-2 gl-flex-shrink-0" name =" chevron-down" />
254
+ </gl-button >
266
255
</template >
267
- <gl-dropdown-item
268
- v-for =" project in unselectedItems"
269
- :key =" project.id"
270
- @click.native.capture.stop =" onSelected(project)"
271
- >
256
+ <template #list-item =" { item } " >
272
257
<div class =" gl-display-flex" >
273
258
<gl-avatar
274
- class =" gl-mr-2 vertical-align-middle"
275
- :alt =" project .name"
259
+ class =" gl-mr-2 gl- vertical-align-middle"
260
+ :alt =" item .name"
276
261
:size =" 16"
277
- :entity-id =" getEntityId(project )"
278
- :entity-name =" project .name"
279
- :src =" project .avatarUrl"
262
+ :entity-id =" getEntityId(item )"
263
+ :entity-name =" item .name"
264
+ :src =" item .avatarUrl"
280
265
:shape =" $options.AVATAR_SHAPE_OPTION_RECT"
281
266
/>
282
267
<div >
283
- <div data-testid =" project-name" data-qa-selector =" project_name" >{{ project .name }}</div >
268
+ <div data-testid =" project-name" data-qa-selector =" project_name" >{{ item .name }}</div >
284
269
<div class =" gl-text-gray-500" data-testid =" project-full-path" >
285
- {{ project .fullPath }}
270
+ {{ item .fullPath }}
286
271
</div >
287
272
</div >
288
273
</div >
289
- </gl-dropdown-item >
290
- <gl-dropdown-item v-show =" noResultsAvailable" class =" gl-pointer-events-none text-secondary" >{{
291
- __('No matching results')
292
- }}</gl-dropdown-item >
293
- <gl-dropdown-item v-if =" loading" >
294
- <gl-loading-icon size =" lg" />
295
- </gl-dropdown-item >
296
- </gl-dropdown >
274
+ </template >
275
+ </gl-collapsible-listbox >
297
276
</template >
0 commit comments