@gitlab/ui 39.3.0 → 39.4.0

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 (254) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/components/base/modal/modal.js +14 -2
  3. package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +240 -0
  4. package/dist/components/base/new_dropdowns/constants.js +20 -0
  5. package/dist/components/base/new_dropdowns/listbox/listbox.js +381 -0
  6. package/dist/components/base/new_dropdowns/listbox/listbox_item.js +77 -0
  7. package/dist/index.css +1 -1
  8. package/dist/index.css.map +1 -1
  9. package/dist/index.js +2 -0
  10. package/dist/utility_classes.css +1 -1
  11. package/dist/utility_classes.css.map +1 -1
  12. package/dist/utils/utils.js +24 -1
  13. package/package.json +6 -12
  14. package/scss_to_js/scss_variables.js +1 -0
  15. package/scss_to_js/scss_variables.json +5 -0
  16. package/src/components/base/avatar_labeled/avatar_labeled.stories.js +2 -1
  17. package/src/components/base/avatar_link/avatar_link.stories.js +2 -3
  18. package/src/components/base/breadcrumb/breadcrumb.md +1 -1
  19. package/src/components/base/breadcrumb/breadcrumb.stories.js +2 -1
  20. package/src/components/base/broadcast_message/broadcast_message.scss +1 -1
  21. package/src/components/base/button/button.scss +1 -1
  22. package/src/components/base/dropdown/dropdown.scss +10 -3
  23. package/src/components/base/dropdown/dropdown_item.scss +1 -0
  24. package/src/components/base/modal/modal.spec.js +20 -0
  25. package/src/components/base/modal/modal.vue +14 -1
  26. package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.spec.js +171 -0
  27. package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +221 -0
  28. package/src/components/base/new_dropdowns/constants.js +22 -0
  29. package/src/components/base/new_dropdowns/listbox/listbox.md +71 -0
  30. package/src/components/base/new_dropdowns/listbox/listbox.spec.js +236 -0
  31. package/src/components/base/new_dropdowns/listbox/listbox.stories.js +276 -0
  32. package/src/components/base/new_dropdowns/listbox/listbox.vue +348 -0
  33. package/src/components/base/new_dropdowns/listbox/listbox_item.spec.js +104 -0
  34. package/src/components/base/new_dropdowns/listbox/listbox_item.vue +59 -0
  35. package/src/index.js +4 -0
  36. package/src/scss/utilities.scss +8 -0
  37. package/src/scss/utility-mixins/color.scss +4 -0
  38. package/src/scss/variables.scss +1 -0
  39. package/src/utils/utils.js +18 -0
  40. package/src/utils/utils.spec.js +41 -1
  41. package/dist/components/base/accordion/accordion.documentation.js +0 -8
  42. package/dist/components/base/accordion/accordion_item.documentation.js +0 -7
  43. package/dist/components/base/alert/alert.documentation.js +0 -13
  44. package/dist/components/base/avatar/avatar.documentation.js +0 -8
  45. package/dist/components/base/avatar_labeled/avatar_labeled.documentation.js +0 -8
  46. package/dist/components/base/avatar_link/avatar_link.documentation.js +0 -8
  47. package/dist/components/base/avatars_inline/avatars_inline.documentation.js +0 -13
  48. package/dist/components/base/badge/badge.documentation.js +0 -8
  49. package/dist/components/base/banner/banner.documentation.js +0 -8
  50. package/dist/components/base/breadcrumb/breadcrumb.documentation.js +0 -8
  51. package/dist/components/base/broadcast_message/broadcast_message.documentation.js +0 -8
  52. package/dist/components/base/button/button.documentation.js +0 -24
  53. package/dist/components/base/button_group/button_group.documentation.js +0 -8
  54. package/dist/components/base/card/card.documentation.js +0 -13
  55. package/dist/components/base/carousel/carousel.documentation.js +0 -8
  56. package/dist/components/base/collapse/collapse.documentation.js +0 -7
  57. package/dist/components/base/datepicker/datepicker.documentation.js +0 -7
  58. package/dist/components/base/daterange_picker/daterange_picker.documentation.js +0 -8
  59. package/dist/components/base/drawer/drawer.documentation.js +0 -8
  60. package/dist/components/base/dropdown/dropdown.documentation.js +0 -8
  61. package/dist/components/base/dropdown/dropdown_item.documentation.js +0 -7
  62. package/dist/components/base/filtered_search/filtered_search.documentation.js +0 -13
  63. package/dist/components/base/filtered_search/filtered_search_suggestion.documentation.js +0 -7
  64. package/dist/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +0 -7
  65. package/dist/components/base/filtered_search/filtered_search_term.documentation.js +0 -12
  66. package/dist/components/base/filtered_search/filtered_search_token.documentation.js +0 -12
  67. package/dist/components/base/filtered_search/filtered_search_token_segment.documentation.js +0 -12
  68. package/dist/components/base/form/form.documentation.js +0 -7
  69. package/dist/components/base/form/form_checkbox/form_checkbox.documentation.js +0 -8
  70. package/dist/components/base/form/form_checkbox_tree/form_checkbox_tree.documentation.js +0 -12
  71. package/dist/components/base/form/form_combobox/form_combobox.documentation.js +0 -13
  72. package/dist/components/base/form/form_group/form_group.documentation.js +0 -7
  73. package/dist/components/base/form/form_input/form_input.documentation.js +0 -8
  74. package/dist/components/base/form/form_input_group/form_input_group.documentation.js +0 -8
  75. package/dist/components/base/form/form_radio/form_radio.documentation.js +0 -8
  76. package/dist/components/base/form/form_radio_group/form_radio_group.documentation.js +0 -8
  77. package/dist/components/base/form/form_select/form_select.documentation.js +0 -8
  78. package/dist/components/base/form/form_text/form_text.documentation.js +0 -14
  79. package/dist/components/base/form/form_textarea/form_textarea.documentation.js +0 -7
  80. package/dist/components/base/form/input_group_text/input_group_text.documentation.js +0 -12
  81. package/dist/components/base/icon/icon.documentation.js +0 -8
  82. package/dist/components/base/infinite_scroll/examples/index.js +0 -49
  83. package/dist/components/base/infinite_scroll/examples/infinite_scroll.all_items.example.js +0 -49
  84. package/dist/components/base/infinite_scroll/examples/infinite_scroll.basic.example.js +0 -62
  85. package/dist/components/base/infinite_scroll/examples/infinite_scroll.finite_total_items.example.js +0 -72
  86. package/dist/components/base/infinite_scroll/examples/infinite_scroll.large_fetched_items.example.js +0 -62
  87. package/dist/components/base/infinite_scroll/examples/infinite_scroll.reverse.example.js +0 -63
  88. package/dist/components/base/infinite_scroll/examples/infinite_scroll.small_fetched_items.example.js +0 -62
  89. package/dist/components/base/infinite_scroll/examples/infinite_scroll.two_way.example.js +0 -94
  90. package/dist/components/base/infinite_scroll/infinite_scroll.documentation.js +0 -8
  91. package/dist/components/base/keyset_pagination/keyset_pagination.documentation.js +0 -13
  92. package/dist/components/base/label/label.documentation.js +0 -8
  93. package/dist/components/base/link/link.documentation.js +0 -7
  94. package/dist/components/base/loading_icon/loading_icon.documentation.js +0 -8
  95. package/dist/components/base/markdown/markdown.documentation.js +0 -12
  96. package/dist/components/base/modal/modal.documentation.js +0 -8
  97. package/dist/components/base/nav/nav.documentation.js +0 -12
  98. package/dist/components/base/navbar/navbar.documentation.js +0 -12
  99. package/dist/components/base/paginated_list/paginated_list.documentation.js +0 -7
  100. package/dist/components/base/pagination/pagination.documentation.js +0 -8
  101. package/dist/components/base/path/path.documentation.js +0 -8
  102. package/dist/components/base/popover/popover.documentation.js +0 -5
  103. package/dist/components/base/search_box_by_click/search_box_by_click.documentation.js +0 -8
  104. package/dist/components/base/search_box_by_type/search_box_by_type.documentation.js +0 -8
  105. package/dist/components/base/segmented_control/segmented_control.documentation.js +0 -8
  106. package/dist/components/base/skeleton_loader/skeleton_loader.documentation.js +0 -13
  107. package/dist/components/base/skeleton_loading/skeleton_loading.documentation.js +0 -7
  108. package/dist/components/base/sorting/sorting.documentation.js +0 -7
  109. package/dist/components/base/sorting/sorting_item.documentation.js +0 -8
  110. package/dist/components/base/table/table.documentation.js +0 -8
  111. package/dist/components/base/table_lite/table_lite.documentation.js +0 -13
  112. package/dist/components/base/tabs/tabs/tabs.documentation.js +0 -8
  113. package/dist/components/base/toast/toast.documentation.js +0 -8
  114. package/dist/components/base/toggle/toggle.documentation.js +0 -13
  115. package/dist/components/base/token/token.documentation.js +0 -5
  116. package/dist/components/base/token_selector/token_selector.documentation.js +0 -12
  117. package/dist/components/base/tooltip/tooltip.documentation.js +0 -8
  118. package/dist/components/charts/area/area.documentation.js +0 -5
  119. package/dist/components/charts/bar/bar.documentation.js +0 -8
  120. package/dist/components/charts/chart/chart.documentation.js +0 -7
  121. package/dist/components/charts/column/column.documentation.js +0 -5
  122. package/dist/components/charts/discrete_scatter/discrete_scatter.documentation.js +0 -5
  123. package/dist/components/charts/gauge/gauge.documentation.js +0 -12
  124. package/dist/components/charts/heatmap/heatmap.documentation.js +0 -8
  125. package/dist/components/charts/line/line.documentation.js +0 -8
  126. package/dist/components/charts/series_label/series_label.documentation.js +0 -7
  127. package/dist/components/charts/single_stat/single_stat.documentation.js +0 -7
  128. package/dist/components/charts/sparkline/sparkline.documentation.js +0 -8
  129. package/dist/components/charts/stacked_column/stacked_column.documentation.js +0 -8
  130. package/dist/components/charts/tooltip/tooltip.documentation.js +0 -8
  131. package/dist/components/editors/rich_text_editor/rich_text_editor.documentation.js +0 -12
  132. package/dist/components/regions/dashboard_skeleton/dashboard_skeleton.documentation.js +0 -7
  133. package/dist/components/regions/empty_state/empty_state.documentation.js +0 -7
  134. package/dist/components/utilities/animated_number/animated_number.documentation.js +0 -13
  135. package/dist/components/utilities/friendly_wrap/friendly_wrap.documentation.js +0 -7
  136. package/dist/components/utilities/intersection_observer/intersection_observer.documentation.js +0 -12
  137. package/dist/components/utilities/intersperse/intersperse.documentation.js +0 -8
  138. package/dist/components/utilities/sprintf/sprintf.documentation.js +0 -8
  139. package/dist/components/utilities/truncate/truncate.documentation.js +0 -7
  140. package/dist/directives/hover_load/hover_load.documentation.js +0 -13
  141. package/dist/directives/outside/outside.documentation.js +0 -13
  142. package/dist/directives/resize_observer/resize_observer.documentation.js +0 -8
  143. package/dist/directives/safe_html/safe_html.documentation.js +0 -8
  144. package/dist/directives/safe_link/safe_link.documentation.js +0 -8
  145. package/documentation/all_components.js +0 -8
  146. package/documentation/components/component_documentation_generator.vue +0 -321
  147. package/documentation/components/example_display.vue +0 -231
  148. package/documentation/components/example_explorer.vue +0 -63
  149. package/documentation/components_documentation.js +0 -111
  150. package/documentation/index.js +0 -8
  151. package/src/components/base/accordion/accordion.documentation.js +0 -6
  152. package/src/components/base/accordion/accordion_item.documentation.js +0 -5
  153. package/src/components/base/alert/alert.documentation.js +0 -6
  154. package/src/components/base/avatar/avatar.documentation.js +0 -6
  155. package/src/components/base/avatar_labeled/avatar_labeled.documentation.js +0 -6
  156. package/src/components/base/avatar_link/avatar_link.documentation.js +0 -6
  157. package/src/components/base/avatars_inline/avatars_inline.documentation.js +0 -6
  158. package/src/components/base/badge/badge.documentation.js +0 -6
  159. package/src/components/base/banner/banner.documentation.js +0 -6
  160. package/src/components/base/breadcrumb/breadcrumb.documentation.js +0 -6
  161. package/src/components/base/broadcast_message/broadcast_message.documentation.js +0 -6
  162. package/src/components/base/button/button.documentation.js +0 -24
  163. package/src/components/base/button_group/button_group.documentation.js +0 -6
  164. package/src/components/base/card/card.documentation.js +0 -6
  165. package/src/components/base/carousel/carousel.documentation.js +0 -6
  166. package/src/components/base/collapse/collapse.documentation.js +0 -5
  167. package/src/components/base/datepicker/datepicker.documentation.js +0 -5
  168. package/src/components/base/daterange_picker/daterange_picker.documentation.js +0 -6
  169. package/src/components/base/drawer/drawer.documentation.js +0 -6
  170. package/src/components/base/dropdown/dropdown.documentation.js +0 -6
  171. package/src/components/base/dropdown/dropdown_item.documentation.js +0 -5
  172. package/src/components/base/filtered_search/filtered_search.documentation.js +0 -6
  173. package/src/components/base/filtered_search/filtered_search_suggestion.documentation.js +0 -5
  174. package/src/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +0 -5
  175. package/src/components/base/filtered_search/filtered_search_term.documentation.js +0 -5
  176. package/src/components/base/filtered_search/filtered_search_token.documentation.js +0 -5
  177. package/src/components/base/filtered_search/filtered_search_token_segment.documentation.js +0 -5
  178. package/src/components/base/form/form.documentation.js +0 -5
  179. package/src/components/base/form/form_checkbox/form_checkbox.documentation.js +0 -6
  180. package/src/components/base/form/form_checkbox_tree/form_checkbox_tree.documentation.js +0 -5
  181. package/src/components/base/form/form_combobox/form_combobox.documentation.js +0 -6
  182. package/src/components/base/form/form_group/form_group.documentation.js +0 -5
  183. package/src/components/base/form/form_input/form_input.documentation.js +0 -6
  184. package/src/components/base/form/form_input_group/form_input_group.documentation.js +0 -6
  185. package/src/components/base/form/form_radio/form_radio.documentation.js +0 -6
  186. package/src/components/base/form/form_radio_group/form_radio_group.documentation.js +0 -6
  187. package/src/components/base/form/form_select/form_select.documentation.js +0 -6
  188. package/src/components/base/form/form_text/form_text.documentation.js +0 -7
  189. package/src/components/base/form/form_textarea/form_textarea.documentation.js +0 -5
  190. package/src/components/base/form/input_group_text/input_group_text.documentation.js +0 -5
  191. package/src/components/base/icon/icon.documentation.js +0 -6
  192. package/src/components/base/infinite_scroll/examples/index.js +0 -57
  193. package/src/components/base/infinite_scroll/examples/infinite_scroll.all_items.example.vue +0 -25
  194. package/src/components/base/infinite_scroll/examples/infinite_scroll.basic.example.vue +0 -43
  195. package/src/components/base/infinite_scroll/examples/infinite_scroll.finite_total_items.example.vue +0 -44
  196. package/src/components/base/infinite_scroll/examples/infinite_scroll.large_fetched_items.example.vue +0 -43
  197. package/src/components/base/infinite_scroll/examples/infinite_scroll.reverse.example.vue +0 -46
  198. package/src/components/base/infinite_scroll/examples/infinite_scroll.small_fetched_items.example.vue +0 -43
  199. package/src/components/base/infinite_scroll/examples/infinite_scroll.two_way.example.vue +0 -75
  200. package/src/components/base/infinite_scroll/infinite_scroll.documentation.js +0 -6
  201. package/src/components/base/keyset_pagination/keyset_pagination.documentation.js +0 -6
  202. package/src/components/base/label/label.documentation.js +0 -6
  203. package/src/components/base/link/link.documentation.js +0 -5
  204. package/src/components/base/loading_icon/loading_icon.documentation.js +0 -6
  205. package/src/components/base/markdown/markdown.documentation.js +0 -5
  206. package/src/components/base/modal/modal.documentation.js +0 -6
  207. package/src/components/base/nav/nav.documentation.js +0 -5
  208. package/src/components/base/navbar/navbar.documentation.js +0 -5
  209. package/src/components/base/paginated_list/paginated_list.documentation.js +0 -5
  210. package/src/components/base/pagination/pagination.documentation.js +0 -6
  211. package/src/components/base/path/path.documentation.js +0 -6
  212. package/src/components/base/popover/popover.documentation.js +0 -3
  213. package/src/components/base/search_box_by_click/search_box_by_click.documentation.js +0 -6
  214. package/src/components/base/search_box_by_type/search_box_by_type.documentation.js +0 -6
  215. package/src/components/base/segmented_control/segmented_control.documentation.js +0 -6
  216. package/src/components/base/skeleton_loader/skeleton_loader.documentation.js +0 -6
  217. package/src/components/base/skeleton_loading/skeleton_loading.documentation.js +0 -5
  218. package/src/components/base/sorting/sorting.documentation.js +0 -5
  219. package/src/components/base/sorting/sorting_item.documentation.js +0 -6
  220. package/src/components/base/table/table.documentation.js +0 -6
  221. package/src/components/base/table_lite/table_lite.documentation.js +0 -6
  222. package/src/components/base/tabs/tabs/tabs.documentation.js +0 -6
  223. package/src/components/base/toast/toast.documentation.js +0 -6
  224. package/src/components/base/toggle/toggle.documentation.js +0 -6
  225. package/src/components/base/token/token.documentation.js +0 -3
  226. package/src/components/base/token_selector/token_selector.documentation.js +0 -5
  227. package/src/components/base/tooltip/tooltip.documentation.js +0 -6
  228. package/src/components/charts/area/area.documentation.js +0 -3
  229. package/src/components/charts/bar/bar.documentation.js +0 -6
  230. package/src/components/charts/chart/chart.documentation.js +0 -5
  231. package/src/components/charts/column/column.documentation.js +0 -3
  232. package/src/components/charts/discrete_scatter/discrete_scatter.documentation.js +0 -3
  233. package/src/components/charts/gauge/gauge.documentation.js +0 -5
  234. package/src/components/charts/heatmap/heatmap.documentation.js +0 -6
  235. package/src/components/charts/line/line.documentation.js +0 -6
  236. package/src/components/charts/series_label/series_label.documentation.js +0 -5
  237. package/src/components/charts/single_stat/single_stat.documentation.js +0 -5
  238. package/src/components/charts/sparkline/sparkline.documentation.js +0 -6
  239. package/src/components/charts/stacked_column/stacked_column.documentation.js +0 -6
  240. package/src/components/charts/tooltip/tooltip.documentation.js +0 -6
  241. package/src/components/editors/rich_text_editor/rich_text_editor.documentation.js +0 -5
  242. package/src/components/regions/dashboard_skeleton/dashboard_skeleton.documentation.js +0 -5
  243. package/src/components/regions/empty_state/empty_state.documentation.js +0 -5
  244. package/src/components/utilities/animated_number/animated_number.documentation.js +0 -6
  245. package/src/components/utilities/friendly_wrap/friendly_wrap.documentation.js +0 -5
  246. package/src/components/utilities/intersection_observer/intersection_observer.documentation.js +0 -5
  247. package/src/components/utilities/intersperse/intersperse.documentation.js +0 -6
  248. package/src/components/utilities/sprintf/sprintf.documentation.js +0 -6
  249. package/src/components/utilities/truncate/truncate.documentation.js +0 -5
  250. package/src/directives/hover_load/hover_load.documentation.js +0 -6
  251. package/src/directives/outside/outside.documentation.js +0 -6
  252. package/src/directives/resize_observer/resize_observer.documentation.js +0 -6
  253. package/src/directives/safe_html/safe_html.documentation.js +0 -6
  254. package/src/directives/safe_link/safe_link.documentation.js +0 -6
@@ -0,0 +1,276 @@
1
+ import {
2
+ buttonCategoryOptions,
3
+ buttonSizeOptions,
4
+ buttonVariantOptions,
5
+ } from '../../../../utils/constants';
6
+ import {
7
+ GlIcon,
8
+ GlListbox,
9
+ GlSearchBoxByType,
10
+ GlButtonGroup,
11
+ GlButton,
12
+ GlAvatar,
13
+ } from '../../../../index';
14
+ import { makeContainer } from '../../../../utils/story_decorators/container';
15
+ import readme from './listbox.md';
16
+
17
+ const defaultValue = (prop) => GlListbox.props[prop].default;
18
+
19
+ const defaultItems = [
20
+ {
21
+ value: 'prod',
22
+ text: 'Product',
23
+ },
24
+ {
25
+ value: 'ppl',
26
+ text: 'People',
27
+ },
28
+ {
29
+ value: 'fin',
30
+ text: 'Finance',
31
+ },
32
+ {
33
+ value: 'leg',
34
+ text: 'Legal',
35
+ },
36
+ {
37
+ value: 'eng',
38
+ text: 'Engineering',
39
+ },
40
+ {
41
+ value: 'sales',
42
+ text: 'Sales',
43
+ },
44
+ {
45
+ value: 'marketing',
46
+ text: 'Marketing',
47
+ },
48
+ {
49
+ value: 'acc',
50
+ text: 'Accounting',
51
+ },
52
+ {
53
+ value: 'hr',
54
+ text: 'Human Resource Management',
55
+ },
56
+ {
57
+ value: 'rnd',
58
+ text: 'Research and Development',
59
+ },
60
+ {
61
+ value: 'cust',
62
+ text: 'Customer Service',
63
+ },
64
+ {
65
+ value: 'sup',
66
+ text: 'Support',
67
+ },
68
+ ];
69
+ const generateProps = ({
70
+ items = defaultItems,
71
+ category = defaultValue('category'),
72
+ variant = defaultValue('variant'),
73
+ size = defaultValue('size'),
74
+ disabled = defaultValue('disabled'),
75
+ loading = defaultValue('loading'),
76
+ noCaret = defaultValue('noCaret'),
77
+ right = defaultValue('right'),
78
+ toggleText,
79
+ textSrOnly = defaultValue('textSrOnly'),
80
+ icon = '',
81
+ multiple = defaultValue('multiple'),
82
+ ariaLabelledby,
83
+ } = {}) => ({
84
+ items,
85
+ category,
86
+ variant,
87
+ size,
88
+ disabled,
89
+ loading,
90
+ noCaret,
91
+ right,
92
+ toggleText,
93
+ textSrOnly,
94
+ icon,
95
+ multiple,
96
+ ariaLabelledby,
97
+ });
98
+
99
+ function openListbox(component) {
100
+ component.$nextTick(() => component.$el.querySelector('.dropdown-toggle').click());
101
+ }
102
+
103
+ const template = (content, label = '') => `
104
+ <div>
105
+ ${label}
106
+ <br/>
107
+ <gl-listbox
108
+ v-model="selected"
109
+ :items="items"
110
+ :category="category"
111
+ :variant="variant"
112
+ :size="size"
113
+ :disabled="disabled"
114
+ :loading="loading"
115
+ :no-caret="noCaret"
116
+ :right="right"
117
+ :toggle-text="toggleText"
118
+ :text-sr-only="textSrOnly"
119
+ :icon="icon"
120
+ :multiple="multiple"
121
+ :aria-labelledby="ariaLabelledby"
122
+ >
123
+ ${content}
124
+ </gl-listbox>
125
+ </div>
126
+ `;
127
+
128
+ export const Default = (args, { argTypes }) => ({
129
+ props: Object.keys(argTypes),
130
+ components: {
131
+ GlListbox,
132
+ },
133
+ data() {
134
+ return {
135
+ selected: defaultItems[1].value,
136
+ };
137
+ },
138
+ mounted() {
139
+ openListbox(this);
140
+ },
141
+ template: template('', `<span class="gl-my-0" id="listbox-label">Select a department</span>`),
142
+ });
143
+ Default.args = generateProps({ ariaLabelledby: 'listbox-label' });
144
+ Default.decorators = [makeContainer({ height: '150px' })];
145
+
146
+ export const HeaderAndFooter = (args, { argTypes }) => ({
147
+ props: Object.keys(argTypes),
148
+ components: {
149
+ GlListbox,
150
+ GlSearchBoxByType,
151
+ GlButtonGroup,
152
+ GlButton,
153
+ },
154
+ data() {
155
+ return {
156
+ selected: [],
157
+ };
158
+ },
159
+ mounted() {
160
+ openListbox(this);
161
+ },
162
+ methods: {
163
+ selectItem(index) {
164
+ this.selected.push(defaultItems[index].value);
165
+ },
166
+ },
167
+ template: template(`<template #header>
168
+ <gl-search-box-by-type/>
169
+ </template>
170
+ <template #footer>
171
+ <div class="gl-border-t-solid gl-border-t-1 gl-border-t-gray-100 gl-display-flex gl-justify-content-center gl-p-3">
172
+ <gl-button-group :vertical="false">
173
+ <gl-button @click="selectItem(0)">1st</gl-button>
174
+ <gl-button @click="selectItem(1)">2nd</gl-button>
175
+ <gl-button @click="selectItem(2)">3rd</gl-button>
176
+ </gl-button-group>
177
+ </div>
178
+ </template>`),
179
+ });
180
+ HeaderAndFooter.args = generateProps({ toggleText: 'Header and Footer', multiple: true });
181
+ HeaderAndFooter.decorators = [makeContainer({ height: '220px' })];
182
+
183
+ export const CustomListItem = (args, { argTypes }) => ({
184
+ props: Object.keys(argTypes),
185
+ data() {
186
+ return {
187
+ selected: ['mikegreiling'],
188
+ };
189
+ },
190
+ components: {
191
+ GlListbox,
192
+ GlIcon,
193
+ GlAvatar,
194
+ },
195
+ mounted() {
196
+ openListbox(this);
197
+ },
198
+ computed: {
199
+ headerText() {
200
+ return this.selected.length !== 1
201
+ ? `${this.selected.length} assignees`
202
+ : this.items.find(({ value }) => value === this.selected[0]).text;
203
+ },
204
+ },
205
+ template: `
206
+ <gl-listbox
207
+ v-model="selected"
208
+ :items="items"
209
+ :category="category"
210
+ :variant="variant"
211
+ :size="size"
212
+ :disabled="disabled"
213
+ :loading="loading"
214
+ :no-caret="noCaret"
215
+ :right="right"
216
+ :toggle-text="headerText"
217
+ :text-sr-only="textSrOnly"
218
+ :icon="icon"
219
+ :multiple="multiple"
220
+ :aria-labelledby="ariaLabelledby"
221
+ >
222
+ <template #list-item="{ item }">
223
+ <span class="gl-display-flex gl-align-items-center">
224
+ <gl-avatar :size="32" class-="gl-mr-3"/>
225
+ <span class="gl-display-flex gl-flex-direction-column">
226
+ <span class="gl-font-weight-bold gl-white-space-nowrap">{{ item.text }}</span>
227
+ <span class="gl-text-gray-400"> {{ item.secondaryText }}</span>
228
+ </span>
229
+ </span>
230
+ </template>
231
+ </gl-listbox>
232
+ `,
233
+ });
234
+
235
+ CustomListItem.args = generateProps({
236
+ items: [
237
+ { value: 'mikegreiling', text: 'Mike Greiling', secondaryText: '@mikegreiling', icon: 'foo' },
238
+ { value: 'ohoral', text: 'Olena Horal-Koretska', secondaryText: '@ohoral', icon: 'bar' },
239
+ { value: 'markian', text: 'Mark Florian', secondaryText: '@markian', icon: 'bin' },
240
+ ],
241
+ multiple: true,
242
+ });
243
+ CustomListItem.decorators = [makeContainer({ height: '200px' })];
244
+
245
+ export default {
246
+ title: 'base/new-dropdowns/listbox',
247
+ component: GlListbox,
248
+ parameters: {
249
+ knobs: { disable: true },
250
+ docs: {
251
+ description: {
252
+ component: readme,
253
+ },
254
+ },
255
+ },
256
+ argTypes: {
257
+ category: {
258
+ control: {
259
+ type: 'select',
260
+ options: buttonCategoryOptions,
261
+ },
262
+ },
263
+ variant: {
264
+ control: {
265
+ type: 'select',
266
+ options: buttonVariantOptions,
267
+ },
268
+ },
269
+ size: {
270
+ control: {
271
+ type: 'select',
272
+ options: buttonSizeOptions,
273
+ },
274
+ },
275
+ },
276
+ };
@@ -0,0 +1,348 @@
1
+ <script>
2
+ import { clamp, uniqueId } from 'lodash';
3
+ import { stopEvent } from '../../../../utils/utils';
4
+ import {
5
+ GL_DROPDOWN_SHOWN,
6
+ GL_DROPDOWN_HIDDEN,
7
+ HOME,
8
+ END,
9
+ ARROW_DOWN,
10
+ ARROW_UP,
11
+ } from '../constants';
12
+ import {
13
+ buttonCategoryOptions,
14
+ buttonSizeOptions,
15
+ dropdownVariantOptions,
16
+ } from '../../../../utils/constants';
17
+ import GlBaseDropdown from '../base_dropdown/base_dropdown.vue';
18
+ import GlListboxItem from './listbox_item.vue';
19
+
20
+ export const ITEM_SELECTOR = '[role="option"]';
21
+
22
+ export default {
23
+ events: {
24
+ GL_DROPDOWN_SHOWN,
25
+ GL_DROPDOWN_HIDDEN,
26
+ },
27
+ components: {
28
+ GlBaseDropdown,
29
+ GlListboxItem,
30
+ },
31
+ model: {
32
+ prop: 'selected',
33
+ event: 'select',
34
+ },
35
+ props: {
36
+ /**
37
+ * Items to display in the dropdown
38
+ */
39
+ items: {
40
+ type: Array,
41
+ required: false,
42
+ default: () => [],
43
+ validator: (items) => {
44
+ return items.every(({ value }) => typeof value === 'string');
45
+ },
46
+ },
47
+ /**
48
+ * array of selected items values for multi-select and selected item value for single-select
49
+ */
50
+ selected: {
51
+ type: [Array, String, Number],
52
+ required: false,
53
+ default: () => [],
54
+ },
55
+ /**
56
+ * Allow multi-selection
57
+ */
58
+ multiple: {
59
+ type: Boolean,
60
+ required: false,
61
+ default: false,
62
+ },
63
+ /**
64
+ * Toggle button text
65
+ */
66
+ toggleText: {
67
+ type: String,
68
+ required: false,
69
+ default: '',
70
+ },
71
+ /**
72
+ * Toggle text to be read by screen readers only
73
+ */
74
+ textSrOnly: {
75
+ type: Boolean,
76
+ required: false,
77
+ default: false,
78
+ },
79
+ /**
80
+ * Styling option - dropdown's toggle category
81
+ */
82
+ category: {
83
+ type: String,
84
+ required: false,
85
+ default: buttonCategoryOptions.primary,
86
+ validator: (value) => Object.keys(buttonCategoryOptions).includes(value),
87
+ },
88
+ /**
89
+ * Styling option - dropdown's toggle variant
90
+ */
91
+ variant: {
92
+ type: String,
93
+ required: false,
94
+ default: dropdownVariantOptions.default,
95
+ validator: (value) => Object.keys(dropdownVariantOptions).includes(value),
96
+ },
97
+ /**
98
+ * The size of the dropdown toggle
99
+ */
100
+ size: {
101
+ type: String,
102
+ required: false,
103
+ default: buttonSizeOptions.medium,
104
+ validator: (value) => Object.keys(buttonSizeOptions).includes(value),
105
+ },
106
+ /**
107
+ * Icon name that will be rendered in the toggle button
108
+ */
109
+ icon: {
110
+ type: String,
111
+ required: false,
112
+ default: '',
113
+ },
114
+ /**
115
+ * Set to "true" to disable the dropdown
116
+ */
117
+ disabled: {
118
+ type: Boolean,
119
+ required: false,
120
+ default: false,
121
+ },
122
+ /**
123
+ * Set to "true" when dropdown content (items) is loading
124
+ */
125
+ loading: {
126
+ type: Boolean,
127
+ required: false,
128
+ default: false,
129
+ },
130
+ /**
131
+ * Additional CSS classes to customize toggle appearance
132
+ */
133
+ toggleClass: {
134
+ type: [String, Array, Object],
135
+ required: false,
136
+ default: null,
137
+ },
138
+ /**
139
+ * Set to "true" to hide the caret
140
+ */
141
+ noCaret: {
142
+ type: Boolean,
143
+ required: false,
144
+ default: false,
145
+ },
146
+ /**
147
+ * Right align listbox menu with respect to the toggle button
148
+ */
149
+ right: {
150
+ type: Boolean,
151
+ required: false,
152
+ default: false,
153
+ },
154
+ /**
155
+ * The `aria-labelledby` attribute value for the toggle button
156
+ */
157
+ ariaLabelledby: {
158
+ type: String,
159
+ required: false,
160
+ default: null,
161
+ },
162
+ },
163
+ data() {
164
+ return {
165
+ selectedValues: [],
166
+ toggleId: uniqueId('dropdown-toggle-btn-'),
167
+ nextFocusedItemIndex: null,
168
+ };
169
+ },
170
+ computed: {
171
+ listboxToggleText() {
172
+ if (!this.toggleText) {
173
+ if (!this.multiple && this.selectedValues.length) {
174
+ return this.items.find(({ value }) => value === this.selectedValues[0])?.text;
175
+ }
176
+ return '';
177
+ }
178
+
179
+ return this.toggleText;
180
+ },
181
+ selectedIndices() {
182
+ return this.selectedValues
183
+ .map((selected) => this.items.findIndex(({ value }) => value === selected))
184
+ .sort();
185
+ },
186
+ },
187
+ watch: {
188
+ selected: {
189
+ immediate: true,
190
+ handler(newSelected) {
191
+ if (Array.isArray(newSelected)) {
192
+ if (!this.multiple && newSelected.length) {
193
+ throw new Error('To allow multi-selection, please, set "multiple" property to "true"');
194
+ }
195
+ this.selectedValues = [...newSelected];
196
+ } else {
197
+ this.selectedValues = [newSelected];
198
+ }
199
+ },
200
+ },
201
+ },
202
+ methods: {
203
+ onShow() {
204
+ this.$nextTick(() => {
205
+ this.focusItem(this.selectedIndices[0] ?? 0, this.getFocusableListItemElements());
206
+ /**
207
+ * Emitted when dropdown is shown
208
+ *
209
+ * @event shown
210
+ */
211
+ this.$emit(GL_DROPDOWN_SHOWN);
212
+ });
213
+ },
214
+ onHide() {
215
+ /**
216
+ * Emitted when dropdown is hidden
217
+ *
218
+ * @event hidden
219
+ */
220
+ this.$emit(GL_DROPDOWN_HIDDEN);
221
+ this.nextFocusedItemIndex = null;
222
+ },
223
+ onKeydown(event) {
224
+ const { code } = event;
225
+ const elements = this.getFocusableListItemElements();
226
+
227
+ if (elements.length < 1) return;
228
+
229
+ let stop = true;
230
+
231
+ if (code === HOME) {
232
+ this.focusItem(0, elements);
233
+ } else if (code === END) {
234
+ this.focusItem(elements.length - 1, elements);
235
+ } else if (code === ARROW_UP) {
236
+ this.focusNextItem(event, elements, -1);
237
+ } else if (code === ARROW_DOWN) {
238
+ this.focusNextItem(event, elements, 1);
239
+ } else {
240
+ stop = false;
241
+ }
242
+
243
+ if (stop) {
244
+ stopEvent(event);
245
+ }
246
+ },
247
+ getFocusableListItemElements() {
248
+ const items = this.$refs.list.querySelectorAll(ITEM_SELECTOR);
249
+ return Array.from(items);
250
+ },
251
+ focusNextItem(event, elements, offset) {
252
+ const { target } = event;
253
+ const currentIndex = elements.indexOf(target);
254
+ const nextIndex = clamp(currentIndex + offset, 0, elements.length - 1);
255
+
256
+ this.focusItem(nextIndex, elements);
257
+ },
258
+ focusItem(index, elements) {
259
+ this.nextFocusedItemIndex = index;
260
+
261
+ this.$nextTick(() => {
262
+ elements[index]?.focus();
263
+ });
264
+ },
265
+ onSelect({ value }, isSelected) {
266
+ if (this.multiple) {
267
+ this.onMultiSelect(value, isSelected);
268
+ } else {
269
+ this.onSingleSelect(value, isSelected);
270
+ }
271
+ },
272
+ isSelected(item) {
273
+ return this.selectedValues.some((value) => value === item.value);
274
+ },
275
+ onSingleSelect(value, isSelected) {
276
+ if (isSelected) {
277
+ /**
278
+ * Emitted when selection is changed
279
+ *
280
+ * @event select
281
+ */
282
+ this.$emit('select', value);
283
+ }
284
+
285
+ this.$refs.baseDropdown.closeAndFocus();
286
+ },
287
+ onMultiSelect(value, isSelected) {
288
+ if (isSelected) {
289
+ this.$emit('select', [...this.selectedValues, value]);
290
+ } else {
291
+ this.$emit(
292
+ 'select',
293
+ this.selectedValues.filter((selectedValue) => selectedValue !== value)
294
+ );
295
+ }
296
+ },
297
+ },
298
+ };
299
+ </script>
300
+
301
+ <template>
302
+ <gl-base-dropdown
303
+ ref="baseDropdown"
304
+ aria-haspopup="listbox"
305
+ :aria-labelledby="ariaLabelledby"
306
+ :toggle-id="toggleId"
307
+ :toggle-text="listboxToggleText"
308
+ :toggle-class="toggleClass"
309
+ :text-sr-only="textSrOnly"
310
+ :category="category"
311
+ :variant="variant"
312
+ :size="size"
313
+ :icon="icon"
314
+ :disabled="disabled"
315
+ :loading="loading"
316
+ :no-caret="noCaret"
317
+ :right="right"
318
+ @[$options.events.GL_DROPDOWN_SHOWN]="onShow"
319
+ @[$options.events.GL_DROPDOWN_HIDDEN]="onHide"
320
+ >
321
+ <!-- @slot Content to display in dropdown header -->
322
+ <slot name="header"></slot>
323
+
324
+ <ul
325
+ ref="list"
326
+ :aria-labelledby="toggleId"
327
+ role="listbox"
328
+ class="gl-new-dropdown-contents gl-list-style-none gl-pl-0 gl-mb-0"
329
+ tabindex="-1"
330
+ @keydown="onKeydown"
331
+ >
332
+ <gl-listbox-item
333
+ v-for="(item, index) in items"
334
+ :key="item.value"
335
+ :is-selected="isSelected(item)"
336
+ :is-focused="nextFocusedItemIndex === index"
337
+ @select="onSelect(item, $event)"
338
+ >
339
+ <!-- @slot Custom template of the listbox item -->
340
+ <slot name="list-item" :item="item">
341
+ {{ item.text }}
342
+ </slot>
343
+ </gl-listbox-item>
344
+ </ul>
345
+ <!-- @slot Content to display in dropdown footer -->
346
+ <slot name="footer"></slot>
347
+ </gl-base-dropdown>
348
+ </template>