@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,22 @@
|
|
|
1
|
+
import { colors, spacers } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
export const LeftHeader = ({ children, dataTest }) => (
|
|
6
|
+
<div data-test={dataTest}>
|
|
7
|
+
{children}
|
|
8
|
+
|
|
9
|
+
<style jsx>{`
|
|
10
|
+
div {
|
|
11
|
+
border-bottom: 1px solid ${colors.grey400};
|
|
12
|
+
flex-grow: 0;
|
|
13
|
+
padding: 0 ${spacers.dp8};
|
|
14
|
+
}
|
|
15
|
+
`}</style>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
LeftHeader.propTypes = {
|
|
20
|
+
children: PropTypes.node,
|
|
21
|
+
dataTest: PropTypes.string,
|
|
22
|
+
}
|
package/src/left-side.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { colors } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
export const LeftSide = ({ children, dataTest, width }) => (
|
|
6
|
+
<div data-test={dataTest}>
|
|
7
|
+
{children}
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
/**
|
|
11
|
+
* Flex basis 0px to make sure right and left side
|
|
12
|
+
* always have the same width
|
|
13
|
+
*/ ''
|
|
14
|
+
}
|
|
15
|
+
<style jsx>{`
|
|
16
|
+
div {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
background-color: ${colors.white};
|
|
20
|
+
border-radius: 3px;
|
|
21
|
+
border: 1px solid ${colors.grey400};
|
|
22
|
+
min-height: 240px;
|
|
23
|
+
max-width: 100%;
|
|
24
|
+
width: ${width};
|
|
25
|
+
}
|
|
26
|
+
`}</style>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
LeftSide.propTypes = {
|
|
31
|
+
width: PropTypes.string.isRequired,
|
|
32
|
+
children: PropTypes.node,
|
|
33
|
+
dataTest: PropTypes.string,
|
|
34
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Reordering not allowed when filtering list": "Reordering not allowed when filtering list",
|
|
3
|
+
"Move selected items to top": "Move selected items to top",
|
|
4
|
+
"Move selected items up": "Move selected items up",
|
|
5
|
+
"Move selected items down": "Move selected items down",
|
|
6
|
+
"Move selected items to bottom": "Move selected items to bottom"
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//------------------------------------------------------------------------------
|
|
2
|
+
// <auto-generated>
|
|
3
|
+
// This code was generated by d2-i18n-generate.
|
|
4
|
+
//
|
|
5
|
+
// Changes to this file may cause incorrect behavior and will be lost if
|
|
6
|
+
// the code is regenerated.
|
|
7
|
+
// </auto-generated>
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
import i18n from '@dhis2/d2-i18n'
|
|
10
|
+
|
|
11
|
+
import enTranslations from './en/translations.json'
|
|
12
|
+
|
|
13
|
+
const namespace = 'default'
|
|
14
|
+
i18n.addResources('en', namespace, enTranslations)
|
|
15
|
+
|
|
16
|
+
export default i18n
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { CircularLoader } from '@dhis2-ui/loader'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React, { Fragment, useRef } from 'react'
|
|
4
|
+
import { EndIntersectionDetector } from './end-intersection-detector.js'
|
|
5
|
+
import { useOptionsKeyMonitor } from './transfer/use-options-key-monitor.js'
|
|
6
|
+
|
|
7
|
+
export const OptionsContainer = ({
|
|
8
|
+
allOptionsKey,
|
|
9
|
+
dataTest,
|
|
10
|
+
emptyComponent,
|
|
11
|
+
onEndReached,
|
|
12
|
+
getOptionClickHandlers,
|
|
13
|
+
highlightedOptions,
|
|
14
|
+
loading,
|
|
15
|
+
renderOption,
|
|
16
|
+
options,
|
|
17
|
+
selected = false,
|
|
18
|
+
selectionHandler,
|
|
19
|
+
toggleHighlightedOption,
|
|
20
|
+
}) => {
|
|
21
|
+
const scrollBoxRef = useRef(null)
|
|
22
|
+
const listRef = useRef(null)
|
|
23
|
+
useOptionsKeyMonitor({
|
|
24
|
+
scrollBoxRef,
|
|
25
|
+
listRef,
|
|
26
|
+
allOptionsKey,
|
|
27
|
+
onEndReached,
|
|
28
|
+
})
|
|
29
|
+
return (
|
|
30
|
+
<div className="optionsContainer">
|
|
31
|
+
{loading && (
|
|
32
|
+
<div className="loading">
|
|
33
|
+
<CircularLoader />
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
|
|
37
|
+
<div className="container" data-test={dataTest} ref={scrollBoxRef}>
|
|
38
|
+
<div className="content-container" ref={listRef}>
|
|
39
|
+
{!options.length && emptyComponent}
|
|
40
|
+
{options.map((option) => {
|
|
41
|
+
const highlighted = !!highlightedOptions.find(
|
|
42
|
+
(highlightedSourceOption) =>
|
|
43
|
+
highlightedSourceOption === option.value
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Fragment key={option.value}>
|
|
48
|
+
{renderOption({
|
|
49
|
+
...option,
|
|
50
|
+
...getOptionClickHandlers(
|
|
51
|
+
option,
|
|
52
|
+
selectionHandler,
|
|
53
|
+
toggleHighlightedOption
|
|
54
|
+
),
|
|
55
|
+
highlighted,
|
|
56
|
+
selected,
|
|
57
|
+
})}
|
|
58
|
+
</Fragment>
|
|
59
|
+
)
|
|
60
|
+
})}
|
|
61
|
+
|
|
62
|
+
{onEndReached && (
|
|
63
|
+
<EndIntersectionDetector
|
|
64
|
+
dataTest={`${dataTest}-endintersectiondetector`}
|
|
65
|
+
rootRef={scrollBoxRef}
|
|
66
|
+
onEndReached={onEndReached}
|
|
67
|
+
/>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<style jsx>{`
|
|
73
|
+
.optionsContainer {
|
|
74
|
+
flex-grow: 1;
|
|
75
|
+
position: relative;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.container {
|
|
80
|
+
overflow-y: auto;
|
|
81
|
+
height: 100%;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.loading {
|
|
85
|
+
display: flex;
|
|
86
|
+
height: 100%;
|
|
87
|
+
width: 100%;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
position: absolute;
|
|
91
|
+
z-index: 2;
|
|
92
|
+
top: 0;
|
|
93
|
+
inset-inline-start: 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.content-container {
|
|
97
|
+
z-index: 1;
|
|
98
|
+
position: relative;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.loading + .container .content-container {
|
|
102
|
+
filter: blur(2px);
|
|
103
|
+
}
|
|
104
|
+
`}</style>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
OptionsContainer.propTypes = {
|
|
110
|
+
allOptionsKey: PropTypes.string.isRequired,
|
|
111
|
+
dataTest: PropTypes.string.isRequired,
|
|
112
|
+
getOptionClickHandlers: PropTypes.func.isRequired,
|
|
113
|
+
emptyComponent: PropTypes.node,
|
|
114
|
+
highlightedOptions: PropTypes.arrayOf(PropTypes.string),
|
|
115
|
+
loading: PropTypes.bool,
|
|
116
|
+
options: PropTypes.arrayOf(
|
|
117
|
+
PropTypes.shape({
|
|
118
|
+
label: PropTypes.string.isRequired,
|
|
119
|
+
value: PropTypes.string.isRequired,
|
|
120
|
+
})
|
|
121
|
+
),
|
|
122
|
+
renderOption: PropTypes.func,
|
|
123
|
+
selected: PropTypes.bool,
|
|
124
|
+
selectionHandler: PropTypes.func,
|
|
125
|
+
toggleHighlightedOption: PropTypes.func,
|
|
126
|
+
onEndReached: PropTypes.func,
|
|
127
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Button } from '@dhis2-ui/button'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { IconRemoveAll } from './icons.js'
|
|
5
|
+
|
|
6
|
+
export const RemoveAll = ({ label, dataTest, disabled, onClick }) => (
|
|
7
|
+
<Button
|
|
8
|
+
dataTest={dataTest}
|
|
9
|
+
disabled={disabled}
|
|
10
|
+
onClick={onClick}
|
|
11
|
+
icon={
|
|
12
|
+
<IconRemoveAll
|
|
13
|
+
disabled={disabled}
|
|
14
|
+
dataTest={`${dataTest}-iconremoveall`}
|
|
15
|
+
/>
|
|
16
|
+
}
|
|
17
|
+
>
|
|
18
|
+
{label}
|
|
19
|
+
</Button>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
RemoveAll.propTypes = {
|
|
23
|
+
dataTest: PropTypes.string.isRequired,
|
|
24
|
+
onClick: PropTypes.func.isRequired,
|
|
25
|
+
disabled: PropTypes.bool,
|
|
26
|
+
label: PropTypes.string,
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Button } from '@dhis2-ui/button'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { IconRemoveIndividual } from './icons.js'
|
|
5
|
+
|
|
6
|
+
export const RemoveIndividual = ({ label, dataTest, disabled, onClick }) => (
|
|
7
|
+
<Button
|
|
8
|
+
dataTest={dataTest}
|
|
9
|
+
disabled={disabled}
|
|
10
|
+
onClick={onClick}
|
|
11
|
+
icon={
|
|
12
|
+
<IconRemoveIndividual
|
|
13
|
+
disabled={disabled}
|
|
14
|
+
dataTest={`${dataTest}-iconremoveindividual`}
|
|
15
|
+
/>
|
|
16
|
+
}
|
|
17
|
+
>
|
|
18
|
+
{label}
|
|
19
|
+
</Button>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
RemoveIndividual.propTypes = {
|
|
23
|
+
dataTest: PropTypes.string.isRequired,
|
|
24
|
+
onClick: PropTypes.func.isRequired,
|
|
25
|
+
disabled: PropTypes.bool,
|
|
26
|
+
label: PropTypes.string,
|
|
27
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { spacers } from '@dhis2/ui-constants'
|
|
2
|
+
import { Button } from '@dhis2-ui/button'
|
|
3
|
+
import { Tooltip } from '@dhis2-ui/tooltip'
|
|
4
|
+
import PropTypes from 'prop-types'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
import {
|
|
7
|
+
IconMoveDown,
|
|
8
|
+
IconMoveToBottom,
|
|
9
|
+
IconMoveToTop,
|
|
10
|
+
IconMoveUp,
|
|
11
|
+
} from './icons.js'
|
|
12
|
+
import i18n from './locales/index.js'
|
|
13
|
+
|
|
14
|
+
const filterActiveTooltip = i18n.t('Reordering not allowed when filtering list')
|
|
15
|
+
|
|
16
|
+
export const ReorderingActions = ({
|
|
17
|
+
dataTest,
|
|
18
|
+
disabledDown,
|
|
19
|
+
disabledUp,
|
|
20
|
+
filterActive,
|
|
21
|
+
onChangeUp,
|
|
22
|
+
onChangeDown,
|
|
23
|
+
onChangeToTop,
|
|
24
|
+
onChangeToBottom,
|
|
25
|
+
}) => {
|
|
26
|
+
const moveToTopLabel = i18n.t('Move selected items to top')
|
|
27
|
+
const moveUpLabel = i18n.t('Move selected items up')
|
|
28
|
+
const moveDownLabel = i18n.t('Move selected items down')
|
|
29
|
+
const moveToBottomLabel = i18n.t('Move selected items to bottom')
|
|
30
|
+
|
|
31
|
+
const renderButtons = (tooltipHandlers = {}) => (
|
|
32
|
+
<div data-test={dataTest} {...tooltipHandlers}>
|
|
33
|
+
<Button
|
|
34
|
+
small
|
|
35
|
+
secondary
|
|
36
|
+
disabled={disabledUp}
|
|
37
|
+
onClick={() => !disabledUp && onChangeToTop()}
|
|
38
|
+
dataTest={`${dataTest}-buttonmovetotop`}
|
|
39
|
+
aria-label={moveToTopLabel}
|
|
40
|
+
icon={
|
|
41
|
+
<IconMoveToTop
|
|
42
|
+
dataTest={`${dataTest}-iconmovetotop`}
|
|
43
|
+
disabled={disabledUp}
|
|
44
|
+
/>
|
|
45
|
+
}
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<Button
|
|
49
|
+
small
|
|
50
|
+
secondary
|
|
51
|
+
disabled={disabledUp}
|
|
52
|
+
onClick={() => !disabledUp && onChangeUp()}
|
|
53
|
+
dataTest={`${dataTest}-buttonmoveup`}
|
|
54
|
+
aria-label={moveUpLabel}
|
|
55
|
+
icon={
|
|
56
|
+
<IconMoveUp
|
|
57
|
+
dataTest={`${dataTest}-iconmoveup`}
|
|
58
|
+
disabled={disabledUp}
|
|
59
|
+
/>
|
|
60
|
+
}
|
|
61
|
+
/>
|
|
62
|
+
<Button
|
|
63
|
+
small
|
|
64
|
+
secondary
|
|
65
|
+
disabled={disabledDown}
|
|
66
|
+
onClick={() => !disabledDown && onChangeDown()}
|
|
67
|
+
dataTest={`${dataTest}-buttonmovedown`}
|
|
68
|
+
aria-label={moveDownLabel}
|
|
69
|
+
icon={
|
|
70
|
+
<IconMoveDown
|
|
71
|
+
dataTest={`${dataTest}-iconmovedown`}
|
|
72
|
+
disabled={disabledDown}
|
|
73
|
+
/>
|
|
74
|
+
}
|
|
75
|
+
/>
|
|
76
|
+
<Button
|
|
77
|
+
small
|
|
78
|
+
secondary
|
|
79
|
+
disabled={disabledDown}
|
|
80
|
+
onClick={() => !disabledDown && onChangeToBottom()}
|
|
81
|
+
dataTest={`${dataTest}-buttonmovetobottom`}
|
|
82
|
+
aria-label={moveToBottomLabel}
|
|
83
|
+
icon={
|
|
84
|
+
<IconMoveToBottom
|
|
85
|
+
dataTest={`${dataTest}-iconmovetobottom`}
|
|
86
|
+
disabled={disabledDown}
|
|
87
|
+
/>
|
|
88
|
+
}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
<style jsx>{`
|
|
92
|
+
div {
|
|
93
|
+
display: flex;
|
|
94
|
+
justify-content: flex-end;
|
|
95
|
+
margin-inline-start: auto;
|
|
96
|
+
width: fit-content;
|
|
97
|
+
gap: ${spacers.dp4};
|
|
98
|
+
padding-top: ${spacers.dp8};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
div:last-child {
|
|
102
|
+
padding-bottom: ${spacers.dp8};
|
|
103
|
+
}
|
|
104
|
+
`}</style>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if (filterActive) {
|
|
109
|
+
return (
|
|
110
|
+
<Tooltip openDelay={500} content={filterActiveTooltip}>
|
|
111
|
+
{({ onMouseOver, onMouseOut, onFocus, onBlur, ref }) =>
|
|
112
|
+
renderButtons({
|
|
113
|
+
ref,
|
|
114
|
+
onMouseOver,
|
|
115
|
+
onMouseOut,
|
|
116
|
+
onFocus,
|
|
117
|
+
onBlur,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
</Tooltip>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return renderButtons()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
ReorderingActions.propTypes = {
|
|
128
|
+
dataTest: PropTypes.string.isRequired,
|
|
129
|
+
onChangeDown: PropTypes.func.isRequired,
|
|
130
|
+
onChangeToBottom: PropTypes.func.isRequired,
|
|
131
|
+
onChangeToTop: PropTypes.func.isRequired,
|
|
132
|
+
onChangeUp: PropTypes.func.isRequired,
|
|
133
|
+
disabledDown: PropTypes.bool,
|
|
134
|
+
disabledUp: PropTypes.bool,
|
|
135
|
+
filterActive: PropTypes.bool,
|
|
136
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { colors, spacers } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
export const RightFooter = ({ children, dataTest }) => (
|
|
6
|
+
<div data-test={dataTest}>
|
|
7
|
+
{children}
|
|
8
|
+
|
|
9
|
+
<style jsx>{`
|
|
10
|
+
div {
|
|
11
|
+
flex-grow: 0;
|
|
12
|
+
border-top: 1px solid ${colors.grey400};
|
|
13
|
+
padding: 0 ${spacers.dp8};
|
|
14
|
+
}
|
|
15
|
+
`}</style>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
RightFooter.propTypes = {
|
|
20
|
+
children: PropTypes.node,
|
|
21
|
+
dataTest: PropTypes.string,
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { colors, spacers } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
export const RightHeader = ({ children, dataTest }) => (
|
|
6
|
+
<div data-test={dataTest}>
|
|
7
|
+
{children}
|
|
8
|
+
|
|
9
|
+
<style jsx>{`
|
|
10
|
+
div {
|
|
11
|
+
border-bottom: 1px solid ${colors.grey400};
|
|
12
|
+
flex-grow: 0;
|
|
13
|
+
padding: 0 ${spacers.dp8};
|
|
14
|
+
}
|
|
15
|
+
`}</style>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
RightHeader.propTypes = {
|
|
20
|
+
children: PropTypes.node,
|
|
21
|
+
dataTest: PropTypes.string,
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { colors } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
export const RightSide = ({ children, dataTest, width }) => (
|
|
6
|
+
<div data-test={dataTest}>
|
|
7
|
+
{children}
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
/**
|
|
11
|
+
* Flex basis 0px to make sure right and left side
|
|
12
|
+
* always have the same width
|
|
13
|
+
*/ ''
|
|
14
|
+
}
|
|
15
|
+
<style jsx>{`
|
|
16
|
+
div {
|
|
17
|
+
background-color: ${colors.white};
|
|
18
|
+
border: 1px solid ${colors.grey400};
|
|
19
|
+
border-radius: 3px;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
max-width: 100%;
|
|
23
|
+
width: ${width};
|
|
24
|
+
}
|
|
25
|
+
`}</style>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
RightSide.propTypes = {
|
|
30
|
+
width: PropTypes.string.isRequired,
|
|
31
|
+
children: PropTypes.node,
|
|
32
|
+
dataTest: PropTypes.string,
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {Object} args
|
|
3
|
+
* @param {Object[]} args.sourceOptions
|
|
4
|
+
* @param {string[]} args.selected
|
|
5
|
+
* @param {Function} args.onChange
|
|
6
|
+
* @param {Function} arg.setHighlightedSourceOptions
|
|
7
|
+
* @returns {void}
|
|
8
|
+
*/
|
|
9
|
+
export const addAllSelectableSourceOptions = ({
|
|
10
|
+
sourceOptions,
|
|
11
|
+
onChange,
|
|
12
|
+
selected,
|
|
13
|
+
setHighlightedSourceOptions,
|
|
14
|
+
}) => {
|
|
15
|
+
const enabledSourceOptions = sourceOptions.filter(
|
|
16
|
+
({ disabled }) => !disabled
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
const newSelected = enabledSourceOptions.reduce(
|
|
20
|
+
(accumulatedSelected, enabledSourceOption) => [
|
|
21
|
+
...accumulatedSelected,
|
|
22
|
+
enabledSourceOption.value,
|
|
23
|
+
],
|
|
24
|
+
selected
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
setHighlightedSourceOptions([])
|
|
28
|
+
|
|
29
|
+
// If we ever allow maxSelection to be any value
|
|
30
|
+
// other than 1 and Infinity, we need to think
|
|
31
|
+
// about how this should behave:
|
|
32
|
+
//
|
|
33
|
+
// Either we hide this button when it's not "Infinity",
|
|
34
|
+
// or we have to decide whether we want to take the first
|
|
35
|
+
// nth options or the last
|
|
36
|
+
onChange({ selected: newSelected })
|
|
37
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {Object} args
|
|
3
|
+
* @param {bool} args.filterable
|
|
4
|
+
* @param {Object[]} args.sourceOptions
|
|
5
|
+
* @param {string[]} args.highlightedSourceOptions
|
|
6
|
+
* @param {string[]} args.selected
|
|
7
|
+
* @param {Function} args.onChange
|
|
8
|
+
* @param {Function} args.setHighlightedSourceOptions
|
|
9
|
+
* @returns void
|
|
10
|
+
*/
|
|
11
|
+
export const addIndividualSourceOptions = ({
|
|
12
|
+
filterable,
|
|
13
|
+
sourceOptions,
|
|
14
|
+
highlightedSourceOptions,
|
|
15
|
+
maxSelections,
|
|
16
|
+
onChange,
|
|
17
|
+
selected,
|
|
18
|
+
setHighlightedSourceOptions,
|
|
19
|
+
}) => {
|
|
20
|
+
/**
|
|
21
|
+
* Creates a subset of the highlighted options to reflect a changed
|
|
22
|
+
* filter value in case previously highlighted options are now
|
|
23
|
+
* hidden.
|
|
24
|
+
*
|
|
25
|
+
* This enables us to keep items highlighted while searching for
|
|
26
|
+
* a particular one.
|
|
27
|
+
*
|
|
28
|
+
* With this subset we only select the subset when the user
|
|
29
|
+
* clicks the "add individuals" button
|
|
30
|
+
*/
|
|
31
|
+
const filteredHighlightedSourceOptions = filterable
|
|
32
|
+
? highlightedSourceOptions.filter((value) =>
|
|
33
|
+
sourceOptions.find(
|
|
34
|
+
(filteredOption) => filteredOption.value === value
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
: highlightedSourceOptions
|
|
38
|
+
|
|
39
|
+
const newSelected = filteredHighlightedSourceOptions.reduce(
|
|
40
|
+
(accumulatedSelected, value) => [
|
|
41
|
+
...accumulatedSelected,
|
|
42
|
+
filteredHighlightedSourceOptions.find(
|
|
43
|
+
(filteredHighlightedSourceOption) =>
|
|
44
|
+
filteredHighlightedSourceOption === value
|
|
45
|
+
),
|
|
46
|
+
],
|
|
47
|
+
selected
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
setHighlightedSourceOptions([])
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* This will extract from the end, hence the "-1 *"
|
|
54
|
+
* As the "newest" additions are always at the end of the array,
|
|
55
|
+
* it's safe to just take the last nth (depending on maxSelection)
|
|
56
|
+
* to always get the right ones
|
|
57
|
+
*/
|
|
58
|
+
onChange({
|
|
59
|
+
selected: newSelected.slice(-1 * maxSelections),
|
|
60
|
+
})
|
|
61
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {Object} args
|
|
3
|
+
* @param {number} args.maxSelections
|
|
4
|
+
* @param {string[]} args.selected
|
|
5
|
+
* @param {Function} args.onChange
|
|
6
|
+
* @param {Function} args.setHighlightedSourceOptions
|
|
7
|
+
* @param {Function} args.setHighlightedPickedOptions
|
|
8
|
+
* @returns void
|
|
9
|
+
*/
|
|
10
|
+
export const createDoubleClickHandlers = ({
|
|
11
|
+
maxSelections,
|
|
12
|
+
onChange,
|
|
13
|
+
selected,
|
|
14
|
+
setHighlightedPickedOptions,
|
|
15
|
+
setHighlightedSourceOptions,
|
|
16
|
+
}) => {
|
|
17
|
+
const selectSingleOption = ({ value }) => {
|
|
18
|
+
const newSelected = selected.includes(value)
|
|
19
|
+
? selected
|
|
20
|
+
: [...selected, value]
|
|
21
|
+
|
|
22
|
+
setHighlightedSourceOptions([])
|
|
23
|
+
onChange({ selected: newSelected.slice(-1 * maxSelections) })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const deselectSingleOption = ({ value }) => {
|
|
27
|
+
const newSelected = selected.filter(
|
|
28
|
+
(curSelected) => curSelected !== value
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
setHighlightedPickedOptions([])
|
|
32
|
+
onChange({ selected: newSelected })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { selectSingleOption, deselectSingleOption }
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {Object[]} options
|
|
3
|
+
* @param {string} filter
|
|
4
|
+
* @returns {Object[]}
|
|
5
|
+
*/
|
|
6
|
+
export const defaultFilterCallback = (options, filter) => {
|
|
7
|
+
if (filter === '') {
|
|
8
|
+
return options
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const regex = new RegExp(filter, 'i')
|
|
12
|
+
return options.filter(({ label }) => label.match(regex))
|
|
13
|
+
} catch {
|
|
14
|
+
console.warn('Invalid regex filter:', filter)
|
|
15
|
+
return options
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the indices, in ascending order, of `highlightedPickedOptions`
|
|
3
|
+
* within `selected`. Values in `highlightedPickedOptions` that don't appear
|
|
4
|
+
* in `selected` are ignored, so callers can pass a potentially stale
|
|
5
|
+
* highlight list without filtering first.
|
|
6
|
+
*
|
|
7
|
+
* Runs in O(n) over `selected` via a Set lookup.
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} args
|
|
10
|
+
* @param {string[]} args.selected
|
|
11
|
+
* @param {string[]} args.highlightedPickedOptions
|
|
12
|
+
* @returns {number[]}
|
|
13
|
+
*/
|
|
14
|
+
export const getHighlightedPickedIndices = ({
|
|
15
|
+
selected,
|
|
16
|
+
highlightedPickedOptions,
|
|
17
|
+
}) => {
|
|
18
|
+
const highlightedSet = new Set(highlightedPickedOptions)
|
|
19
|
+
const indices = []
|
|
20
|
+
selected.forEach((value, index) => {
|
|
21
|
+
if (highlightedSet.has(value)) {
|
|
22
|
+
indices.push(index)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
return indices
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getModeByModifierKey } from '../common/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {Object} option
|
|
5
|
+
* @param {Function} selectionHandler
|
|
6
|
+
* @param {Function} toggleHighlightedOption
|
|
7
|
+
* @returns {Object}
|
|
8
|
+
*/
|
|
9
|
+
export const getOptionClickHandlers = (
|
|
10
|
+
option,
|
|
11
|
+
selectionHandler,
|
|
12
|
+
toggleHighlightedOption
|
|
13
|
+
) => ({
|
|
14
|
+
onClick: (_, event) => {
|
|
15
|
+
const mode = getModeByModifierKey(event)
|
|
16
|
+
toggleHighlightedOption({ option, mode })
|
|
17
|
+
},
|
|
18
|
+
onDoubleClick: selectionHandler,
|
|
19
|
+
})
|