@dhis2-ui/select 10.16.2 → 10.16.3

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 (274) hide show
  1. package/package.json +17 -16
  2. package/src/index.js +9 -0
  3. package/src/locales/ar/translations.json +7 -0
  4. package/src/locales/ar_IQ/translations.json +7 -0
  5. package/src/locales/ckb/translations.json +7 -0
  6. package/src/locales/cs/translations.json +7 -0
  7. package/src/locales/da/translations.json +7 -0
  8. package/src/locales/en/translations.json +11 -0
  9. package/src/locales/en_US/translations.json +9 -0
  10. package/src/locales/es/translations.json +7 -0
  11. package/src/locales/es_419/translations.json +7 -0
  12. package/src/locales/fr/translations.json +7 -0
  13. package/src/locales/hi_IN/translations.json +7 -0
  14. package/src/locales/id/translations.json +7 -0
  15. package/src/locales/index.js +80 -0
  16. package/src/locales/km/translations.json +7 -0
  17. package/src/locales/lo/translations.json +7 -0
  18. package/src/locales/my/translations.json +7 -0
  19. package/src/locales/nb/translations.json +7 -0
  20. package/src/locales/nl/translations.json +7 -0
  21. package/src/locales/prs/translations.json +7 -0
  22. package/src/locales/ps/translations.json +7 -0
  23. package/src/locales/pt/translations.json +7 -0
  24. package/src/locales/pt_BR/translations.json +7 -0
  25. package/src/locales/ru/translations.json +7 -0
  26. package/src/locales/sv/translations.json +7 -0
  27. package/src/locales/tet/translations.json +7 -0
  28. package/src/locales/tg/translations.json +7 -0
  29. package/src/locales/uk/translations.json +7 -0
  30. package/src/locales/ur/translations.json +7 -0
  31. package/src/locales/uz_Latn/translations.json +7 -0
  32. package/src/locales/uz_UZ_Cyrl/translations.json +7 -0
  33. package/src/locales/uz_UZ_Latn/translations.json +7 -0
  34. package/src/locales/vi/translations.json +7 -0
  35. package/src/locales/zh/translations.json +9 -0
  36. package/src/locales/zh_CN/translations.json +7 -0
  37. package/src/multi-select/__tests__/multi-select.test.js +23 -0
  38. package/src/multi-select/__tests__/selection-list.test.js +102 -0
  39. package/src/multi-select/features/accepts_blur_cb/index.js +14 -0
  40. package/src/multi-select/features/accepts_blur_cb.feature +8 -0
  41. package/src/multi-select/features/accepts_focus_cb/index.js +14 -0
  42. package/src/multi-select/features/accepts_focus_cb.feature +6 -0
  43. package/src/multi-select/features/accepts_initial_focus/index.js +5 -0
  44. package/src/multi-select/features/accepts_initial_focus.feature +5 -0
  45. package/src/multi-select/features/accepts_loading/index.js +22 -0
  46. package/src/multi-select/features/accepts_loading.feature +14 -0
  47. package/src/multi-select/features/accepts_max_height/index.js +31 -0
  48. package/src/multi-select/features/accepts_max_height.feature +11 -0
  49. package/src/multi-select/features/accepts_placeholder/index.js +25 -0
  50. package/src/multi-select/features/accepts_placeholder.feature +10 -0
  51. package/src/multi-select/features/accepts_prefix/index.js +19 -0
  52. package/src/multi-select/features/accepts_prefix.feature +10 -0
  53. package/src/multi-select/features/allows_invalid_options/index.js +27 -0
  54. package/src/multi-select/features/allows_invalid_options.feature +12 -0
  55. package/src/multi-select/features/allows_selecting/index.js +81 -0
  56. package/src/multi-select/features/allows_selecting.feature +44 -0
  57. package/src/multi-select/features/can_be_cleared/index.js +22 -0
  58. package/src/multi-select/features/can_be_cleared.feature +6 -0
  59. package/src/multi-select/features/can_be_disabled/index.js +36 -0
  60. package/src/multi-select/features/can_be_disabled.feature +32 -0
  61. package/src/multi-select/features/can_be_empty/index.js +28 -0
  62. package/src/multi-select/features/can_be_empty.feature +16 -0
  63. package/src/multi-select/features/can_be_filtered/index.js +23 -0
  64. package/src/multi-select/features/can_be_filtered.feature +14 -0
  65. package/src/multi-select/features/can_be_opened_and_closed/index.js +32 -0
  66. package/src/multi-select/features/can_be_opened_and_closed.feature +41 -0
  67. package/src/multi-select/features/common/index.js +49 -0
  68. package/src/multi-select/features/duplicate_option_values/index.js +21 -0
  69. package/src/multi-select/features/duplicate_option_values.feature +7 -0
  70. package/src/multi-select/features/menu_width_matches_input/index.js +53 -0
  71. package/src/multi-select/features/menu_width_matches_input.feature +11 -0
  72. package/src/multi-select/features/position/index.js +160 -0
  73. package/src/multi-select/features/position.feature +46 -0
  74. package/src/multi-select/features/shows_selections/index.js +18 -0
  75. package/src/multi-select/features/shows_selections.feature +9 -0
  76. package/src/multi-select/filterable-menu.js +43 -0
  77. package/src/multi-select/index.js +1 -0
  78. package/src/multi-select/input.js +129 -0
  79. package/src/multi-select/menu.js +91 -0
  80. package/src/multi-select/multi-select.e2e.stories.js +407 -0
  81. package/src/multi-select/multi-select.js +148 -0
  82. package/src/multi-select/multi-select.prod.stories.js +408 -0
  83. package/src/multi-select/selection-list.js +79 -0
  84. package/src/multi-select-field/__tests__/multi-select-field.test.js +29 -0
  85. package/src/multi-select-field/features/accepts_help_text/index.js +9 -0
  86. package/src/multi-select-field/features/accepts_help_text.feature +5 -0
  87. package/src/multi-select-field/features/accepts_label/index.js +9 -0
  88. package/src/multi-select-field/features/accepts_label.feature +5 -0
  89. package/src/multi-select-field/features/accepts_validation_text/index.js +9 -0
  90. package/src/multi-select-field/features/accepts_validation_text.feature +5 -0
  91. package/src/multi-select-field/features/can_be_required/index.js +11 -0
  92. package/src/multi-select-field/features/can_be_required.feature +5 -0
  93. package/src/multi-select-field/features/has_default_clear_text/index.js +16 -0
  94. package/src/multi-select-field/features/has_default_clear_text.feature +6 -0
  95. package/src/multi-select-field/features/has_default_empty_text/index.js +13 -0
  96. package/src/multi-select-field/features/has_default_empty_text.feature +6 -0
  97. package/src/multi-select-field/features/has_default_filter_nomatch_text/index.js +17 -0
  98. package/src/multi-select-field/features/has_default_filter_nomatch_text.feature +7 -0
  99. package/src/multi-select-field/features/has_default_filter_placeholder/index.js +15 -0
  100. package/src/multi-select-field/features/has_default_filter_placeholder.feature +6 -0
  101. package/src/multi-select-field/features/has_default_loading_text/index.js +13 -0
  102. package/src/multi-select-field/features/has_default_loading_text.feature +6 -0
  103. package/src/multi-select-field/index.js +1 -0
  104. package/src/multi-select-field/multi-select-field.e2e.stories.js +45 -0
  105. package/src/multi-select-field/multi-select-field.js +178 -0
  106. package/src/multi-select-field/multi-select-field.prod.stories.js +137 -0
  107. package/src/multi-select-option/index.js +1 -0
  108. package/src/multi-select-option/multi-select-option.js +70 -0
  109. package/src/select/__tests__/select.test.js +31 -0
  110. package/src/select/debounce/debounce.js +30 -0
  111. package/src/select/debounce/debounce.spec.js +35 -0
  112. package/src/select/empty.js +28 -0
  113. package/src/select/filter-input.js +42 -0
  114. package/src/select/filterable-menu.js +106 -0
  115. package/src/select/index.js +14 -0
  116. package/src/select/input-clear-button.js +66 -0
  117. package/src/select/input-placeholder.js +30 -0
  118. package/src/select/input-prefix.js +32 -0
  119. package/src/select/input-wrapper.js +114 -0
  120. package/src/select/loading.js +33 -0
  121. package/src/select/menu-wrapper.js +52 -0
  122. package/src/select/no-match.js +28 -0
  123. package/src/select/option-helpers.js +34 -0
  124. package/src/select/select.js +256 -0
  125. package/src/simple-single-select/__stories__/DefaultPosition.js +37 -0
  126. package/src/simple-single-select/__stories__/Dense.js +17 -0
  127. package/src/simple-single-select/__stories__/Empty.js +15 -0
  128. package/src/simple-single-select/__stories__/EmptyWithEmptyNode.js +27 -0
  129. package/src/simple-single-select/__stories__/EmptyWithEmptyText.js +16 -0
  130. package/src/simple-single-select/__stories__/FlippedPosition.js +36 -0
  131. package/src/simple-single-select/__stories__/InfiniteLoading.js +65 -0
  132. package/src/simple-single-select/__stories__/Loading.js +15 -0
  133. package/src/simple-single-select/__stories__/ShiftedIntoView.js +35 -0
  134. package/src/simple-single-select/__stories__/WithClearButton.js +26 -0
  135. package/src/simple-single-select/__stories__/WithCustomLowMaxHeight.js +17 -0
  136. package/src/simple-single-select/__stories__/WithCustomOptions.js +78 -0
  137. package/src/simple-single-select/__stories__/WithDisabledOption.js +25 -0
  138. package/src/simple-single-select/__stories__/WithFilterField.js +30 -0
  139. package/src/simple-single-select/__stories__/WithInitialFocus.js +29 -0
  140. package/src/simple-single-select/__stories__/WithManyOptions.js +19 -0
  141. package/src/simple-single-select/__stories__/WithMultipleSelectsAndScrollableParent.js +65 -0
  142. package/src/simple-single-select/__stories__/WithNoMatchText.js +25 -0
  143. package/src/simple-single-select/__stories__/WithOnBlur.js +22 -0
  144. package/src/simple-single-select/__stories__/WithOnFocus.js +22 -0
  145. package/src/simple-single-select/__stories__/WithOptionsAndDisabled.js +17 -0
  146. package/src/simple-single-select/__stories__/WithOptionsAndLoading.js +23 -0
  147. package/src/simple-single-select/__stories__/WithOptionsAndLoadingText.js +24 -0
  148. package/src/simple-single-select/__stories__/WithPlaceholder.js +18 -0
  149. package/src/simple-single-select/__stories__/WithPlaceholderAndSelection.js +20 -0
  150. package/src/simple-single-select/__stories__/WithPrefix.js +17 -0
  151. package/src/simple-single-select/__stories__/WithPrefixAndSelection.js +20 -0
  152. package/src/simple-single-select/__stories__/WithRTL.js +29 -0
  153. package/src/simple-single-select/__stories__/WithScrollingParent.js +51 -0
  154. package/src/simple-single-select/__stories__/WithSelection.js +19 -0
  155. package/src/simple-single-select/__stories__/WithSelectionAndDisabled.js +20 -0
  156. package/src/simple-single-select/__stories__/WithServerSideFilteringDemo.js +245 -0
  157. package/src/simple-single-select/__stories__/WithSimpleFilteringDemo.js +50 -0
  158. package/src/simple-single-select/__stories__/WithoutOptionsAndLoading.js +16 -0
  159. package/src/simple-single-select/__stories__/WithoutSelection.js +17 -0
  160. package/src/simple-single-select/__stories__/options.js +96 -0
  161. package/src/simple-single-select/__stories__/simple-single-select-server-side-filtering/CustomDataProvider.js +70 -0
  162. package/src/simple-single-select/__stories__/simple-single-select-server-side-filtering/dataElements.js +4271 -0
  163. package/src/simple-single-select/__stories__/simple-single-select-server-side-filtering/index.js +1 -0
  164. package/src/simple-single-select/features/keyboard-interactions.test.e2e.js +581 -0
  165. package/src/simple-single-select/features/menu-positioning.test.e2e.js +187 -0
  166. package/src/simple-single-select/index.js +2 -0
  167. package/src/simple-single-select/is-option-hidden.js +21 -0
  168. package/src/simple-single-select/menu/empty.js +27 -0
  169. package/src/simple-single-select/menu/filter.js +61 -0
  170. package/src/simple-single-select/menu/index.js +1 -0
  171. package/src/simple-single-select/menu/loading.js +38 -0
  172. package/src/simple-single-select/menu/menu.js +171 -0
  173. package/src/simple-single-select/menu/no-match.js +28 -0
  174. package/src/simple-single-select/menu/option.js +161 -0
  175. package/src/simple-single-select/menu/options-list.js +107 -0
  176. package/src/simple-single-select/selected-value/clear-button.js +71 -0
  177. package/src/simple-single-select/selected-value/container.js +146 -0
  178. package/src/simple-single-select/selected-value/index.js +1 -0
  179. package/src/simple-single-select/selected-value/placeholder.js +24 -0
  180. package/src/simple-single-select/selected-value/prefix.js +26 -0
  181. package/src/simple-single-select/selected-value/selected-value.js +174 -0
  182. package/src/simple-single-select/shared-prop-types.js +8 -0
  183. package/src/simple-single-select/simple-single-select.e2e.stories.js +216 -0
  184. package/src/simple-single-select/simple-single-select.js +389 -0
  185. package/src/simple-single-select/simple-single-select.prod.stories.js +41 -0
  186. package/src/simple-single-select/simple-single-select.test.js +1136 -0
  187. package/src/simple-single-select/use-handle-key-press/index.js +2 -0
  188. package/src/simple-single-select/use-handle-key-press/use-focus-option.js +24 -0
  189. package/src/simple-single-select/use-handle-key-press/use-handle-key-press-on-combobox.js +259 -0
  190. package/src/simple-single-select/use-handle-key-press/use-handle-key-press-on-filter-input.js +147 -0
  191. package/src/simple-single-select/use-handle-key-press/use-handle-typing.js +79 -0
  192. package/src/simple-single-select/use-handle-key-press/use-highlight-first-option-on-previous-page.js +62 -0
  193. package/src/simple-single-select/use-handle-key-press/use-highlight-first-visible-option.js +38 -0
  194. package/src/simple-single-select/use-handle-key-press/use-highlight-last-option-on-next-page.js +60 -0
  195. package/src/simple-single-select/use-handle-key-press/use-highlight-last-visible-option.js +52 -0
  196. package/src/simple-single-select/use-handle-key-press/use-page-down.js +50 -0
  197. package/src/simple-single-select/use-handle-key-press/use-page-up.js +50 -0
  198. package/src/simple-single-select/use-handle-key-press/utils.js +23 -0
  199. package/src/simple-single-select-field/index.js +1 -0
  200. package/src/simple-single-select-field/simple-single-select-field.js +198 -0
  201. package/src/simple-single-select-field/simple-single-select-field.prod.stories.js +188 -0
  202. package/src/simple-single-select-field/simple-single-select-field.test.js +144 -0
  203. package/src/single-select/__tests__/selection.test.js +102 -0
  204. package/src/single-select/__tests__/single-select.test.js +23 -0
  205. package/src/single-select/features/accepts_blur_cb/index.js +14 -0
  206. package/src/single-select/features/accepts_blur_cb.feature +8 -0
  207. package/src/single-select/features/accepts_focus_cb/index.js +14 -0
  208. package/src/single-select/features/accepts_focus_cb.feature +6 -0
  209. package/src/single-select/features/accepts_initial_focus/index.js +5 -0
  210. package/src/single-select/features/accepts_initial_focus.feature +5 -0
  211. package/src/single-select/features/accepts_loading/index.js +22 -0
  212. package/src/single-select/features/accepts_loading.feature +14 -0
  213. package/src/single-select/features/accepts_max_height/index.js +31 -0
  214. package/src/single-select/features/accepts_max_height.feature +11 -0
  215. package/src/single-select/features/accepts_placeholder/index.js +25 -0
  216. package/src/single-select/features/accepts_placeholder.feature +10 -0
  217. package/src/single-select/features/accepts_prefix/index.js +19 -0
  218. package/src/single-select/features/accepts_prefix.feature +10 -0
  219. package/src/single-select/features/allows_invalid_options/index.js +28 -0
  220. package/src/single-select/features/allows_invalid_options.feature +12 -0
  221. package/src/single-select/features/allows_selecting/index.js +38 -0
  222. package/src/single-select/features/allows_selecting.feature +21 -0
  223. package/src/single-select/features/can_be_cleared/index.js +22 -0
  224. package/src/single-select/features/can_be_cleared.feature +6 -0
  225. package/src/single-select/features/can_be_disabled/index.js +39 -0
  226. package/src/single-select/features/can_be_disabled.feature +32 -0
  227. package/src/single-select/features/can_be_empty/index.js +28 -0
  228. package/src/single-select/features/can_be_empty.feature +16 -0
  229. package/src/single-select/features/can_be_filtered/index.js +23 -0
  230. package/src/single-select/features/can_be_filtered.feature +14 -0
  231. package/src/single-select/features/can_be_opened_and_closed/index.js +32 -0
  232. package/src/single-select/features/can_be_opened_and_closed.feature +41 -0
  233. package/src/single-select/features/common/index.js +44 -0
  234. package/src/single-select/features/duplicate_option_values/index.js +26 -0
  235. package/src/single-select/features/duplicate_option_values.feature +7 -0
  236. package/src/single-select/features/menu_width_matches_input/index.js +53 -0
  237. package/src/single-select/features/menu_width_matches_input.feature +11 -0
  238. package/src/single-select/features/position/index.js +111 -0
  239. package/src/single-select/features/position.feature +26 -0
  240. package/src/single-select/features/shows_selections/index.js +9 -0
  241. package/src/single-select/features/shows_selections.feature +5 -0
  242. package/src/single-select/filterable-menu.js +41 -0
  243. package/src/single-select/index.js +1 -0
  244. package/src/single-select/input.js +101 -0
  245. package/src/single-select/menu.js +75 -0
  246. package/src/single-select/selection.js +60 -0
  247. package/src/single-select/single-select.e2e.stories.js +376 -0
  248. package/src/single-select/single-select.js +146 -0
  249. package/src/single-select/single-select.prod.stories.js +358 -0
  250. package/src/single-select-field/__tests__/single-select-field.test.js +29 -0
  251. package/src/single-select-field/features/accepts_help_text/index.js +9 -0
  252. package/src/single-select-field/features/accepts_help_text.feature +5 -0
  253. package/src/single-select-field/features/accepts_label/index.js +9 -0
  254. package/src/single-select-field/features/accepts_label.feature +5 -0
  255. package/src/single-select-field/features/accepts_validation_text/index.js +9 -0
  256. package/src/single-select-field/features/accepts_validation_text.feature +5 -0
  257. package/src/single-select-field/features/can_be_required/index.js +11 -0
  258. package/src/single-select-field/features/can_be_required.feature +5 -0
  259. package/src/single-select-field/features/has_default_clear_text/index.js +16 -0
  260. package/src/single-select-field/features/has_default_clear_text.feature +6 -0
  261. package/src/single-select-field/features/has_default_empty_text/index.js +13 -0
  262. package/src/single-select-field/features/has_default_empty_text.feature +6 -0
  263. package/src/single-select-field/features/has_default_filter_nomatch_text/index.js +17 -0
  264. package/src/single-select-field/features/has_default_filter_nomatch_text.feature +7 -0
  265. package/src/single-select-field/features/has_default_filter_placeholder/index.js +15 -0
  266. package/src/single-select-field/features/has_default_filter_placeholder.feature +6 -0
  267. package/src/single-select-field/features/has_default_loading_text/index.js +13 -0
  268. package/src/single-select-field/features/has_default_loading_text.feature +6 -0
  269. package/src/single-select-field/index.js +1 -0
  270. package/src/single-select-field/single-select-field.e2e.stories.js +45 -0
  271. package/src/single-select-field/single-select-field.js +178 -0
  272. package/src/single-select-field/single-select-field.prod.stories.js +146 -0
  273. package/src/single-select-option/index.js +1 -0
  274. package/src/single-select-option/single-select-option.js +84 -0
@@ -0,0 +1,53 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MultiSelect with hidden sibling elements', () => {
4
+ cy.visitStory('MultiSelect', 'Menu width changing')
5
+ cy.get('[data-test="dhis2-uicore-multiselect"]').then(($el) => {
6
+ cy.wrap($el.outerWidth()).as('originalWidth')
7
+ })
8
+ })
9
+ When('the button is clicked', () => {
10
+ cy.get('[data-test="dhis2-uicore-button"]').click()
11
+ })
12
+ Then('the siblings are displayed', () => {
13
+ cy.get('.toggler').should('exist').and('have.length', 2)
14
+ })
15
+ Then('the menu width has decreased', () => {
16
+ cy.get('[data-test="dhis2-uicore-multiselect"]').then(($el) => {
17
+ const newWidth = $el.outerWidth()
18
+ cy.get('@originalWidth').should('be.greaterThan', newWidth)
19
+ })
20
+ })
21
+ When('the MultiSelect menu is open', () => {
22
+ cy.get('[data-test="dhis2-uicore-select-menu-menuwrapper"]').should(
23
+ 'be.visible'
24
+ )
25
+ })
26
+ Then('the MultiSelect input is left aligned with the menu', () => {
27
+ const selectDataTest = '[data-test="dhis2-uicore-multiselect"]'
28
+ const menuDataTest = '[data-test="dhis2-uicore-select-menu-menuwrapper"]'
29
+
30
+ cy.getAll(selectDataTest, menuDataTest).should(([inputs, menus]) => {
31
+ expect(inputs.length).to.equal(1)
32
+ expect(menus.length).to.equal(1)
33
+
34
+ const $input = inputs[0]
35
+ const $menu = menus[0]
36
+
37
+ const inputRect = $input.getBoundingClientRect()
38
+ const menuRect = $menu.getBoundingClientRect()
39
+
40
+ expect(Math.round(inputRect.left)).to.equal(Math.round(menuRect.left))
41
+ })
42
+ })
43
+ When('the MultiSelect input and menu have the same width', () => {
44
+ const inputSelector = '[data-test="dhis2-uicore-select"]'
45
+ const menuSelector = '[data-test="dhis2-uicore-select-menu-menuwrapper"]'
46
+
47
+ cy.getAll(inputSelector, menuSelector).should(([$input, $menu]) => {
48
+ const inputWidth = Math.round($input.outerWidth())
49
+ const menuWidth = Math.round($menu.outerWidth())
50
+
51
+ expect(inputWidth).to.equal(menuWidth)
52
+ })
53
+ })
@@ -0,0 +1,11 @@
1
+ Feature: The menu always has the same size as the input
2
+
3
+ Scenario: The MultiSelect input changes width while the menu is closed
4
+ Given a MultiSelect with hidden sibling elements
5
+ When the button is clicked
6
+ Then the siblings are displayed
7
+ And the menu width has decreased
8
+ When the MultiSelect input is clicked
9
+ Then the MultiSelect menu is open
10
+ And the MultiSelect input is left aligned with the menu
11
+ And the MultiSelect input and menu have the same width
@@ -0,0 +1,160 @@
1
+ import { Given, Then, When } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given(
4
+ 'there is enough space below the anchor to fit the MultiSelect menu',
5
+ () => {
6
+ cy.visitStory('MultiSelect', 'Default position')
7
+ }
8
+ )
9
+
10
+ Given(
11
+ 'there is not enough space below the anchor to fit the MultiSelect menu',
12
+ () => {
13
+ cy.visitStory('MultiSelect', 'Flipped position')
14
+ }
15
+ )
16
+
17
+ Given(
18
+ 'there is not enough space above or below the anchor to fit the MultiSelect menu',
19
+ () => {
20
+ cy.visitStory('MultiSelect', 'Shifted into view')
21
+ }
22
+ )
23
+
24
+ Given('the input is empty', () => {
25
+ cy.get('[data-test="dhis2-uicore-select-input"]')
26
+ .should('exist')
27
+ .should('have.length', 1)
28
+ .then((inputs) => {
29
+ const $input = inputs[0]
30
+ const inputRect = $input.getBoundingClientRect()
31
+
32
+ return inputRect.height
33
+ })
34
+ .as('emptyInputHeight')
35
+
36
+ cy.get('[data-test="dhis2-uicore-select-input"] .root').should('be.empty')
37
+ })
38
+
39
+ When('the MultiSelect is clicked', () => {
40
+ cy.get('[data-test="dhis2-uicore-multiselect"]').click()
41
+ })
42
+
43
+ When('the window is scrolled down', () => {
44
+ cy.scrollTo(0, 800)
45
+ })
46
+
47
+ When('the window is resized to a greater width', () => {
48
+ cy.viewport(1200, 660)
49
+ })
50
+
51
+ When('an option is clicked', () => {
52
+ cy.contains('option one').click()
53
+ })
54
+
55
+ Then('the Input remains the same height', () => {
56
+ const emptyInputHeight = '@emptyInputHeight'
57
+ const inputDataTest = '[data-test="dhis2-uicore-select-input"]'
58
+
59
+ cy.getAll(emptyInputHeight, inputDataTest).should(
60
+ ([emptyInputHeight, inputs]) => {
61
+ expect(inputs.length).to.equal(1)
62
+
63
+ const $input = inputs[0]
64
+ const inputRect = $input.getBoundingClientRect()
65
+
66
+ expect(inputRect.height).to.equal(emptyInputHeight)
67
+ }
68
+ )
69
+ })
70
+
71
+ Then('the top of the menu is aligned with the bottom of the input', () => {
72
+ const selectDataTest = '[data-test="dhis2-uicore-multiselect"]'
73
+ const menuDataTest = '[data-test="dhis2-uicore-select-menu-menuwrapper"]'
74
+
75
+ cy.getAll(selectDataTest, menuDataTest).should(([selects, menus]) => {
76
+ expect(selects.length).to.equal(1)
77
+ expect(menus.length).to.equal(1)
78
+
79
+ const $select = selects[0]
80
+ const $menu = menus[0]
81
+
82
+ const selectRect = $select.getBoundingClientRect()
83
+ const menuRect = $menu.getBoundingClientRect()
84
+
85
+ expect(menuRect.top).to.be.closeTo(selectRect.bottom, 1)
86
+ })
87
+ })
88
+
89
+ Then('the bottom of the menu is aligned with the top of the input', () => {
90
+ const selectDataTest = '[data-test="dhis2-uicore-multiselect"]'
91
+ const menuDataTest = '[data-test="dhis2-uicore-select-menu-menuwrapper"]'
92
+
93
+ cy.getAll(selectDataTest, menuDataTest).should(([selects, menus]) => {
94
+ expect(selects.length).to.equal(1)
95
+ expect(menus.length).to.equal(1)
96
+
97
+ const $select = selects[0]
98
+ const $menu = menus[0]
99
+
100
+ const selectRect = $select.getBoundingClientRect()
101
+ const menuRect = $menu.getBoundingClientRect()
102
+
103
+ expect(selectRect.top).to.be.closeTo(menuRect.bottom, 1)
104
+ })
105
+ })
106
+
107
+ Then('it is rendered on top of the MultiSelect', () => {
108
+ const selectDataTest = '[data-test="dhis2-uicore-multiselect"]'
109
+ const menuDataTest = '[data-test="dhis2-uicore-select-menu-menuwrapper"]'
110
+
111
+ cy.getAll(selectDataTest, menuDataTest).should(([selects, menus]) => {
112
+ expect(selects.length).to.equal(1)
113
+ expect(menus.length).to.equal(1)
114
+
115
+ const $select = selects[0]
116
+ const $menu = menus[0]
117
+
118
+ const selectRect = $select.getBoundingClientRect()
119
+ const menuRect = $menu.getBoundingClientRect()
120
+
121
+ expect(selectRect.top).to.be.greaterThan(menuRect.top)
122
+ expect(menuRect.bottom).to.be.greaterThan(selectRect.bottom)
123
+ })
124
+ })
125
+
126
+ Then('the left of the Menu is aligned with the left of the Input', () => {
127
+ const selectDataTest = '[data-test="dhis2-uicore-multiselect"]'
128
+ const menuDataTest = '[data-test="dhis2-uicore-select-menu-menuwrapper"]'
129
+
130
+ cy.getAll(selectDataTest, menuDataTest).should(([selects, menus]) => {
131
+ expect(selects.length).to.equal(1)
132
+ expect(menus.length).to.equal(1)
133
+
134
+ const $select = selects[0]
135
+ const $menu = menus[0]
136
+
137
+ const selectRect = $select.getBoundingClientRect()
138
+ const menuRect = $menu.getBoundingClientRect()
139
+
140
+ expect(selectRect.left).to.be.closeTo(menuRect.left, 1)
141
+ })
142
+ })
143
+
144
+ Then('the Menu and the Input have an equal width', () => {
145
+ const inputDataTest = '[data-test="dhis2-uicore-multiselect"] .root-input'
146
+ const menuDataTest = '[data-test="dhis2-uicore-select-menu-menuwrapper"]'
147
+
148
+ cy.getAll(inputDataTest, menuDataTest).should(([inputs, menus]) => {
149
+ expect(inputs.length).to.equal(1)
150
+ expect(menus.length).to.equal(1)
151
+
152
+ const $input = inputs[0]
153
+ const $menu = menus[0]
154
+
155
+ const inputRect = $input.getBoundingClientRect()
156
+ const menuRect = $menu.getBoundingClientRect()
157
+
158
+ expect(inputRect.width).to.be.closeTo(menuRect.width, 1)
159
+ })
160
+ })
@@ -0,0 +1,46 @@
1
+ Feature: Position of MultiSelect menu dropdown
2
+
3
+ Scenario: Default rendering
4
+ Given there is enough space below the anchor to fit the MultiSelect menu
5
+ When the MultiSelect is clicked
6
+ Then the top of the menu is aligned with the bottom of the input
7
+ And the left of the Menu is aligned with the left of the Input
8
+
9
+ Scenario: Flipped rendering when insufficient space below
10
+ Given there is not enough space below the anchor to fit the MultiSelect menu
11
+ When the MultiSelect is clicked
12
+ Then the bottom of the menu is aligned with the top of the input
13
+ And the left of the Menu is aligned with the left of the Input
14
+
15
+ Scenario: Shifting into view when insufficient space below and above
16
+ Given there is not enough space above or below the anchor to fit the MultiSelect menu
17
+ When the MultiSelect is clicked
18
+ Then it is rendered on top of the MultiSelect
19
+ And the left of the Menu is aligned with the left of the Input
20
+
21
+ Scenario: Staying in position when the window is scrolled
22
+ Given there is enough space below the anchor to fit the MultiSelect menu
23
+ When the MultiSelect is clicked
24
+ And the window is scrolled down
25
+ Then the top of the menu is aligned with the bottom of the input
26
+ And the left of the Menu is aligned with the left of the Input
27
+
28
+ Scenario: Adusting the menu width when the window resizes
29
+ Given there is enough space below the anchor to fit the MultiSelect menu
30
+ When the MultiSelect is clicked
31
+ Then the left of the Menu is aligned with the left of the Input
32
+ And the Menu and the Input have an equal width
33
+ When the window is resized to a greater width
34
+ Then the left of the Menu is aligned with the left of the Input
35
+ And the Menu and the Input have an equal width
36
+
37
+ Scenario: Positioning the menu when an option is selected
38
+ Given a MultiSelect is rendered to which options can be added
39
+ And the input is empty
40
+ When the MultiSelect is clicked
41
+ Then the top of the menu is aligned with the bottom of the input
42
+ And the left of the Menu is aligned with the left of the Input
43
+ When an option is clicked
44
+ Then the Input remains the same height
45
+ And the top of the menu is aligned with the bottom of the input
46
+ And the left of the Menu is aligned with the left of the Input
@@ -0,0 +1,18 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MultiSelect with options and a selection is rendered', () => {
4
+ cy.visitStory('MultiSelect', 'With options and a selection')
5
+ })
6
+
7
+ Given('a MultiSelect with options and multiple selections is rendered', () => {
8
+ cy.visitStory('MultiSelect', 'With options and multiple selections')
9
+ })
10
+
11
+ Then('the selection is displayed', () => {
12
+ cy.contains('option one').should('be.visible')
13
+ })
14
+
15
+ Then('the selections are displayed', () => {
16
+ cy.contains('option one').should('be.visible')
17
+ cy.contains('option two').should('be.visible')
18
+ })
@@ -0,0 +1,9 @@
1
+ Feature: Show current selections
2
+
3
+ Scenario: The multiselect has a single selection
4
+ Given a MultiSelect with options and a selection is rendered
5
+ Then the selection is displayed
6
+
7
+ Scenario: The multiselect has multiple selections
8
+ Given a MultiSelect with options and multiple selections is rendered
9
+ Then the selections are displayed
@@ -0,0 +1,43 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import { FilterableMenu as CommonFilterableMenu } from '../select/index.js'
4
+ import { Menu } from './menu.js'
5
+
6
+ const FilterableMenu = ({
7
+ dataTest,
8
+ options,
9
+ onChange,
10
+ selected,
11
+ empty,
12
+ handleClose,
13
+ handleFocusInput,
14
+ placeholder,
15
+ noMatchText,
16
+ }) => (
17
+ <CommonFilterableMenu
18
+ dataTest={dataTest}
19
+ options={options}
20
+ onChange={onChange}
21
+ selected={selected}
22
+ empty={empty}
23
+ handleClose={handleClose}
24
+ handleFocusInput={handleFocusInput}
25
+ placeholder={placeholder}
26
+ noMatchText={noMatchText}
27
+ Menu={Menu}
28
+ />
29
+ )
30
+
31
+ FilterableMenu.propTypes = {
32
+ dataTest: PropTypes.string.isRequired,
33
+ noMatchText: PropTypes.string.isRequired,
34
+ empty: PropTypes.node,
35
+ handleClose: PropTypes.func,
36
+ handleFocusInput: PropTypes.func,
37
+ options: PropTypes.node,
38
+ placeholder: PropTypes.string,
39
+ selected: PropTypes.arrayOf(PropTypes.string),
40
+ onChange: PropTypes.func,
41
+ }
42
+
43
+ export { FilterableMenu }
@@ -0,0 +1 @@
1
+ export { MultiSelect } from './multi-select.js'
@@ -0,0 +1,129 @@
1
+ import { requiredIf } from '@dhis2/prop-types'
2
+ import { colors } from '@dhis2/ui-constants'
3
+ import cx from 'classnames'
4
+ import PropTypes from 'prop-types'
5
+ import React from 'react'
6
+ import i18n from '../locales/index.js'
7
+ import {
8
+ InputClearButton,
9
+ InputPlaceholder,
10
+ InputPrefix,
11
+ } from '../select/index.js'
12
+ import { SelectionList } from './selection-list.js'
13
+
14
+ const Input = ({
15
+ selected,
16
+ onChange,
17
+ clearable,
18
+ clearText,
19
+ placeholder,
20
+ dataTest,
21
+ prefix,
22
+ options,
23
+ className,
24
+ disabled,
25
+ inputMaxHeight = '100px',
26
+ collapseSelectionAfter,
27
+ }) => {
28
+ const hasSelection = selected.length > 0
29
+ const shouldCollapse =
30
+ hasSelection &&
31
+ typeof collapseSelectionAfter === 'number' &&
32
+ selected.length > collapseSelectionAfter
33
+ const onClear = (e) => {
34
+ const data = { selected: [] }
35
+
36
+ e.stopPropagation()
37
+ onChange(data, e)
38
+ }
39
+
40
+ return (
41
+ <div className={cx('root', className)}>
42
+ <InputPrefix prefix={prefix} dataTest={`${dataTest}-prefix`} />
43
+ {!hasSelection && !prefix && (
44
+ <InputPlaceholder
45
+ placeholder={placeholder}
46
+ dataTest={`${dataTest}-placeholder`}
47
+ />
48
+ )}
49
+ {hasSelection && shouldCollapse && (
50
+ <span
51
+ className="collapsed-selection-text"
52
+ data-test={`${dataTest}-selection-count`}
53
+ >
54
+ {i18n.t('{{count}} selected', { count: selected.length })}
55
+ </span>
56
+ )}
57
+ {hasSelection && !shouldCollapse && (
58
+ <div className="root-input">
59
+ {/* the wrapper div above is necessary to enforce wrapping on overflow */}
60
+ <SelectionList
61
+ selected={selected}
62
+ onChange={onChange}
63
+ options={options}
64
+ disabled={disabled}
65
+ />
66
+ </div>
67
+ )}
68
+ {hasSelection && clearable && !disabled && (
69
+ <div className="root-right">
70
+ <InputClearButton
71
+ onClear={onClear}
72
+ clearText={clearText}
73
+ dataTest={`${dataTest}-clear`}
74
+ />
75
+ </div>
76
+ )}
77
+
78
+ <style jsx>{`
79
+ .root {
80
+ display: flex;
81
+ align-items: center;
82
+ color: ${colors.grey900};
83
+ font-size: 14px;
84
+ line-height: 16px;
85
+ }
86
+
87
+ .root-input {
88
+ overflow-y: auto;
89
+ flex: 1;
90
+ display: flex;
91
+ flex-flow: wrap;
92
+ gap: 4px;
93
+ }
94
+
95
+ .root-right {
96
+ margin-inline-start: auto;
97
+ }
98
+
99
+ .collapsed-selection-text {
100
+ flex: 1;
101
+ user-select: none;
102
+ }
103
+ `}</style>
104
+
105
+ <style jsx>{`
106
+ .root-input {
107
+ max-height: ${inputMaxHeight};
108
+ }
109
+ `}</style>
110
+ </div>
111
+ )
112
+ }
113
+
114
+ Input.propTypes = {
115
+ dataTest: PropTypes.string.isRequired,
116
+ className: PropTypes.string,
117
+ clearText: requiredIf((props) => props.clearable, PropTypes.string),
118
+ clearable: PropTypes.bool,
119
+ collapseSelectionAfter: PropTypes.number,
120
+ disabled: PropTypes.bool,
121
+ inputMaxHeight: PropTypes.string,
122
+ options: PropTypes.node,
123
+ placeholder: PropTypes.string,
124
+ prefix: PropTypes.string,
125
+ selected: PropTypes.arrayOf(PropTypes.string),
126
+ onChange: PropTypes.func,
127
+ }
128
+
129
+ export { Input }
@@ -0,0 +1,91 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import {
4
+ Empty,
5
+ filterIgnored,
6
+ checkIfValidOption,
7
+ removeOption,
8
+ findOption,
9
+ } from '../select/index.js'
10
+
11
+ const onDisabledClick = (_, e) => {
12
+ e.stopPropagation()
13
+ e.preventDefault()
14
+ }
15
+
16
+ const createHandler =
17
+ ({ isActive, onChange, selected, value }) =>
18
+ (_, e) => {
19
+ e.stopPropagation()
20
+
21
+ // If the option is currently selected remove it from the array of selected options
22
+ if (isActive) {
23
+ const filtered = removeOption(value, selected)
24
+ const data = { selected: filtered }
25
+
26
+ return onChange(data, e)
27
+ }
28
+
29
+ // Otherwise, add it to selected
30
+ const data = {
31
+ selected: selected.concat([value]),
32
+ }
33
+ return onChange(data, e)
34
+ }
35
+
36
+ const Menu = ({ options, onChange, selected, empty = '', dataTest }) => {
37
+ const renderedOptions = filterIgnored(options)
38
+
39
+ if (React.Children.count(renderedOptions) === 0) {
40
+ // If it's a string, supply it to our <Empty> component so it looks better
41
+ if (typeof empty === 'string') {
42
+ return <Empty message={empty} dataTest={`${dataTest}-empty`} />
43
+ }
44
+
45
+ // Otherwise just render the supplied markup
46
+ return empty
47
+ }
48
+
49
+ const children = React.Children.map(options, (child) => {
50
+ const isValidOption = checkIfValidOption(child)
51
+
52
+ // Return early if the child isn't an option, to prevent attaching handlers etc.
53
+ if (!isValidOption) {
54
+ return child
55
+ }
56
+
57
+ const { value, label, disabled: isDisabled } = child.props
58
+
59
+ // Active means the option is currently selected
60
+ const isActive = !!findOption(value, selected)
61
+
62
+ // Create the appropriate click handler for the option
63
+ const onClick = isDisabled
64
+ ? onDisabledClick
65
+ : createHandler({
66
+ isActive,
67
+ onChange,
68
+ selected,
69
+ value,
70
+ label,
71
+ })
72
+
73
+ return React.cloneElement(child, {
74
+ ...child.props,
75
+ onClick,
76
+ active: isActive,
77
+ })
78
+ })
79
+
80
+ return <React.Fragment>{children}</React.Fragment>
81
+ }
82
+
83
+ Menu.propTypes = {
84
+ dataTest: PropTypes.string.isRequired,
85
+ empty: PropTypes.node,
86
+ options: PropTypes.node,
87
+ selected: PropTypes.arrayOf(PropTypes.string),
88
+ onChange: PropTypes.func,
89
+ }
90
+
91
+ export { Menu }