@dhis2-ui/organisation-unit-tree 10.16.2 → 10.16.3

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.
Files changed (142) hide show
  1. package/package.json +8 -7
  2. package/src/__e2e__/children_as_child_nodes.js +23 -0
  3. package/src/__e2e__/common.js +70 -0
  4. package/src/__e2e__/controlled_expanded.js +89 -0
  5. package/src/__e2e__/displaying_loading_error.js +45 -0
  6. package/src/__e2e__/expanded.js +42 -0
  7. package/src/__e2e__/force_reload.js +66 -0
  8. package/src/__e2e__/get-organisation-unit-data.js +119 -0
  9. package/src/__e2e__/highlight.js +23 -0
  10. package/src/__e2e__/loading_state.js +37 -0
  11. package/src/__e2e__/multi_selection.js +24 -0
  12. package/src/__e2e__/namespace.js +1 -0
  13. package/src/__e2e__/no_selection.js +32 -0
  14. package/src/__e2e__/path_based_filtering.js +49 -0
  15. package/src/__e2e__/single_selection.js +46 -0
  16. package/src/__e2e__/sub_unit_as_root.js +28 -0
  17. package/src/__e2e__/tree_api.js +55 -0
  18. package/src/__stories__/collapsed.js +11 -0
  19. package/src/__stories__/custom-expanded-imperative-open.js +181 -0
  20. package/src/__stories__/custom-node-label.js +19 -0
  21. package/src/__stories__/development-stories.js +86 -0
  22. package/src/__stories__/expanded.js +12 -0
  23. package/src/__stories__/filtered-root.js +15 -0
  24. package/src/__stories__/filtered.js +13 -0
  25. package/src/__stories__/force-reload-all.js +46 -0
  26. package/src/__stories__/force-reload-one-unit.js +36 -0
  27. package/src/__stories__/highlighted.js +13 -0
  28. package/src/__stories__/indeterminate.js +13 -0
  29. package/src/__stories__/loading-error-grandchild.js +39 -0
  30. package/src/__stories__/loading.js +27 -0
  31. package/src/__stories__/multiple-roots.js +20 -0
  32. package/src/__stories__/no-selection.js +16 -0
  33. package/src/__stories__/replace-roots.js +28 -0
  34. package/src/__stories__/root-error.js +36 -0
  35. package/src/__stories__/root-loading.js +34 -0
  36. package/src/__stories__/rtl.js +14 -0
  37. package/src/__stories__/selected-multiple.js +18 -0
  38. package/src/__stories__/shared.js +192 -0
  39. package/src/__stories__/single-selection.js +16 -0
  40. package/src/features/children_as_child_nodes/index.js +29 -0
  41. package/src/features/children_as_child_nodes.feature +6 -0
  42. package/src/features/controlled_expanded/index.js +86 -0
  43. package/src/features/controlled_expanded.feature +11 -0
  44. package/src/features/displaying_loading_error/index.js +46 -0
  45. package/src/features/displaying_loading_error.feature +24 -0
  46. package/src/features/expanded/index.js +87 -0
  47. package/src/features/expanded.feature +27 -0
  48. package/src/features/force_reload/index.js +36 -0
  49. package/src/features/force_reload.feature +7 -0
  50. package/src/features/highlight/index.js +9 -0
  51. package/src/features/highlight.feature +5 -0
  52. package/src/features/loading_state/index.js +26 -0
  53. package/src/features/loading_state.feature +7 -0
  54. package/src/features/multi_selection/index.js +94 -0
  55. package/src/features/multi_selection.feature +31 -0
  56. package/src/features/no_selection/index.js +41 -0
  57. package/src/features/no_selection.feature +13 -0
  58. package/src/features/path_based_filtering/index.js +97 -0
  59. package/src/features/path_based_filtering.feature +24 -0
  60. package/src/features/single_selection/index.js +41 -0
  61. package/src/features/single_selection.feature +20 -0
  62. package/src/features/sub_unit_as_root/index.js +83 -0
  63. package/src/features/sub_unit_as_root.feature +34 -0
  64. package/src/features/tree_api/index.js +121 -0
  65. package/src/features/tree_api.feature +37 -0
  66. package/src/get-all-expanded-paths/get-all-expanded-paths.js +32 -0
  67. package/src/get-all-expanded-paths/get-all-expanded-paths.test.js +22 -0
  68. package/src/get-all-expanded-paths/index.js +1 -0
  69. package/src/helpers/index.js +3 -0
  70. package/src/helpers/is-path-included.js +15 -0
  71. package/src/helpers/left-trim-to-root-id.js +3 -0
  72. package/src/helpers/sort-node-children-alphabetically.js +5 -0
  73. package/src/index.js +6 -0
  74. package/src/locales/ar/translations.json +5 -0
  75. package/src/locales/cs/translations.json +5 -0
  76. package/src/locales/en/translations.json +5 -0
  77. package/src/locales/es/translations.json +5 -0
  78. package/src/locales/es_419/translations.json +5 -0
  79. package/src/locales/fr/translations.json +5 -0
  80. package/src/locales/index.js +50 -0
  81. package/src/locales/lo/translations.json +5 -0
  82. package/src/locales/nb/translations.json +5 -0
  83. package/src/locales/nl/translations.json +5 -0
  84. package/src/locales/pt/translations.json +5 -0
  85. package/src/locales/ru/translations.json +5 -0
  86. package/src/locales/uk/translations.json +5 -0
  87. package/src/locales/uz_Latn/translations.json +5 -0
  88. package/src/locales/uz_UZ_Cyrl/translations.json +5 -0
  89. package/src/locales/uz_UZ_Latn/translations.json +5 -0
  90. package/src/locales/vi/translations.json +5 -0
  91. package/src/locales/zh/translations.json +5 -0
  92. package/src/locales/zh_CN/translations.json +5 -0
  93. package/src/organisation-unit-node/compute-child-nodes.js +27 -0
  94. package/src/organisation-unit-node/compute-child-nodes.test.js +85 -0
  95. package/src/organisation-unit-node/error-message.js +23 -0
  96. package/src/organisation-unit-node/has-descendant-selected-paths.js +15 -0
  97. package/src/organisation-unit-node/has-descendant-selected-paths.test.js +30 -0
  98. package/src/organisation-unit-node/index.js +1 -0
  99. package/src/organisation-unit-node/label/disabled-selection-label.js +26 -0
  100. package/src/organisation-unit-node/label/icon-empty.js +31 -0
  101. package/src/organisation-unit-node/label/icon-folder-closed.js +38 -0
  102. package/src/organisation-unit-node/label/icon-folder-open.js +49 -0
  103. package/src/organisation-unit-node/label/icon-single.js +41 -0
  104. package/src/organisation-unit-node/label/icon.js +35 -0
  105. package/src/organisation-unit-node/label/iconized-checkbox.js +67 -0
  106. package/src/organisation-unit-node/label/index.js +1 -0
  107. package/src/organisation-unit-node/label/label-container.js +36 -0
  108. package/src/organisation-unit-node/label/label.js +146 -0
  109. package/src/organisation-unit-node/label/single-selection-label.js +60 -0
  110. package/src/organisation-unit-node/loading-spinner.js +22 -0
  111. package/src/organisation-unit-node/organisation-unit-node-children.js +123 -0
  112. package/src/organisation-unit-node/organisation-unit-node.js +190 -0
  113. package/src/organisation-unit-node/use-open-state.js +37 -0
  114. package/src/organisation-unit-node/use-open-state.test.js +111 -0
  115. package/src/organisation-unit-node/use-org-children.js +63 -0
  116. package/src/organisation-unit-node/use-org-children.test.js +314 -0
  117. package/src/organisation-unit-node/use-org-data/index.js +1 -0
  118. package/src/organisation-unit-node/use-org-data/use-org-data.js +40 -0
  119. package/src/organisation-unit-node/use-org-data/use-org-data.test.js +137 -0
  120. package/src/organisation-unit-tree/default-render-node-label/default-render-node-label.js +1 -0
  121. package/src/organisation-unit-tree/default-render-node-label/index.js +1 -0
  122. package/src/organisation-unit-tree/filter-root-ids.js +9 -0
  123. package/src/organisation-unit-tree/index.js +3 -0
  124. package/src/organisation-unit-tree/organisation-unit-tree-root-error.js +20 -0
  125. package/src/organisation-unit-tree/organisation-unit-tree-root-loading.js +22 -0
  126. package/src/organisation-unit-tree/organisation-unit-tree.js +253 -0
  127. package/src/organisation-unit-tree/organisation-unit-tree.test.js +77 -0
  128. package/src/organisation-unit-tree/use-expanded/create-expand-handlers.js +45 -0
  129. package/src/organisation-unit-tree/use-expanded/create-expand-handlers.test.js +54 -0
  130. package/src/organisation-unit-tree/use-expanded/index.js +1 -0
  131. package/src/organisation-unit-tree/use-expanded/use-expanded.js +42 -0
  132. package/src/organisation-unit-tree/use-expanded/use-expanded.test.js +73 -0
  133. package/src/organisation-unit-tree/use-force-reload.js +22 -0
  134. package/src/organisation-unit-tree/use-force-reload.test.js +43 -0
  135. package/src/organisation-unit-tree/use-root-org-data/index.js +1 -0
  136. package/src/organisation-unit-tree/use-root-org-data/patch-missing-display-name.js +20 -0
  137. package/src/organisation-unit-tree/use-root-org-data/patch-missing-display-name.test.js +24 -0
  138. package/src/organisation-unit-tree/use-root-org-data/use-root-org-data.js +60 -0
  139. package/src/organisation-unit-tree/use-root-org-data/use-root-org-unit.test.js +184 -0
  140. package/src/organisation-unit-tree.e2e.stories.js +28 -0
  141. package/src/organisation-unit-tree.prod.stories.js +70 -0
  142. package/src/prop-types.js +33 -0
@@ -0,0 +1,31 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+
4
+ export const IconEmpty = ({ dataTest }) => (
5
+ <svg
6
+ height="18px"
7
+ version="1.1"
8
+ viewBox="0 0 18 18"
9
+ width="18px"
10
+ data-test={`${dataTest}-iconempty`}
11
+ >
12
+ <g
13
+ fill="none"
14
+ fillRule="evenodd"
15
+ id="icon/empty"
16
+ stroke="none"
17
+ strokeWidth="1"
18
+ />
19
+
20
+ <style jsx>{`
21
+ svg {
22
+ display: block;
23
+ margin: 3px 0;
24
+ }
25
+ `}</style>
26
+ </svg>
27
+ )
28
+
29
+ IconEmpty.propTypes = {
30
+ dataTest: PropTypes.string.isRequired,
31
+ }
@@ -0,0 +1,38 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+
4
+ export const IconFolderClosed = ({ dataTest }) => (
5
+ <svg
6
+ width="18px"
7
+ height="18px"
8
+ viewBox="0 0 18 18"
9
+ version="1.1"
10
+ data-test={`${dataTest}-iconfolderclosed`}
11
+ >
12
+ <g
13
+ id="icon/folder/closed"
14
+ stroke="none"
15
+ strokeWidth="1"
16
+ fill="none"
17
+ fillRule="evenodd"
18
+ >
19
+ <path
20
+ d="M2,3.5 C1.17157288,3.5 0.5,4.17157288 0.5,5 L0.5,13 C0.5,13.8284271 1.17157288,14.5 2,14.5 L12,14.5 C12.8284271,14.5 13.5,13.8284271 13.5,13 L13.5,7 C13.5,6.17157288 12.8284271,5.5 12,5.5 L6.69098301,5.5 L5.82917961,3.7763932 C5.7444836,3.60700119 5.57135204,3.5 5.38196601,3.5 L2,3.5 Z"
21
+ id="Path-2"
22
+ stroke="#6C7787"
23
+ fill="#D5DDE5"
24
+ />
25
+ </g>
26
+
27
+ <style jsx>{`
28
+ svg {
29
+ display: block;
30
+ margin: 3px 0;
31
+ }
32
+ `}</style>
33
+ </svg>
34
+ )
35
+
36
+ IconFolderClosed.propTypes = {
37
+ dataTest: PropTypes.string.isRequired,
38
+ }
@@ -0,0 +1,49 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+
4
+ export const IconFolderOpen = ({ dataTest }) => (
5
+ <svg
6
+ width="18px"
7
+ height="18px"
8
+ viewBox="0 0 18 18"
9
+ version="1.1"
10
+ data-test={`${dataTest}-iconfolderopen`}
11
+ >
12
+ <g
13
+ id="icon/folder/open"
14
+ stroke="none"
15
+ strokeWidth="1"
16
+ fill="none"
17
+ fillRule="evenodd"
18
+ >
19
+ <g
20
+ id="Group"
21
+ transform="translate(0.000000, 3.000000)"
22
+ stroke="#6C7787"
23
+ >
24
+ <path
25
+ d="M2,0.5 C1.17157288,0.5 0.5,1.17157288 0.5,2 L0.5,10 C0.5,10.8284271 1.17157288,11.5 2,11.5 L12,11.5 C12.8284271,11.5 13.5,10.8284271 13.5,10 L13.5,4 C13.5,3.17157288 12.8284271,2.5 12,2.5 L6.69098301,2.5 L5.82917961,0.776393202 C5.7444836,0.607001188 5.57135204,0.5 5.38196601,0.5 L2,0.5 Z"
26
+ id="Path-2"
27
+ fill="#A0ADBA"
28
+ />
29
+
30
+ <path
31
+ d="M1.53632259,10.7093809 C1.47575089,10.7941813 1.44318932,10.8957885 1.44318932,11 C1.44318932,11.2761424 1.66704695,11.5 1.94318932,11.5 L12.4853821,11.5 C12.6468577,11.5 12.7983931,11.4220172 12.8922488,11.2906191 L16.4636774,6.2906191 C16.5242491,6.20581872 16.5568107,6.10421149 16.5568107,6 C16.5568107,5.72385763 16.3329531,5.5 16.0568107,5.5 L5.5146179,5.5 C5.35314234,5.5 5.20160692,5.57798284 5.10775116,5.7093809 L1.53632259,10.7093809 Z"
32
+ id="Path-3"
33
+ fill="#FBFCFD"
34
+ />
35
+ </g>
36
+ </g>
37
+
38
+ <style jsx>{`
39
+ svg {
40
+ margin: 3px 0;
41
+ display: block;
42
+ }
43
+ `}</style>
44
+ </svg>
45
+ )
46
+
47
+ IconFolderOpen.propTypes = {
48
+ dataTest: PropTypes.string.isRequired,
49
+ }
@@ -0,0 +1,41 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+
4
+ export const IconSingle = ({ dataTest }) => (
5
+ <svg
6
+ height="18px"
7
+ version="1.1"
8
+ viewBox="0 0 18 18"
9
+ width="18px"
10
+ data-test={`${dataTest}-iconsingle`}
11
+ >
12
+ <g
13
+ fill="none"
14
+ fillRule="evenodd"
15
+ id="icon/single"
16
+ stroke="none"
17
+ strokeWidth="1"
18
+ >
19
+ <rect
20
+ fill="#A0ADBA"
21
+ height="4"
22
+ id="Rectangle"
23
+ rx="1"
24
+ width="4"
25
+ x="7"
26
+ y="7"
27
+ />
28
+ </g>
29
+
30
+ <style jsx>{`
31
+ svg {
32
+ display: block;
33
+ margin: 3px 0;
34
+ }
35
+ `}</style>
36
+ </svg>
37
+ )
38
+
39
+ IconSingle.propTypes = {
40
+ dataTest: PropTypes.string.isRequired,
41
+ }
@@ -0,0 +1,35 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import { IconEmpty } from './icon-empty.js'
4
+ import { IconFolderClosed } from './icon-folder-closed.js'
5
+ import { IconFolderOpen } from './icon-folder-open.js'
6
+ import { IconSingle } from './icon-single.js'
7
+
8
+ /**
9
+ * @param {Object} props
10
+ * @param {bool} props.hasChildren
11
+ * @param {bool} props.open
12
+ * @returns {React.Component}
13
+ */
14
+ export const Icon = ({ loading, hasChildren, open, dataTest }) => {
15
+ if (loading) {
16
+ return <IconEmpty dataTest={dataTest} />
17
+ }
18
+
19
+ if (!hasChildren) {
20
+ return <IconSingle dataTest={dataTest} />
21
+ }
22
+
23
+ if (open) {
24
+ return <IconFolderOpen dataTest={dataTest} />
25
+ }
26
+
27
+ return <IconFolderClosed dataTest={dataTest} />
28
+ }
29
+
30
+ Icon.propTypes = {
31
+ dataTest: PropTypes.string.isRequired,
32
+ hasChildren: PropTypes.bool,
33
+ loading: PropTypes.bool,
34
+ open: PropTypes.bool,
35
+ }
@@ -0,0 +1,67 @@
1
+ import { Checkbox } from '@dhis2-ui/checkbox'
2
+ import PropTypes from 'prop-types'
3
+ import React from 'react'
4
+ import { Icon } from './icon.js'
5
+
6
+ export const IconizedCheckbox = ({
7
+ checked,
8
+ dataTest,
9
+ hasChildren,
10
+ indeterminate,
11
+ children,
12
+ loading,
13
+ name,
14
+ open,
15
+ value,
16
+ onChange,
17
+ }) => {
18
+ const icon = (
19
+ <Icon
20
+ loading={loading}
21
+ open={open}
22
+ hasChildren={hasChildren}
23
+ dataTest={dataTest}
24
+ />
25
+ )
26
+
27
+ const checkboxLabel = (
28
+ <>
29
+ <span>{icon}</span>
30
+ {children}
31
+
32
+ <style jsx>{`
33
+ span {
34
+ display: inline-block;
35
+ margin-inline-end: 4px;
36
+ }
37
+ `}</style>
38
+ </>
39
+ )
40
+
41
+ return (
42
+ <>
43
+ <Checkbox
44
+ dense
45
+ checked={checked}
46
+ name={name}
47
+ value={value}
48
+ label={checkboxLabel}
49
+ indeterminate={indeterminate}
50
+ onChange={onChange}
51
+ />
52
+ </>
53
+ )
54
+ }
55
+
56
+ IconizedCheckbox.propTypes = {
57
+ checked: PropTypes.bool.isRequired,
58
+ children: PropTypes.any.isRequired,
59
+ dataTest: PropTypes.string.isRequired,
60
+ hasChildren: PropTypes.bool.isRequired,
61
+ indeterminate: PropTypes.bool.isRequired,
62
+ loading: PropTypes.bool.isRequired,
63
+ name: PropTypes.string.isRequired,
64
+ open: PropTypes.bool.isRequired,
65
+ value: PropTypes.string.isRequired,
66
+ onChange: PropTypes.func.isRequired,
67
+ }
@@ -0,0 +1 @@
1
+ export { Label } from './label.js'
@@ -0,0 +1,36 @@
1
+ import { colors } from '@dhis2/ui-constants'
2
+ import cx from 'classnames'
3
+ import PropTypes from 'prop-types'
4
+ import React from 'react'
5
+
6
+ /**
7
+ * @param {Object} props
8
+ * @param {bool} props.highlighted
9
+ * @param {React.Component|React.Component[]} props.children
10
+ * @returns {React.Component}
11
+ */
12
+ export const LabelContainer = ({ highlighted, children }) => (
13
+ <div className={cx({ highlighted })}>
14
+ <span>{children}</span>
15
+
16
+ <style jsx>{`
17
+ div {
18
+ display: flex;
19
+ }
20
+
21
+ span {
22
+ display: block;
23
+ }
24
+
25
+ .highlighted {
26
+ background: ${colors.teal200};
27
+ padding-inline-end: 4px;
28
+ }
29
+ `}</style>
30
+ </div>
31
+ )
32
+
33
+ LabelContainer.propTypes = {
34
+ children: PropTypes.node,
35
+ highlighted: PropTypes.bool,
36
+ }
@@ -0,0 +1,146 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import { orgUnitPathPropType } from '../../prop-types.js'
4
+ import { DisabledSelectionLabel } from './disabled-selection-label.js'
5
+ import { IconizedCheckbox } from './iconized-checkbox.js'
6
+ import { LabelContainer } from './label-container.js'
7
+ import { SingleSelectionLabel } from './single-selection-label.js'
8
+
9
+ const createNewSelected = ({ selected, path, checked, singleSelection }) => {
10
+ const pathIndex = selected.indexOf(path)
11
+
12
+ if (checked && pathIndex !== -1) {
13
+ return selected
14
+ }
15
+ if (singleSelection && checked) {
16
+ return [path]
17
+ }
18
+ if (checked) {
19
+ return [...selected, path]
20
+ }
21
+ if (pathIndex === -1) {
22
+ return selected
23
+ }
24
+ if (singleSelection) {
25
+ return []
26
+ }
27
+ if (selected.indexOf(path) === 0) {
28
+ return selected.slice(1)
29
+ }
30
+
31
+ const prevSlice = selected.slice(0, pathIndex)
32
+ const nextSlice = selected.slice(pathIndex + 1)
33
+ return [...prevSlice, ...nextSlice]
34
+ }
35
+
36
+ const Label = ({
37
+ checked,
38
+ children,
39
+ dataTest,
40
+ disableSelection,
41
+ fullPath,
42
+ hasChildren,
43
+ hasSelectedDescendants,
44
+ highlighted,
45
+ loading,
46
+ node,
47
+ onChange,
48
+ onToggleOpen,
49
+ open,
50
+ rootId,
51
+ selected,
52
+ singleSelection,
53
+ }) => {
54
+ const onClick = ({ checked }, event) => {
55
+ const newSelected = createNewSelected({
56
+ path: fullPath,
57
+ selected,
58
+ checked,
59
+ singleSelection,
60
+ rootId,
61
+ })
62
+
63
+ // @TODO: It'd make more sense to pass the node as an object
64
+ // instead of spread it. But that'd be a breaking change
65
+ const payload = {
66
+ ...node,
67
+ path: fullPath,
68
+ checked,
69
+ selected: newSelected,
70
+ }
71
+
72
+ onChange(payload, event)
73
+ }
74
+
75
+ if (disableSelection) {
76
+ return (
77
+ <LabelContainer highlighted={highlighted}>
78
+ <DisabledSelectionLabel
79
+ loading={loading}
80
+ onToggleOpen={onToggleOpen}
81
+ >
82
+ {children}
83
+ </DisabledSelectionLabel>
84
+ </LabelContainer>
85
+ )
86
+ }
87
+
88
+ if (singleSelection) {
89
+ return (
90
+ <LabelContainer highlighted={highlighted}>
91
+ <SingleSelectionLabel
92
+ checked={checked}
93
+ onChange={onClick}
94
+ loading={loading}
95
+ >
96
+ {children}
97
+ </SingleSelectionLabel>
98
+ </LabelContainer>
99
+ )
100
+ }
101
+
102
+ return (
103
+ <LabelContainer highlighted={highlighted}>
104
+ <IconizedCheckbox
105
+ dataTest={dataTest}
106
+ checked={checked}
107
+ name="org-unit"
108
+ value={node.id}
109
+ loading={loading}
110
+ indeterminate={!checked && hasSelectedDescendants}
111
+ onChange={onClick}
112
+ open={open}
113
+ hasChildren={hasChildren}
114
+ >
115
+ {children}
116
+ </IconizedCheckbox>
117
+ </LabelContainer>
118
+ )
119
+ }
120
+
121
+ Label.propTypes = {
122
+ // This is `any` so it can be customized by the app
123
+ children: PropTypes.any.isRequired,
124
+ dataTest: PropTypes.string.isRequired,
125
+ fullPath: PropTypes.string.isRequired,
126
+ hasChildren: PropTypes.bool.isRequired,
127
+ loading: PropTypes.bool.isRequired,
128
+ node: PropTypes.shape({
129
+ displayName: PropTypes.string.isRequired,
130
+ id: PropTypes.string.isRequired,
131
+ children: PropTypes.number,
132
+ path: PropTypes.string,
133
+ }).isRequired,
134
+ open: PropTypes.bool.isRequired,
135
+ rootId: PropTypes.string.isRequired,
136
+ onChange: PropTypes.func.isRequired,
137
+ onToggleOpen: PropTypes.func.isRequired,
138
+ checked: PropTypes.bool,
139
+ disableSelection: PropTypes.bool,
140
+ hasSelectedDescendants: PropTypes.bool,
141
+ highlighted: PropTypes.bool,
142
+ selected: PropTypes.arrayOf(orgUnitPathPropType),
143
+ singleSelection: PropTypes.bool,
144
+ }
145
+
146
+ export { Label }
@@ -0,0 +1,60 @@
1
+ import { colors } from '@dhis2/ui-constants'
2
+ import cx from 'classnames'
3
+ import PropTypes from 'prop-types'
4
+ import React from 'react'
5
+
6
+ /**
7
+ * @param {Object} props
8
+ * @param {string} props.label
9
+ * @param {bool} [props.checked]
10
+ * @param {bool} [props.loading]
11
+ * @param {Function} [props.onChange]
12
+ * @returns {React.Component}
13
+ */
14
+ export const SingleSelectionLabel = ({
15
+ checked,
16
+ children,
17
+ onChange,
18
+ loading,
19
+ }) => (
20
+ <span
21
+ onClick={(event) => {
22
+ const payload = { checked: !checked }
23
+ onChange(payload, event)
24
+ }}
25
+ className={cx({ checked, loading })}
26
+ >
27
+ {children}
28
+
29
+ <style jsx>{`
30
+ span {
31
+ background: transparent;
32
+ border-radius: 3px;
33
+ color: ${colors.grey900};
34
+ cursor: pointer;
35
+ display: inline-block;
36
+ font-size: 14px;
37
+ line-height: 24px;
38
+ padding: 0 5px;
39
+ user-select: none;
40
+ white-space: nowrap;
41
+ }
42
+
43
+ .checked {
44
+ background: ${colors.teal700};
45
+ color: white;
46
+ }
47
+
48
+ .loading {
49
+ cursor: auto;
50
+ }
51
+ `}</style>
52
+ </span>
53
+ )
54
+
55
+ SingleSelectionLabel.propTypes = {
56
+ children: PropTypes.any.isRequired,
57
+ checked: PropTypes.bool,
58
+ loading: PropTypes.bool,
59
+ onChange: PropTypes.func,
60
+ }
@@ -0,0 +1,22 @@
1
+ import { CircularLoader } from '@dhis2-ui/loader'
2
+ import React from 'react'
3
+ import { resolve } from 'styled-jsx/css'
4
+
5
+ const loadingSpinnerStyles = resolve`
6
+ .extrasmall {
7
+ display: block;
8
+ margin: 3px 0;
9
+ }
10
+ `
11
+
12
+ export const LoadingSpinner = () => (
13
+ <div>
14
+ <CircularLoader extrasmall className={loadingSpinnerStyles.className} />
15
+ <style>{loadingSpinnerStyles.styles}</style>
16
+ <style jsx>{`
17
+ div {
18
+ width: 24px;
19
+ }
20
+ `}</style>
21
+ </div>
22
+ )
@@ -0,0 +1,123 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import { isPathIncluded } from '../helpers/index.js'
4
+ import i18n from '../locales/index.js'
5
+ import { orgUnitPathPropType } from '../prop-types.js'
6
+ import { LoadingSpinner } from './loading-spinner.js'
7
+ import { useOrgChildren } from './use-org-children.js'
8
+
9
+ const getFilteredChildren = ({ orgChildren, filter, node }) => {
10
+ if (!filter?.length) {
11
+ return orgChildren
12
+ }
13
+
14
+ return orgChildren.filter((child) => {
15
+ return isPathIncluded(filter, `${node.path}/${child.id}`)
16
+ })
17
+ }
18
+
19
+ export const OrganisationUnitNodeChildren = ({
20
+ node,
21
+ autoExpandLoadingError,
22
+ dataTest,
23
+ disableSelection,
24
+ displayProperty,
25
+ expanded,
26
+ filter,
27
+ highlighted,
28
+ isUserDataViewFallback,
29
+ onChange,
30
+ onChildrenLoaded,
31
+ onCollapse,
32
+ onExpand,
33
+ parentPath,
34
+ renderNodeLabel,
35
+ rootId,
36
+ selected,
37
+ singleSelection,
38
+ suppressAlphabeticalSorting,
39
+ OrganisationUnitNode,
40
+ }) => {
41
+ const orgChildren = useOrgChildren({
42
+ node,
43
+ isUserDataViewFallback,
44
+ suppressAlphabeticalSorting,
45
+ onComplete: onChildrenLoaded,
46
+ displayProperty,
47
+ })
48
+
49
+ const displayChildren =
50
+ orgChildren.called && !orgChildren.loading && !orgChildren.error
51
+ const filteredChildren = displayChildren
52
+ ? getFilteredChildren({ orgChildren: orgChildren.data, filter, node })
53
+ : []
54
+
55
+ return (
56
+ <>
57
+ {orgChildren.loading && <LoadingSpinner />}
58
+ {orgChildren.error && `Error: ${orgChildren.error}`}
59
+ {displayChildren &&
60
+ !filteredChildren.length &&
61
+ i18n.t('No children match filter')}
62
+
63
+ {!!filteredChildren.length &&
64
+ filteredChildren.map((child) => {
65
+ const childPath = `${parentPath}/${child.id}`
66
+
67
+ return (
68
+ <OrganisationUnitNode
69
+ autoExpandLoadingError={autoExpandLoadingError}
70
+ dataTest={dataTest}
71
+ disableSelection={disableSelection}
72
+ displayName={child.displayName}
73
+ displayProperty={displayProperty}
74
+ expanded={expanded}
75
+ filter={filter}
76
+ highlighted={highlighted}
77
+ id={child.id}
78
+ isUserDataViewFallback={isUserDataViewFallback}
79
+ key={childPath}
80
+ onChange={onChange}
81
+ onChildrenLoaded={onChildrenLoaded}
82
+ onCollapse={onCollapse}
83
+ onExpand={onExpand}
84
+ path={childPath}
85
+ renderNodeLabel={renderNodeLabel}
86
+ rootId={rootId}
87
+ selected={selected}
88
+ singleSelection={singleSelection}
89
+ suppressAlphabeticalSorting={
90
+ suppressAlphabeticalSorting
91
+ }
92
+ />
93
+ )
94
+ })}
95
+ </>
96
+ )
97
+ }
98
+
99
+ OrganisationUnitNodeChildren.propTypes = {
100
+ // Prevent cirular imports
101
+ OrganisationUnitNode: PropTypes.func.isRequired,
102
+ dataTest: PropTypes.string.isRequired,
103
+ node: PropTypes.object.isRequired,
104
+ parentPath: PropTypes.string.isRequired,
105
+ renderNodeLabel: PropTypes.func.isRequired,
106
+ rootId: PropTypes.string.isRequired,
107
+ onChange: PropTypes.func.isRequired,
108
+
109
+ autoExpandLoadingError: PropTypes.bool,
110
+ disableSelection: PropTypes.bool,
111
+ displayProperty: PropTypes.oneOf(['displayName', 'displayShortName']),
112
+ expanded: PropTypes.arrayOf(orgUnitPathPropType),
113
+ filter: PropTypes.arrayOf(orgUnitPathPropType),
114
+ highlighted: PropTypes.arrayOf(orgUnitPathPropType),
115
+ isUserDataViewFallback: PropTypes.bool,
116
+ selected: PropTypes.arrayOf(orgUnitPathPropType),
117
+ singleSelection: PropTypes.bool,
118
+ suppressAlphabeticalSorting: PropTypes.bool,
119
+
120
+ onChildrenLoaded: PropTypes.func,
121
+ onCollapse: PropTypes.func,
122
+ onExpand: PropTypes.func,
123
+ }