Skip to content

Commit

Permalink
feat(transfer): add requested improvements
Browse files Browse the repository at this point in the history
Fixes TECH-378
Fixes TECH-379
  • Loading branch information
Mohammer5 authored Aug 14, 2020
2 parents bf79e80 + 13a1804 commit 5ccddf3
Show file tree
Hide file tree
Showing 15 changed files with 914 additions and 251 deletions.
35 changes: 35 additions & 0 deletions packages/widgets/src/Transfer/EndIntersectionDetector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { IntersectionDetector } from '@dhis2/ui-core'
import propTypes from '@dhis2/prop-types'
import React from 'react'

export const EndIntersectionDetector = ({
rootRef,
onEndReached,
dataTest,
}) => (
<div data-test={dataTest}>
<IntersectionDetector
rootRef={rootRef}
onChange={({ isIntersecting }) => isIntersecting && onEndReached()}
/>

<style jsx>{`
div {
width: 100%;
height: 50px;
position: absolute;
z-index: -1;
bottom: 0;
left: 0;
}
`}</style>
</div>
)

EndIntersectionDetector.propTypes = {
rootRef: propTypes.shape({
current: propTypes.instanceOf(HTMLElement),
}).isRequired,
onEndReached: propTypes.func.isRequired,
dataTest: propTypes.string,
}
144 changes: 144 additions & 0 deletions packages/widgets/src/Transfer/OptionsContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { CircularLoader } from '@dhis2/ui-core'
import { spacers } from '@dhis2/ui-constants'
import React, { Fragment, useEffect, useRef, useState } from 'react'
import propTypes from '@dhis2/prop-types'

import { EndIntersectionDetector } from './EndIntersectionDetector.js'

export const OptionsContainer = ({
dataTest,
emptyComponent,
onEndReached,
getOptionClickHandlers,
highlightedOptions,
loading,
renderOption,
options,
selectionHandler,
toggleHighlightedOption,
}) => {
const [remountCounter, setRemountCounter] = useState(0)
const [resizeObserver, setResizeObserver] = useState(null)
const optionsRef = useRef(null)
const wrapperRef = useRef(null)

useEffect(() => {
if (onEndReached && wrapperRef.current) {
// The initial call is irrelevant as there has been
// no resize yet that we want to react to
let firstCall = false

const observer = new ResizeObserver(() => {
if (!firstCall) {
const newCounter = remountCounter + 1
setRemountCounter(newCounter)
firstCall = true
}
})

observer.observe(wrapperRef.current)
setResizeObserver(observer)

return () => observer.disconnect()
}
}, [onEndReached, wrapperRef.current, setRemountCounter])

return (
<div className="optionsContainer">
{loading && (
<div className="loading">
<CircularLoader />
</div>
)}

<div className="container" data-test={dataTest} ref={optionsRef}>
<div className="content-container" ref={wrapperRef}>
{!options.length && emptyComponent}
{options.map(option => {
const highlighted = !!highlightedOptions.find(
highlightedSourceOption =>
highlightedSourceOption === option.value
)

return (
<Fragment key={option.value}>
{renderOption({
...option,
...getOptionClickHandlers(
option,
selectionHandler,
toggleHighlightedOption
),
highlighted,
selected: false,
})}
</Fragment>
)
})}

{onEndReached && resizeObserver && (
<EndIntersectionDetector
dataTest={`${dataTest}-endintersectiondetector`}
key={`key-${remountCounter}`}
rootRef={optionsRef}
onEndReached={onEndReached}
/>
)}
</div>
</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;
left: 0;
}
.content-container {
z-index: 1;
position: relative;
}
.loading + .container .content-container {
filter: blur(2px);
}
`}</style>
</div>
)
}

OptionsContainer.propTypes = {
dataTest: propTypes.string.isRequired,
getOptionClickHandlers: propTypes.func.isRequired,
emptyComponent: propTypes.node,
highlightedOptions: propTypes.arrayOf(propTypes.string),
loading: propTypes.bool,
options: propTypes.arrayOf(
propTypes.shape({
label: propTypes.string.isRequired,
value: propTypes.string.isRequired,
})
),
renderOption: propTypes.func,
selectionHandler: propTypes.func,
toggleHighlightedOption: propTypes.func,
onEndReached: propTypes.func,
}
30 changes: 25 additions & 5 deletions packages/widgets/src/Transfer/PickedOptions.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
import { spacers } from '@dhis2/ui-constants'
import React from 'react'
import propTypes from '@dhis2/prop-types'

import { spacers } from '@dhis2/ui-constants'
import { EndIntersectionDetector } from './EndIntersectionDetector.js'

export const PickedOptions = ({
children,
dataTest,
selectedEmptyComponent,
pickedOptionsRef,
onPickedEndReached,
}) => (
<div data-test={dataTest}>
{!React.Children.count(children) && selectedEmptyComponent}
{children}
<div className="container" data-test={dataTest} ref={pickedOptionsRef}>
<div className="content-container">
{!React.Children.count(children) && selectedEmptyComponent}
{children}

{onPickedEndReached && (
<EndIntersectionDetector
rootRef={pickedOptionsRef}
onEndReached={onPickedEndReached}
/>
)}
</div>

<style jsx>{`
div {
.container {
padding: ${spacers.dp4} 0;
flex-grow: 1;
overflow-y: auto;
}
.content-container {
position: relative;
}
`}</style>
</div>
)

PickedOptions.propTypes = {
children: propTypes.node.isRequired,
dataTest: propTypes.string.isRequired,
pickedOptionsRef: propTypes.shape({
current: propTypes.instanceOf(HTMLElement),
}),
selectedEmptyComponent: propTypes.node,
onPickedEndReached: propTypes.func,
}
30 changes: 25 additions & 5 deletions packages/widgets/src/Transfer/SourceOptions.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
import { spacers } from '@dhis2/ui-constants'
import React from 'react'
import propTypes from '@dhis2/prop-types'

import { spacers } from '@dhis2/ui-constants'
import { EndIntersectionDetector } from './EndIntersectionDetector.js'

export const SourceOptions = ({
children,
dataTest,
sourceEmptyPlaceholder,
sourceOptionsRef,
onSourceEndReached,
}) => (
<div data-test={dataTest}>
{children}
{!React.Children.count(children) && sourceEmptyPlaceholder}
<div className="container" data-test={dataTest} ref={sourceOptionsRef}>
<div className="content-container">
{children}
{!React.Children.count(children) && sourceEmptyPlaceholder}

{onSourceEndReached && (
<EndIntersectionDetector
rootRef={sourceOptionsRef}
onEndReached={onSourceEndReached}
/>
)}
</div>

<style jsx>{`
div {
.container {
padding: ${spacers.dp4} 0;
flex-grow: 1;
overflow-y: auto;
}
.content-container {
position: relative;
}
`}</style>
</div>
)
Expand All @@ -26,4 +42,8 @@ SourceOptions.propTypes = {
dataTest: propTypes.string.isRequired,
children: propTypes.node,
sourceEmptyPlaceholder: propTypes.node,
sourceOptionsRef: propTypes.shape({
current: propTypes.instanceOf(HTMLElement),
}),
onSourceEndReached: propTypes.func,
}
Loading

0 comments on commit 5ccddf3

Please sign in to comment.