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