Skip to content

Commit 8970f50

Browse files
committed
feat: make infinite loading work for simple transfer
1 parent 2a04671 commit 8970f50

File tree

3 files changed

+78
-81
lines changed

3 files changed

+78
-81
lines changed

components/simple-transfer/src/options-container.js

+74-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import { spacers } from '@dhis2/ui-constants'
12
import { CircularLoader } from '@dhis2-ui/loader'
23
import PropTypes from 'prop-types'
3-
import React, { Fragment, useRef } from 'react'
4+
import React, { Fragment, useEffect, useRef } from 'react'
45
import { SimpleTransferOption } from './simple-transfer-option.js'
56

67
export const OptionsContainer = ({
78
dataTest,
89
emptyComponent,
10+
onEndReached,
911
highlightedOptions,
1012
loading,
1113
maxSelections,
@@ -14,8 +16,37 @@ export const OptionsContainer = ({
1416
selectionHandler,
1517
setHighlightedOptions,
1618
}) => {
17-
const optionsRef = useRef(null)
19+
const selectRef = useRef(null)
1820
const wrapperRef = useRef(null)
21+
// const resizeCounter = useResizeCounter(wrapperRef.current)
22+
const lastOptionRef = useRef(null)
23+
24+
useEffect(() => {
25+
const observer = new IntersectionObserver(
26+
(entries) => {
27+
entries.forEach((entry) => {
28+
if (entry.isIntersecting) {
29+
onEndReached && onEndReached()
30+
}
31+
})
32+
},
33+
{
34+
root: wrapperRef.current,
35+
threshold: 1.0,
36+
}
37+
)
38+
39+
if (lastOptionRef.current) {
40+
observer.observe(lastOptionRef.current)
41+
}
42+
43+
return () => {
44+
if (lastOptionRef.current) {
45+
observer.unobserve(lastOptionRef.current)
46+
}
47+
observer.disconnect()
48+
}
49+
}, [options])
1950

2051
return (
2152
<div className="optionsContainer">
@@ -25,14 +56,14 @@ export const OptionsContainer = ({
2556
</div>
2657
)}
2758

28-
<div className="container" data-test={dataTest} ref={optionsRef}>
59+
<div className="container" data-test={dataTest} ref={wrapperRef}>
2960
{!options.length && emptyComponent}
3061
{!!options.length && (
3162
<select
63+
ref={selectRef}
64+
className="content-select"
3265
multiple={maxSelections === Infinity}
3366
size={maxSelections === 1 ? 2 : undefined}
34-
className="content-container"
35-
ref={wrapperRef}
3667
onChange={(e) => {
3768
const nextSelected = [...e.target.options].reduce(
3869
(curNextSelected, option) => {
@@ -47,7 +78,7 @@ export const OptionsContainer = ({
4778
setHighlightedOptions(nextSelected)
4879
}}
4980
>
50-
{options.map((option) => {
81+
{options.map((option, index) => {
5182
const highlighted = !!highlightedOptions.find(
5283
(highlightedSourceOption) =>
5384
highlightedSourceOption === option.value
@@ -61,6 +92,11 @@ export const OptionsContainer = ({
6192
highlighted={highlighted}
6293
selected={selected}
6394
onDoubleClick={selectionHandler}
95+
lastOptionReference={
96+
index === options.length - 1
97+
? lastOptionRef
98+
: undefined
99+
}
64100
/>
65101
</Fragment>
66102
)
@@ -70,6 +106,37 @@ export const OptionsContainer = ({
70106
</div>
71107

72108
<style jsx>{`
109+
.optionsContainer {
110+
flex-grow: 1;
111+
padding: ${spacers.dp4} 0;
112+
position: relative;
113+
overflow: hidden;
114+
}
115+
116+
.container {
117+
overflow-y: auto;
118+
height: 100%;
119+
}
120+
121+
.loading {
122+
display: flex;
123+
height: 100%;
124+
width: 100%;
125+
align-items: center;
126+
justify-content: center;
127+
position: absolute;
128+
z-index: 2;
129+
top: 0;
130+
inset-inline-start: 0;
131+
}
132+
133+
.content-select {
134+
border: none;
135+
position: relative;
136+
height: 100%;
137+
width: 100%;
138+
}
139+
73140
.loading + .container .content-container {
74141
filter: blur(2px);
75142
}
@@ -93,4 +160,5 @@ OptionsContainer.propTypes = {
93160
),
94161
selected: PropTypes.bool,
95162
selectionHandler: PropTypes.func,
163+
onEndReached: PropTypes.func,
96164
}

components/simple-transfer/src/simple-transfer-option.js

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const SimpleTransferOption = ({
1010
onDoubleClick,
1111
label,
1212
value,
13+
lastOptionReference,
1314
}) => {
1415
return (
1516
<>
@@ -19,6 +20,7 @@ export const SimpleTransferOption = ({
1920
data-value={value}
2021
value={value}
2122
onDoubleClick={() => onDoubleClick({ value }, event)}
23+
ref={lastOptionReference}
2224
>
2325
{label}
2426
</option>
@@ -56,5 +58,6 @@ SimpleTransferOption.propTypes = {
5658
className: PropTypes.string,
5759
dataTest: PropTypes.string,
5860
disabled: PropTypes.bool,
61+
lastOptionReference: PropTypes.object,
5962
onDoubleClick: PropTypes.func,
6063
}

components/simple-transfer/src/simple-transfer.prod.stories.js

+1-75
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { SingleSelectField, SingleSelectOption } from '@dhis2-ui/select'
22
import { Tab, TabBar } from '@dhis2-ui/tab'
33
import PropTypes from 'prop-types'
44
import React, { useEffect, useState } from 'react'
5-
import { SimpleTransferOption } from './simple-transfer-option.js'
65
import { SimpleTransfer } from './simple-transfer.js'
76

87
const subtitle = 'Allows users to select options from a list'
@@ -218,79 +217,6 @@ FilteredPlaceholder.args = {
218217
filterPlaceholder: 'Search',
219218
}
220219

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 <SimpleTransfer {...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 <SimpleTransferOption {...option} />
291-
},
292-
}
293-
294220
export const CustomButtonText = StatefulTemplate.bind({})
295221
CustomButtonText.args = {
296222
addAllText: 'Add all',
@@ -482,7 +408,7 @@ export const InfiniteLoading = (args) => {
482408
// state for whether the next page's options are being loaded
483409
const [loading, setLoading] = useState(false)
484410
// captures the current page
485-
const [page, setPage] = useState(0)
411+
const [page, setPage] = useState(1)
486412
// all options (incl. available AND selected options)
487413
const [options, setOptions] = useState([])
488414
// selected options

0 commit comments

Comments
 (0)