@dhis2-ui/selector-bar 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.
- package/package.json +13 -12
- package/src/index.js +2 -0
- package/src/locales/ar/translations.json +3 -0
- package/src/locales/cs/translations.json +3 -0
- package/src/locales/en/translations.json +3 -0
- package/src/locales/es/translations.json +3 -0
- package/src/locales/es_419/translations.json +3 -0
- package/src/locales/fr/translations.json +3 -0
- package/src/locales/hi_IN/translations.json +3 -0
- package/src/locales/id/translations.json +3 -0
- package/src/locales/index.js +50 -0
- package/src/locales/lo/translations.json +3 -0
- package/src/locales/nb/translations.json +3 -0
- package/src/locales/pt/translations.json +3 -0
- package/src/locales/ru/translations.json +3 -0
- package/src/locales/uk/translations.json +3 -0
- package/src/locales/uz_UZ_Cyrl/translations.json +3 -0
- package/src/locales/uz_UZ_Latn/translations.json +3 -0
- package/src/locales/vi/translations.json +3 -0
- package/src/locales/zh/translations.json +3 -0
- package/src/locales/zh_CN/translations.json +3 -0
- package/src/selector-bar/__stories__/common.js +232 -0
- package/src/selector-bar/__stories__/keep-org-unit-expanded.js +71 -0
- package/src/selector-bar/__stories__/rtl.js +105 -0
- package/src/selector-bar/__stories__/with-multiple-rows.js +131 -0
- package/src/selector-bar/__stories__/with-right-hand-side-content.js +103 -0
- package/src/selector-bar/__stories__/with-some-inputs.js +95 -0
- package/src/selector-bar/index.js +1 -0
- package/src/selector-bar/selector-bar.js +171 -0
- package/src/selector-bar/selector-bar.prod.stories.js +6 -0
- package/src/selector-bar/selector-bar.test.js +124 -0
- package/src/selector-bar-item/features/toggle-children/index.js +27 -0
- package/src/selector-bar-item/features/toggle-children.feature +11 -0
- package/src/selector-bar-item/index.js +1 -0
- package/src/selector-bar-item/selector-bar-item.e2e.stories.js +64 -0
- package/src/selector-bar-item/selector-bar-item.js +190 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { SingleSelectField, SingleSelectOption } from '@dhis2-ui/select'
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
import { SelectorBarItem } from './selector-bar-item.js'
|
|
4
|
+
|
|
5
|
+
const options = [
|
|
6
|
+
{ value: 'value 1', label: 'label 1' },
|
|
7
|
+
{ value: 'value 2', label: 'label 2' },
|
|
8
|
+
{ value: 'value 3', label: 'label 3' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
const createStateDecorator = (args) => {
|
|
12
|
+
return (fn) => {
|
|
13
|
+
const [selected, setSelected] = useState(args?.selected || '')
|
|
14
|
+
const [open, setOpen] = useState(args?.open || false)
|
|
15
|
+
return fn({ selected, open, setSelected, setOpen })
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const createStory =
|
|
20
|
+
() =>
|
|
21
|
+
(_, { open, setOpen, selected, setSelected }) =>
|
|
22
|
+
(
|
|
23
|
+
<SelectorBarItem
|
|
24
|
+
label="Selection bar item"
|
|
25
|
+
noValueMessage="No value message"
|
|
26
|
+
open={open}
|
|
27
|
+
setOpen={setOpen}
|
|
28
|
+
value={
|
|
29
|
+
options.find(({ value }) => value === selected)?.label || ''
|
|
30
|
+
}
|
|
31
|
+
>
|
|
32
|
+
<SingleSelectField
|
|
33
|
+
selected={selected}
|
|
34
|
+
onChange={({ selected: nextSelected }) => {
|
|
35
|
+
setSelected(nextSelected)
|
|
36
|
+
setOpen(false)
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{options.map(({ value, label }) => (
|
|
40
|
+
<SingleSelectOption
|
|
41
|
+
key={value}
|
|
42
|
+
value={value}
|
|
43
|
+
label={label}
|
|
44
|
+
/>
|
|
45
|
+
))}
|
|
46
|
+
</SingleSelectField>
|
|
47
|
+
</SelectorBarItem>
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
export default { title: 'SelectorBarItem' }
|
|
51
|
+
|
|
52
|
+
export const ClosedNoValue = createStory()
|
|
53
|
+
ClosedNoValue.decorators = [createStateDecorator()]
|
|
54
|
+
|
|
55
|
+
export const OpenNoValue = createStory()
|
|
56
|
+
OpenNoValue.decorators = [createStateDecorator({ open: true })]
|
|
57
|
+
|
|
58
|
+
export const ClosedWithValue = createStory()
|
|
59
|
+
ClosedWithValue.decorators = [createStateDecorator({ selected: 'value 1' })]
|
|
60
|
+
|
|
61
|
+
export const OpenWithValue = createStory()
|
|
62
|
+
OpenWithValue.decorators = [
|
|
63
|
+
createStateDecorator({ open: true, selected: 'value 1' }),
|
|
64
|
+
]
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { colors, spacers } from '@dhis2/ui-constants'
|
|
2
|
+
import { IconChevronUp24, IconChevronDown24 } from '@dhis2/ui-icons'
|
|
3
|
+
import { Card } from '@dhis2-ui/card'
|
|
4
|
+
import { Layer } from '@dhis2-ui/layer'
|
|
5
|
+
import { Popper } from '@dhis2-ui/popper'
|
|
6
|
+
import cx from 'classnames'
|
|
7
|
+
import PropTypes from 'prop-types'
|
|
8
|
+
import React, { useRef } from 'react'
|
|
9
|
+
|
|
10
|
+
const offsetModifier = {
|
|
11
|
+
name: 'offset',
|
|
12
|
+
options: {
|
|
13
|
+
offset: [0, 2],
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The reason this component expects the "open" state and "setOpen" function is
|
|
19
|
+
* because certain actions inside the props.children might have to set "open"
|
|
20
|
+
* to false as well.
|
|
21
|
+
* A good example is selecting a value (which is not a click outside) and this
|
|
22
|
+
* component neither has any control over that component nor does it have a way
|
|
23
|
+
* to "observe" that behavior. Especially for selection bar items that allow
|
|
24
|
+
* "multiple" selections, this would be more or less impossible to predict
|
|
25
|
+
* inside this component
|
|
26
|
+
*/
|
|
27
|
+
export const SelectorBarItem = ({
|
|
28
|
+
children,
|
|
29
|
+
className,
|
|
30
|
+
dataTest = 'dhis2-ui-selectorbaritem',
|
|
31
|
+
disabled,
|
|
32
|
+
displayOnly,
|
|
33
|
+
label,
|
|
34
|
+
noValueMessage,
|
|
35
|
+
open,
|
|
36
|
+
setOpen,
|
|
37
|
+
value,
|
|
38
|
+
onClearSelectionClick,
|
|
39
|
+
}) => {
|
|
40
|
+
const buttonRef = useRef()
|
|
41
|
+
const Icon = open ? IconChevronUp24 : IconChevronDown24
|
|
42
|
+
|
|
43
|
+
const handleKeyDown = (evt) => {
|
|
44
|
+
if (open && setOpen && evt.key === 'Tab') {
|
|
45
|
+
// Move focus to trigger and defer close so Tab advances before the popup unmounts.
|
|
46
|
+
buttonRef.current?.focus()
|
|
47
|
+
requestAnimationFrame(() => setOpen(false))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<button
|
|
53
|
+
ref={buttonRef}
|
|
54
|
+
className={cx(
|
|
55
|
+
'selector-bar-item',
|
|
56
|
+
className,
|
|
57
|
+
!displayOnly ? 'openable' : ''
|
|
58
|
+
)}
|
|
59
|
+
disabled={disabled}
|
|
60
|
+
onClick={() => setOpen && setOpen(true)}
|
|
61
|
+
onKeyDown={handleKeyDown}
|
|
62
|
+
data-test={dataTest}
|
|
63
|
+
>
|
|
64
|
+
<span className="label">{label}</span>
|
|
65
|
+
|
|
66
|
+
{!disabled && (
|
|
67
|
+
<>
|
|
68
|
+
<span className="value">{value || noValueMessage}</span>
|
|
69
|
+
{value && onClearSelectionClick && (
|
|
70
|
+
<span
|
|
71
|
+
className="clear-icon"
|
|
72
|
+
onClick={(evt) => {
|
|
73
|
+
evt.stopPropagation()
|
|
74
|
+
onClearSelectionClick()
|
|
75
|
+
}}
|
|
76
|
+
data-test={`${dataTest}-clear-icon`}
|
|
77
|
+
>
|
|
78
|
+
<svg
|
|
79
|
+
width="14"
|
|
80
|
+
height="14"
|
|
81
|
+
viewBox="0 0 14 14"
|
|
82
|
+
fill="none"
|
|
83
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
84
|
+
>
|
|
85
|
+
<path d="M7 14C10.866 14 14 10.866 14 7C14 3.13401 10.866 0 7 0C3.13401 0 0 3.13401 0 7C0 10.866 3.13401 14 7 14ZM4.29289 4.29289C4.68342 3.90237 5.31658 3.90237 5.70711 4.29289L7 5.58579L8.29289 4.29289C8.68342 3.90237 9.31658 3.90237 9.70711 4.29289C10.0976 4.68342 10.0976 5.31658 9.70711 5.70711L8.41421 7L9.70711 8.29289C10.0976 8.68342 10.0976 9.31658 9.70711 9.70711C9.31658 10.0976 8.68342 10.0976 8.29289 9.70711L7 8.41421L5.70711 9.70711C5.31658 10.0976 4.68342 10.0976 4.29289 9.70711C3.90237 9.31658 3.90237 8.68342 4.29289 8.29289L5.58579 7L4.29289 5.70711C3.90237 5.31658 3.90237 4.68342 4.29289 4.29289Z" />
|
|
86
|
+
</svg>
|
|
87
|
+
</span>
|
|
88
|
+
)}
|
|
89
|
+
</>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{!displayOnly && (
|
|
93
|
+
<span className="toggle-icon">
|
|
94
|
+
<Icon />
|
|
95
|
+
</span>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{open && (
|
|
99
|
+
<Layer
|
|
100
|
+
onBackdropClick={(_, evt) => {
|
|
101
|
+
evt.stopPropagation()
|
|
102
|
+
setOpen(false)
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<Popper
|
|
106
|
+
reference={buttonRef}
|
|
107
|
+
placement="bottom-start"
|
|
108
|
+
modifiers={[offsetModifier]}
|
|
109
|
+
>
|
|
110
|
+
<Card>{children}</Card>
|
|
111
|
+
</Popper>
|
|
112
|
+
</Layer>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
<style jsx>{`
|
|
116
|
+
.selector-bar-item {
|
|
117
|
+
display: flex;
|
|
118
|
+
background: none;
|
|
119
|
+
height: 40px;
|
|
120
|
+
align-items: center;
|
|
121
|
+
${
|
|
122
|
+
/*
|
|
123
|
+
* The arrow icon has ~4px empty space on the sides,
|
|
124
|
+
* that's why the padding on the right is only 8px.
|
|
125
|
+
*/ ''
|
|
126
|
+
}
|
|
127
|
+
padding-block: 0;
|
|
128
|
+
padding-inline-start: ${spacers.dp8};
|
|
129
|
+
padding-inline-end: ${spacers.dp12};
|
|
130
|
+
font-size: 14px;
|
|
131
|
+
line-height: 16px;
|
|
132
|
+
border: none;
|
|
133
|
+
box-shadow: 0px 0px 0px 1px ${colors.grey400};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.selector-bar-item.openable {
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.selector-bar-item:disabled {
|
|
141
|
+
cursor: not-allowed;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.label {
|
|
145
|
+
color: ${colors.grey600};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.value {
|
|
149
|
+
padding-inline-start: ${spacers.dp8};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.clear-icon {
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
margin-inline-start: ${spacers.dp4};
|
|
156
|
+
padding: ${spacers.dp4};
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
}
|
|
159
|
+
.clear-icon svg path {
|
|
160
|
+
fill: ${colors.grey500};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.clear-icon:hover svg path {
|
|
164
|
+
fill: ${colors.grey700};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.toggle-icon {
|
|
168
|
+
display: flex;
|
|
169
|
+
margin-inline-start: ${spacers.dp4};
|
|
170
|
+
height: 100%;
|
|
171
|
+
align-items: center;
|
|
172
|
+
}
|
|
173
|
+
`}</style>
|
|
174
|
+
</button>
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
SelectorBarItem.propTypes = {
|
|
179
|
+
label: PropTypes.string.isRequired,
|
|
180
|
+
children: PropTypes.any,
|
|
181
|
+
className: PropTypes.string,
|
|
182
|
+
dataTest: PropTypes.string,
|
|
183
|
+
disabled: PropTypes.bool,
|
|
184
|
+
displayOnly: PropTypes.bool,
|
|
185
|
+
noValueMessage: PropTypes.string,
|
|
186
|
+
open: PropTypes.bool,
|
|
187
|
+
setOpen: PropTypes.func,
|
|
188
|
+
value: PropTypes.string,
|
|
189
|
+
onClearSelectionClick: PropTypes.func,
|
|
190
|
+
}
|