@dhis2-ui/selector-bar 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.
Files changed (38) hide show
  1. package/build/cjs/selector-bar-item/selector-bar-item.js +9 -0
  2. package/build/es/selector-bar-item/selector-bar-item.js +9 -0
  3. package/package.json +13 -12
  4. package/src/index.js +2 -0
  5. package/src/locales/ar/translations.json +3 -0
  6. package/src/locales/cs/translations.json +3 -0
  7. package/src/locales/en/translations.json +3 -0
  8. package/src/locales/es/translations.json +3 -0
  9. package/src/locales/es_419/translations.json +3 -0
  10. package/src/locales/fr/translations.json +3 -0
  11. package/src/locales/hi_IN/translations.json +3 -0
  12. package/src/locales/id/translations.json +3 -0
  13. package/src/locales/index.js +50 -0
  14. package/src/locales/lo/translations.json +3 -0
  15. package/src/locales/nb/translations.json +3 -0
  16. package/src/locales/pt/translations.json +3 -0
  17. package/src/locales/ru/translations.json +3 -0
  18. package/src/locales/uk/translations.json +3 -0
  19. package/src/locales/uz_UZ_Cyrl/translations.json +3 -0
  20. package/src/locales/uz_UZ_Latn/translations.json +3 -0
  21. package/src/locales/vi/translations.json +3 -0
  22. package/src/locales/zh/translations.json +3 -0
  23. package/src/locales/zh_CN/translations.json +3 -0
  24. package/src/selector-bar/__stories__/common.js +232 -0
  25. package/src/selector-bar/__stories__/keep-org-unit-expanded.js +71 -0
  26. package/src/selector-bar/__stories__/rtl.js +105 -0
  27. package/src/selector-bar/__stories__/with-multiple-rows.js +131 -0
  28. package/src/selector-bar/__stories__/with-right-hand-side-content.js +103 -0
  29. package/src/selector-bar/__stories__/with-some-inputs.js +95 -0
  30. package/src/selector-bar/index.js +1 -0
  31. package/src/selector-bar/selector-bar.js +171 -0
  32. package/src/selector-bar/selector-bar.prod.stories.js +6 -0
  33. package/src/selector-bar/selector-bar.test.js +124 -0
  34. package/src/selector-bar-item/features/toggle-children/index.js +27 -0
  35. package/src/selector-bar-item/features/toggle-children.feature +11 -0
  36. package/src/selector-bar-item/index.js +1 -0
  37. package/src/selector-bar-item/selector-bar-item.e2e.stories.js +64 -0
  38. package/src/selector-bar-item/selector-bar-item.js +190 -0
@@ -0,0 +1,131 @@
1
+ import { OrganisationUnitTree } from '@dhis2-ui/organisation-unit-tree'
2
+ import React from 'react'
3
+ import { SelectorBar, SelectorBarItem } from '../../index.js'
4
+ import {
5
+ MenuSelect,
6
+ createDecoratorCustomDataProvider,
7
+ createStatefulDecorator,
8
+ dataSets,
9
+ decoratorCommonStyles,
10
+ workflows,
11
+ } from './common.js'
12
+
13
+ export const WithMultipleRows = (
14
+ _,
15
+ {
16
+ workflow,
17
+ setWorkflow,
18
+ workflowOpen,
19
+ setWorkflowOpen,
20
+ dataSet,
21
+ setDataSet,
22
+ dataSetOpen,
23
+ setDataSetOpen,
24
+ orgUnit,
25
+ setOrgUnit,
26
+ orgUnitOpen,
27
+ setOrgUnitOpen,
28
+ }
29
+ ) => {
30
+ return (
31
+ <div className="narrow-container">
32
+ <SelectorBar
33
+ disableClearSelections
34
+ onClearSelectionClick={() => null}
35
+ >
36
+ <SelectorBarItem
37
+ disabled={false}
38
+ label="Workflow"
39
+ value={workflow?.label}
40
+ noValueMessage="Choose a workflow"
41
+ open={workflowOpen}
42
+ setOpen={setWorkflowOpen}
43
+ >
44
+ <MenuSelect
45
+ values={workflows}
46
+ selected={workflow?.value}
47
+ onChange={({ selected }) => {
48
+ setWorkflow(
49
+ workflows.find(
50
+ (currentWorkflow) =>
51
+ selected === currentWorkflow.value
52
+ )
53
+ )
54
+ setWorkflowOpen(false)
55
+ }}
56
+ />
57
+ </SelectorBarItem>
58
+
59
+ <SelectorBarItem
60
+ disabled={!workflow}
61
+ label="Data set"
62
+ value={dataSet?.label}
63
+ noValueMessage="Choose a data set"
64
+ open={dataSetOpen}
65
+ setOpen={setDataSetOpen}
66
+ >
67
+ <MenuSelect
68
+ values={dataSets}
69
+ selected={dataSet?.value}
70
+ onChange={({ selected }) => {
71
+ setDataSet(
72
+ dataSets.find(
73
+ (currentWorkflow) =>
74
+ selected === currentWorkflow.value
75
+ )
76
+ )
77
+ setDataSetOpen(false)
78
+ }}
79
+ />
80
+ </SelectorBarItem>
81
+
82
+ <SelectorBarItem
83
+ disabled={!workflow && !dataSet}
84
+ label="Org unit"
85
+ value={orgUnit?.displayName}
86
+ noValueMessage="Choose an organisation unit"
87
+ open={orgUnitOpen}
88
+ setOpen={setOrgUnitOpen}
89
+ >
90
+ <div
91
+ style={{
92
+ width: 400,
93
+ minHeight: 400,
94
+ maxHeight: '70vh',
95
+ }}
96
+ >
97
+ <OrganisationUnitTree
98
+ singleSelection
99
+ onChange={(nextOrgUnit, evt) => {
100
+ evt.stopPropagation()
101
+ setOrgUnit(nextOrgUnit)
102
+ setOrgUnitOpen(false)
103
+ }}
104
+ roots={['A0000000000']}
105
+ selected={orgUnit ? [orgUnit.path] : []}
106
+ />
107
+ </div>
108
+ </SelectorBarItem>
109
+ </SelectorBar>
110
+
111
+ <p style={{ padding: '0 16px', textAlign: 'center' }}>
112
+ This story shows the selection bar component with multiple rows
113
+ when a workflow has been selected.
114
+ </p>
115
+
116
+ <style jsx>{`
117
+ .narrow-container {
118
+ margin: 0 auto;
119
+ width: 100%;
120
+ max-width: 600px;
121
+ }
122
+ `}</style>
123
+ </div>
124
+ )
125
+ }
126
+
127
+ WithMultipleRows.decorators = [
128
+ decoratorCommonStyles,
129
+ createStatefulDecorator({ workflow: workflows[0] }),
130
+ createDecoratorCustomDataProvider(),
131
+ ]
@@ -0,0 +1,103 @@
1
+ import { Button } from '@dhis2-ui/button'
2
+ import React from 'react'
3
+ import { SelectorBar, SelectorBarItem } from '../../index.js'
4
+ import {
5
+ OrgUnitSelect,
6
+ MenuSelect,
7
+ createDecoratorCustomDataProvider,
8
+ createStatefulDecorator,
9
+ decoratorCommonStyles,
10
+ workflows,
11
+ } from './common.js'
12
+
13
+ const RHSContent = () => (
14
+ <div className="rhs-content">
15
+ <Button small>Options</Button>
16
+ <style jsx>{`
17
+ .rhs-content {
18
+ display: flex;
19
+ height: 40px;
20
+ padding: 0 16px;
21
+ align-items: center;
22
+ }
23
+ `}</style>
24
+ </div>
25
+ )
26
+
27
+ export const WithAdditionalContent = (
28
+ _,
29
+ {
30
+ workflow,
31
+ setWorkflow,
32
+ workflowOpen,
33
+ setWorkflowOpen,
34
+ orgUnit,
35
+ setOrgUnit,
36
+ orgUnitOpen,
37
+ setOrgUnitOpen,
38
+ }
39
+ ) => {
40
+ return (
41
+ <SelectorBar
42
+ disableClearSelections={!workflow || !orgUnit}
43
+ onClearSelectionClick={() => {
44
+ setWorkflow(null)
45
+ setOrgUnit(null)
46
+ }}
47
+ additionalContent={<RHSContent />}
48
+ >
49
+ <SelectorBarItem
50
+ disabled={false}
51
+ label="Workflow"
52
+ value={workflow?.label}
53
+ noValueMessage="Choose a workflow"
54
+ open={workflowOpen}
55
+ setOpen={setWorkflowOpen}
56
+ onClearSelectionClick={() => {
57
+ setWorkflow(null)
58
+ }}
59
+ >
60
+ <MenuSelect
61
+ values={workflows}
62
+ selected={workflow?.value}
63
+ onChange={({ selected }) => {
64
+ setWorkflow(
65
+ workflows.find(
66
+ (currentWorkflow) =>
67
+ selected === currentWorkflow.value
68
+ )
69
+ )
70
+ setWorkflowOpen(false)
71
+ }}
72
+ />
73
+ </SelectorBarItem>
74
+
75
+ <SelectorBarItem
76
+ disabled={!workflow}
77
+ label="Org unit"
78
+ value={orgUnit?.displayName}
79
+ noValueMessage="Choose an organisation unit"
80
+ open={orgUnitOpen}
81
+ setOpen={setOrgUnitOpen}
82
+ onClearSelectionClick={() => {
83
+ setOrgUnit(null)
84
+ }}
85
+ >
86
+ <OrgUnitSelect
87
+ selected={orgUnit ? [orgUnit.path] : []}
88
+ onChange={(nextOrgUnit, evt) => {
89
+ evt.stopPropagation()
90
+ setOrgUnit(nextOrgUnit)
91
+ setOrgUnitOpen(false)
92
+ }}
93
+ />
94
+ </SelectorBarItem>
95
+ </SelectorBar>
96
+ )
97
+ }
98
+
99
+ WithAdditionalContent.decorators = [
100
+ decoratorCommonStyles,
101
+ createStatefulDecorator(),
102
+ createDecoratorCustomDataProvider(),
103
+ ]
@@ -0,0 +1,95 @@
1
+ import React from 'react'
2
+ import { SelectorBar, SelectorBarItem } from '../../index.js'
3
+ import {
4
+ OrgUnitSelect,
5
+ MenuSelect,
6
+ createDecoratorCustomDataProvider,
7
+ createStatefulDecorator,
8
+ decoratorCommonStyles,
9
+ workflows,
10
+ } from './common.js'
11
+
12
+ export const WithSomeInputs = (
13
+ _,
14
+ {
15
+ workflow,
16
+ setWorkflow,
17
+ workflowOpen,
18
+ setWorkflowOpen,
19
+ orgUnit,
20
+ setOrgUnit,
21
+ orgUnitOpen,
22
+ setOrgUnitOpen,
23
+ }
24
+ ) => {
25
+ return (
26
+ <SelectorBar
27
+ disableClearSelections={!workflow && !orgUnit}
28
+ onClearSelectionClick={() => {
29
+ setWorkflow(null)
30
+ setOrgUnit(null)
31
+ }}
32
+ >
33
+ <SelectorBarItem
34
+ disabled={false}
35
+ label="Workflow"
36
+ value={workflow?.label}
37
+ noValueMessage="Choose a workflow"
38
+ open={workflowOpen}
39
+ setOpen={setWorkflowOpen}
40
+ onClearSelectionClick={() => {
41
+ setWorkflow(null)
42
+ }}
43
+ >
44
+ <MenuSelect
45
+ values={workflows}
46
+ selected={workflow?.value}
47
+ onChange={({ selected }) => {
48
+ setWorkflow(
49
+ workflows.find(
50
+ (currentWorkflow) =>
51
+ selected === currentWorkflow.value
52
+ )
53
+ )
54
+ setWorkflowOpen(false)
55
+ }}
56
+ />
57
+ </SelectorBarItem>
58
+
59
+ <SelectorBarItem
60
+ disabled={!workflow}
61
+ label="Org unit"
62
+ value={orgUnit?.displayName}
63
+ noValueMessage="Choose an organisation unit"
64
+ open={orgUnitOpen}
65
+ setOpen={setOrgUnitOpen}
66
+ onClearSelectionClick={() => {
67
+ setOrgUnit(null)
68
+ }}
69
+ >
70
+ <OrgUnitSelect
71
+ selected={orgUnit ? [orgUnit.path] : []}
72
+ onChange={(nextOrgUnit, evt) => {
73
+ evt.stopPropagation()
74
+ setOrgUnit(nextOrgUnit)
75
+ setOrgUnitOpen(false)
76
+ }}
77
+ />
78
+ </SelectorBarItem>
79
+ <SelectorBarItem
80
+ displayOnly={true}
81
+ label="Person"
82
+ value="John doe"
83
+ onClearSelectionClick={() => {
84
+ alert('This selection would be cleared')
85
+ }}
86
+ />
87
+ </SelectorBar>
88
+ )
89
+ }
90
+
91
+ WithSomeInputs.decorators = [
92
+ decoratorCommonStyles,
93
+ createStatefulDecorator({ workflow: workflows[0] }),
94
+ createDecoratorCustomDataProvider(),
95
+ ]
@@ -0,0 +1 @@
1
+ export { SelectorBar } from './selector-bar.js'
@@ -0,0 +1,171 @@
1
+ import { colors, spacers } from '@dhis2/ui-constants'
2
+ import { Button } from '@dhis2-ui/button'
3
+ import cx from 'classnames'
4
+ import PropTypes from 'prop-types'
5
+ import React, { useRef, useState, useEffect } from 'react'
6
+ import i18n from '../locales/index.js'
7
+
8
+ const ClearSelection = ({ disabled, onClick }) => {
9
+ return (
10
+ <div className="clear-selections">
11
+ <Button small secondary disabled={disabled} onClick={onClick}>
12
+ {i18n.t('Clear selections')}
13
+ </Button>
14
+
15
+ <style jsx>{`
16
+ .clear-selections {
17
+ display: flex;
18
+ align-items: center;
19
+ height: 40px;
20
+ padding: 0 ${spacers.dp8};
21
+ }
22
+ `}</style>
23
+ </div>
24
+ )
25
+ }
26
+
27
+ ClearSelection.propTypes = {
28
+ disabled: PropTypes.bool,
29
+ onClick: PropTypes.func,
30
+ }
31
+
32
+ export const SelectorBar = ({
33
+ className,
34
+ children,
35
+ onClearSelectionClick,
36
+ dataTest = 'dhis2-ui-selectorbar',
37
+ disableClearSelections,
38
+ additionalContent,
39
+ ariaLabel,
40
+ }) => {
41
+ const container = useRef(null)
42
+
43
+ const [childrenToFocus, setChildrenToFocus] = useState([])
44
+
45
+ useEffect(() => {
46
+ if (container.current) {
47
+ const controlsDiv = container.current.querySelector('.controls')
48
+ if (controlsDiv) {
49
+ const childElements = Array.from(controlsDiv.children)
50
+ childElements.forEach((child) => {
51
+ child.tabIndex = -1
52
+ })
53
+ setChildrenToFocus(childElements)
54
+ }
55
+ }
56
+ }, [children])
57
+
58
+ const handleKeyDown = (event) => {
59
+ const currentFocus = document.activeElement
60
+
61
+ if (container.current && container.current === currentFocus) {
62
+ if (childrenToFocus.length > 0 && childrenToFocus[0]) {
63
+ childrenToFocus[0].focus()
64
+ }
65
+ return
66
+ }
67
+ if (!childrenToFocus.length) {
68
+ return
69
+ }
70
+
71
+ const currentIndex = childrenToFocus.findIndex(
72
+ (element) => element === currentFocus
73
+ )
74
+
75
+ if (currentIndex === -1) {
76
+ return
77
+ }
78
+
79
+ if (event.key === 'ArrowRight') {
80
+ event.preventDefault()
81
+ const nextIndex = (currentIndex + 1) % childrenToFocus.length
82
+ childrenToFocus[nextIndex].focus()
83
+ }
84
+
85
+ if (event.key === 'ArrowLeft') {
86
+ event.preventDefault()
87
+ const prevIndex =
88
+ (currentIndex - 1 + childrenToFocus.length) %
89
+ childrenToFocus.length
90
+ childrenToFocus[prevIndex].focus()
91
+ }
92
+ }
93
+
94
+ return (
95
+ <>
96
+ <div
97
+ className={cx(
98
+ 'container',
99
+ { withRHSContents: additionalContent },
100
+ className
101
+ )}
102
+ data-test={dataTest}
103
+ onKeyDown={handleKeyDown}
104
+ tabIndex={0}
105
+ ref={container}
106
+ role="toolbar"
107
+ aria-label={ariaLabel}
108
+ >
109
+ <div className="controls">
110
+ {/* {React.Children.map(children, (child, index) =>
111
+ React.cloneElement(child, {
112
+ ref: childrenRefs[index],
113
+ })
114
+ )} */}
115
+ {children}
116
+ {onClearSelectionClick && (
117
+ <ClearSelection
118
+ disabled={disableClearSelections}
119
+ onClick={onClearSelectionClick}
120
+ />
121
+ )}
122
+ </div>
123
+
124
+ {additionalContent && (
125
+ <div className="additional-contents">
126
+ {additionalContent}
127
+ </div>
128
+ )}
129
+
130
+ <style jsx>{`
131
+ .container {
132
+ background: ${colors.white};
133
+ box-shadow: inset 0 -1px 0 0 ${colors.grey400};
134
+ padding-bottom: 1px;
135
+ }
136
+
137
+ .withRHSContents {
138
+ display: flex;
139
+ }
140
+
141
+ .controls {
142
+ display: flex;
143
+ flex-wrap: wrap;
144
+ flex-grow: 1;
145
+ gap: 1px;
146
+ }
147
+
148
+ .additional-contents {
149
+ ${
150
+ /*
151
+ * Specs define the space to be 12px.
152
+ * 8px already come from the clear selection component
153
+ */ ''
154
+ }
155
+ padding-inline-start: 4px;
156
+ }
157
+ `}</style>
158
+ </div>
159
+ </>
160
+ )
161
+ }
162
+
163
+ SelectorBar.propTypes = {
164
+ children: PropTypes.any.isRequired,
165
+ additionalContent: PropTypes.any,
166
+ ariaLabel: PropTypes.string,
167
+ className: PropTypes.string,
168
+ dataTest: PropTypes.string,
169
+ disableClearSelections: PropTypes.bool,
170
+ onClearSelectionClick: PropTypes.func,
171
+ }
@@ -0,0 +1,6 @@
1
+ export default { title: 'SelectionBar' }
2
+ export { WithSomeInputs } from './__stories__/with-some-inputs.js'
3
+ export { WithAdditionalContent } from './__stories__/with-right-hand-side-content.js'
4
+ export { KeepOrgUnitExpanded } from './__stories__/keep-org-unit-expanded.js'
5
+ export { WithMultipleRows } from './__stories__/with-multiple-rows.js'
6
+ export { RTL } from './__stories__/rtl.js'
@@ -0,0 +1,124 @@
1
+ import '@testing-library/jest-dom'
2
+ import { render, screen } from '@testing-library/react'
3
+ import React from 'react'
4
+ import { SelectorBarItem } from '../selector-bar-item/index.js'
5
+ import { SelectorBar } from './selector-bar.js'
6
+
7
+ const noop = () => null
8
+
9
+ describe('SelectorBar', () => {
10
+ it('should render the selection bar items', () => {
11
+ render(
12
+ <SelectorBar>
13
+ <SelectorBarItem
14
+ label="Selection bar item 1"
15
+ noValueMessage="No value message 1"
16
+ open={false}
17
+ setOpen={noop}
18
+ >
19
+ Content
20
+ </SelectorBarItem>
21
+
22
+ <SelectorBarItem
23
+ label="Selection bar item 2"
24
+ noValueMessage="No value message 2"
25
+ open={false}
26
+ setOpen={noop}
27
+ >
28
+ Content
29
+ </SelectorBarItem>
30
+ </SelectorBar>
31
+ )
32
+
33
+ expect(screen.getByText('Selection bar item 1')).not.toBeNull()
34
+ expect(screen.getByText('Selection bar item 2')).not.toBeNull()
35
+ })
36
+
37
+ it('should not disable the clear selection button by default', () => {
38
+ render(
39
+ <SelectorBar onClearSelectionClick={noop}>
40
+ <SelectorBarItem
41
+ label="label"
42
+ noValueMessage="msg"
43
+ open={false}
44
+ setOpen={noop}
45
+ >
46
+ Content
47
+ </SelectorBarItem>
48
+ </SelectorBar>
49
+ )
50
+
51
+ const clearBtn = screen.getByText('Clear selections')
52
+ expect(clearBtn).not.toBeNull()
53
+ expect(clearBtn).not.toBeDisabled()
54
+ })
55
+
56
+ it('should disable the clear selection button', () => {
57
+ render(
58
+ <SelectorBar disableClearSelections onClearSelectionClick={noop}>
59
+ <SelectorBarItem
60
+ label="label"
61
+ noValueMessage="msg"
62
+ open={false}
63
+ setOpen={noop}
64
+ >
65
+ Content
66
+ </SelectorBarItem>
67
+ </SelectorBar>
68
+ )
69
+
70
+ const clearBtn = screen.getByText('Clear selections')
71
+ expect(clearBtn).not.toBeNull()
72
+ expect(clearBtn).toBeDisabled()
73
+ })
74
+
75
+ it('should render the optional content', () => {
76
+ render(
77
+ <SelectorBar additionalContent={<div>Foobar</div>}>
78
+ <SelectorBarItem
79
+ label="label"
80
+ noValueMessage="msg"
81
+ open={false}
82
+ setOpen={noop}
83
+ >
84
+ Content
85
+ </SelectorBarItem>
86
+ </SelectorBar>
87
+ )
88
+
89
+ const extraContent = screen.getByText('Foobar')
90
+ expect(extraContent).not.toBeNull()
91
+ })
92
+
93
+ it('should render the displayOnly value', () => {
94
+ render(
95
+ <SelectorBar>
96
+ <SelectorBarItem
97
+ displayOnly={true}
98
+ label="Person"
99
+ value="John doe"
100
+ />
101
+ </SelectorBar>
102
+ )
103
+
104
+ const value = screen.getByText('John doe')
105
+ expect(value).not.toBeNull()
106
+ })
107
+
108
+ it('should render the cross icon', () => {
109
+ render(
110
+ <SelectorBar>
111
+ <SelectorBarItem
112
+ label="label"
113
+ value="selected value"
114
+ onClearSelectionClick={noop}
115
+ />
116
+ </SelectorBar>
117
+ )
118
+
119
+ const clearIcon = screen.getByTestId(
120
+ 'dhis2-ui-selectorbaritem-clear-icon'
121
+ )
122
+ expect(clearIcon).not.toBeNull()
123
+ })
124
+ })
@@ -0,0 +1,27 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('the selector bar item is closed', () => {
4
+ cy.visitStory('SelectorBarItem', 'closed no value')
5
+ })
6
+
7
+ Given('the selector bar item is open', () => {
8
+ cy.visitStory('SelectorBarItem', 'open no value')
9
+ })
10
+
11
+ When('the user opens the selector bar item', () => {
12
+ cy.contains('Selection bar item', { selector: 'button' }).click()
13
+ })
14
+
15
+ When('the user closes the selector bar item', () => {
16
+ cy.get('body').click('topLeft')
17
+ })
18
+
19
+ Then('the children should be displayed', () => {
20
+ cy.get('[data-test="dhis2-uiwidgets-singleselectfield"]').should('exist')
21
+ })
22
+
23
+ Then('the children should not be displayed', () => {
24
+ cy.get('[data-test="dhis2-uiwidgets-singleselectfield"]').should(
25
+ 'not.exist'
26
+ )
27
+ })
@@ -0,0 +1,11 @@
1
+ Feature: The display of the children can be toggled
2
+
3
+ Scenario: The children are being opened
4
+ Given the selector bar item is closed
5
+ When the user opens the selector bar item
6
+ Then the children should be displayed
7
+
8
+ Scenario: The children are being closed
9
+ Given the selector bar item is open
10
+ When the user closes the selector bar item
11
+ Then the children should not be displayed
@@ -0,0 +1 @@
1
+ export { SelectorBarItem } from './selector-bar-item.js'