@dhis2-ui/transfer 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.
- package/package.json +10 -9
- package/src/__e2e__/add_remove-highlighted-options.e2e.stories.js +30 -0
- package/src/__e2e__/common/options.js +90 -0
- package/src/__e2e__/common/stateful-decorator.js +33 -0
- package/src/__e2e__/common.js +0 -0
- package/src/__e2e__/disabled-transfer-buttons.e2e.stories.js +49 -0
- package/src/__e2e__/disabled-transfer-options.e2e.stories.js +21 -0
- package/src/__e2e__/display-order.e2e.stories.js +24 -0
- package/src/__e2e__/filter-options-list.e2e.stories.js +87 -0
- package/src/__e2e__/highlight-range-of-options.e2e.stories.js +52 -0
- package/src/__e2e__/loading_lists.e2e.stories.js +26 -0
- package/src/__e2e__/notify_at_end_of_list.e2e.stories.js +116 -0
- package/src/__e2e__/reorder-with-buttons.e2e.stories.js +35 -0
- package/src/__e2e__/set_unset-highlighted-option.e2e.stories.js +30 -0
- package/src/__e2e__/transferring-items.e2e.stories.js +27 -0
- package/src/__tests__/common.test.js +131 -0
- package/src/__tests__/helper/add-all-selectable-source-options.test.js +46 -0
- package/src/__tests__/helper/add-individual-source-options.test.js +80 -0
- package/src/__tests__/helper/default-filter-callback.test.js +45 -0
- package/src/__tests__/helper/is-reorder-down-disabled.test.js +96 -0
- package/src/__tests__/helper/is-reorder-up-disabled.test.js +96 -0
- package/src/__tests__/helper/move-highlighted-picked-option-down.test.js +111 -0
- package/src/__tests__/helper/move-highlighted-picked-option-to-bottom.test.js +101 -0
- package/src/__tests__/helper/move-highlighted-picked-option-to-top.test.js +101 -0
- package/src/__tests__/helper/move-highlighted-picked-option-up.test.js +111 -0
- package/src/__tests__/helper/remove-all-picked-options.test.js +29 -0
- package/src/__tests__/helper/remove-individual-picked-options.test.js +38 -0
- package/src/__tests__/helper/use-highlighted-option/create-toggle-highlighted-option.test.js +104 -0
- package/src/__tests__/helper/use-highlighted-option/toggle-add.test.js +84 -0
- package/src/__tests__/helper/use-highlighted-option/toggle-range.test.js +150 -0
- package/src/__tests__/helper/use-highlighted-option/toggle-replace.test.js +39 -0
- package/src/__tests__/helper/use-highlighted-option.test.js +41 -0
- package/src/__tests__/reordering-actions.test.js +165 -0
- package/src/__tests__/transfer.test.js +137 -0
- package/src/actions.js +33 -0
- package/src/add-all.js +27 -0
- package/src/add-individual.js +27 -0
- package/src/common/find-option-index.js +9 -0
- package/src/common/get-mode-by-modifier-key.js +35 -0
- package/src/common/index.js +5 -0
- package/src/common/is-option.js +7 -0
- package/src/common/modes.js +11 -0
- package/src/common/remove-option.js +19 -0
- package/src/common/toggle-value.js +18 -0
- package/src/container.js +23 -0
- package/src/end-intersection-detector.js +37 -0
- package/src/features/add_remove-highlighted-options/index.js +92 -0
- package/src/features/add_remove-highlighted-options.feature +41 -0
- package/src/features/common/index.js +8 -0
- package/src/features/disabled-transfer-buttons/index.js +118 -0
- package/src/features/disabled-transfer-buttons.feature +46 -0
- package/src/features/disabled-transfer-options/index.js +182 -0
- package/src/features/disabled-transfer-options.feature +42 -0
- package/src/features/display-order/index.js +205 -0
- package/src/features/display-order.feature +30 -0
- package/src/features/filter-options-list/index.js +133 -0
- package/src/features/filter-options-list.feature +40 -0
- package/src/features/highlight-range-of-options/index.js +336 -0
- package/src/features/highlight-range-of-options.feature +70 -0
- package/src/features/loading_lists/index.js +43 -0
- package/src/features/loading_lists.feature +19 -0
- package/src/features/notify_at_end_of_list/index.js +125 -0
- package/src/features/notify_at_end_of_list.feature +64 -0
- package/src/features/reorder-with-buttons/index.js +181 -0
- package/src/features/reorder-with-buttons.feature +138 -0
- package/src/features/set_unset-highlighted-option/index.js +121 -0
- package/src/features/set_unset-highlighted-option.feature +42 -0
- package/src/features/transferring-items/index.js +375 -0
- package/src/features/transferring-items.feature +44 -0
- package/src/filter.js +38 -0
- package/src/icons.js +194 -0
- package/src/index.js +2 -0
- package/src/left-footer.js +22 -0
- package/src/left-header.js +22 -0
- package/src/left-side.js +34 -0
- package/src/locales/en/translations.json +7 -0
- package/src/locales/index.js +16 -0
- package/src/options-container.js +127 -0
- package/src/remove-all.js +27 -0
- package/src/remove-individual.js +27 -0
- package/src/reordering-actions.js +136 -0
- package/src/right-footer.js +22 -0
- package/src/right-header.js +22 -0
- package/src/right-side.js +33 -0
- package/src/transfer/add-all-selectable-source-options.js +37 -0
- package/src/transfer/add-individual-source-options.js +61 -0
- package/src/transfer/create-double-click-handlers.js +36 -0
- package/src/transfer/default-filter-callback.js +17 -0
- package/src/transfer/get-highlighted-picked-indices.js +26 -0
- package/src/transfer/get-option-click-handlers.js +19 -0
- package/src/transfer/index.js +17 -0
- package/src/transfer/is-reorder-down-disabled.js +34 -0
- package/src/transfer/is-reorder-up-disabled.js +30 -0
- package/src/transfer/move-highlighted-picked-option-down.js +54 -0
- package/src/transfer/move-highlighted-picked-option-to-bottom.js +44 -0
- package/src/transfer/move-highlighted-picked-option-to-top.js +38 -0
- package/src/transfer/move-highlighted-picked-option-up.js +47 -0
- package/src/transfer/remove-all-picked-options.js +13 -0
- package/src/transfer/remove-individual-picked-options.js +49 -0
- package/src/transfer/use-filter.js +17 -0
- package/src/transfer/use-highlighted-options/create-toggle-highlighted-option.js +64 -0
- package/src/transfer/use-highlighted-options/toggle-add.js +20 -0
- package/src/transfer/use-highlighted-options/toggle-range.js +61 -0
- package/src/transfer/use-highlighted-options/toggle-replace.js +26 -0
- package/src/transfer/use-highlighted-options.js +34 -0
- package/src/transfer/use-options-key-monitor.js +41 -0
- package/src/transfer-option.js +91 -0
- package/src/transfer.js +539 -0
- 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 }
|