@dhis2-ui/select 10.16.1 → 10.16.3-alpha.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 (278) hide show
  1. package/build/cjs/select/select.js +5 -2
  2. package/build/cjs/simple-single-select/use-handle-key-press/use-handle-key-press-on-filter-input.js +2 -2
  3. package/build/es/select/select.js +5 -2
  4. package/build/es/simple-single-select/use-handle-key-press/use-handle-key-press-on-filter-input.js +2 -2
  5. package/package.json +17 -16
  6. package/src/index.js +9 -0
  7. package/src/locales/ar/translations.json +7 -0
  8. package/src/locales/ar_IQ/translations.json +7 -0
  9. package/src/locales/ckb/translations.json +7 -0
  10. package/src/locales/cs/translations.json +7 -0
  11. package/src/locales/da/translations.json +7 -0
  12. package/src/locales/en/translations.json +11 -0
  13. package/src/locales/en_US/translations.json +9 -0
  14. package/src/locales/es/translations.json +7 -0
  15. package/src/locales/es_419/translations.json +7 -0
  16. package/src/locales/fr/translations.json +7 -0
  17. package/src/locales/hi_IN/translations.json +7 -0
  18. package/src/locales/id/translations.json +7 -0
  19. package/src/locales/index.js +80 -0
  20. package/src/locales/km/translations.json +7 -0
  21. package/src/locales/lo/translations.json +7 -0
  22. package/src/locales/my/translations.json +7 -0
  23. package/src/locales/nb/translations.json +7 -0
  24. package/src/locales/nl/translations.json +7 -0
  25. package/src/locales/prs/translations.json +7 -0
  26. package/src/locales/ps/translations.json +7 -0
  27. package/src/locales/pt/translations.json +7 -0
  28. package/src/locales/pt_BR/translations.json +7 -0
  29. package/src/locales/ru/translations.json +7 -0
  30. package/src/locales/sv/translations.json +7 -0
  31. package/src/locales/tet/translations.json +7 -0
  32. package/src/locales/tg/translations.json +7 -0
  33. package/src/locales/uk/translations.json +7 -0
  34. package/src/locales/ur/translations.json +7 -0
  35. package/src/locales/uz_Latn/translations.json +7 -0
  36. package/src/locales/uz_UZ_Cyrl/translations.json +7 -0
  37. package/src/locales/uz_UZ_Latn/translations.json +7 -0
  38. package/src/locales/vi/translations.json +7 -0
  39. package/src/locales/zh/translations.json +9 -0
  40. package/src/locales/zh_CN/translations.json +7 -0
  41. package/src/multi-select/__tests__/multi-select.test.js +23 -0
  42. package/src/multi-select/__tests__/selection-list.test.js +102 -0
  43. package/src/multi-select/features/accepts_blur_cb/index.js +14 -0
  44. package/src/multi-select/features/accepts_blur_cb.feature +8 -0
  45. package/src/multi-select/features/accepts_focus_cb/index.js +14 -0
  46. package/src/multi-select/features/accepts_focus_cb.feature +6 -0
  47. package/src/multi-select/features/accepts_initial_focus/index.js +5 -0
  48. package/src/multi-select/features/accepts_initial_focus.feature +5 -0
  49. package/src/multi-select/features/accepts_loading/index.js +22 -0
  50. package/src/multi-select/features/accepts_loading.feature +14 -0
  51. package/src/multi-select/features/accepts_max_height/index.js +31 -0
  52. package/src/multi-select/features/accepts_max_height.feature +11 -0
  53. package/src/multi-select/features/accepts_placeholder/index.js +25 -0
  54. package/src/multi-select/features/accepts_placeholder.feature +10 -0
  55. package/src/multi-select/features/accepts_prefix/index.js +19 -0
  56. package/src/multi-select/features/accepts_prefix.feature +10 -0
  57. package/src/multi-select/features/allows_invalid_options/index.js +27 -0
  58. package/src/multi-select/features/allows_invalid_options.feature +12 -0
  59. package/src/multi-select/features/allows_selecting/index.js +81 -0
  60. package/src/multi-select/features/allows_selecting.feature +44 -0
  61. package/src/multi-select/features/can_be_cleared/index.js +22 -0
  62. package/src/multi-select/features/can_be_cleared.feature +6 -0
  63. package/src/multi-select/features/can_be_disabled/index.js +36 -0
  64. package/src/multi-select/features/can_be_disabled.feature +32 -0
  65. package/src/multi-select/features/can_be_empty/index.js +28 -0
  66. package/src/multi-select/features/can_be_empty.feature +16 -0
  67. package/src/multi-select/features/can_be_filtered/index.js +23 -0
  68. package/src/multi-select/features/can_be_filtered.feature +14 -0
  69. package/src/multi-select/features/can_be_opened_and_closed/index.js +32 -0
  70. package/src/multi-select/features/can_be_opened_and_closed.feature +41 -0
  71. package/src/multi-select/features/common/index.js +49 -0
  72. package/src/multi-select/features/duplicate_option_values/index.js +21 -0
  73. package/src/multi-select/features/duplicate_option_values.feature +7 -0
  74. package/src/multi-select/features/menu_width_matches_input/index.js +53 -0
  75. package/src/multi-select/features/menu_width_matches_input.feature +11 -0
  76. package/src/multi-select/features/position/index.js +160 -0
  77. package/src/multi-select/features/position.feature +46 -0
  78. package/src/multi-select/features/shows_selections/index.js +18 -0
  79. package/src/multi-select/features/shows_selections.feature +9 -0
  80. package/src/multi-select/filterable-menu.js +43 -0
  81. package/src/multi-select/index.js +1 -0
  82. package/src/multi-select/input.js +129 -0
  83. package/src/multi-select/menu.js +91 -0
  84. package/src/multi-select/multi-select.e2e.stories.js +407 -0
  85. package/src/multi-select/multi-select.js +148 -0
  86. package/src/multi-select/multi-select.prod.stories.js +408 -0
  87. package/src/multi-select/selection-list.js +79 -0
  88. package/src/multi-select-field/__tests__/multi-select-field.test.js +29 -0
  89. package/src/multi-select-field/features/accepts_help_text/index.js +9 -0
  90. package/src/multi-select-field/features/accepts_help_text.feature +5 -0
  91. package/src/multi-select-field/features/accepts_label/index.js +9 -0
  92. package/src/multi-select-field/features/accepts_label.feature +5 -0
  93. package/src/multi-select-field/features/accepts_validation_text/index.js +9 -0
  94. package/src/multi-select-field/features/accepts_validation_text.feature +5 -0
  95. package/src/multi-select-field/features/can_be_required/index.js +11 -0
  96. package/src/multi-select-field/features/can_be_required.feature +5 -0
  97. package/src/multi-select-field/features/has_default_clear_text/index.js +16 -0
  98. package/src/multi-select-field/features/has_default_clear_text.feature +6 -0
  99. package/src/multi-select-field/features/has_default_empty_text/index.js +13 -0
  100. package/src/multi-select-field/features/has_default_empty_text.feature +6 -0
  101. package/src/multi-select-field/features/has_default_filter_nomatch_text/index.js +17 -0
  102. package/src/multi-select-field/features/has_default_filter_nomatch_text.feature +7 -0
  103. package/src/multi-select-field/features/has_default_filter_placeholder/index.js +15 -0
  104. package/src/multi-select-field/features/has_default_filter_placeholder.feature +6 -0
  105. package/src/multi-select-field/features/has_default_loading_text/index.js +13 -0
  106. package/src/multi-select-field/features/has_default_loading_text.feature +6 -0
  107. package/src/multi-select-field/index.js +1 -0
  108. package/src/multi-select-field/multi-select-field.e2e.stories.js +45 -0
  109. package/src/multi-select-field/multi-select-field.js +178 -0
  110. package/src/multi-select-field/multi-select-field.prod.stories.js +137 -0
  111. package/src/multi-select-option/index.js +1 -0
  112. package/src/multi-select-option/multi-select-option.js +70 -0
  113. package/src/select/__tests__/select.test.js +31 -0
  114. package/src/select/debounce/debounce.js +30 -0
  115. package/src/select/debounce/debounce.spec.js +35 -0
  116. package/src/select/empty.js +28 -0
  117. package/src/select/filter-input.js +42 -0
  118. package/src/select/filterable-menu.js +106 -0
  119. package/src/select/index.js +14 -0
  120. package/src/select/input-clear-button.js +66 -0
  121. package/src/select/input-placeholder.js +30 -0
  122. package/src/select/input-prefix.js +32 -0
  123. package/src/select/input-wrapper.js +114 -0
  124. package/src/select/loading.js +33 -0
  125. package/src/select/menu-wrapper.js +52 -0
  126. package/src/select/no-match.js +28 -0
  127. package/src/select/option-helpers.js +34 -0
  128. package/src/select/select.js +256 -0
  129. package/src/simple-single-select/__stories__/DefaultPosition.js +37 -0
  130. package/src/simple-single-select/__stories__/Dense.js +17 -0
  131. package/src/simple-single-select/__stories__/Empty.js +15 -0
  132. package/src/simple-single-select/__stories__/EmptyWithEmptyNode.js +27 -0
  133. package/src/simple-single-select/__stories__/EmptyWithEmptyText.js +16 -0
  134. package/src/simple-single-select/__stories__/FlippedPosition.js +36 -0
  135. package/src/simple-single-select/__stories__/InfiniteLoading.js +65 -0
  136. package/src/simple-single-select/__stories__/Loading.js +15 -0
  137. package/src/simple-single-select/__stories__/ShiftedIntoView.js +35 -0
  138. package/src/simple-single-select/__stories__/WithClearButton.js +26 -0
  139. package/src/simple-single-select/__stories__/WithCustomLowMaxHeight.js +17 -0
  140. package/src/simple-single-select/__stories__/WithCustomOptions.js +78 -0
  141. package/src/simple-single-select/__stories__/WithDisabledOption.js +25 -0
  142. package/src/simple-single-select/__stories__/WithFilterField.js +30 -0
  143. package/src/simple-single-select/__stories__/WithInitialFocus.js +29 -0
  144. package/src/simple-single-select/__stories__/WithManyOptions.js +19 -0
  145. package/src/simple-single-select/__stories__/WithMultipleSelectsAndScrollableParent.js +65 -0
  146. package/src/simple-single-select/__stories__/WithNoMatchText.js +25 -0
  147. package/src/simple-single-select/__stories__/WithOnBlur.js +22 -0
  148. package/src/simple-single-select/__stories__/WithOnFocus.js +22 -0
  149. package/src/simple-single-select/__stories__/WithOptionsAndDisabled.js +17 -0
  150. package/src/simple-single-select/__stories__/WithOptionsAndLoading.js +23 -0
  151. package/src/simple-single-select/__stories__/WithOptionsAndLoadingText.js +24 -0
  152. package/src/simple-single-select/__stories__/WithPlaceholder.js +18 -0
  153. package/src/simple-single-select/__stories__/WithPlaceholderAndSelection.js +20 -0
  154. package/src/simple-single-select/__stories__/WithPrefix.js +17 -0
  155. package/src/simple-single-select/__stories__/WithPrefixAndSelection.js +20 -0
  156. package/src/simple-single-select/__stories__/WithRTL.js +29 -0
  157. package/src/simple-single-select/__stories__/WithScrollingParent.js +51 -0
  158. package/src/simple-single-select/__stories__/WithSelection.js +19 -0
  159. package/src/simple-single-select/__stories__/WithSelectionAndDisabled.js +20 -0
  160. package/src/simple-single-select/__stories__/WithServerSideFilteringDemo.js +245 -0
  161. package/src/simple-single-select/__stories__/WithSimpleFilteringDemo.js +50 -0
  162. package/src/simple-single-select/__stories__/WithoutOptionsAndLoading.js +16 -0
  163. package/src/simple-single-select/__stories__/WithoutSelection.js +17 -0
  164. package/src/simple-single-select/__stories__/options.js +96 -0
  165. package/src/simple-single-select/__stories__/simple-single-select-server-side-filtering/CustomDataProvider.js +70 -0
  166. package/src/simple-single-select/__stories__/simple-single-select-server-side-filtering/dataElements.js +4271 -0
  167. package/src/simple-single-select/__stories__/simple-single-select-server-side-filtering/index.js +1 -0
  168. package/src/simple-single-select/features/keyboard-interactions.test.e2e.js +581 -0
  169. package/src/simple-single-select/features/menu-positioning.test.e2e.js +187 -0
  170. package/src/simple-single-select/index.js +2 -0
  171. package/src/simple-single-select/is-option-hidden.js +21 -0
  172. package/src/simple-single-select/menu/empty.js +27 -0
  173. package/src/simple-single-select/menu/filter.js +61 -0
  174. package/src/simple-single-select/menu/index.js +1 -0
  175. package/src/simple-single-select/menu/loading.js +38 -0
  176. package/src/simple-single-select/menu/menu.js +171 -0
  177. package/src/simple-single-select/menu/no-match.js +28 -0
  178. package/src/simple-single-select/menu/option.js +161 -0
  179. package/src/simple-single-select/menu/options-list.js +107 -0
  180. package/src/simple-single-select/selected-value/clear-button.js +71 -0
  181. package/src/simple-single-select/selected-value/container.js +146 -0
  182. package/src/simple-single-select/selected-value/index.js +1 -0
  183. package/src/simple-single-select/selected-value/placeholder.js +24 -0
  184. package/src/simple-single-select/selected-value/prefix.js +26 -0
  185. package/src/simple-single-select/selected-value/selected-value.js +174 -0
  186. package/src/simple-single-select/shared-prop-types.js +8 -0
  187. package/src/simple-single-select/simple-single-select.e2e.stories.js +216 -0
  188. package/src/simple-single-select/simple-single-select.js +389 -0
  189. package/src/simple-single-select/simple-single-select.prod.stories.js +41 -0
  190. package/src/simple-single-select/simple-single-select.test.js +1136 -0
  191. package/src/simple-single-select/use-handle-key-press/index.js +2 -0
  192. package/src/simple-single-select/use-handle-key-press/use-focus-option.js +24 -0
  193. package/src/simple-single-select/use-handle-key-press/use-handle-key-press-on-combobox.js +259 -0
  194. package/src/simple-single-select/use-handle-key-press/use-handle-key-press-on-filter-input.js +147 -0
  195. package/src/simple-single-select/use-handle-key-press/use-handle-typing.js +79 -0
  196. package/src/simple-single-select/use-handle-key-press/use-highlight-first-option-on-previous-page.js +62 -0
  197. package/src/simple-single-select/use-handle-key-press/use-highlight-first-visible-option.js +38 -0
  198. package/src/simple-single-select/use-handle-key-press/use-highlight-last-option-on-next-page.js +60 -0
  199. package/src/simple-single-select/use-handle-key-press/use-highlight-last-visible-option.js +52 -0
  200. package/src/simple-single-select/use-handle-key-press/use-page-down.js +50 -0
  201. package/src/simple-single-select/use-handle-key-press/use-page-up.js +50 -0
  202. package/src/simple-single-select/use-handle-key-press/utils.js +23 -0
  203. package/src/simple-single-select-field/index.js +1 -0
  204. package/src/simple-single-select-field/simple-single-select-field.js +198 -0
  205. package/src/simple-single-select-field/simple-single-select-field.prod.stories.js +188 -0
  206. package/src/simple-single-select-field/simple-single-select-field.test.js +144 -0
  207. package/src/single-select/__tests__/selection.test.js +102 -0
  208. package/src/single-select/__tests__/single-select.test.js +23 -0
  209. package/src/single-select/features/accepts_blur_cb/index.js +14 -0
  210. package/src/single-select/features/accepts_blur_cb.feature +8 -0
  211. package/src/single-select/features/accepts_focus_cb/index.js +14 -0
  212. package/src/single-select/features/accepts_focus_cb.feature +6 -0
  213. package/src/single-select/features/accepts_initial_focus/index.js +5 -0
  214. package/src/single-select/features/accepts_initial_focus.feature +5 -0
  215. package/src/single-select/features/accepts_loading/index.js +22 -0
  216. package/src/single-select/features/accepts_loading.feature +14 -0
  217. package/src/single-select/features/accepts_max_height/index.js +31 -0
  218. package/src/single-select/features/accepts_max_height.feature +11 -0
  219. package/src/single-select/features/accepts_placeholder/index.js +25 -0
  220. package/src/single-select/features/accepts_placeholder.feature +10 -0
  221. package/src/single-select/features/accepts_prefix/index.js +19 -0
  222. package/src/single-select/features/accepts_prefix.feature +10 -0
  223. package/src/single-select/features/allows_invalid_options/index.js +28 -0
  224. package/src/single-select/features/allows_invalid_options.feature +12 -0
  225. package/src/single-select/features/allows_selecting/index.js +38 -0
  226. package/src/single-select/features/allows_selecting.feature +21 -0
  227. package/src/single-select/features/can_be_cleared/index.js +22 -0
  228. package/src/single-select/features/can_be_cleared.feature +6 -0
  229. package/src/single-select/features/can_be_disabled/index.js +39 -0
  230. package/src/single-select/features/can_be_disabled.feature +32 -0
  231. package/src/single-select/features/can_be_empty/index.js +28 -0
  232. package/src/single-select/features/can_be_empty.feature +16 -0
  233. package/src/single-select/features/can_be_filtered/index.js +23 -0
  234. package/src/single-select/features/can_be_filtered.feature +14 -0
  235. package/src/single-select/features/can_be_opened_and_closed/index.js +32 -0
  236. package/src/single-select/features/can_be_opened_and_closed.feature +41 -0
  237. package/src/single-select/features/common/index.js +44 -0
  238. package/src/single-select/features/duplicate_option_values/index.js +26 -0
  239. package/src/single-select/features/duplicate_option_values.feature +7 -0
  240. package/src/single-select/features/menu_width_matches_input/index.js +53 -0
  241. package/src/single-select/features/menu_width_matches_input.feature +11 -0
  242. package/src/single-select/features/position/index.js +111 -0
  243. package/src/single-select/features/position.feature +26 -0
  244. package/src/single-select/features/shows_selections/index.js +9 -0
  245. package/src/single-select/features/shows_selections.feature +5 -0
  246. package/src/single-select/filterable-menu.js +41 -0
  247. package/src/single-select/index.js +1 -0
  248. package/src/single-select/input.js +101 -0
  249. package/src/single-select/menu.js +75 -0
  250. package/src/single-select/selection.js +60 -0
  251. package/src/single-select/single-select.e2e.stories.js +376 -0
  252. package/src/single-select/single-select.js +146 -0
  253. package/src/single-select/single-select.prod.stories.js +358 -0
  254. package/src/single-select-field/__tests__/single-select-field.test.js +29 -0
  255. package/src/single-select-field/features/accepts_help_text/index.js +9 -0
  256. package/src/single-select-field/features/accepts_help_text.feature +5 -0
  257. package/src/single-select-field/features/accepts_label/index.js +9 -0
  258. package/src/single-select-field/features/accepts_label.feature +5 -0
  259. package/src/single-select-field/features/accepts_validation_text/index.js +9 -0
  260. package/src/single-select-field/features/accepts_validation_text.feature +5 -0
  261. package/src/single-select-field/features/can_be_required/index.js +11 -0
  262. package/src/single-select-field/features/can_be_required.feature +5 -0
  263. package/src/single-select-field/features/has_default_clear_text/index.js +16 -0
  264. package/src/single-select-field/features/has_default_clear_text.feature +6 -0
  265. package/src/single-select-field/features/has_default_empty_text/index.js +13 -0
  266. package/src/single-select-field/features/has_default_empty_text.feature +6 -0
  267. package/src/single-select-field/features/has_default_filter_nomatch_text/index.js +17 -0
  268. package/src/single-select-field/features/has_default_filter_nomatch_text.feature +7 -0
  269. package/src/single-select-field/features/has_default_filter_placeholder/index.js +15 -0
  270. package/src/single-select-field/features/has_default_filter_placeholder.feature +6 -0
  271. package/src/single-select-field/features/has_default_loading_text/index.js +13 -0
  272. package/src/single-select-field/features/has_default_loading_text.feature +6 -0
  273. package/src/single-select-field/index.js +1 -0
  274. package/src/single-select-field/single-select-field.e2e.stories.js +45 -0
  275. package/src/single-select-field/single-select-field.js +178 -0
  276. package/src/single-select-field/single-select-field.prod.stories.js +146 -0
  277. package/src/single-select-option/index.js +1 -0
  278. package/src/single-select-option/single-select-option.js +84 -0
@@ -0,0 +1,14 @@
1
+ Feature: Filtering the MultiSelect options
2
+
3
+ Scenario: The user enters a filter string to filter the options
4
+ Given a filterable MultiSelect with options is rendered
5
+ And the MultiSelect is open
6
+ When the user enters a filter string
7
+ Then the matching options are displayed
8
+
9
+ Scenario: The user enters a filter string that doesn't match any options
10
+ Given a filterable MultiSelect with options is rendered
11
+ And the MultiSelect is open
12
+ When the user enters a filter string that doesn't match any options
13
+ Then the no match text is displayed
14
+ And the options are not rendered
@@ -0,0 +1,32 @@
1
+ import { Given, When } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MultiSelect with options is rendered', () => {
4
+ cy.visitStory('MultiSelect', 'With options')
5
+ })
6
+
7
+ Given('the MultiSelect is closed', () => {
8
+ cy.contains('option one').should('not.exist')
9
+ cy.contains('option two').should('not.exist')
10
+ cy.contains('option three').should('not.exist')
11
+ })
12
+
13
+ Given('the MultiSelect is focused', () => {
14
+ cy.get('[data-test="dhis2-uicore-select-input"]').focus()
15
+ cy.focused().should('have.attr', 'data-test', 'dhis2-uicore-select-input')
16
+ })
17
+
18
+ When('the down arrowkey is pressed on the focused element', () => {
19
+ cy.focused().type('{downarrow}')
20
+ })
21
+
22
+ When('the spacebar is pressed on the focused element', () => {
23
+ cy.focused().type(' ')
24
+ })
25
+
26
+ When('the up arrowkey is pressed on the focused element', () => {
27
+ cy.focused().type('{uparrow}')
28
+ })
29
+
30
+ When('the escape key is pressed on the focused element', () => {
31
+ cy.focused().type('{esc}')
32
+ })
@@ -0,0 +1,41 @@
1
+ Feature: Opening and closing the MultiSelect
2
+
3
+ Scenario: The user clicks the MultiSelect input to display the options
4
+ Given a MultiSelect with options is rendered
5
+ And the MultiSelect is closed
6
+ When the MultiSelect input is clicked
7
+ Then the options are displayed
8
+
9
+ Scenario: The user presses the down arrowkey to display the options
10
+ Given a MultiSelect with options is rendered
11
+ And the MultiSelect is closed
12
+ And the MultiSelect is focused
13
+ When the down arrowkey is pressed on the focused element
14
+ Then the options are displayed
15
+
16
+ Scenario: The user presses the up arrowkey to display the options
17
+ Given a MultiSelect with options is rendered
18
+ And the MultiSelect is closed
19
+ And the MultiSelect is focused
20
+ When the up arrowkey is pressed on the focused element
21
+ Then the options are displayed
22
+
23
+ Scenario: The user presses the spacebar to display the options
24
+ Given a MultiSelect with options is rendered
25
+ And the MultiSelect is closed
26
+ And the MultiSelect is focused
27
+ When the spacebar is pressed on the focused element
28
+ Then the options are displayed
29
+
30
+ Scenario: The user clicks the backdrop layer to hide the options
31
+ Given a MultiSelect with options is rendered
32
+ And the MultiSelect is open
33
+ When the user clicks the backdrop layer
34
+ Then the options are not rendered
35
+
36
+ Scenario: The user presses the escape key to hide the options
37
+ Given a MultiSelect with options is rendered
38
+ And the MultiSelect is open
39
+ And the MultiSelect is focused
40
+ When the escape key is pressed on the focused element
41
+ Then the options are not rendered
@@ -0,0 +1,49 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MultiSelect with options and onChange handler is rendered', () => {
4
+ cy.visitStory('MultiSelect', 'With options and on change')
5
+ })
6
+
7
+ Given(
8
+ 'a MultiSelect with options, a selection and onChange handler is rendered',
9
+ () => {
10
+ cy.visitStory('MultiSelect', 'With options, a selection and on change')
11
+ }
12
+ )
13
+
14
+ Given('a MultiSelect is rendered to which options can be added', () => {
15
+ cy.visitStory('MultiSelect', 'With options that can be added to the input')
16
+ cy.get('[data-test="dhis2-uicore-multiselect"]').should('exist')
17
+ })
18
+
19
+ Given('the MultiSelect is open', () => {
20
+ cy.get('[data-test="dhis2-uicore-select-input"]').click()
21
+
22
+ cy.contains('option one').should('be.visible')
23
+ cy.contains('option two').should('be.visible')
24
+ cy.contains('option three').should('be.visible')
25
+ })
26
+
27
+ When('the MultiSelect input is clicked', () => {
28
+ cy.get('[data-test="dhis2-uicore-select-input"]').click()
29
+ })
30
+
31
+ When('the user clicks the backdrop layer', () => {
32
+ cy.get('[data-test="dhis2-uicore-layer"]').click()
33
+ })
34
+
35
+ Then('the options are not rendered', () => {
36
+ cy.contains('option one').should('not.exist')
37
+ cy.contains('option two').should('not.exist')
38
+ cy.contains('option three').should('not.exist')
39
+ })
40
+
41
+ Then('the options are displayed', () => {
42
+ cy.contains('option one').should('be.visible')
43
+ cy.contains('option two').should('be.visible')
44
+ cy.contains('option three').should('be.visible')
45
+ })
46
+
47
+ Then('the MultiSelect has focus', () => {
48
+ cy.focused().should('have.attr', 'data-test', 'dhis2-uicore-select-input')
49
+ })
@@ -0,0 +1,21 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given(
4
+ 'a MultiSelect with options with a duplicate value and this value is selected',
5
+ () => {
6
+ cy.visitStory('MultiSelect', 'With duplicate selected option values')
7
+ }
8
+ )
9
+ Then(
10
+ 'the first option with the selected value is displayed in the input',
11
+ () => {
12
+ cy.get('[data-test="dhis2-uicore-select-input"]').should(
13
+ 'contain',
14
+ 'option one'
15
+ )
16
+ }
17
+ )
18
+ Then('both options are highlighted in the dropdown', () => {
19
+ cy.get('input[name="option one"]').should('have.attr', 'checked')
20
+ cy.get('input[name="option one a"]').should('have.attr', 'checked')
21
+ })
@@ -0,0 +1,7 @@
1
+ Feature: Duplicate option values result in a conflict between the input and dropdown UI
2
+
3
+ Scenario: The MultiSelect has options with a duplicate value and this value is selected
4
+ Given a MultiSelect with options with a duplicate value and this value is selected
5
+ And the MultiSelect is open
6
+ Then the first option with the selected value is displayed in the input
7
+ But both options are highlighted in the dropdown
@@ -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 }