@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.
Files changed (140) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +1 -1
  3. package/dist/components/base/breadcrumb/breadcrumb.js +10 -5
  4. package/dist/components/base/{filtered_search/examples/filtered_search.single_unique.example.js → breadcrumb/breadcrumb_item.js} +32 -28
  5. package/dist/components/base/dropdown/dropdown.documentation.js +1 -5
  6. package/dist/components/base/dropdown/dropdown_item.documentation.js +2 -3
  7. package/dist/components/base/filtered_search/filtered_search.documentation.js +2 -66
  8. package/dist/components/base/filtered_search/filtered_search.js +51 -20
  9. package/dist/components/base/filtered_search/filtered_search_suggestion.documentation.js +2 -8
  10. package/dist/components/base/filtered_search/filtered_search_suggestion.js +5 -1
  11. package/dist/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +2 -7
  12. package/dist/components/base/filtered_search/filtered_search_suggestion_list.js +4 -0
  13. package/dist/components/base/filtered_search/filtered_search_term.documentation.js +2 -44
  14. package/dist/components/base/filtered_search/filtered_search_term.js +37 -0
  15. package/dist/components/base/filtered_search/filtered_search_token.documentation.js +2 -31
  16. package/dist/components/base/filtered_search/filtered_search_token.js +80 -23
  17. package/dist/components/base/filtered_search/filtered_search_token_segment.documentation.js +2 -46
  18. package/dist/components/base/filtered_search/filtered_search_token_segment.js +48 -0
  19. package/dist/components/base/filtered_search/filtered_search_utils.js +42 -9
  20. package/dist/components/base/form/form_checkbox_tree/form_checkbox_tree.documentation.js +2 -27
  21. package/dist/components/base/form/form_checkbox_tree/form_checkbox_tree.js +16 -1
  22. package/dist/components/charts/series_label/series_label.js +6 -1
  23. package/dist/index.css +1 -1
  24. package/dist/index.css.map +1 -1
  25. package/dist/utils/use_mock_intersection_observer.js +2 -2
  26. package/documentation/components_documentation.js +0 -4
  27. package/documentation/documented_stories.js +10 -1
  28. package/package.json +11 -9
  29. package/src/components/base/avatar_link/avatar_link.stories.js +2 -2
  30. package/src/components/base/breadcrumb/breadcrumb.spec.js +24 -10
  31. package/src/components/base/breadcrumb/breadcrumb.vue +11 -6
  32. package/src/components/base/breadcrumb/breadcrumb_item.spec.js +45 -0
  33. package/src/components/base/breadcrumb/breadcrumb_item.vue +43 -0
  34. package/src/components/base/dropdown/dropdown.documentation.js +0 -3
  35. package/src/components/base/dropdown/dropdown.md +7 -2
  36. package/src/components/base/dropdown/dropdown.stories.js +487 -439
  37. package/src/components/base/dropdown/dropdown_item.documentation.js +0 -1
  38. package/src/components/base/dropdown/dropdown_item.md +0 -6
  39. package/src/components/base/dropdown/dropdown_item.stories.js +107 -35
  40. package/src/components/base/filtered_search/filtered_search.documentation.js +0 -76
  41. package/src/components/base/filtered_search/filtered_search.md +3 -4
  42. package/src/components/base/filtered_search/filtered_search.spec.js +37 -12
  43. package/src/components/base/filtered_search/filtered_search.stories.js +260 -17
  44. package/src/components/base/filtered_search/filtered_search.vue +57 -14
  45. package/src/components/base/filtered_search/filtered_search_suggestion.documentation.js +0 -6
  46. package/src/components/base/filtered_search/filtered_search_suggestion.md +1 -7
  47. package/src/components/base/filtered_search/filtered_search_suggestion.stories.js +26 -18
  48. package/src/components/base/filtered_search/filtered_search_suggestion.vue +6 -0
  49. package/src/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +0 -5
  50. package/src/components/base/filtered_search/filtered_search_suggestion_list.md +1 -7
  51. package/src/components/base/filtered_search/filtered_search_suggestion_list.stories.js +33 -25
  52. package/src/components/base/filtered_search/filtered_search_suggestion_list.vue +5 -0
  53. package/src/components/base/filtered_search/filtered_search_term.documentation.js +0 -41
  54. package/src/components/base/filtered_search/filtered_search_term.md +0 -2
  55. package/src/components/base/filtered_search/filtered_search_term.stories.js +33 -26
  56. package/src/components/base/filtered_search/filtered_search_term.vue +54 -0
  57. package/src/components/base/filtered_search/filtered_search_token.documentation.js +0 -26
  58. package/src/components/base/filtered_search/filtered_search_token.md +1 -3
  59. package/src/components/base/filtered_search/filtered_search_token.spec.js +31 -1
  60. package/src/components/base/filtered_search/filtered_search_token.stories.js +137 -132
  61. package/src/components/base/filtered_search/filtered_search_token.vue +93 -21
  62. package/src/components/base/filtered_search/filtered_search_token_segment.documentation.js +0 -43
  63. package/src/components/base/filtered_search/filtered_search_token_segment.md +0 -2
  64. package/src/components/base/filtered_search/filtered_search_token_segment.stories.js +86 -79
  65. package/src/components/base/filtered_search/filtered_search_token_segment.vue +42 -0
  66. package/src/components/base/filtered_search/filtered_search_utils.js +38 -5
  67. package/src/components/base/form/form.stories.js +2 -0
  68. package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.documentation.js +0 -26
  69. package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.md +0 -4
  70. package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.stories.js +123 -92
  71. package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.vue +13 -1
  72. package/src/components/base/form/form_radio/form_radio.spec.js +21 -8
  73. package/src/components/base/form/form_radio_group/form_radio_group.stories.js +2 -1
  74. package/src/components/base/markdown/markdown.scss +21 -0
  75. package/src/components/base/markdown/markdown_typescale_demo.html +17 -6
  76. package/src/components/base/navbar/navbar.stories.js +2 -1
  77. package/src/components/base/skeleton_loader/skeleton_loader.stories.js +67 -21
  78. package/src/components/base/tabs/tabs/tabs.stories.js +2 -2
  79. package/src/components/charts/series_label/series_label.stories.js +6 -3
  80. package/src/components/charts/series_label/series_label.vue +3 -0
  81. package/src/scss/typescale/typescale.md +0 -2
  82. package/src/scss/typescale/typescale.stories.js +17 -4
  83. package/src/utils/use_mock_intersection_observer.js +3 -3
  84. package/dist/components/base/dropdown/dropdown_divider.documentation.js +0 -8
  85. package/dist/components/base/dropdown/dropdown_form.documentation.js +0 -17
  86. package/dist/components/base/dropdown/dropdown_section_header.documentation.js +0 -8
  87. package/dist/components/base/dropdown/dropdown_text.documentation.js +0 -8
  88. package/dist/components/base/dropdown/examples/dropdown.default.example.js +0 -38
  89. package/dist/components/base/dropdown/examples/dropdown.links.example.js +0 -38
  90. package/dist/components/base/dropdown/examples/dropdown.with_avatar_and_secondary_text.example.js +0 -38
  91. package/dist/components/base/dropdown/examples/dropdown.with_checked_items.example.js +0 -38
  92. package/dist/components/base/dropdown/examples/dropdown.with_clear_all.example.js +0 -38
  93. package/dist/components/base/dropdown/examples/dropdown.with_divider.example.js +0 -38
  94. package/dist/components/base/dropdown/examples/dropdown.with_form.example.js +0 -38
  95. package/dist/components/base/dropdown/examples/dropdown.with_header.example.js +0 -38
  96. package/dist/components/base/dropdown/examples/dropdown.with_highlighted_items.example.js +0 -38
  97. package/dist/components/base/dropdown/examples/dropdown.with_icons.example.js +0 -38
  98. package/dist/components/base/dropdown/examples/dropdown.with_right_align.example.js +0 -38
  99. package/dist/components/base/dropdown/examples/dropdown.with_search.example.js +0 -67
  100. package/dist/components/base/dropdown/examples/dropdown.with_section_headers.example.js +0 -38
  101. package/dist/components/base/dropdown/examples/index.js +0 -85
  102. package/dist/components/base/filtered_search/examples/filtered_search.default.example.js +0 -422
  103. package/dist/components/base/filtered_search/examples/filtered_search.friendly.example.js +0 -423
  104. package/dist/components/base/filtered_search/examples/filtered_search.history.example.js +0 -91
  105. package/dist/components/base/filtered_search/examples/filtered_search.multi_select.example.js +0 -196
  106. package/dist/components/base/filtered_search/examples/index.js +0 -32
  107. package/dist/components/base/form/form_checkbox_tree/examples/form_checkbox_tree.basic.example.js +0 -103
  108. package/dist/components/base/form/form_checkbox_tree/examples/index.js +0 -13
  109. package/src/components/base/dropdown/dropdown_divider.documentation.js +0 -6
  110. package/src/components/base/dropdown/dropdown_divider.md +0 -7
  111. package/src/components/base/dropdown/dropdown_divider.stories.js +0 -16
  112. package/src/components/base/dropdown/dropdown_form.documentation.js +0 -9
  113. package/src/components/base/dropdown/dropdown_form.md +0 -4
  114. package/src/components/base/dropdown/dropdown_form.stories.js +0 -17
  115. package/src/components/base/dropdown/dropdown_section_header.documentation.js +0 -6
  116. package/src/components/base/dropdown/dropdown_section_header.stories.js +0 -17
  117. package/src/components/base/dropdown/dropdown_text.documentation.js +0 -6
  118. package/src/components/base/dropdown/dropdown_text.stories.js +0 -16
  119. package/src/components/base/dropdown/examples/dropdown.default.example.vue +0 -7
  120. package/src/components/base/dropdown/examples/dropdown.links.example.vue +0 -7
  121. package/src/components/base/dropdown/examples/dropdown.with_avatar_and_secondary_text.example.vue +0 -7
  122. package/src/components/base/dropdown/examples/dropdown.with_checked_items.example.vue +0 -6
  123. package/src/components/base/dropdown/examples/dropdown.with_clear_all.example.vue +0 -7
  124. package/src/components/base/dropdown/examples/dropdown.with_divider.example.vue +0 -9
  125. package/src/components/base/dropdown/examples/dropdown.with_form.example.vue +0 -10
  126. package/src/components/base/dropdown/examples/dropdown.with_header.example.vue +0 -7
  127. package/src/components/base/dropdown/examples/dropdown.with_highlighted_items.example.vue +0 -9
  128. package/src/components/base/dropdown/examples/dropdown.with_icons.example.vue +0 -43
  129. package/src/components/base/dropdown/examples/dropdown.with_right_align.example.vue +0 -7
  130. package/src/components/base/dropdown/examples/dropdown.with_search.example.vue +0 -38
  131. package/src/components/base/dropdown/examples/dropdown.with_section_headers.example.vue +0 -10
  132. package/src/components/base/dropdown/examples/index.js +0 -99
  133. package/src/components/base/filtered_search/examples/filtered_search.default.example.vue +0 -298
  134. package/src/components/base/filtered_search/examples/filtered_search.friendly.example.vue +0 -300
  135. package/src/components/base/filtered_search/examples/filtered_search.history.example.vue +0 -50
  136. package/src/components/base/filtered_search/examples/filtered_search.multi_select.example.vue +0 -132
  137. package/src/components/base/filtered_search/examples/filtered_search.single_unique.example.vue +0 -31
  138. package/src/components/base/filtered_search/examples/index.js +0 -38
  139. package/src/components/base/form/form_checkbox_tree/examples/form_checkbox_tree.basic.example.vue +0 -77
  140. 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
- }, 1000);
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
- }, 2000);
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
- }, 2000);
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
- }, 2000);
218
+ }, 500);
216
219
  },
217
220
  },
218
221
  watch: {
@@ -296,19 +299,259 @@ const components = {
296
299
  GlFilteredSearch,
297
300
  };
298
301
 
299
- documentedStoriesOf('base/filtered-search', readme)
300
- .addDecorator(withKnobs)
301
- .add('default', () => ({
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
- tokens,
305
- value: [
306
- { type: 'author', value: { data: 'beta', operator: '=' } },
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">,&nbsp;</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
- template: `<gl-filtered-search :available-tokens="tokens" :value="value" />`,
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="`${token.type}-${idx}`"
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
- ```js
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
- documentedStoriesOf('base/filtered-search/suggestion', readme)
9
- .addDecorator(withKnobs)
10
- .add('default', () => ({
11
- components: { GlFilteredSearchSuggestion },
12
- provide: {
13
- filteredSearchSuggestionListInstance: {
14
- register: noop,
15
- unregister: noop,
16
- $emit: noop,
17
- activeItem: null,
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
- template: `
21
- <ul>
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>
@@ -2,9 +2,4 @@ import description from './filtered_search_suggestion_list.md';
2
2
 
3
3
  export default {
4
4
  description,
5
- propsInfo: {
6
- initialValue: {
7
- additionalInfo: 'Value to be initially selected in list',
8
- },
9
- },
10
5
  };
@@ -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
- ```js
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>