@gitlab/ui 38.0.1 → 38.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +1 -1
- package/dist/components/base/breadcrumb/breadcrumb.js +10 -5
- package/dist/components/base/{filtered_search/examples/filtered_search.single_unique.example.js → breadcrumb/breadcrumb_item.js} +32 -28
- package/dist/components/base/dropdown/dropdown.documentation.js +1 -5
- package/dist/components/base/dropdown/dropdown_item.documentation.js +2 -3
- package/dist/components/base/filtered_search/filtered_search.documentation.js +2 -66
- package/dist/components/base/filtered_search/filtered_search.js +51 -20
- package/dist/components/base/filtered_search/filtered_search_suggestion.documentation.js +2 -8
- package/dist/components/base/filtered_search/filtered_search_suggestion.js +5 -1
- package/dist/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +2 -7
- package/dist/components/base/filtered_search/filtered_search_suggestion_list.js +4 -0
- package/dist/components/base/filtered_search/filtered_search_term.documentation.js +2 -44
- package/dist/components/base/filtered_search/filtered_search_term.js +37 -0
- package/dist/components/base/filtered_search/filtered_search_token.documentation.js +2 -31
- package/dist/components/base/filtered_search/filtered_search_token.js +80 -23
- package/dist/components/base/filtered_search/filtered_search_token_segment.documentation.js +2 -46
- package/dist/components/base/filtered_search/filtered_search_token_segment.js +48 -0
- package/dist/components/base/filtered_search/filtered_search_utils.js +42 -9
- package/dist/components/base/form/form_checkbox_tree/form_checkbox_tree.documentation.js +2 -27
- package/dist/components/base/form/form_checkbox_tree/form_checkbox_tree.js +16 -1
- package/dist/components/charts/series_label/series_label.js +6 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/utils/use_mock_intersection_observer.js +2 -2
- package/documentation/components_documentation.js +0 -4
- package/documentation/documented_stories.js +10 -1
- package/package.json +11 -9
- package/src/components/base/avatar_link/avatar_link.stories.js +2 -2
- package/src/components/base/breadcrumb/breadcrumb.spec.js +24 -10
- package/src/components/base/breadcrumb/breadcrumb.vue +11 -6
- package/src/components/base/breadcrumb/breadcrumb_item.spec.js +45 -0
- package/src/components/base/breadcrumb/breadcrumb_item.vue +43 -0
- package/src/components/base/dropdown/dropdown.documentation.js +0 -3
- package/src/components/base/dropdown/dropdown.md +7 -2
- package/src/components/base/dropdown/dropdown.stories.js +487 -439
- package/src/components/base/dropdown/dropdown_item.documentation.js +0 -1
- package/src/components/base/dropdown/dropdown_item.md +0 -6
- package/src/components/base/dropdown/dropdown_item.stories.js +107 -35
- package/src/components/base/filtered_search/filtered_search.documentation.js +0 -76
- package/src/components/base/filtered_search/filtered_search.md +3 -4
- package/src/components/base/filtered_search/filtered_search.spec.js +37 -12
- package/src/components/base/filtered_search/filtered_search.stories.js +260 -17
- package/src/components/base/filtered_search/filtered_search.vue +57 -14
- package/src/components/base/filtered_search/filtered_search_suggestion.documentation.js +0 -6
- package/src/components/base/filtered_search/filtered_search_suggestion.md +1 -7
- package/src/components/base/filtered_search/filtered_search_suggestion.stories.js +26 -18
- package/src/components/base/filtered_search/filtered_search_suggestion.vue +6 -0
- package/src/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +0 -5
- package/src/components/base/filtered_search/filtered_search_suggestion_list.md +1 -7
- package/src/components/base/filtered_search/filtered_search_suggestion_list.stories.js +33 -25
- package/src/components/base/filtered_search/filtered_search_suggestion_list.vue +5 -0
- package/src/components/base/filtered_search/filtered_search_term.documentation.js +0 -41
- package/src/components/base/filtered_search/filtered_search_term.md +0 -2
- package/src/components/base/filtered_search/filtered_search_term.stories.js +33 -26
- package/src/components/base/filtered_search/filtered_search_term.vue +54 -0
- package/src/components/base/filtered_search/filtered_search_token.documentation.js +0 -26
- package/src/components/base/filtered_search/filtered_search_token.md +1 -3
- package/src/components/base/filtered_search/filtered_search_token.spec.js +31 -1
- package/src/components/base/filtered_search/filtered_search_token.stories.js +137 -132
- package/src/components/base/filtered_search/filtered_search_token.vue +93 -21
- package/src/components/base/filtered_search/filtered_search_token_segment.documentation.js +0 -43
- package/src/components/base/filtered_search/filtered_search_token_segment.md +0 -2
- package/src/components/base/filtered_search/filtered_search_token_segment.stories.js +86 -79
- package/src/components/base/filtered_search/filtered_search_token_segment.vue +42 -0
- package/src/components/base/filtered_search/filtered_search_utils.js +38 -5
- package/src/components/base/form/form.stories.js +2 -0
- package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.documentation.js +0 -26
- package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.md +0 -4
- package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.stories.js +123 -92
- package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.vue +13 -1
- package/src/components/base/form/form_radio/form_radio.spec.js +21 -8
- package/src/components/base/form/form_radio_group/form_radio_group.stories.js +2 -1
- package/src/components/base/markdown/markdown.scss +21 -0
- package/src/components/base/markdown/markdown_typescale_demo.html +17 -6
- package/src/components/base/navbar/navbar.stories.js +2 -1
- package/src/components/base/skeleton_loader/skeleton_loader.stories.js +67 -21
- package/src/components/base/tabs/tabs/tabs.stories.js +2 -2
- package/src/components/charts/series_label/series_label.stories.js +6 -3
- package/src/components/charts/series_label/series_label.vue +3 -0
- package/src/scss/typescale/typescale.md +0 -2
- package/src/scss/typescale/typescale.stories.js +17 -4
- package/src/utils/use_mock_intersection_observer.js +3 -3
- package/dist/components/base/dropdown/dropdown_divider.documentation.js +0 -8
- package/dist/components/base/dropdown/dropdown_form.documentation.js +0 -17
- package/dist/components/base/dropdown/dropdown_section_header.documentation.js +0 -8
- package/dist/components/base/dropdown/dropdown_text.documentation.js +0 -8
- package/dist/components/base/dropdown/examples/dropdown.default.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.links.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_avatar_and_secondary_text.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_checked_items.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_clear_all.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_divider.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_form.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_header.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_highlighted_items.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_icons.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_right_align.example.js +0 -38
- package/dist/components/base/dropdown/examples/dropdown.with_search.example.js +0 -67
- package/dist/components/base/dropdown/examples/dropdown.with_section_headers.example.js +0 -38
- package/dist/components/base/dropdown/examples/index.js +0 -85
- package/dist/components/base/filtered_search/examples/filtered_search.default.example.js +0 -422
- package/dist/components/base/filtered_search/examples/filtered_search.friendly.example.js +0 -423
- package/dist/components/base/filtered_search/examples/filtered_search.history.example.js +0 -91
- package/dist/components/base/filtered_search/examples/filtered_search.multi_select.example.js +0 -196
- package/dist/components/base/filtered_search/examples/index.js +0 -32
- package/dist/components/base/form/form_checkbox_tree/examples/form_checkbox_tree.basic.example.js +0 -103
- package/dist/components/base/form/form_checkbox_tree/examples/index.js +0 -13
- package/src/components/base/dropdown/dropdown_divider.documentation.js +0 -6
- package/src/components/base/dropdown/dropdown_divider.md +0 -7
- package/src/components/base/dropdown/dropdown_divider.stories.js +0 -16
- package/src/components/base/dropdown/dropdown_form.documentation.js +0 -9
- package/src/components/base/dropdown/dropdown_form.md +0 -4
- package/src/components/base/dropdown/dropdown_form.stories.js +0 -17
- package/src/components/base/dropdown/dropdown_section_header.documentation.js +0 -6
- package/src/components/base/dropdown/dropdown_section_header.stories.js +0 -17
- package/src/components/base/dropdown/dropdown_text.documentation.js +0 -6
- package/src/components/base/dropdown/dropdown_text.stories.js +0 -16
- package/src/components/base/dropdown/examples/dropdown.default.example.vue +0 -7
- package/src/components/base/dropdown/examples/dropdown.links.example.vue +0 -7
- package/src/components/base/dropdown/examples/dropdown.with_avatar_and_secondary_text.example.vue +0 -7
- package/src/components/base/dropdown/examples/dropdown.with_checked_items.example.vue +0 -6
- package/src/components/base/dropdown/examples/dropdown.with_clear_all.example.vue +0 -7
- package/src/components/base/dropdown/examples/dropdown.with_divider.example.vue +0 -9
- package/src/components/base/dropdown/examples/dropdown.with_form.example.vue +0 -10
- package/src/components/base/dropdown/examples/dropdown.with_header.example.vue +0 -7
- package/src/components/base/dropdown/examples/dropdown.with_highlighted_items.example.vue +0 -9
- package/src/components/base/dropdown/examples/dropdown.with_icons.example.vue +0 -43
- package/src/components/base/dropdown/examples/dropdown.with_right_align.example.vue +0 -7
- package/src/components/base/dropdown/examples/dropdown.with_search.example.vue +0 -38
- package/src/components/base/dropdown/examples/dropdown.with_section_headers.example.vue +0 -10
- package/src/components/base/dropdown/examples/index.js +0 -99
- package/src/components/base/filtered_search/examples/filtered_search.default.example.vue +0 -298
- package/src/components/base/filtered_search/examples/filtered_search.friendly.example.vue +0 -300
- package/src/components/base/filtered_search/examples/filtered_search.history.example.vue +0 -50
- package/src/components/base/filtered_search/examples/filtered_search.multi_select.example.vue +0 -132
- package/src/components/base/filtered_search/examples/filtered_search.single_unique.example.vue +0 -31
- package/src/components/base/filtered_search/examples/index.js +0 -38
- package/src/components/base/form/form_checkbox_tree/examples/form_checkbox_tree.basic.example.vue +0 -77
- package/src/components/base/form/form_checkbox_tree/examples/index.js +0 -15
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { withKnobs } from '@storybook/addon-knobs';
|
|
2
|
-
import { documentedStoriesOf } from '../../../../documentation/documented_stories';
|
|
3
1
|
import {
|
|
4
2
|
GlFilteredSearch,
|
|
5
3
|
GlFilteredSearchToken,
|
|
6
4
|
GlFilteredSearchSuggestion,
|
|
5
|
+
GlFilteredSearchSuggestionList,
|
|
6
|
+
GlFilteredSearchTerm,
|
|
7
|
+
GlFilteredSearchTokenSegment,
|
|
7
8
|
GlLoadingIcon,
|
|
9
|
+
GlIcon,
|
|
8
10
|
GlToken,
|
|
9
11
|
GlAvatar,
|
|
10
12
|
} from '../../../index';
|
|
11
13
|
import { setStoryTimeout } from '../../../utils/test_utils';
|
|
14
|
+
import { makeContainer } from '../../../utils/story_decorators/container';
|
|
12
15
|
import readme from './filtered_search.md';
|
|
13
16
|
|
|
14
17
|
const fakeUsers = [
|
|
@@ -54,14 +57,14 @@ const UserToken = {
|
|
|
54
57
|
setStoryTimeout(() => {
|
|
55
58
|
this.loadingView = false;
|
|
56
59
|
this.activeUser = fakeUsers.find((u) => u.username === this.value.data);
|
|
57
|
-
},
|
|
60
|
+
}, 500);
|
|
58
61
|
},
|
|
59
62
|
loadSuggestions() {
|
|
60
63
|
this.loadingSuggestions = true;
|
|
61
64
|
setStoryTimeout(() => {
|
|
62
65
|
this.loadingSuggestions = false;
|
|
63
66
|
this.users = fakeUsers;
|
|
64
|
-
},
|
|
67
|
+
}, 500);
|
|
65
68
|
},
|
|
66
69
|
},
|
|
67
70
|
watch: {
|
|
@@ -130,7 +133,7 @@ const MilestoneToken = {
|
|
|
130
133
|
setStoryTimeout(() => {
|
|
131
134
|
this.loadingSuggestions = false;
|
|
132
135
|
this.milestones = fakeMilestones;
|
|
133
|
-
},
|
|
136
|
+
}, 500);
|
|
134
137
|
},
|
|
135
138
|
},
|
|
136
139
|
watch: {
|
|
@@ -212,7 +215,7 @@ const LabelToken = {
|
|
|
212
215
|
setStoryTimeout(() => {
|
|
213
216
|
this.loadingSuggestions = false;
|
|
214
217
|
this.labels = fakeLabels;
|
|
215
|
-
},
|
|
218
|
+
}, 500);
|
|
216
219
|
},
|
|
217
220
|
},
|
|
218
221
|
watch: {
|
|
@@ -296,19 +299,259 @@ const components = {
|
|
|
296
299
|
GlFilteredSearch,
|
|
297
300
|
};
|
|
298
301
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
export const Default = () => ({
|
|
303
|
+
data() {
|
|
304
|
+
return {
|
|
305
|
+
tokens,
|
|
306
|
+
value: [
|
|
307
|
+
{ type: 'author', value: { data: 'beta', operator: '=' } },
|
|
308
|
+
{ type: 'label', value: { data: 'Bug', operator: '=' } },
|
|
309
|
+
'raw text',
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
},
|
|
313
|
+
components,
|
|
314
|
+
template: `<gl-filtered-search :available-tokens="tokens" :value="value" />`,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
export const WithHistoryItems = () => ({
|
|
318
|
+
components,
|
|
319
|
+
data() {
|
|
320
|
+
return {
|
|
321
|
+
tokens: [
|
|
322
|
+
{
|
|
323
|
+
type: 'demotoken',
|
|
324
|
+
title: 'Unique',
|
|
325
|
+
icon: 'document',
|
|
326
|
+
token: GlFilteredSearchToken,
|
|
327
|
+
operators: [{ value: '=', description: 'is', default: 'true' }],
|
|
328
|
+
options: [
|
|
329
|
+
{ icon: 'heart', title: 'heart', value: 1 },
|
|
330
|
+
{ icon: 'hook', title: 'hook', value: 2 },
|
|
331
|
+
],
|
|
332
|
+
unique: true,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
value: [],
|
|
336
|
+
historyItems: [
|
|
337
|
+
[{ type: 'demotoken', value: { operator: '=', data: 1 } }, 'item 1'],
|
|
338
|
+
['item 2', { type: 'demotoken', value: { operator: '=', data: 2 } }],
|
|
339
|
+
],
|
|
340
|
+
};
|
|
341
|
+
},
|
|
342
|
+
methods: {
|
|
343
|
+
isString(val) {
|
|
344
|
+
return typeof val === 'string';
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
mounted() {
|
|
348
|
+
this.$nextTick(() => this.$el.querySelector('.gl-dropdown-toggle').click());
|
|
349
|
+
},
|
|
350
|
+
template: `
|
|
351
|
+
<div>
|
|
352
|
+
{{ value }}
|
|
353
|
+
<gl-filtered-search v-model="value" :available-tokens="tokens" :history-items="historyItems">
|
|
354
|
+
<template #history-item="{ historyItem }">
|
|
355
|
+
<template v-for="(token, idx) in historyItem">
|
|
356
|
+
<span v-if="isString(token)" :key="idx" class="gl-px-1">{{ token }}</span>
|
|
357
|
+
<span v-else :key="idx" class="gl-px-1">
|
|
358
|
+
<strong>{{ token.type }}</strong> {{ token.value.operator }}
|
|
359
|
+
<strong>{{ token.value.data }}</strong>
|
|
360
|
+
</span>
|
|
361
|
+
</template>
|
|
362
|
+
</template>
|
|
363
|
+
</gl-filtered-search>
|
|
364
|
+
</div>
|
|
365
|
+
`,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
export const WithFriendlyText = () => ({
|
|
369
|
+
components,
|
|
370
|
+
data() {
|
|
371
|
+
return {
|
|
372
|
+
tokens: [
|
|
373
|
+
{
|
|
374
|
+
type: 'weight',
|
|
375
|
+
icon: 'weight',
|
|
376
|
+
title: 'Weight',
|
|
377
|
+
unique: true,
|
|
378
|
+
token: GlFilteredSearchToken,
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
type: 'confidential',
|
|
382
|
+
icon: 'eye-slash',
|
|
383
|
+
title: 'Confidential',
|
|
384
|
+
unique: true,
|
|
385
|
+
token: GlFilteredSearchToken,
|
|
386
|
+
options: [
|
|
387
|
+
{ icon: 'eye-slash', value: 'Yes', title: 'Yes' },
|
|
388
|
+
{ icon: 'eye', value: 'No', title: 'No' },
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
value: [
|
|
393
|
+
{ type: 'weight', value: { data: '3', operator: '=' } },
|
|
394
|
+
{ type: 'confidential', value: { data: 'Yes', operator: '!=' } },
|
|
395
|
+
],
|
|
396
|
+
};
|
|
397
|
+
},
|
|
398
|
+
template: `
|
|
399
|
+
<gl-filtered-search
|
|
400
|
+
v-model="value"
|
|
401
|
+
:available-tokens="tokens"
|
|
402
|
+
:show-friendly-text="true"
|
|
403
|
+
/>
|
|
404
|
+
`,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
export const WithMultiSelect = () => {
|
|
408
|
+
const MultiUserToken = {
|
|
409
|
+
props: ['value', 'active', 'config'],
|
|
410
|
+
components: {
|
|
411
|
+
GlFilteredSearchToken,
|
|
412
|
+
GlFilteredSearchSuggestion,
|
|
413
|
+
GlLoadingIcon,
|
|
414
|
+
GlIcon,
|
|
415
|
+
GlAvatar,
|
|
416
|
+
},
|
|
417
|
+
inheritAttrs: false,
|
|
302
418
|
data() {
|
|
303
419
|
return {
|
|
304
|
-
|
|
305
|
-
value: [
|
|
306
|
-
|
|
307
|
-
{ type: 'label', value: { data: 'Bug', operator: '=' } },
|
|
308
|
-
'raw text',
|
|
309
|
-
],
|
|
420
|
+
users: fakeUsers,
|
|
421
|
+
selectedUsernames: this.value.data ? this.value.data.split(',') : [],
|
|
422
|
+
activeUser: null,
|
|
310
423
|
};
|
|
311
424
|
},
|
|
425
|
+
computed: {
|
|
426
|
+
filteredUsers() {
|
|
427
|
+
return this.users.filter((user) => user.username.includes(this.value.data));
|
|
428
|
+
},
|
|
429
|
+
selectedUsers() {
|
|
430
|
+
return this.config.multiSelect
|
|
431
|
+
? this.users.filter((user) => this.selectedUsernames.includes(user.username))
|
|
432
|
+
: this.users.filter((user) => user.username === this.activeUser);
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
methods: {
|
|
436
|
+
loadView() {
|
|
437
|
+
this.activeUser = fakeUsers.find((u) => u.username === this.value.data);
|
|
438
|
+
},
|
|
439
|
+
loadSuggestions() {
|
|
440
|
+
this.users = fakeUsers;
|
|
441
|
+
},
|
|
442
|
+
handleSelect(username) {
|
|
443
|
+
if (!this.config.multiSelect) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (this.selectedUsernames.includes(username)) {
|
|
448
|
+
this.selectedUsernames = this.selectedUsernames.filter((user) => user !== username);
|
|
449
|
+
} else {
|
|
450
|
+
this.selectedUsernames.push(username);
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
isLastUser(index) {
|
|
454
|
+
return index === this.selectedUsers.length - 1;
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
watch: {
|
|
458
|
+
// eslint-disable-next-line func-names
|
|
459
|
+
'value.data': function () {
|
|
460
|
+
if (this.active) {
|
|
461
|
+
this.loadSuggestions();
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
active: {
|
|
465
|
+
immediate: true,
|
|
466
|
+
handler(newValue) {
|
|
467
|
+
if (!newValue) {
|
|
468
|
+
this.loadView();
|
|
469
|
+
} else {
|
|
470
|
+
this.loadSuggestions();
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
template: `
|
|
476
|
+
<gl-filtered-search-token
|
|
477
|
+
v-bind="{ ...this.$props, ...this.$attrs }"
|
|
478
|
+
v-on="$listeners"
|
|
479
|
+
:multi-select-values="selectedUsernames"
|
|
480
|
+
@select="handleSelect"
|
|
481
|
+
>
|
|
482
|
+
<template #view="{ inputValue }">
|
|
483
|
+
<template v-for="(user, index) in selectedUsers">
|
|
484
|
+
<gl-avatar :size="16" :entity-name="user.username" shape="circle" />
|
|
485
|
+
{{ user.name }}
|
|
486
|
+
<span v-if="!isLastUser(index)" class="gl-mx-2">, </span>
|
|
487
|
+
</template>
|
|
488
|
+
</template>
|
|
489
|
+
<template #suggestions>
|
|
490
|
+
<gl-filtered-search-suggestion :key="user.id" v-for="user in filteredUsers" :value="user.username">
|
|
491
|
+
<div class="gl-display-flex gl-align-items-center">
|
|
492
|
+
<gl-icon
|
|
493
|
+
v-if="config.multiSelect"
|
|
494
|
+
name="check"
|
|
495
|
+
class="gl-mr-3 gl-text-gray-700"
|
|
496
|
+
:class="{ 'gl-visibility-hidden': !selectedUsernames.includes(user.username) }"
|
|
497
|
+
/>
|
|
498
|
+
<gl-avatar :size="32" :entity-name="user.username" />
|
|
499
|
+
<div>
|
|
500
|
+
<p class="gl-m-0">{{ user.name }}</p>
|
|
501
|
+
<p class="gl-m-0">@{{ user.username }}</p>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
</gl-filtered-search-suggestion>
|
|
505
|
+
</template>
|
|
506
|
+
</gl-filtered-search-token>
|
|
507
|
+
`,
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
return {
|
|
312
511
|
components,
|
|
313
|
-
|
|
314
|
-
|
|
512
|
+
data() {
|
|
513
|
+
return {
|
|
514
|
+
tokens: [
|
|
515
|
+
{
|
|
516
|
+
type: 'assignee',
|
|
517
|
+
icon: 'user',
|
|
518
|
+
title: 'Assignee',
|
|
519
|
+
token: MultiUserToken,
|
|
520
|
+
operators: [
|
|
521
|
+
{ value: '=', description: 'is', default: 'true' },
|
|
522
|
+
{ value: '!=', description: 'is not one of' },
|
|
523
|
+
{ value: '||', description: 'is one of' },
|
|
524
|
+
],
|
|
525
|
+
multiSelect: true,
|
|
526
|
+
},
|
|
527
|
+
],
|
|
528
|
+
value: [{ type: 'assignee', value: { data: 'alpha,beta', operator: '=' } }],
|
|
529
|
+
};
|
|
530
|
+
},
|
|
531
|
+
template: `
|
|
532
|
+
<gl-filtered-search v-model="value" :available-tokens="tokens" />
|
|
533
|
+
`,
|
|
534
|
+
};
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
export default {
|
|
538
|
+
title: 'base/filtered-search',
|
|
539
|
+
// Make room for suggestion lists
|
|
540
|
+
decorators: [makeContainer({ height: '250px' })],
|
|
541
|
+
component: GlFilteredSearch,
|
|
542
|
+
subcomponents: {
|
|
543
|
+
GlFilteredSearchSuggestion,
|
|
544
|
+
GlFilteredSearchSuggestionList,
|
|
545
|
+
GlFilteredSearchTerm,
|
|
546
|
+
GlFilteredSearchTokenSegment,
|
|
547
|
+
GlFilteredSearchToken,
|
|
548
|
+
},
|
|
549
|
+
parameters: {
|
|
550
|
+
docs: {
|
|
551
|
+
description: {
|
|
552
|
+
component: readme,
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
argTypes: {},
|
|
557
|
+
};
|
|
@@ -8,8 +8,9 @@ import GlSearchBoxByClick from '../search_box_by_click/search_box_by_click.vue';
|
|
|
8
8
|
import GlFilteredSearchTerm from './filtered_search_term.vue';
|
|
9
9
|
import {
|
|
10
10
|
isEmptyTerm,
|
|
11
|
-
TERM_TOKEN_TYPE,
|
|
12
11
|
INTENT_ACTIVATE_PREVIOUS,
|
|
12
|
+
createTerm,
|
|
13
|
+
ensureTokenId,
|
|
13
14
|
normalizeTokens,
|
|
14
15
|
denormalizeTokens,
|
|
15
16
|
needDenormalization,
|
|
@@ -19,18 +20,12 @@ Vue.use(PortalVue);
|
|
|
19
20
|
|
|
20
21
|
let portalUuid = 0;
|
|
21
22
|
|
|
22
|
-
function createTerm(data = '') {
|
|
23
|
-
return {
|
|
24
|
-
type: TERM_TOKEN_TYPE,
|
|
25
|
-
value: { data },
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
function initialState() {
|
|
30
24
|
return [createTerm()];
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
export default {
|
|
28
|
+
name: 'GlFilteredSearch',
|
|
34
29
|
components: {
|
|
35
30
|
GlSearchBoxByClick,
|
|
36
31
|
GlIcon,
|
|
@@ -51,16 +46,25 @@ export default {
|
|
|
51
46
|
},
|
|
52
47
|
inheritAttrs: false,
|
|
53
48
|
props: {
|
|
49
|
+
/**
|
|
50
|
+
* If provided, used as value of filtered search
|
|
51
|
+
*/
|
|
54
52
|
value: {
|
|
55
53
|
required: false,
|
|
56
54
|
type: Array,
|
|
57
55
|
default: () => [],
|
|
58
56
|
},
|
|
57
|
+
/**
|
|
58
|
+
* Available tokens
|
|
59
|
+
*/
|
|
59
60
|
availableTokens: {
|
|
60
61
|
type: Array,
|
|
61
62
|
required: false,
|
|
62
63
|
default: () => [],
|
|
63
64
|
},
|
|
65
|
+
/**
|
|
66
|
+
* If provided, used as history items for this component
|
|
67
|
+
*/
|
|
64
68
|
placeholder: {
|
|
65
69
|
type: String,
|
|
66
70
|
required: false,
|
|
@@ -76,21 +80,34 @@ export default {
|
|
|
76
80
|
required: false,
|
|
77
81
|
default: null,
|
|
78
82
|
},
|
|
83
|
+
/**
|
|
84
|
+
* Additional classes to add to the suggestion list menu. NOTE: this not reactive, and the value
|
|
85
|
+
* must be available and fixed when the component is instantiated
|
|
86
|
+
*/
|
|
79
87
|
suggestionsListClass: {
|
|
80
88
|
type: [String, Array, Object],
|
|
81
89
|
required: false,
|
|
82
90
|
default: null,
|
|
83
91
|
},
|
|
92
|
+
/**
|
|
93
|
+
* Display operators' descriptions instead of their values (e.g., "is" instead of "=").
|
|
94
|
+
*/
|
|
84
95
|
showFriendlyText: {
|
|
85
96
|
type: Boolean,
|
|
86
97
|
required: false,
|
|
87
98
|
default: false,
|
|
88
99
|
},
|
|
100
|
+
/**
|
|
101
|
+
* HTML attributes to add to the search button
|
|
102
|
+
*/
|
|
89
103
|
searchButtonAttributes: {
|
|
90
104
|
type: Object,
|
|
91
105
|
required: false,
|
|
92
106
|
default: () => ({}),
|
|
93
107
|
},
|
|
108
|
+
/**
|
|
109
|
+
* HTML attributes to add to the search input
|
|
110
|
+
*/
|
|
94
111
|
searchInputAttributes: {
|
|
95
112
|
type: Object,
|
|
96
113
|
required: false,
|
|
@@ -137,10 +154,21 @@ export default {
|
|
|
137
154
|
watch: {
|
|
138
155
|
tokens: {
|
|
139
156
|
handler() {
|
|
157
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
158
|
+
const invalidToken = this.tokens.find((token) => !token.id);
|
|
159
|
+
if (invalidToken) {
|
|
160
|
+
throw new Error(`Token does not have an id:\n${JSON.stringify(invalidToken)}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
140
164
|
if (this.tokens.length === 0 || !this.isLastTokenEmpty()) {
|
|
141
165
|
this.tokens.push(createTerm());
|
|
142
166
|
}
|
|
143
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Emitted when the tokens (value) changes
|
|
170
|
+
* @property {array} tokens
|
|
171
|
+
*/
|
|
144
172
|
this.$emit('input', this.tokens);
|
|
145
173
|
},
|
|
146
174
|
deep: true,
|
|
@@ -228,7 +256,7 @@ export default {
|
|
|
228
256
|
},
|
|
229
257
|
|
|
230
258
|
replaceToken(idx, token) {
|
|
231
|
-
this.$set(this.tokens, idx, { ...token, value: { data: '', ...token.value } });
|
|
259
|
+
this.$set(this.tokens, idx, ensureTokenId({ ...token, value: { data: '', ...token.value } }));
|
|
232
260
|
this.activeTokenIdx = idx;
|
|
233
261
|
},
|
|
234
262
|
|
|
@@ -242,10 +270,7 @@ export default {
|
|
|
242
270
|
return;
|
|
243
271
|
}
|
|
244
272
|
|
|
245
|
-
const newTokens = newStrings.map((data) => (
|
|
246
|
-
type: TERM_TOKEN_TYPE,
|
|
247
|
-
value: { data },
|
|
248
|
-
}));
|
|
273
|
+
const newTokens = newStrings.map((data) => createTerm(data));
|
|
249
274
|
|
|
250
275
|
this.tokens.splice(idx + 1, 0, ...newTokens);
|
|
251
276
|
|
|
@@ -261,6 +286,10 @@ export default {
|
|
|
261
286
|
},
|
|
262
287
|
|
|
263
288
|
submit() {
|
|
289
|
+
/**
|
|
290
|
+
* Emitted when search is submitted
|
|
291
|
+
* @property {array} tokens
|
|
292
|
+
*/
|
|
264
293
|
this.$emit('submit', normalizeTokens(cloneDeep(this.tokens)));
|
|
265
294
|
},
|
|
266
295
|
|
|
@@ -273,6 +302,19 @@ export default {
|
|
|
273
302
|
</script>
|
|
274
303
|
|
|
275
304
|
<template>
|
|
305
|
+
<!--
|
|
306
|
+
Emitted when search is cleared
|
|
307
|
+
@event clear
|
|
308
|
+
-->
|
|
309
|
+
<!--
|
|
310
|
+
Emitted when item from history is selected
|
|
311
|
+
@event history-item-selected
|
|
312
|
+
@property {object} value History item
|
|
313
|
+
-->
|
|
314
|
+
<!--
|
|
315
|
+
Emitted when clear history button is clicked
|
|
316
|
+
@event clear-history
|
|
317
|
+
-->
|
|
276
318
|
<gl-search-box-by-click
|
|
277
319
|
v-bind="$attrs"
|
|
278
320
|
:value="tokens"
|
|
@@ -287,6 +329,7 @@ export default {
|
|
|
287
329
|
@clear-history="$emit('clear-history')"
|
|
288
330
|
>
|
|
289
331
|
<template #history-item="slotScope">
|
|
332
|
+
<!-- @slot Slot to customize history item in history dropdown. Used only if using history items -->
|
|
290
333
|
<slot name="history-item" v-bind="slotScope"></slot>
|
|
291
334
|
</template>
|
|
292
335
|
<template #input>
|
|
@@ -295,7 +338,7 @@ export default {
|
|
|
295
338
|
<component
|
|
296
339
|
:is="getTokenComponent(token.type)"
|
|
297
340
|
ref="tokens"
|
|
298
|
-
:key="
|
|
341
|
+
:key="token.id"
|
|
299
342
|
v-model="token.value"
|
|
300
343
|
:config="getTokenEntry(token.type)"
|
|
301
344
|
:active="activeTokenIdx === idx"
|
|
@@ -2,10 +2,4 @@ import description from './filtered_search_suggestion.md';
|
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
4
|
description,
|
|
5
|
-
bootstrapComponent: 'b-dropdown-item',
|
|
6
|
-
propsInfo: {
|
|
7
|
-
value: {
|
|
8
|
-
additionalInfo: 'Value which will be emitted if this suggestion will be selected in list',
|
|
9
|
-
},
|
|
10
|
-
},
|
|
11
5
|
};
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
# Filtered Search Suggestion
|
|
2
|
-
|
|
3
|
-
<!-- STORY -->
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
1
|
The filtered search suggestion component is a wrapper around `GlDropdownItem`, which registers
|
|
8
2
|
suggestions in a top-level suggestion list:
|
|
9
3
|
|
|
10
|
-
```
|
|
4
|
+
```html
|
|
11
5
|
<gl-filtered-search-suggestion-list>
|
|
12
6
|
<gl-filtered-search-suggestion value="foo">Example suggestion</gl-filtered-search-suggestion>
|
|
13
7
|
<gl-filtered-search-suggestion value="bar">Example suggestion 2</gl-filtered-search-suggestion>
|
|
@@ -1,25 +1,33 @@
|
|
|
1
|
-
import { withKnobs } from '@storybook/addon-knobs';
|
|
2
1
|
import { GlFilteredSearchSuggestion } from '../../../index';
|
|
3
|
-
import { documentedStoriesOf } from '../../../../documentation/documented_stories';
|
|
4
2
|
import readme from './filtered_search_suggestion.md';
|
|
5
3
|
|
|
6
4
|
const noop = () => {};
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
export const Default = () => ({
|
|
7
|
+
components: { GlFilteredSearchSuggestion },
|
|
8
|
+
provide: {
|
|
9
|
+
filteredSearchSuggestionListInstance: {
|
|
10
|
+
register: noop,
|
|
11
|
+
unregister: noop,
|
|
12
|
+
$emit: noop,
|
|
13
|
+
activeItem: null,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
template: `
|
|
17
|
+
<ul>
|
|
18
|
+
<gl-filtered-search-suggestion value="demo">Demo suggestion</gl-filtered-search-suggestion>
|
|
19
|
+
</ul>
|
|
20
|
+
`,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
title: 'base/filtered-search/suggestion',
|
|
25
|
+
component: GlFilteredSearchSuggestion,
|
|
26
|
+
parameters: {
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
component: readme,
|
|
18
30
|
},
|
|
19
31
|
},
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<gl-filtered-search-suggestion value="demo">Demo suggestion</gl-filtered-search-suggestion>
|
|
23
|
-
</ul>
|
|
24
|
-
`,
|
|
25
|
-
}));
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
import GlDropdownItem from '../dropdown/dropdown_item.vue';
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
|
+
name: 'GlFilteredSearchSuggestion',
|
|
5
6
|
components: {
|
|
6
7
|
GlDropdownItem,
|
|
7
8
|
},
|
|
8
9
|
inject: ['filteredSearchSuggestionListInstance'],
|
|
9
10
|
inheritAttrs: false,
|
|
10
11
|
props: {
|
|
12
|
+
/**
|
|
13
|
+
* Value that will be emitted if this suggestion is selected.
|
|
14
|
+
*/
|
|
11
15
|
value: {
|
|
12
16
|
required: true,
|
|
13
17
|
validator: () => true,
|
|
@@ -47,11 +51,13 @@ export default {
|
|
|
47
51
|
<gl-dropdown-item
|
|
48
52
|
ref="item"
|
|
49
53
|
class="gl-filtered-search-suggestion"
|
|
54
|
+
data-testid="filtered-search-suggestion"
|
|
50
55
|
:class="{ 'gl-filtered-search-suggestion-active': isActive }"
|
|
51
56
|
v-bind="$attrs"
|
|
52
57
|
href="#"
|
|
53
58
|
@mousedown.native.prevent="emitValue"
|
|
54
59
|
>
|
|
60
|
+
<!-- @slot The suggestion content. -->
|
|
55
61
|
<slot></slot>
|
|
56
62
|
</gl-dropdown-item>
|
|
57
63
|
</template>
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
# Filtered Search Suggestion
|
|
2
|
-
|
|
3
|
-
<!-- STORY -->
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
1
|
The filtered search suggestion list component is responsible for managing underlying suggestion instances.
|
|
8
2
|
You obtain the ref for this component and manage suggestion selection via the component public API:
|
|
9
3
|
|
|
@@ -11,7 +5,7 @@ You obtain the ref for this component and manage suggestion selection via the co
|
|
|
11
5
|
- `nextItem()` - Selects the next suggestion. If last suggestion was selected, selection is cleared.
|
|
12
6
|
- `prevItem()` - Selects the previous suggestion. If first suggestion was selected, selection is cleared.
|
|
13
7
|
|
|
14
|
-
```
|
|
8
|
+
```html
|
|
15
9
|
<gl-filtered-search-suggestion-list ref="suggestions">
|
|
16
10
|
<gl-filtered-search-suggestion value="foo">Example suggestion</gl-filtered-search-suggestion>
|
|
17
11
|
<gl-filtered-search-suggestion value="bar">Example suggestion 2</gl-filtered-search-suggestion>
|