@dhis2-ui/transfer 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 (109) hide show
  1. package/package.json +10 -9
  2. package/src/__e2e__/add_remove-highlighted-options.e2e.stories.js +30 -0
  3. package/src/__e2e__/common/options.js +90 -0
  4. package/src/__e2e__/common/stateful-decorator.js +33 -0
  5. package/src/__e2e__/common.js +0 -0
  6. package/src/__e2e__/disabled-transfer-buttons.e2e.stories.js +49 -0
  7. package/src/__e2e__/disabled-transfer-options.e2e.stories.js +21 -0
  8. package/src/__e2e__/display-order.e2e.stories.js +24 -0
  9. package/src/__e2e__/filter-options-list.e2e.stories.js +87 -0
  10. package/src/__e2e__/highlight-range-of-options.e2e.stories.js +52 -0
  11. package/src/__e2e__/loading_lists.e2e.stories.js +26 -0
  12. package/src/__e2e__/notify_at_end_of_list.e2e.stories.js +116 -0
  13. package/src/__e2e__/reorder-with-buttons.e2e.stories.js +35 -0
  14. package/src/__e2e__/set_unset-highlighted-option.e2e.stories.js +30 -0
  15. package/src/__e2e__/transferring-items.e2e.stories.js +27 -0
  16. package/src/__tests__/common.test.js +131 -0
  17. package/src/__tests__/helper/add-all-selectable-source-options.test.js +46 -0
  18. package/src/__tests__/helper/add-individual-source-options.test.js +80 -0
  19. package/src/__tests__/helper/default-filter-callback.test.js +45 -0
  20. package/src/__tests__/helper/is-reorder-down-disabled.test.js +96 -0
  21. package/src/__tests__/helper/is-reorder-up-disabled.test.js +96 -0
  22. package/src/__tests__/helper/move-highlighted-picked-option-down.test.js +111 -0
  23. package/src/__tests__/helper/move-highlighted-picked-option-to-bottom.test.js +101 -0
  24. package/src/__tests__/helper/move-highlighted-picked-option-to-top.test.js +101 -0
  25. package/src/__tests__/helper/move-highlighted-picked-option-up.test.js +111 -0
  26. package/src/__tests__/helper/remove-all-picked-options.test.js +29 -0
  27. package/src/__tests__/helper/remove-individual-picked-options.test.js +38 -0
  28. package/src/__tests__/helper/use-highlighted-option/create-toggle-highlighted-option.test.js +104 -0
  29. package/src/__tests__/helper/use-highlighted-option/toggle-add.test.js +84 -0
  30. package/src/__tests__/helper/use-highlighted-option/toggle-range.test.js +150 -0
  31. package/src/__tests__/helper/use-highlighted-option/toggle-replace.test.js +39 -0
  32. package/src/__tests__/helper/use-highlighted-option.test.js +41 -0
  33. package/src/__tests__/reordering-actions.test.js +165 -0
  34. package/src/__tests__/transfer.test.js +137 -0
  35. package/src/actions.js +33 -0
  36. package/src/add-all.js +27 -0
  37. package/src/add-individual.js +27 -0
  38. package/src/common/find-option-index.js +9 -0
  39. package/src/common/get-mode-by-modifier-key.js +35 -0
  40. package/src/common/index.js +5 -0
  41. package/src/common/is-option.js +7 -0
  42. package/src/common/modes.js +11 -0
  43. package/src/common/remove-option.js +19 -0
  44. package/src/common/toggle-value.js +18 -0
  45. package/src/container.js +23 -0
  46. package/src/end-intersection-detector.js +37 -0
  47. package/src/features/add_remove-highlighted-options/index.js +92 -0
  48. package/src/features/add_remove-highlighted-options.feature +41 -0
  49. package/src/features/common/index.js +8 -0
  50. package/src/features/disabled-transfer-buttons/index.js +118 -0
  51. package/src/features/disabled-transfer-buttons.feature +46 -0
  52. package/src/features/disabled-transfer-options/index.js +182 -0
  53. package/src/features/disabled-transfer-options.feature +42 -0
  54. package/src/features/display-order/index.js +205 -0
  55. package/src/features/display-order.feature +30 -0
  56. package/src/features/filter-options-list/index.js +133 -0
  57. package/src/features/filter-options-list.feature +40 -0
  58. package/src/features/highlight-range-of-options/index.js +336 -0
  59. package/src/features/highlight-range-of-options.feature +70 -0
  60. package/src/features/loading_lists/index.js +43 -0
  61. package/src/features/loading_lists.feature +19 -0
  62. package/src/features/notify_at_end_of_list/index.js +125 -0
  63. package/src/features/notify_at_end_of_list.feature +64 -0
  64. package/src/features/reorder-with-buttons/index.js +181 -0
  65. package/src/features/reorder-with-buttons.feature +138 -0
  66. package/src/features/set_unset-highlighted-option/index.js +121 -0
  67. package/src/features/set_unset-highlighted-option.feature +42 -0
  68. package/src/features/transferring-items/index.js +375 -0
  69. package/src/features/transferring-items.feature +44 -0
  70. package/src/filter.js +38 -0
  71. package/src/icons.js +194 -0
  72. package/src/index.js +2 -0
  73. package/src/left-footer.js +22 -0
  74. package/src/left-header.js +22 -0
  75. package/src/left-side.js +34 -0
  76. package/src/locales/en/translations.json +7 -0
  77. package/src/locales/index.js +16 -0
  78. package/src/options-container.js +127 -0
  79. package/src/remove-all.js +27 -0
  80. package/src/remove-individual.js +27 -0
  81. package/src/reordering-actions.js +136 -0
  82. package/src/right-footer.js +22 -0
  83. package/src/right-header.js +22 -0
  84. package/src/right-side.js +33 -0
  85. package/src/transfer/add-all-selectable-source-options.js +37 -0
  86. package/src/transfer/add-individual-source-options.js +61 -0
  87. package/src/transfer/create-double-click-handlers.js +36 -0
  88. package/src/transfer/default-filter-callback.js +17 -0
  89. package/src/transfer/get-highlighted-picked-indices.js +26 -0
  90. package/src/transfer/get-option-click-handlers.js +19 -0
  91. package/src/transfer/index.js +17 -0
  92. package/src/transfer/is-reorder-down-disabled.js +34 -0
  93. package/src/transfer/is-reorder-up-disabled.js +30 -0
  94. package/src/transfer/move-highlighted-picked-option-down.js +54 -0
  95. package/src/transfer/move-highlighted-picked-option-to-bottom.js +44 -0
  96. package/src/transfer/move-highlighted-picked-option-to-top.js +38 -0
  97. package/src/transfer/move-highlighted-picked-option-up.js +47 -0
  98. package/src/transfer/remove-all-picked-options.js +13 -0
  99. package/src/transfer/remove-individual-picked-options.js +49 -0
  100. package/src/transfer/use-filter.js +17 -0
  101. package/src/transfer/use-highlighted-options/create-toggle-highlighted-option.js +64 -0
  102. package/src/transfer/use-highlighted-options/toggle-add.js +20 -0
  103. package/src/transfer/use-highlighted-options/toggle-range.js +61 -0
  104. package/src/transfer/use-highlighted-options/toggle-replace.js +26 -0
  105. package/src/transfer/use-highlighted-options.js +34 -0
  106. package/src/transfer/use-options-key-monitor.js +41 -0
  107. package/src/transfer-option.js +91 -0
  108. package/src/transfer.js +539 -0
  109. package/src/transfer.prod.stories.js +621 -0
@@ -0,0 +1,621 @@
1
+ import { SingleSelectField, SingleSelectOption } from '@dhis2-ui/select'
2
+ import { Tab, TabBar } from '@dhis2-ui/tab'
3
+ import PropTypes from 'prop-types'
4
+ import React, { useCallback, useEffect, useMemo, useState } from 'react'
5
+ import { TransferOption } from './transfer-option.js'
6
+ import { Transfer } from './transfer.js'
7
+
8
+ const subtitle = 'Allows users to select options from a list'
9
+
10
+ const description = `
11
+ #### Usage
12
+
13
+ Use a transfer component wherever a user needs to make a complex selection. Simple selections can be achieved with checkboxes, radio buttons or a select.
14
+
15
+ There are use-cases that are particularly suitable for a transfer component:
16
+
17
+ - when a user needs to select some options from several different groups at the same time
18
+ - if the selection needs to have a defined order
19
+ - when the user will be interacting with and editing the selection often
20
+ - if a user needs to easily compare non-selected and selected options
21
+ - if a user is making selections as part of a complex flow, especially where there are many options to choose from
22
+
23
+ #### Terminology
24
+
25
+ This component has to differentiate between different types of options,
26
+ here's an explanation of their meaning:
27
+
28
+ - source options: These are options listed on the left and are available for selection
29
+ - picked options: These options have been selected by the user and are on the right side
30
+ - highlighted option: These are visually highlighted options than can be on either side and are ready for transferral with the action buttons to the other side
31
+ - filtered options: These are the displayed source options filtered by a search term or a custom search algorithm. The api surface uses "selected" for "picked" to be consistent with the rest of the library
32
+
33
+ #### More details
34
+
35
+ See more about the options available for Transfers at [Design System: Transfer](https://github.com/dhis2/design-system/blob/master/organisms/transfer.md#transfer).
36
+
37
+ \`\`\`js
38
+ import { Transfer, TransferOption } from '@dhis2/ui'
39
+ \`\`\`
40
+ `
41
+
42
+ const options = [
43
+ {
44
+ label: 'ANC 1st visit',
45
+ value: 'anc_1st_visit',
46
+ },
47
+ {
48
+ label: 'ANC 2nd visit',
49
+ value: 'anc_2nd_visit',
50
+ },
51
+ {
52
+ label: 'ANC 3rd visit',
53
+ value: 'anc_3rd_visit',
54
+ },
55
+ {
56
+ label: 'ANC 4th or more visits',
57
+ value: 'anc_4th_or_more_visits',
58
+ },
59
+ {
60
+ label: 'ARI treated with antibiotics (pneumonia) follow-up',
61
+ value: 'ari_treated_with_antibiotics_(pneumonia)_follow-up',
62
+ },
63
+ {
64
+ label: 'ARI treated with antibiotics (pneumonia) new',
65
+ value: 'ari_treated_with_antibiotics_(pneumonia)_new',
66
+ },
67
+ {
68
+ label: 'ARI treated with antibiotics (pneumonia) referrals',
69
+ value: 'ari_treated_with_antibiotics_(pneumonia)_referrals',
70
+ },
71
+ {
72
+ label: 'ARI treated without antibiotics (cough) follow-up',
73
+ value: 'ari_treated_without_antibiotics_(cough)_follow-up',
74
+ },
75
+ {
76
+ label: 'ARI treated without antibiotics (cough) new',
77
+ value: 'ari_treated_without_antibiotics_(cough)_new',
78
+ },
79
+ {
80
+ label: 'ARI treated without antibiotics (cough) referrals',
81
+ value: 'ari_treated_without_antibiotics_(cough)_referrals',
82
+ },
83
+ {
84
+ label: 'ART No clients who stopped TRT due to TRT failure',
85
+ value: 'art_no_clients_who_stopped_trt_due_to_trt_failure',
86
+ },
87
+ {
88
+ label: 'ART No clients who stopped TRT due to adverse clinical status/event',
89
+ value: 'art_no_clients_who_stopped_trt_due_to_adverse_clinical_status/event',
90
+ },
91
+ {
92
+ label: 'ART No clients with change of regimen due to drug toxicity',
93
+ value: 'art_no_clients_with_change_of_regimen_due_to_drug_toxicity',
94
+ },
95
+ {
96
+ label: 'ART No clients with new adverse drug reaction',
97
+ value: 'art_no_clients_with_new_adverse_drug_reaction',
98
+ },
99
+ {
100
+ label: 'ART No started Opportunist Infection prophylaxis',
101
+ value: 'art_no_started_opportunist_infection_prophylaxis',
102
+ },
103
+ {
104
+ label: 'ART clients with new adverse clinical event',
105
+ value: 'art_clients_with_new_adverse_clinical_event',
106
+ },
107
+ {
108
+ label: 'ART defaulters',
109
+ value: 'art_defaulters',
110
+ },
111
+ {
112
+ label: 'ART enrollment stage 1',
113
+ value: 'art_enrollment_stage_1',
114
+ },
115
+ {
116
+ label: 'ART enrollment stage 2',
117
+ value: 'art_enrollment_stage_2',
118
+ },
119
+ {
120
+ label: 'ART enrollment stage 3',
121
+ value: 'art_enrollment_stage_3',
122
+ },
123
+ {
124
+ label: 'ART enrollment stage 4',
125
+ value: 'art_enrollment_stage_4',
126
+ },
127
+ {
128
+ label: 'ART entry point: No PMTCT',
129
+ value: 'art_entry_point:_no_pmtct',
130
+ },
131
+ ]
132
+
133
+ /**
134
+ * Default args are needed because storybook currently struggles with
135
+ * functions as default props: they are sent to the component as strings when
136
+ * `{...args}` is spread into the component in the Template, which causes an
137
+ * error that looks like 'renderOption is not a function'
138
+ *
139
+ * https://github.com/storybookjs/storybook/issues/12455#issuecomment-702763930
140
+ */
141
+ export default {
142
+ title: 'Transfer',
143
+ component: Transfer,
144
+ parameters: {
145
+ componentSubtitle: subtitle,
146
+ docs: {
147
+ description: { component: description },
148
+ source: { type: 'code' },
149
+ },
150
+ },
151
+ // Default args:
152
+ args: {
153
+ dataTest: 'dhis2-uicore-transfer',
154
+ height: '240px',
155
+ initialSearchTerm: '',
156
+ initialSearchTermPicked: '',
157
+ maxSelections: Infinity,
158
+ optionsWidth: '320px',
159
+ selected: [],
160
+ selectedWidth: '320px',
161
+ options: options,
162
+ },
163
+ }
164
+
165
+ const StatefulTemplate = ({ initiallySelected = [], ...args }) => {
166
+ const [selected, setSelected] = useState(initiallySelected)
167
+ const onChange = (payload) => setSelected(payload.selected)
168
+
169
+ return <Transfer {...args} selected={selected} onChange={onChange} />
170
+ }
171
+ StatefulTemplate.propTypes = { initiallySelected: PropTypes.array }
172
+
173
+ export const SingleSelection = StatefulTemplate.bind({})
174
+ SingleSelection.args = { maxSelections: 1 }
175
+
176
+ export const Multiple = StatefulTemplate.bind({})
177
+ Multiple.args = { options: options.slice(0, 3) }
178
+
179
+ export const Header = StatefulTemplate.bind({})
180
+ Header.args = {
181
+ leftHeader: <h3>Header on the left side</h3>,
182
+ rightHeader: <h4>Header on the right side</h4>,
183
+ }
184
+
185
+ export const OptionsFooter = StatefulTemplate.bind({})
186
+ OptionsFooter.args = {
187
+ leftFooter: (
188
+ <a
189
+ href="#"
190
+ style={{
191
+ color: 'grey',
192
+ padding: '8px 0',
193
+ display: 'block',
194
+ }}
195
+ >
196
+ Reload list
197
+ </a>
198
+ ),
199
+ }
200
+
201
+ export const Filtered = StatefulTemplate.bind({})
202
+ Filtered.args = {
203
+ filterable: true,
204
+ initialSearchTerm: 'ANC',
205
+ }
206
+
207
+ export const FilteredPicked = StatefulTemplate.bind({})
208
+ FilteredPicked.args = {
209
+ filterablePicked: true,
210
+ initialSearchTermPicked: 'ANC',
211
+ initiallySelected: options.map(({ value }) => value),
212
+ }
213
+
214
+ export const FilteredPlaceholder = StatefulTemplate.bind({})
215
+ FilteredPlaceholder.args = {
216
+ filterable: true,
217
+ filterLabel: 'Filter with placeholder',
218
+ filterPlaceholder: 'Search',
219
+ }
220
+
221
+ const renderOption = ({ label, value, onClick, highlighted, selected }) => (
222
+ <p
223
+ onClick={(event) => onClick({ label, value }, event)}
224
+ style={{
225
+ background: highlighted ? 'green' : 'blue',
226
+ color: selected ? 'orange' : 'white',
227
+ }}
228
+ >
229
+ Custom: {label} (label), {value} (value)
230
+ </p>
231
+ )
232
+
233
+ const RenderOptionCode = () => (
234
+ <>
235
+ <strong>Custom option code:</strong>
236
+ <code>
237
+ <pre>{`const renderOption = ({ label, value, onClick, highlighted, selected }) => (
238
+ <p
239
+ onClick={event => onClick({ label, value }, event)}
240
+ style={{
241
+ background: highlighted ? 'green' : 'blue',
242
+ color: selected ? 'orange' : 'white',
243
+ }}
244
+ >
245
+ Custom: {label} (label), {value} (value)
246
+ </p>
247
+ )`}</pre>
248
+ </code>
249
+ </>
250
+ )
251
+
252
+ const StatefulTemplateCustomRenderOption = ({
253
+ initiallySelected = [],
254
+ ...args
255
+ }) => {
256
+ const [selected, setSelected] = useState(initiallySelected)
257
+ const onChange = (payload) => setSelected(payload.selected)
258
+
259
+ return <Transfer {...args} selected={selected} onChange={onChange} />
260
+ }
261
+ StatefulTemplateCustomRenderOption.propTypes = {
262
+ initiallySelected: PropTypes.array,
263
+ }
264
+
265
+ export const CustomListOptions = (args) => (
266
+ <>
267
+ <RenderOptionCode />
268
+ <StatefulTemplateCustomRenderOption {...args} />
269
+ </>
270
+ )
271
+ CustomListOptions.args = {
272
+ renderOption,
273
+ options: options.slice(0, 2),
274
+ initiallySelected: options.slice(0, 2).map(({ value }) => value),
275
+ }
276
+
277
+ export const IndividualCustomOption = StatefulTemplateCustomRenderOption.bind(
278
+ {}
279
+ )
280
+ IndividualCustomOption.args = {
281
+ addAllText: 'Add all',
282
+ addIndividualText: 'Add individual',
283
+ removeAllText: 'Remove all',
284
+ removeIndividualText: 'Remove individual',
285
+ renderOption: (option) => {
286
+ if (option.value === options[0].value) {
287
+ return renderOption(option)
288
+ }
289
+
290
+ return <TransferOption {...option} />
291
+ },
292
+ }
293
+
294
+ export const CustomButtonText = StatefulTemplate.bind({})
295
+ CustomButtonText.args = {
296
+ addAllText: 'Add all',
297
+ addIndividualText: 'Add individual',
298
+ removeAllText: 'Remove all',
299
+ removeIndividualText: 'Remove individual',
300
+ }
301
+
302
+ export const SourceEmptyPlaceholder = StatefulTemplate.bind({})
303
+ SourceEmptyPlaceholder.args = {
304
+ sourceEmptyPlaceholder: (
305
+ <p style={{ textAlign: 'center' }}>
306
+ No options found.
307
+ <br />
308
+ <a href="#" style={{ color: 'grey' }}>
309
+ Add option
310
+ </a>
311
+ </p>
312
+ ),
313
+ options: [],
314
+ }
315
+
316
+ export const PickedEmptyComponent = StatefulTemplate.bind({})
317
+ PickedEmptyComponent.args = {
318
+ selectedEmptyComponent: (
319
+ <p style={{ textAlign: 'center' }}>
320
+ You have not selected anything yet
321
+ <br />
322
+ </p>
323
+ ),
324
+ }
325
+
326
+ export const Reordering = StatefulTemplate.bind({})
327
+ Reordering.args = {
328
+ enableOrderChange: true,
329
+ options: options.slice(0, 20),
330
+ initiallySelected: options.slice(0, 8).map(({ value }) => value),
331
+ }
332
+
333
+ export const ReorderingWithPickedFilter = StatefulTemplate.bind({})
334
+ ReorderingWithPickedFilter.storyName = 'Reordering with picked filter'
335
+ ReorderingWithPickedFilter.args = {
336
+ enableOrderChange: true,
337
+ filterablePicked: true,
338
+ filterPlaceholderPicked: 'Search picked options',
339
+ options: options.slice(0, 20),
340
+ initiallySelected: options.slice(0, 8).map(({ value }) => value),
341
+ }
342
+
343
+ const ReorderingWithRightFooterTemplate = ({
344
+ initiallySelected = [],
345
+ ...args
346
+ }) => {
347
+ const [selected, setSelected] = useState(initiallySelected)
348
+ const onChange = (payload) => setSelected(payload.selected)
349
+
350
+ return (
351
+ <Transfer
352
+ {...args}
353
+ selected={selected}
354
+ onChange={onChange}
355
+ rightFooter={
356
+ <span style={{ padding: '0 8px' }}>Right footer content</span>
357
+ }
358
+ />
359
+ )
360
+ }
361
+ ReorderingWithRightFooterTemplate.propTypes = {
362
+ initiallySelected: PropTypes.array,
363
+ }
364
+
365
+ export const ReorderingWithRightFooterContent =
366
+ ReorderingWithRightFooterTemplate.bind({})
367
+ ReorderingWithRightFooterContent.storyName =
368
+ 'Reordering with right footer content'
369
+ ReorderingWithRightFooterContent.args = {
370
+ enableOrderChange: true,
371
+ options: options.slice(0, 20),
372
+ initiallySelected: options.slice(0, 8).map(({ value }) => value),
373
+ }
374
+
375
+ export const ReorderingWithCustomOptions =
376
+ StatefulTemplateCustomRenderOption.bind({})
377
+ ReorderingWithCustomOptions.storyName = 'Reordering with custom options'
378
+ ReorderingWithCustomOptions.args = {
379
+ enableOrderChange: true,
380
+ options: options.slice(0, 20),
381
+ initiallySelected: options.slice(0, 8).map(({ value }) => value),
382
+ renderOption: (option) => {
383
+ if (option.value === options[0].value) {
384
+ return renderOption(option)
385
+ }
386
+
387
+ if (option.value === options[2].value) {
388
+ return renderOption(option)
389
+ }
390
+
391
+ if (option.value === options[5].value) {
392
+ return renderOption(option)
393
+ }
394
+
395
+ return <TransferOption {...option} />
396
+ },
397
+ }
398
+
399
+ export const IncreasedOptionsHeight = StatefulTemplate.bind({})
400
+ IncreasedOptionsHeight.args = {
401
+ maxSelections: Infinity,
402
+ filterable: true,
403
+ height: '400px',
404
+ }
405
+
406
+ export const DifferentWidths = StatefulTemplate.bind({})
407
+ DifferentWidths.args = {
408
+ optionsWidth: '500px',
409
+ selectedWidth: '240px',
410
+ }
411
+
412
+ const createCustomFilteringInHeader = (hideFilterInput) => {
413
+ const relativePeriods = options.slice(0, 10).map((option, index) => ({
414
+ ...option,
415
+ relativePeriod: true,
416
+ year: index < 5 ? '2020' : '2019',
417
+ }))
418
+
419
+ const fixedPeriods = options.slice(10, 20).map((option, index) => ({
420
+ ...option,
421
+ relativePeriod: false,
422
+ year: index < 5 ? '2020' : '2019',
423
+ }))
424
+
425
+ const allOptions = [...relativePeriods, ...fixedPeriods]
426
+
427
+ const Header = ({
428
+ onClick,
429
+ relativePeriod,
430
+ selectedYear,
431
+ onSelectedYearChange,
432
+ }) => (
433
+ <>
434
+ <TabBar>
435
+ <Tab
436
+ selected={relativePeriod}
437
+ onClick={() => onClick({ relativePeriod: true })}
438
+ >
439
+ Relative periods
440
+ </Tab>
441
+
442
+ <Tab
443
+ selected={!relativePeriod}
444
+ onClick={() => onClick({ relativePeriod: false })}
445
+ >
446
+ Fixed periods
447
+ </Tab>
448
+ </TabBar>
449
+
450
+ <p style={{ margin: 0, height: 10 }} />
451
+
452
+ <SingleSelectField
453
+ label="Year"
454
+ selected={selectedYear}
455
+ onChange={onSelectedYearChange}
456
+ >
457
+ <SingleSelectOption value="2020" label="2020" />
458
+ <SingleSelectOption value="2019" label="2019" />
459
+ </SingleSelectField>
460
+ </>
461
+ )
462
+ /* eslint-enable react/prop-types */
463
+
464
+ const CustomTransfer = (props) => {
465
+ const [filter, setFilter] = useState('')
466
+ const [relativePeriod, setRelativePeriod] = useState(true)
467
+ const [year, setYear] = useState('2020')
468
+ const filterCallback = (options, filter) => {
469
+ const optionsWithYear = options.filter(
470
+ (option) => option.year === year
471
+ )
472
+
473
+ const optionsWithPeriod = optionsWithYear.filter(
474
+ (option) => option.relativePeriod === relativePeriod
475
+ )
476
+
477
+ if (filter === '') {
478
+ return optionsWithPeriod
479
+ }
480
+
481
+ return optionsWithPeriod.filter(
482
+ ({ label }) => label.indexOf(filter) !== -1
483
+ )
484
+ }
485
+
486
+ const header = (
487
+ <Header
488
+ relativePeriod={relativePeriod}
489
+ selectedYear={year}
490
+ onSelectedYearChange={({ selected }) => setYear(selected)}
491
+ onClick={({ relativePeriod: newRelativePeriod }) =>
492
+ setRelativePeriod(newRelativePeriod)
493
+ }
494
+ />
495
+ )
496
+
497
+ return (
498
+ <Transfer
499
+ {...props}
500
+ options={allOptions}
501
+ filterable
502
+ hideFilterInput={hideFilterInput}
503
+ searchTerm={filter}
504
+ filterCallback={filterCallback}
505
+ leftHeader={header}
506
+ rightHeader={
507
+ <p>
508
+ <b>Selected Periods</b>
509
+ </p>
510
+ }
511
+ onFilterChange={({ value }) => setFilter(value)}
512
+ height="400px"
513
+ filterLabel="Filter options"
514
+ filterPlaceholder="Search"
515
+ />
516
+ )
517
+ }
518
+
519
+ return ({ initiallySelected, ...args }) => {
520
+ const [selected, setSelected] = useState(initiallySelected)
521
+ const onChange = (payload) => setSelected(payload.selected)
522
+
523
+ return (
524
+ <CustomTransfer {...args} selected={selected} onChange={onChange} />
525
+ )
526
+ }
527
+ }
528
+
529
+ export const CustomFilteringWithFilterInput =
530
+ createCustomFilteringInHeader(false)
531
+
532
+ export const CustomFilteringWithoutFilterInput =
533
+ createCustomFilteringInHeader(true)
534
+
535
+ const optionsPool = options
536
+ const pageSize = 5
537
+
538
+ /*
539
+ * The page size is: 5
540
+ * To keep the code as small as possible, handling selecting items is not
541
+ included
542
+ */
543
+ const preSelectedOptions = optionsPool.slice(optionsPool.length - 4)
544
+ const waitMs = async (ms) => new Promise((resolve) => setTimeout(resolve, ms))
545
+ export const InfiniteLoading = (args) => {
546
+ useEffect(() => {
547
+ console.clear()
548
+ }, [])
549
+
550
+ // state for whether the next page's options are being loaded
551
+ const [loading, setLoading] = useState(false)
552
+ // prevent fetches after list is complete
553
+ const [isOptionsListComplete, setIsOptionsListComplete] = useState(false)
554
+ const [options, setOptions] = useState([])
555
+ // selected options
556
+ const [selected] = useState(
557
+ // Last 4 options preselected
558
+ preSelectedOptions.map(({ value }) => value)
559
+ )
560
+ const selectedOptionsLookup = useMemo(
561
+ () =>
562
+ preSelectedOptions.reduce((lookup, option) => {
563
+ lookup[option.value] = option
564
+ return lookup
565
+ }, {}),
566
+ []
567
+ )
568
+
569
+ const loadMoreOptions = useCallback(async () => {
570
+ setLoading(true)
571
+ await waitMs(2000)
572
+ const newOptions = optionsPool.slice(
573
+ options.length,
574
+ Math.min(options.length + pageSize, optionsPool.length)
575
+ )
576
+ const combinedOptions = [...options, ...newOptions]
577
+ setOptions(combinedOptions)
578
+ if (combinedOptions.length === optionsPool.length) {
579
+ setIsOptionsListComplete(true)
580
+ }
581
+ setLoading(false)
582
+ }, [options])
583
+
584
+ const onEndReached = useCallback(() => {
585
+ if (!isOptionsListComplete) {
586
+ loadMoreOptions()
587
+ }
588
+ }, [loadMoreOptions, isOptionsListComplete])
589
+
590
+ return (
591
+ <Transfer
592
+ {...args}
593
+ loading={loading}
594
+ options={options}
595
+ selected={selected}
596
+ onChange={() => null /* noop */}
597
+ onEndReached={onEndReached}
598
+ selectedOptionsLookup={selectedOptionsLookup}
599
+ />
600
+ )
601
+ }
602
+
603
+ export const LoadingSource = StatefulTemplate.bind({})
604
+ LoadingSource.args = {
605
+ loading: true,
606
+ options: options.slice(0, 3),
607
+ }
608
+
609
+ export const LoadingPicked = StatefulTemplate.bind({})
610
+ LoadingPicked.args = {
611
+ loadingPicked: true,
612
+ options: options.slice(0, 3),
613
+ initiallySelected: options.slice(0, 2).map(({ value }) => value),
614
+ }
615
+
616
+ export const RTL = (args) => (
617
+ <div dir="rtl">
618
+ <Multiple {...args} />
619
+ </div>
620
+ )
621
+ RTL.args = { options: options.slice(0, 3), enableOrderChange: true }