From ae1cd2468397cf6e92c90a81b4e6334a9d6522e7 Mon Sep 17 00:00:00 2001
From: Flaminia Cavallo <flaminia@dhis2.org>
Date: Thu, 31 Oct 2024 11:35:59 +0100
Subject: [PATCH] feat: make infinite loading work for simple transfer

---
 components/simple-transfer/README.md          |  2 +-
 ..._remove-highlighted-options.e2e.stories.js |  8 ++
 .../disabled-transfer-buttons.feature         |  2 +-
 .../disabled-transfer-options.feature         |  2 +-
 .../src/features/display-order.feature        |  2 +-
 .../src/features/filter-options-list.feature  |  2 +-
 .../src/features/reorder-with-buttons.feature |  2 +-
 .../set_unset-highlighted-option.feature      |  2 +-
 .../src/features/transferring-items.feature   |  2 +-
 .../simple-transfer/src/options-container.js  | 80 +++++++++++++++++--
 .../src/simple-transfer-option.js             |  3 +
 .../src/simple-transfer.prod.stories.js       | 76 +-----------------
 components/simple-transfer/types/index.d.ts   |  2 +-
 13 files changed, 95 insertions(+), 90 deletions(-)

diff --git a/components/simple-transfer/README.md b/components/simple-transfer/README.md
index 5d67b5904f..9c76bebfdf 100644
--- a/components/simple-transfer/README.md
+++ b/components/simple-transfer/README.md
@@ -5,4 +5,4 @@
 > guide](https://github.com/dhis2/ui/blob/master/docs/getting-started.md)
 > for more information.
 
-For usage instructions see [the documentation for this component](https://ui.dhis2.nu/components/transfer).
+For usage instructions see [the documentation for this component](https://ui.dhis2.nu/components/simple-transfer).
diff --git a/components/simple-transfer/src/__e2e__/add_remove-highlighted-options.e2e.stories.js b/components/simple-transfer/src/__e2e__/add_remove-highlighted-options.e2e.stories.js
index 0a60f202d8..401bfd6038 100644
--- a/components/simple-transfer/src/__e2e__/add_remove-highlighted-options.e2e.stories.js
+++ b/components/simple-transfer/src/__e2e__/add_remove-highlighted-options.e2e.stories.js
@@ -8,6 +8,14 @@ export default {
     decorators: [statefulDecorator()],
 }
 
+export const HasOptions = (_, { onChange, selected }) => (
+    <SimpleTransfer
+        filterable
+        selected={selected}
+        onChange={onChange}
+        options={options}
+    />
+)
 export const HasSelected = (_, { onChange, selected }) => (
     <SimpleTransfer onChange={onChange} selected={selected} options={options} />
 )
diff --git a/components/simple-transfer/src/features/disabled-transfer-buttons.feature b/components/simple-transfer/src/features/disabled-transfer-buttons.feature
index caeb6e4e15..e3b8f5c5da 100644
--- a/components/simple-transfer/src/features/disabled-transfer-buttons.feature
+++ b/components/simple-transfer/src/features/disabled-transfer-buttons.feature
@@ -1,4 +1,4 @@
-@component-transfer @button-states
+@component-simple-transfer @button-states
 Feature: Disable transfer buttons when actions are not possible
 
     Scenario: None of the selectable options are highlighted
diff --git a/components/simple-transfer/src/features/disabled-transfer-options.feature b/components/simple-transfer/src/features/disabled-transfer-options.feature
index 97389870a7..0ec34cb496 100644
--- a/components/simple-transfer/src/features/disabled-transfer-options.feature
+++ b/components/simple-transfer/src/features/disabled-transfer-options.feature
@@ -1,4 +1,4 @@
-@component-transfer @disabled-options
+@component-simple-transfer @disabled-options
 Feature: Options can be disabled
 
     Scenario: The user clicks a disabled option
diff --git a/components/simple-transfer/src/features/display-order.feature b/components/simple-transfer/src/features/display-order.feature
index e788df0bd0..e442260da0 100644
--- a/components/simple-transfer/src/features/display-order.feature
+++ b/components/simple-transfer/src/features/display-order.feature
@@ -1,4 +1,4 @@
-@component-transfer @display-ordering
+@component-simple-transfer @display-ordering
 Feature: Display order of items in lists
 
     Scenario: All supplied options are rendered in the options-side
diff --git a/components/simple-transfer/src/features/filter-options-list.feature b/components/simple-transfer/src/features/filter-options-list.feature
index 8e784f6a20..b5dea5d6d0 100644
--- a/components/simple-transfer/src/features/filter-options-list.feature
+++ b/components/simple-transfer/src/features/filter-options-list.feature
@@ -1,4 +1,4 @@
-@component-transfer @filtering
+@component-simple-transfer @filtering
 Feature: Filter options list
 
     Background:
diff --git a/components/simple-transfer/src/features/reorder-with-buttons.feature b/components/simple-transfer/src/features/reorder-with-buttons.feature
index f087aa1d30..294f0713e9 100644
--- a/components/simple-transfer/src/features/reorder-with-buttons.feature
+++ b/components/simple-transfer/src/features/reorder-with-buttons.feature
@@ -1,4 +1,4 @@
-@component-transfer @reordering
+@component-simple-transfer @reordering
 Feature: Reorder items in the selected list using buttons
 
     Background:
diff --git a/components/simple-transfer/src/features/set_unset-highlighted-option.feature b/components/simple-transfer/src/features/set_unset-highlighted-option.feature
index 825a0bdfa5..12a3e1ac7d 100644
--- a/components/simple-transfer/src/features/set_unset-highlighted-option.feature
+++ b/components/simple-transfer/src/features/set_unset-highlighted-option.feature
@@ -1,4 +1,4 @@
-@component-transfer @highlighting
+@component-simple-transfer @highlighting
 Feature: Set&unset the highlighted option
 
     Scenario Outline: The user clicks an item that is not already highlighted
diff --git a/components/simple-transfer/src/features/transferring-items.feature b/components/simple-transfer/src/features/transferring-items.feature
index 3a81c231ca..94681252e2 100644
--- a/components/simple-transfer/src/features/transferring-items.feature
+++ b/components/simple-transfer/src/features/transferring-items.feature
@@ -1,4 +1,4 @@
-@component-transfer @transferring
+@component-simple-transfer @transferring
 Feature: Transferring items between lists
 
     Scenario: The user selects multiple items
diff --git a/components/simple-transfer/src/options-container.js b/components/simple-transfer/src/options-container.js
index 8986e4ef87..5bb5b569fc 100644
--- a/components/simple-transfer/src/options-container.js
+++ b/components/simple-transfer/src/options-container.js
@@ -1,11 +1,13 @@
+import { spacers } from '@dhis2/ui-constants'
 import { CircularLoader } from '@dhis2-ui/loader'
 import PropTypes from 'prop-types'
-import React, { Fragment, useRef } from 'react'
+import React, { Fragment, useEffect, useRef } from 'react'
 import { SimpleTransferOption } from './simple-transfer-option.js'
 
 export const OptionsContainer = ({
     dataTest,
     emptyComponent,
+    onEndReached,
     highlightedOptions,
     loading,
     maxSelections,
@@ -14,8 +16,37 @@ export const OptionsContainer = ({
     selectionHandler,
     setHighlightedOptions,
 }) => {
-    const optionsRef = useRef(null)
+    const selectRef = useRef(null)
     const wrapperRef = useRef(null)
+    // const resizeCounter = useResizeCounter(wrapperRef.current)
+    const lastOptionRef = useRef(null)
+
+    useEffect(() => {
+        const observer = new IntersectionObserver(
+            (entries) => {
+                entries.forEach((entry) => {
+                    if (entry.isIntersecting) {
+                        onEndReached && onEndReached()
+                    }
+                })
+            },
+            {
+                root: wrapperRef.current,
+                threshold: 1.0,
+            }
+        )
+
+        if (lastOptionRef.current) {
+            observer.observe(lastOptionRef.current)
+        }
+
+        return () => {
+            if (lastOptionRef.current) {
+                observer.unobserve(lastOptionRef.current)
+            }
+            observer.disconnect()
+        }
+    }, [options])
 
     return (
         <div className="optionsContainer">
@@ -25,14 +56,14 @@ export const OptionsContainer = ({
                 </div>
             )}
 
-            <div className="container" data-test={dataTest} ref={optionsRef}>
+            <div className="container" data-test={dataTest} ref={wrapperRef}>
                 {!options.length && emptyComponent}
                 {!!options.length && (
                     <select
+                        ref={selectRef}
+                        className="content-select"
                         multiple={maxSelections === Infinity}
                         size={maxSelections === 1 ? 2 : undefined}
-                        className="content-container"
-                        ref={wrapperRef}
                         onChange={(e) => {
                             const nextSelected = [...e.target.options].reduce(
                                 (curNextSelected, option) => {
@@ -47,7 +78,7 @@ export const OptionsContainer = ({
                             setHighlightedOptions(nextSelected)
                         }}
                     >
-                        {options.map((option) => {
+                        {options.map((option, index) => {
                             const highlighted = !!highlightedOptions.find(
                                 (highlightedSourceOption) =>
                                     highlightedSourceOption === option.value
@@ -61,6 +92,11 @@ export const OptionsContainer = ({
                                         highlighted={highlighted}
                                         selected={selected}
                                         onDoubleClick={selectionHandler}
+                                        lastOptionReference={
+                                            index === options.length - 1
+                                                ? lastOptionRef
+                                                : undefined
+                                        }
                                     />
                                 </Fragment>
                             )
@@ -70,6 +106,37 @@ export const OptionsContainer = ({
             </div>
 
             <style jsx>{`
+                .optionsContainer {
+                    flex-grow: 1;
+                    padding: ${spacers.dp4} 0;
+                    position: relative;
+                    overflow: hidden;
+                }
+
+                .container {
+                    overflow-y: auto;
+                    height: 100%;
+                }
+
+                .loading {
+                    display: flex;
+                    height: 100%;
+                    width: 100%;
+                    align-items: center;
+                    justify-content: center;
+                    position: absolute;
+                    z-index: 2;
+                    top: 0;
+                    inset-inline-start: 0;
+                }
+
+                .content-select {
+                    border: none;
+                    position: relative;
+                    height: 100%;
+                    width: 100%;
+                }
+
                 .loading + .container .content-container {
                     filter: blur(2px);
                 }
@@ -93,4 +160,5 @@ OptionsContainer.propTypes = {
     ),
     selected: PropTypes.bool,
     selectionHandler: PropTypes.func,
+    onEndReached: PropTypes.func,
 }
diff --git a/components/simple-transfer/src/simple-transfer-option.js b/components/simple-transfer/src/simple-transfer-option.js
index b56c71ba1c..703c8cd5cc 100644
--- a/components/simple-transfer/src/simple-transfer-option.js
+++ b/components/simple-transfer/src/simple-transfer-option.js
@@ -10,6 +10,7 @@ export const SimpleTransferOption = ({
     onDoubleClick,
     label,
     value,
+    lastOptionReference,
 }) => {
     return (
         <>
@@ -19,6 +20,7 @@ export const SimpleTransferOption = ({
                 data-value={value}
                 value={value}
                 onDoubleClick={() => onDoubleClick({ value }, event)}
+                ref={lastOptionReference}
             >
                 {label}
             </option>
@@ -56,5 +58,6 @@ SimpleTransferOption.propTypes = {
     className: PropTypes.string,
     dataTest: PropTypes.string,
     disabled: PropTypes.bool,
+    lastOptionReference: PropTypes.object,
     onDoubleClick: PropTypes.func,
 }
diff --git a/components/simple-transfer/src/simple-transfer.prod.stories.js b/components/simple-transfer/src/simple-transfer.prod.stories.js
index c676a0dbea..1b5ada6e7d 100644
--- a/components/simple-transfer/src/simple-transfer.prod.stories.js
+++ b/components/simple-transfer/src/simple-transfer.prod.stories.js
@@ -2,7 +2,6 @@ import { SingleSelectField, SingleSelectOption } from '@dhis2-ui/select'
 import { Tab, TabBar } from '@dhis2-ui/tab'
 import PropTypes from 'prop-types'
 import React, { useEffect, useState } from 'react'
-import { SimpleTransferOption } from './simple-transfer-option.js'
 import { SimpleTransfer } from './simple-transfer.js'
 
 const subtitle = 'Allows users to select options from a list'
@@ -218,79 +217,6 @@ FilteredPlaceholder.args = {
     filterPlaceholder: 'Search',
 }
 
-const renderOption = ({ label, value, onClick, highlighted, selected }) => (
-    <p
-        onClick={(event) => onClick({ label, value }, event)}
-        style={{
-            background: highlighted ? 'green' : 'blue',
-            color: selected ? 'orange' : 'white',
-        }}
-    >
-        Custom: {label} (label), {value} (value)
-    </p>
-)
-
-const RenderOptionCode = () => (
-    <>
-        <strong>Custom option code:</strong>
-        <code>
-            <pre>{`const renderOption = ({ label, value, onClick, highlighted, selected }) => (
-                <p
-                    onClick={event => onClick({ label, value }, event)}
-                    style={{
-                        background: highlighted ? 'green' : 'blue',
-                        color: selected ? 'orange' : 'white',
-                    }}
-                >
-                    Custom: {label} (label), {value} (value)
-                </p>
-            )`}</pre>
-        </code>
-    </>
-)
-
-const StatefulTemplateCustomRenderOption = ({
-    initiallySelected = [],
-    ...args
-}) => {
-    const [selected, setSelected] = useState(initiallySelected)
-    const onChange = (payload) => setSelected(payload.selected)
-
-    return <SimpleTransfer {...args} selected={selected} onChange={onChange} />
-}
-StatefulTemplateCustomRenderOption.propTypes = {
-    initiallySelected: PropTypes.array,
-}
-
-export const CustomListOptions = (args) => (
-    <>
-        <RenderOptionCode />
-        <StatefulTemplateCustomRenderOption {...args} />
-    </>
-)
-CustomListOptions.args = {
-    renderOption,
-    options: options.slice(0, 2),
-    initiallySelected: options.slice(0, 2).map(({ value }) => value),
-}
-
-export const IndividualCustomOption = StatefulTemplateCustomRenderOption.bind(
-    {}
-)
-IndividualCustomOption.args = {
-    addAllText: 'Add all',
-    addIndividualText: 'Add individual',
-    removeAllText: 'Remove all',
-    removeIndividualText: 'Remove individual',
-    renderOption: (option) => {
-        if (option.value === options[0].value) {
-            return renderOption(option)
-        }
-
-        return <SimpleTransferOption {...option} />
-    },
-}
-
 export const CustomButtonText = StatefulTemplate.bind({})
 CustomButtonText.args = {
     addAllText: 'Add all',
@@ -482,7 +408,7 @@ export const InfiniteLoading = (args) => {
     // state for whether the next page's options are being loaded
     const [loading, setLoading] = useState(false)
     // captures the current page
-    const [page, setPage] = useState(0)
+    const [page, setPage] = useState(1)
     // all options (incl. available AND selected options)
     const [options, setOptions] = useState([])
     // selected options
diff --git a/components/simple-transfer/types/index.d.ts b/components/simple-transfer/types/index.d.ts
index a9b2bfd741..7dbf73092a 100644
--- a/components/simple-transfer/types/index.d.ts
+++ b/components/simple-transfer/types/index.d.ts
@@ -51,7 +51,7 @@ export interface SimpleTransferProps {
     onFilterChangePicked?: InputChangeHandler
 }
 
-export const Transfer: React.FC<SimpleTransferProps>
+export const SimpleTransfer: React.FC<SimpleTransferProps>
 
 export type TransferOptionOnClickProp = (payload: { value: string }) => void