@dhis2-ui/checkbox 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 +6 -5
- package/src/checkbox/__tests__/checkbox.test.js +31 -0
- package/src/checkbox/checkbox-icon.js +201 -0
- package/src/checkbox/checkbox.e2e.stories.js +55 -0
- package/src/checkbox/checkbox.js +214 -0
- package/src/checkbox/checkbox.prod.stories.js +182 -0
- package/src/checkbox/features/accepts_initial_focus/index.js +9 -0
- package/src/checkbox/features/accepts_initial_focus.feature +5 -0
- package/src/checkbox/features/accepts_label/index.js +11 -0
- package/src/checkbox/features/accepts_label.feature +5 -0
- package/src/checkbox/features/can_be_blurred/index.js +19 -0
- package/src/checkbox/features/can_be_blurred.feature +6 -0
- package/src/checkbox/features/can_be_changed/index.js +19 -0
- package/src/checkbox/features/can_be_changed.feature +6 -0
- package/src/checkbox/features/can_be_disabled/index.js +15 -0
- package/src/checkbox/features/can_be_disabled.feature +6 -0
- package/src/checkbox/features/can_be_focused/index.js +19 -0
- package/src/checkbox/features/can_be_focused.feature +6 -0
- package/src/checkbox/features/has_indeterminate_prop/index.js +19 -0
- package/src/checkbox/features/has_indeterminate_prop.feature +9 -0
- package/src/checkbox/index.js +1 -0
- package/src/checkbox-field/__tests__/checkbox-field.test.js +32 -0
- package/src/checkbox-field/checkbox-field.e2e.stories.js +26 -0
- package/src/checkbox-field/checkbox-field.js +116 -0
- package/src/checkbox-field/checkbox-field.prod.stories.js +149 -0
- package/src/checkbox-field/features/accepts_help_text/index.js +11 -0
- package/src/checkbox-field/features/accepts_help_text.feature +5 -0
- package/src/checkbox-field/features/accepts_label/index.js +11 -0
- package/src/checkbox-field/features/accepts_label.feature +5 -0
- package/src/checkbox-field/features/accepts_validation_text/index.js +11 -0
- package/src/checkbox-field/features/accepts_validation_text.feature +5 -0
- package/src/checkbox-field/features/can_be_required/index.js +11 -0
- package/src/checkbox-field/features/can_be_required.feature +5 -0
- package/src/checkbox-field/index.js +1 -0
- package/src/index.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2-ui/checkbox",
|
|
3
|
-
"version": "10.16.1",
|
|
3
|
+
"version": "10.16.3-alpha.1",
|
|
4
4
|
"description": "UI Checkbox",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,15 +33,16 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@dhis2/prop-types": "^3.1.2",
|
|
36
|
-
"@dhis2-ui/field": "10.16.1",
|
|
37
|
-
"@dhis2-ui/required": "10.16.1",
|
|
38
|
-
"@dhis2/ui-constants": "10.16.1",
|
|
36
|
+
"@dhis2-ui/field": "10.16.3-alpha.1",
|
|
37
|
+
"@dhis2-ui/required": "10.16.3-alpha.1",
|
|
38
|
+
"@dhis2/ui-constants": "10.16.3-alpha.1",
|
|
39
39
|
"classnames": "^2.3.1",
|
|
40
40
|
"prop-types": "^15.7.2"
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
43
43
|
"build",
|
|
44
|
-
"types"
|
|
44
|
+
"types",
|
|
45
|
+
"src"
|
|
45
46
|
],
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"react": "^18.3.1",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { render, fireEvent, screen } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Checkbox } from '../checkbox.js'
|
|
4
|
+
|
|
5
|
+
describe('<Checkbox />', () => {
|
|
6
|
+
it('should call the onKeyDown callback when provided', () => {
|
|
7
|
+
const onKeyDown = jest.fn()
|
|
8
|
+
|
|
9
|
+
render(
|
|
10
|
+
<Checkbox
|
|
11
|
+
name="foo"
|
|
12
|
+
value="bar"
|
|
13
|
+
checked={false}
|
|
14
|
+
onKeyDown={onKeyDown}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
fireEvent.keyDown(screen.getByRole('checkbox'), {
|
|
19
|
+
key: 'Enter',
|
|
20
|
+
code: 'Enter',
|
|
21
|
+
charCode: 13,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
expect(onKeyDown).toHaveBeenCalledWith(
|
|
25
|
+
{ name: 'foo', value: 'bar', checked: true },
|
|
26
|
+
expect.objectContaining({})
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
expect(onKeyDown).toHaveBeenCalledTimes(1)
|
|
30
|
+
})
|
|
31
|
+
})
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { colors } from '@dhis2/ui-constants'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import css from 'styled-jsx/css'
|
|
5
|
+
|
|
6
|
+
const commonStyles = css`
|
|
7
|
+
svg {
|
|
8
|
+
display: block;
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
}
|
|
11
|
+
svg .border {
|
|
12
|
+
fill: ${colors.grey800};
|
|
13
|
+
}
|
|
14
|
+
svg .background,
|
|
15
|
+
svg .indeterminate,
|
|
16
|
+
svg .checkmark {
|
|
17
|
+
fill: ${colors.white};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
svg.checked .background,
|
|
21
|
+
svg.indeterminate .background {
|
|
22
|
+
fill: ${colors.teal500};
|
|
23
|
+
}
|
|
24
|
+
svg.valid .background {
|
|
25
|
+
fill: ${colors.blue600};
|
|
26
|
+
}
|
|
27
|
+
svg.warning .background {
|
|
28
|
+
fill: ${colors.yellow700};
|
|
29
|
+
}
|
|
30
|
+
svg.error .background {
|
|
31
|
+
fill: ${colors.red500};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
svg.checked .border,
|
|
35
|
+
svg.indeterminate .border {
|
|
36
|
+
fill: ${colors.teal900};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
svg:not(.checked) .checkmark,
|
|
40
|
+
svg:not(.indeterminate) .indeterminate {
|
|
41
|
+
fill: none;
|
|
42
|
+
}
|
|
43
|
+
svg:not(.checked):not(.indeterminate) .background {
|
|
44
|
+
fill: ${colors.white};
|
|
45
|
+
}
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
export function CheckboxRegular({ className }) {
|
|
49
|
+
return (
|
|
50
|
+
<svg
|
|
51
|
+
viewBox="0 0 18 18"
|
|
52
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
+
className={className}
|
|
54
|
+
>
|
|
55
|
+
<rect
|
|
56
|
+
className="background"
|
|
57
|
+
x="0"
|
|
58
|
+
y="0"
|
|
59
|
+
width="18"
|
|
60
|
+
height="18"
|
|
61
|
+
rx="3"
|
|
62
|
+
></rect>
|
|
63
|
+
<path
|
|
64
|
+
d="M15,0 C16.6568542,0 18,1.34314575 18,3 L18,15 C18,16.6568542 16.6568542,18 15,18 L3,18 C1.34314575,18 0,16.6568542 0,15 L0,3 C0,1.34314575 1.34314575,0 3,0 L15,0 Z M15,1 L3,1 C1.9456382,1 1.08183488,1.81587779 1.00548574,2.85073766 L1,3 L1,15 C1,16.0543618 1.81587779,16.9181651 2.85073766,16.9945143 L3,17 L15,17 C16.0543618,17 16.9181651,16.1841222 16.9945143,15.1492623 L17,15 L17,3 C17,1.9456382 16.1841222,1.08183488 15.1492623,1.00548574 L15,1 Z"
|
|
65
|
+
className="border"
|
|
66
|
+
></path>
|
|
67
|
+
<path
|
|
68
|
+
d="M4.74451387,8.02852516 C4.39595738,8.11985643 4.12375034,8.39235188 4.03251575,8.74127768 C3.94128115,9.09020348 4.04521722,9.46125864 4.30437605,9.71182782 L7.29823844,12.7078028 C7.68753993,13.0973991 8.31859767,13.0973991 8.70789916,12.7078028 L13.695624,6.71585285 C13.9547828,6.46528367 14.0587189,6.09422851 13.9674843,5.74530271 C13.8762497,5.39637691 13.6040426,5.12388146 13.2554861,5.0325502 C12.9069296,4.94121893 12.5362672,5.04526513 12.2859632,5.30469855 L8.0030688,10.5910713 L5.71403676,8.30067351 C5.46373279,8.0412401 5.09307036,7.9371939 4.74451387,8.02852516 Z"
|
|
69
|
+
className="checkmark"
|
|
70
|
+
></path>
|
|
71
|
+
<rect
|
|
72
|
+
className="indeterminate"
|
|
73
|
+
x="4"
|
|
74
|
+
y="8"
|
|
75
|
+
width="10"
|
|
76
|
+
height="2"
|
|
77
|
+
rx="1"
|
|
78
|
+
></rect>
|
|
79
|
+
<style jsx>{commonStyles}</style>
|
|
80
|
+
<style jsx>{`
|
|
81
|
+
svg {
|
|
82
|
+
width: 18px;
|
|
83
|
+
height: 18px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
svg.disabled .background {
|
|
87
|
+
fill: ${colors.grey300};
|
|
88
|
+
}
|
|
89
|
+
svg.disabled .border {
|
|
90
|
+
fill: ${colors.grey400};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
svg.valid .border {
|
|
94
|
+
fill: ${colors.blue600};
|
|
95
|
+
}
|
|
96
|
+
svg.valid.checked .border,
|
|
97
|
+
svg.valid.indeterminate .border {
|
|
98
|
+
fill: ${colors.blue700};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
svg.warning .border {
|
|
102
|
+
fill: ${colors.yellow700};
|
|
103
|
+
}
|
|
104
|
+
svg.warning.checked .border,
|
|
105
|
+
svg.warning.indeterminate .border {
|
|
106
|
+
fill: ${colors.yellow800};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
svg.error .border {
|
|
110
|
+
fill: ${colors.red500};
|
|
111
|
+
}
|
|
112
|
+
svg.error.checked .border,
|
|
113
|
+
svg.error.indeterminate .border {
|
|
114
|
+
fill: ${colors.red700};
|
|
115
|
+
}
|
|
116
|
+
`}</style>
|
|
117
|
+
</svg>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
CheckboxRegular.propTypes = {
|
|
121
|
+
className: PropTypes.string,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function CheckboxDense({ className }) {
|
|
125
|
+
return (
|
|
126
|
+
<svg
|
|
127
|
+
viewBox="0 0 14 14"
|
|
128
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
129
|
+
className={className}
|
|
130
|
+
>
|
|
131
|
+
<rect
|
|
132
|
+
className="background"
|
|
133
|
+
x="0"
|
|
134
|
+
y="0"
|
|
135
|
+
width="14"
|
|
136
|
+
height="14"
|
|
137
|
+
rx="2"
|
|
138
|
+
></rect>
|
|
139
|
+
<path
|
|
140
|
+
d="M2,0 L12,0 C13.1045695,0 14,0.8954305 14,2 L14,12 C14,13.1045695 13.1045695,14 12,14 L2,14 C0.8954305,14 0,13.1045695 0,12 L0,2 C0,0.8954305 0.8954305,0 2,0 Z M2,1 C1.44771525,1 1,1.44771525 1,2 L1,12 C1,12.5522847 1.44771525,13 2,13 L12,13 C12.5522847,13 13,12.5522847 13,12 L13,2 C13,1.44771525 12.5522847,1 12,1 L2,1 Z"
|
|
141
|
+
className="border"
|
|
142
|
+
></path>
|
|
143
|
+
<path
|
|
144
|
+
d="M10.3520005,3.30015877 L5.54268293,8.03252726 L3.64634146,6.16652726 L2.28668615,7.5044281 L5.54310367,10.7010587 L11.7145993,4.628307 L10.3520005,3.30015877 Z M5.54268293,9.43547274 L5.61193184,9.36733181 L5.54226219,9.29894127 L5.47309681,9.367 L5.54268293,9.43547274 Z M10.6496329,4.41023392 L10.6495057,4.35652297 L10.5648412,4.35672356 L10.2854007,4.631693 L10.3553166,4.69984123 L10.6496329,4.41023392 Z M3.42891348,7.22439304 L3.35030905,7.22439304 L3.35034135,7.27820863 L3.64634146,7.56947274 L3.71331385,7.5035719 L3.42891348,7.22439304 Z"
|
|
145
|
+
className="checkmark"
|
|
146
|
+
></path>
|
|
147
|
+
<rect
|
|
148
|
+
className="indeterminate"
|
|
149
|
+
x="3"
|
|
150
|
+
y="6"
|
|
151
|
+
width="8"
|
|
152
|
+
height="2"
|
|
153
|
+
></rect>
|
|
154
|
+
<style jsx>{commonStyles}</style>
|
|
155
|
+
<style jsx>{`
|
|
156
|
+
svg {
|
|
157
|
+
width: 14px;
|
|
158
|
+
height: 14px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
svg.disabled .background {
|
|
162
|
+
fill: ${colors.grey400};
|
|
163
|
+
}
|
|
164
|
+
svg.disabled .border {
|
|
165
|
+
fill: ${colors.grey400};
|
|
166
|
+
}
|
|
167
|
+
svg.disabled.checked .border,
|
|
168
|
+
svg.disabled.indeterminate .border {
|
|
169
|
+
fill: ${colors.grey500};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
svg.valid .border {
|
|
173
|
+
fill: ${colors.blue600};
|
|
174
|
+
}
|
|
175
|
+
svg.valid.checked .border,
|
|
176
|
+
svg.valid.indeterminate .border {
|
|
177
|
+
fill: ${colors.blue800};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
svg.warning .border {
|
|
181
|
+
fill: ${colors.yellow700};
|
|
182
|
+
}
|
|
183
|
+
svg.warning.checked .border,
|
|
184
|
+
svg.warning.indeterminate .border {
|
|
185
|
+
fill: ${colors.yellow800};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
svg.error .border {
|
|
189
|
+
fill: ${colors.red500};
|
|
190
|
+
}
|
|
191
|
+
svg.error.checked .border,
|
|
192
|
+
svg.error.indeterminate .border {
|
|
193
|
+
fill: ${colors.red800};
|
|
194
|
+
}
|
|
195
|
+
`}</style>
|
|
196
|
+
</svg>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
CheckboxDense.propTypes = {
|
|
200
|
+
className: PropTypes.string,
|
|
201
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Checkbox } from './index.js'
|
|
3
|
+
|
|
4
|
+
window.onClick = window.Cypress && window.Cypress.cy.stub()
|
|
5
|
+
window.onChange = window.Cypress && window.Cypress.cy.stub()
|
|
6
|
+
window.onBlur = window.Cypress && window.Cypress.cy.stub()
|
|
7
|
+
window.onFocus = window.Cypress && window.Cypress.cy.stub()
|
|
8
|
+
|
|
9
|
+
export default { title: 'Checkbox' }
|
|
10
|
+
export const WithOnChange = () => (
|
|
11
|
+
<Checkbox
|
|
12
|
+
name="Ex"
|
|
13
|
+
label="Checkbox"
|
|
14
|
+
value="default"
|
|
15
|
+
onChange={window.onChange}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
export const WithInitialFocusAndOnBlur = () => (
|
|
19
|
+
<Checkbox
|
|
20
|
+
initialFocus
|
|
21
|
+
name="Ex"
|
|
22
|
+
label="Checkbox"
|
|
23
|
+
value="default"
|
|
24
|
+
onBlur={window.onBlur}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
export const WithOnFocus = () => (
|
|
28
|
+
<Checkbox
|
|
29
|
+
name="Ex"
|
|
30
|
+
label="Checkbox"
|
|
31
|
+
value="default"
|
|
32
|
+
onFocus={window.onFocus}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
export const DisabledWithOnClick = () => (
|
|
36
|
+
<Checkbox
|
|
37
|
+
name="Ex"
|
|
38
|
+
label="Checkbox"
|
|
39
|
+
value="default"
|
|
40
|
+
disabled
|
|
41
|
+
onClick={window.onClick}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
export const WithLabel = () => (
|
|
45
|
+
<Checkbox name="Ex" label="The label" value="default" />
|
|
46
|
+
)
|
|
47
|
+
export const WithInitialFocus = () => (
|
|
48
|
+
<Checkbox name="Ex" label="The label" value="default" initialFocus />
|
|
49
|
+
)
|
|
50
|
+
export const IndeterminateProp = () => (
|
|
51
|
+
<Checkbox name="Ex" label="The label" value="default" indeterminate />
|
|
52
|
+
)
|
|
53
|
+
export const NoIndeterminateProp = () => (
|
|
54
|
+
<Checkbox name="Ex" label="The label" value="default" />
|
|
55
|
+
)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { mutuallyExclusive } from '@dhis2/prop-types'
|
|
2
|
+
import { colors, spacers, theme, sharedPropTypes } from '@dhis2/ui-constants'
|
|
3
|
+
import cx from 'classnames'
|
|
4
|
+
import PropTypes from 'prop-types'
|
|
5
|
+
import React, { Component, createRef } from 'react'
|
|
6
|
+
import { CheckboxRegular, CheckboxDense } from './checkbox-icon.js'
|
|
7
|
+
|
|
8
|
+
class Checkbox extends Component {
|
|
9
|
+
ref = createRef()
|
|
10
|
+
|
|
11
|
+
componentDidMount() {
|
|
12
|
+
if (this.props.initialFocus) {
|
|
13
|
+
this.ref.current.focus()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.setIndeterminate(this.props.indeterminate)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
componentDidUpdate(prevProps) {
|
|
20
|
+
if (prevProps.indeterminate !== this.props.indeterminate) {
|
|
21
|
+
this.setIndeterminate(this.props.indeterminate)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setIndeterminate(indeterminate) {
|
|
26
|
+
this.ref.current.indeterminate = indeterminate
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
handleChange = (e) => {
|
|
30
|
+
if (this.props.onChange) {
|
|
31
|
+
this.props.onChange(this.createHandlerPayload(), e)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
handleBlur = (e) => {
|
|
36
|
+
if (this.props.onBlur) {
|
|
37
|
+
this.props.onBlur(this.createHandlerPayload(), e)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
handleFocus = (e) => {
|
|
42
|
+
if (this.props.onFocus) {
|
|
43
|
+
this.props.onFocus(this.createHandlerPayload(), e)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
handleKeyDown = (e) => {
|
|
48
|
+
if (this.props.onKeyDown) {
|
|
49
|
+
this.props.onKeyDown(this.createHandlerPayload(), e)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
createHandlerPayload() {
|
|
54
|
+
return {
|
|
55
|
+
value: this.props.value,
|
|
56
|
+
name: this.props.name,
|
|
57
|
+
checked: !this.props.checked,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static defaultProps = {
|
|
62
|
+
checked: false,
|
|
63
|
+
indeterminate: false,
|
|
64
|
+
dataTest: 'dhis2-uicore-checkbox',
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
render() {
|
|
68
|
+
const {
|
|
69
|
+
checked = false,
|
|
70
|
+
indeterminate = false,
|
|
71
|
+
className,
|
|
72
|
+
disabled,
|
|
73
|
+
error,
|
|
74
|
+
label,
|
|
75
|
+
name,
|
|
76
|
+
tabIndex,
|
|
77
|
+
valid,
|
|
78
|
+
value,
|
|
79
|
+
warning,
|
|
80
|
+
dense,
|
|
81
|
+
dataTest = 'dhis2-uicore-checkbox',
|
|
82
|
+
} = this.props
|
|
83
|
+
|
|
84
|
+
const classes = cx({
|
|
85
|
+
checked,
|
|
86
|
+
indeterminate,
|
|
87
|
+
disabled,
|
|
88
|
+
valid,
|
|
89
|
+
error,
|
|
90
|
+
warning,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<label
|
|
95
|
+
className={cx(className, {
|
|
96
|
+
disabled,
|
|
97
|
+
dense,
|
|
98
|
+
})}
|
|
99
|
+
data-test={dataTest}
|
|
100
|
+
>
|
|
101
|
+
<input
|
|
102
|
+
type="checkbox"
|
|
103
|
+
ref={this.ref}
|
|
104
|
+
name={name}
|
|
105
|
+
value={value}
|
|
106
|
+
checked={checked}
|
|
107
|
+
disabled={disabled}
|
|
108
|
+
tabIndex={tabIndex}
|
|
109
|
+
onChange={this.handleChange}
|
|
110
|
+
onFocus={this.handleFocus}
|
|
111
|
+
onKeyDown={this.handleKeyDown}
|
|
112
|
+
onBlur={this.handleBlur}
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
<div className={cx('icon', { dense })}>
|
|
116
|
+
{dense ? (
|
|
117
|
+
<CheckboxDense className={classes} />
|
|
118
|
+
) : (
|
|
119
|
+
<CheckboxRegular className={classes} />
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{label}
|
|
124
|
+
|
|
125
|
+
<style jsx>{`
|
|
126
|
+
label {
|
|
127
|
+
display: flex;
|
|
128
|
+
position: relative;
|
|
129
|
+
flex-direction: row;
|
|
130
|
+
align-items: center;
|
|
131
|
+
justify-content: flex-start;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
color: ${colors.grey900};
|
|
134
|
+
font-size: 14px;
|
|
135
|
+
line-height: 19px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
label.dense {
|
|
139
|
+
font-size: 14px;
|
|
140
|
+
line-height: 16px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
label.disabled {
|
|
144
|
+
cursor: not-allowed;
|
|
145
|
+
color: ${theme.disabled};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
input {
|
|
149
|
+
opacity: 0;
|
|
150
|
+
position: absolute;
|
|
151
|
+
/* The same size as the icon */
|
|
152
|
+
height: 18px;
|
|
153
|
+
width: 18px;
|
|
154
|
+
/* The same offset as the icon, 2px border, 1px padding */
|
|
155
|
+
inset-inline-start: 3px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
label.dense input {
|
|
159
|
+
/* The same size as the dense icon */
|
|
160
|
+
height: 14px;
|
|
161
|
+
width: 14px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.icon {
|
|
165
|
+
user-select: none;
|
|
166
|
+
margin-inline-end: ${spacers.dp4};
|
|
167
|
+
border: 2px solid transparent;
|
|
168
|
+
padding: 1px;
|
|
169
|
+
border-radius: 5px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
label.dense .icon {
|
|
173
|
+
margin-inline-end: 3px;
|
|
174
|
+
border-radius: 4px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
input:focus + .icon {
|
|
178
|
+
outline: 3px solid ${theme.focus};
|
|
179
|
+
outline-offset: -3px;
|
|
180
|
+
}
|
|
181
|
+
`}</style>
|
|
182
|
+
</label>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const uniqueOnStatePropType = mutuallyExclusive(
|
|
188
|
+
['checked', 'indeterminate'],
|
|
189
|
+
PropTypes.bool
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
Checkbox.propTypes = {
|
|
193
|
+
checked: uniqueOnStatePropType,
|
|
194
|
+
className: PropTypes.string,
|
|
195
|
+
dataTest: PropTypes.string,
|
|
196
|
+
dense: PropTypes.bool,
|
|
197
|
+
disabled: PropTypes.bool,
|
|
198
|
+
error: sharedPropTypes.statusPropType,
|
|
199
|
+
indeterminate: uniqueOnStatePropType,
|
|
200
|
+
initialFocus: PropTypes.bool,
|
|
201
|
+
label: PropTypes.node,
|
|
202
|
+
name: PropTypes.string,
|
|
203
|
+
tabIndex: PropTypes.string,
|
|
204
|
+
valid: sharedPropTypes.statusPropType,
|
|
205
|
+
value: PropTypes.string,
|
|
206
|
+
warning: sharedPropTypes.statusPropType,
|
|
207
|
+
onBlur: PropTypes.func,
|
|
208
|
+
/** Called with signature `(object, event)` */
|
|
209
|
+
onChange: PropTypes.func,
|
|
210
|
+
onFocus: PropTypes.func,
|
|
211
|
+
onKeyDown: PropTypes.func,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { Checkbox }
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Checkbox } from './index.js'
|
|
4
|
+
|
|
5
|
+
const subtitle =
|
|
6
|
+
'A checkbox is a control that allows a user to toggle an option.'
|
|
7
|
+
|
|
8
|
+
const description = `
|
|
9
|
+
Checkboxes are used when an option can be toggled on or off. Toggling a checkbox on (true) is always considered a positive action and should reflect a positive/true/on state. Multiple checkboxes can be used in a list where a user can toggle multiple elements.
|
|
10
|
+
|
|
11
|
+
Do not use checkboxes in a list of several options where only a single option can be toggled, use [radio buttons](../?path=/docs/forms-radio-radio--default) here instead.
|
|
12
|
+
|
|
13
|
+
If there are many options that need to select from, consider using a [select](../?path=/docs/forms-single-select-single-select--with-options) instead.
|
|
14
|
+
|
|
15
|
+
\`\`\`js
|
|
16
|
+
import { Checkbox } from '@dhis2/ui'
|
|
17
|
+
\`\`\`
|
|
18
|
+
`
|
|
19
|
+
|
|
20
|
+
window.onChange = (payload, event) => {
|
|
21
|
+
console.log('onClick payload', payload)
|
|
22
|
+
console.log('onClick event', event)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
window.onFocus = (payload, event) => {
|
|
26
|
+
console.log('onFocus payload', payload)
|
|
27
|
+
console.log('onFocus event', event)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
window.onBlur = (payload, event) => {
|
|
31
|
+
console.log('onBlur payload', payload)
|
|
32
|
+
console.log('onBlur event', event)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const onChange = (...args) => window.onChange(...args)
|
|
36
|
+
const onFocus = (...args) => window.onFocus(...args)
|
|
37
|
+
const onBlur = (...args) => window.onBlur(...args)
|
|
38
|
+
|
|
39
|
+
const defaultArgs = { name: 'Ex', label: 'Checkbox', onChange, onFocus, onBlur }
|
|
40
|
+
|
|
41
|
+
const uniqueOnStateArgType = {
|
|
42
|
+
table: {
|
|
43
|
+
type: {
|
|
44
|
+
summary: 'bool',
|
|
45
|
+
detail: "'checked' and 'indeterminate' are mutually exclusive props",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
control: { type: 'boolean' },
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default {
|
|
52
|
+
title: 'Checkbox',
|
|
53
|
+
component: Checkbox,
|
|
54
|
+
parameters: {
|
|
55
|
+
componentSubtitle: subtitle,
|
|
56
|
+
docs: { description: { component: description } },
|
|
57
|
+
},
|
|
58
|
+
// Sets default args on all stories unless overridden
|
|
59
|
+
args: { ...defaultArgs },
|
|
60
|
+
argTypes: {
|
|
61
|
+
checked: { ...uniqueOnStateArgType },
|
|
62
|
+
indeterminate: { ...uniqueOnStateArgType },
|
|
63
|
+
valid: { ...sharedPropTypes.statusArgType },
|
|
64
|
+
warning: { ...sharedPropTypes.statusArgType },
|
|
65
|
+
error: { ...sharedPropTypes.statusArgType },
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const Template = (args) => <Checkbox {...args} />
|
|
70
|
+
|
|
71
|
+
const CheckedUncheckedTemplate = (args) => (
|
|
72
|
+
<>
|
|
73
|
+
<Checkbox {...args} />
|
|
74
|
+
<Checkbox checked {...args} />
|
|
75
|
+
</>
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
export const Default = Template.bind({})
|
|
79
|
+
Default.args = { value: 'default' }
|
|
80
|
+
|
|
81
|
+
export const FocusedUnchecked = (args) => (
|
|
82
|
+
<>
|
|
83
|
+
<Checkbox
|
|
84
|
+
initialFocus
|
|
85
|
+
value="default"
|
|
86
|
+
className="initially-focused"
|
|
87
|
+
{...args}
|
|
88
|
+
/>
|
|
89
|
+
<Checkbox value="default2" className="initially-unfocused" {...args} />
|
|
90
|
+
</>
|
|
91
|
+
)
|
|
92
|
+
FocusedUnchecked.storyName = 'Focused unchecked'
|
|
93
|
+
/**
|
|
94
|
+
* 'initialFocus' causes docs page to scroll away every time a control is
|
|
95
|
+
* changed, so it's disabled
|
|
96
|
+
*/
|
|
97
|
+
FocusedUnchecked.parameters = { docs: { disable: true } }
|
|
98
|
+
|
|
99
|
+
export const FocusedChecked = (args) => (
|
|
100
|
+
<>
|
|
101
|
+
<Checkbox initialFocus checked value="default" {...args} />
|
|
102
|
+
<Checkbox checked value="default2" {...args} />
|
|
103
|
+
</>
|
|
104
|
+
)
|
|
105
|
+
FocusedChecked.storyName = 'Focused checked'
|
|
106
|
+
FocusedChecked.parameters = { docs: { disable: true } }
|
|
107
|
+
|
|
108
|
+
export const Checked = Template.bind({})
|
|
109
|
+
Checked.args = { checked: true, value: 'checked' }
|
|
110
|
+
|
|
111
|
+
export const Indeterminate = Template.bind({})
|
|
112
|
+
Indeterminate.args = { indeterminate: true, value: 'checked' }
|
|
113
|
+
|
|
114
|
+
export const Disabled = CheckedUncheckedTemplate.bind({})
|
|
115
|
+
Disabled.args = { disabled: true, value: 'disabled' }
|
|
116
|
+
|
|
117
|
+
export const Valid = CheckedUncheckedTemplate.bind({})
|
|
118
|
+
Valid.args = { valid: true, value: 'valid' }
|
|
119
|
+
|
|
120
|
+
export const Warning = CheckedUncheckedTemplate.bind({})
|
|
121
|
+
Warning.args = { warning: true, value: 'warning' }
|
|
122
|
+
|
|
123
|
+
export const Error = CheckedUncheckedTemplate.bind({})
|
|
124
|
+
Error.args = { error: true, value: 'error' }
|
|
125
|
+
|
|
126
|
+
export const ImageLabel = Template.bind({})
|
|
127
|
+
ImageLabel.args = {
|
|
128
|
+
label: <img src="https://picsum.photos/id/82/200/100" />,
|
|
129
|
+
value: 'with-help',
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const DefaultDense = Template.bind({})
|
|
133
|
+
DefaultDense.args = { dense: true, value: 'default' }
|
|
134
|
+
DefaultDense.storyName = 'Default - Dense'
|
|
135
|
+
|
|
136
|
+
export const FocusedUncheckedDense = Template.bind({})
|
|
137
|
+
FocusedUncheckedDense.args = {
|
|
138
|
+
dense: true,
|
|
139
|
+
initialFocus: true,
|
|
140
|
+
value: 'default',
|
|
141
|
+
}
|
|
142
|
+
FocusedUncheckedDense.storyName = 'Focused unchecked - Dense'
|
|
143
|
+
FocusedUncheckedDense.parameters = { docs: { disable: true } }
|
|
144
|
+
|
|
145
|
+
export const FocusedCheckedDense = Template.bind({})
|
|
146
|
+
FocusedCheckedDense.args = { ...FocusedUncheckedDense.args, checked: true }
|
|
147
|
+
FocusedCheckedDense.storyName = 'Focused checked - Dense'
|
|
148
|
+
FocusedCheckedDense.parameters = { docs: { disable: true } }
|
|
149
|
+
|
|
150
|
+
export const CheckedDense = Template.bind({})
|
|
151
|
+
CheckedDense.args = { dense: true, checked: true, value: 'checked' }
|
|
152
|
+
CheckedDense.storyName = 'Checked - Dense'
|
|
153
|
+
|
|
154
|
+
export const IndeterminateDense = Template.bind({})
|
|
155
|
+
IndeterminateDense.args = { dense: true, indeterminate: true, value: 'checked' }
|
|
156
|
+
IndeterminateDense.storyName = 'Indeterminate - Dense'
|
|
157
|
+
|
|
158
|
+
export const DisabledDense = CheckedUncheckedTemplate.bind({})
|
|
159
|
+
DisabledDense.args = { ...Disabled.args, dense: true }
|
|
160
|
+
DisabledDense.storyName = 'Disabled - Dense'
|
|
161
|
+
|
|
162
|
+
export const ValidDense = CheckedUncheckedTemplate.bind({})
|
|
163
|
+
ValidDense.args = { ...Valid.args, dense: true }
|
|
164
|
+
ValidDense.storyName = 'Valid - Dense'
|
|
165
|
+
|
|
166
|
+
export const WarningDense = CheckedUncheckedTemplate.bind({})
|
|
167
|
+
WarningDense.args = { ...Warning.args, dense: true }
|
|
168
|
+
WarningDense.storyName = 'Warning - Dense'
|
|
169
|
+
|
|
170
|
+
export const ErrorDense = CheckedUncheckedTemplate.bind({})
|
|
171
|
+
ErrorDense.args = { ...Error.args, dense: true }
|
|
172
|
+
ErrorDense.storyName = 'Error - Dense'
|
|
173
|
+
|
|
174
|
+
export const ImageLabelDense = Template.bind({})
|
|
175
|
+
ImageLabelDense.args = { ...ImageLabel.args, dense: true }
|
|
176
|
+
ImageLabelDense.storyName = 'Image label - Dense'
|
|
177
|
+
|
|
178
|
+
export const RTL = (args) => (
|
|
179
|
+
<div dir="rtl">
|
|
180
|
+
<Checkbox {...args} />
|
|
181
|
+
</div>
|
|
182
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Checkbox with initialFocus is rendered', () => {
|
|
4
|
+
cy.visitStory('Checkbox', 'With initial focus')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the Checkbox is focused', () => {
|
|
8
|
+
cy.focused().parent('[data-test="dhis2-uicore-checkbox"]').should('exist')
|
|
9
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Checkbox with a label is rendered', () => {
|
|
4
|
+
cy.visitStory('Checkbox', 'With label')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the label is shown', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-checkbox"]')
|
|
9
|
+
.contains('The label')
|
|
10
|
+
.should('be.visible')
|
|
11
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Checkbox with initialFocus and onBlur handler is rendered', () => {
|
|
4
|
+
cy.visitStory('Checkbox', 'With initial focus and on blur')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the Checkbox is blurred', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-checkbox"] input').blur()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then('the onBlur handler is called', () => {
|
|
12
|
+
cy.window().should((win) => {
|
|
13
|
+
expect(win.onBlur).to.be.calledWith({
|
|
14
|
+
value: 'default',
|
|
15
|
+
name: 'Ex',
|
|
16
|
+
checked: true,
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Checkbox with onChange handler is rendered', () => {
|
|
4
|
+
cy.visitStory('Checkbox', 'With on change')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the Checkbox is clicked', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-checkbox"]').click()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then('the onChange handler is called', () => {
|
|
12
|
+
cy.window().should((win) => {
|
|
13
|
+
expect(win.onChange).to.be.calledWith({
|
|
14
|
+
value: 'default',
|
|
15
|
+
name: 'Ex',
|
|
16
|
+
checked: true,
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a disabled Checkbox with onClick handler is rendered', () => {
|
|
4
|
+
cy.visitStory('Checkbox', 'Disabled with on click')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the Checkbox is clicked', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-checkbox"] input').click({ force: true })
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then('the onClick handler is not called', () => {
|
|
12
|
+
cy.window().should((win) => {
|
|
13
|
+
expect(win.onClick).not.to.be.called
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Checkbox with onFocus handler is rendered', () => {
|
|
4
|
+
cy.visitStory('Checkbox', 'With on focus')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the Checkbox is focused', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-checkbox"] input').focus()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then('the onFocus handler is called', () => {
|
|
12
|
+
cy.window().should((win) => {
|
|
13
|
+
expect(win.onFocus).to.be.calledWith({
|
|
14
|
+
value: 'default',
|
|
15
|
+
name: 'Ex',
|
|
16
|
+
checked: true,
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('the checkbox is marked as indeterminate', () => {
|
|
4
|
+
cy.visitStory('Checkbox', 'Indeterminate prop')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Given('the checkbox is not marked as indeterminate', () => {
|
|
8
|
+
cy.visitStory('Checkbox', 'No indeterminate prop')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then("its input-element's indeterminate prop is {word}", (bool) => {
|
|
12
|
+
cy.get('input').should(($input) => {
|
|
13
|
+
if (bool === 'true') {
|
|
14
|
+
expect($input[0].indeterminate).to.be.true
|
|
15
|
+
} else {
|
|
16
|
+
expect($input[0].indeterminate).to.be.false
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Feature: The input component has an indeterminate prop
|
|
2
|
+
|
|
3
|
+
Scenario: The checkbox has the indeterminate prop
|
|
4
|
+
Given the checkbox is marked as indeterminate
|
|
5
|
+
Then its input-element's indeterminate prop is true
|
|
6
|
+
|
|
7
|
+
Scenario: The checkbox does not have the indeterminate prop
|
|
8
|
+
Given the checkbox is not marked as indeterminate
|
|
9
|
+
Then its input-element's indeterminate prop is false
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Checkbox } from './checkbox.js'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { render, fireEvent, screen } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { CheckboxField } from '../checkbox-field.js'
|
|
4
|
+
|
|
5
|
+
describe('<CheckboxField />', () => {
|
|
6
|
+
it('should call the onKeyDown callback when provided', () => {
|
|
7
|
+
const onKeyDown = jest.fn()
|
|
8
|
+
|
|
9
|
+
render(
|
|
10
|
+
<CheckboxField
|
|
11
|
+
label="Label"
|
|
12
|
+
name="foo"
|
|
13
|
+
value="bar"
|
|
14
|
+
checked={false}
|
|
15
|
+
onKeyDown={onKeyDown}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
fireEvent.keyDown(screen.getByRole('checkbox'), {
|
|
20
|
+
key: 'Enter',
|
|
21
|
+
code: 'Enter',
|
|
22
|
+
charCode: 13,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
expect(onKeyDown).toHaveBeenCalledWith(
|
|
26
|
+
{ name: 'foo', value: 'bar', checked: true },
|
|
27
|
+
expect.objectContaining({})
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
expect(onKeyDown).toHaveBeenCalledTimes(1)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { CheckboxField } from './index.js'
|
|
3
|
+
|
|
4
|
+
export default { title: 'CheckboxField' }
|
|
5
|
+
export const WithLabelAndRequired = () => (
|
|
6
|
+
<CheckboxField name="Ex" label="CheckboxField" required value="checked" />
|
|
7
|
+
)
|
|
8
|
+
export const WithHelpText = () => (
|
|
9
|
+
<CheckboxField
|
|
10
|
+
name="Ex"
|
|
11
|
+
label="CheckboxField"
|
|
12
|
+
value="checked"
|
|
13
|
+
helpText="Help text"
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
export const WithLabel = () => (
|
|
17
|
+
<CheckboxField name="Ex" label="The label" value="checked" />
|
|
18
|
+
)
|
|
19
|
+
export const WithValidationText = () => (
|
|
20
|
+
<CheckboxField
|
|
21
|
+
name="Ex"
|
|
22
|
+
label="The label"
|
|
23
|
+
value="checked"
|
|
24
|
+
validationText="Validation text"
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import { Field } from '@dhis2-ui/field'
|
|
3
|
+
import { Required } from '@dhis2-ui/required'
|
|
4
|
+
import PropTypes from 'prop-types'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
import { Checkbox } from '../checkbox/index.js'
|
|
7
|
+
|
|
8
|
+
const AddRequired = ({ label, required, dataTest }) => (
|
|
9
|
+
<React.Fragment>
|
|
10
|
+
{label}
|
|
11
|
+
{required && <Required dataTest={`${dataTest}-required`} />}
|
|
12
|
+
</React.Fragment>
|
|
13
|
+
)
|
|
14
|
+
AddRequired.propTypes = {
|
|
15
|
+
dataTest: PropTypes.string,
|
|
16
|
+
label: PropTypes.node,
|
|
17
|
+
required: PropTypes.bool,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const CheckboxField = ({
|
|
21
|
+
value,
|
|
22
|
+
label,
|
|
23
|
+
name,
|
|
24
|
+
className,
|
|
25
|
+
tabIndex,
|
|
26
|
+
onChange,
|
|
27
|
+
onFocus,
|
|
28
|
+
onKeyDown,
|
|
29
|
+
onBlur,
|
|
30
|
+
checked,
|
|
31
|
+
disabled,
|
|
32
|
+
valid,
|
|
33
|
+
warning,
|
|
34
|
+
error,
|
|
35
|
+
dense,
|
|
36
|
+
initialFocus,
|
|
37
|
+
required,
|
|
38
|
+
helpText,
|
|
39
|
+
validationText,
|
|
40
|
+
dataTest = 'dhis2-uiwidgets-checkboxfield',
|
|
41
|
+
}) => (
|
|
42
|
+
<Field
|
|
43
|
+
className={className}
|
|
44
|
+
dataTest={dataTest}
|
|
45
|
+
helpText={helpText}
|
|
46
|
+
validationText={validationText}
|
|
47
|
+
error={error}
|
|
48
|
+
warning={warning}
|
|
49
|
+
valid={valid}
|
|
50
|
+
disabled={disabled}
|
|
51
|
+
>
|
|
52
|
+
<Checkbox
|
|
53
|
+
value={value}
|
|
54
|
+
label={
|
|
55
|
+
<AddRequired
|
|
56
|
+
label={label}
|
|
57
|
+
required={required}
|
|
58
|
+
dataTest={dataTest}
|
|
59
|
+
/>
|
|
60
|
+
}
|
|
61
|
+
name={name}
|
|
62
|
+
tabIndex={tabIndex}
|
|
63
|
+
onChange={onChange}
|
|
64
|
+
onFocus={onFocus}
|
|
65
|
+
onKeyDown={onKeyDown}
|
|
66
|
+
onBlur={onBlur}
|
|
67
|
+
checked={checked}
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
valid={valid}
|
|
70
|
+
warning={warning}
|
|
71
|
+
error={error}
|
|
72
|
+
dense={dense}
|
|
73
|
+
initialFocus={initialFocus}
|
|
74
|
+
/>
|
|
75
|
+
</Field>
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
CheckboxField.propTypes = {
|
|
79
|
+
checked: PropTypes.bool,
|
|
80
|
+
className: PropTypes.string,
|
|
81
|
+
dataTest: PropTypes.string,
|
|
82
|
+
/** Smaller dimensions for information-dense layouts */
|
|
83
|
+
dense: PropTypes.bool,
|
|
84
|
+
/** Disables the checkbox */
|
|
85
|
+
disabled: PropTypes.bool,
|
|
86
|
+
/** Applies 'error' styling to checkbox and validation text for feedback. Mutually exclusive with `warning` and `valid` props */
|
|
87
|
+
error: sharedPropTypes.statusPropType,
|
|
88
|
+
/** Useful instructions for the user */
|
|
89
|
+
helpText: PropTypes.string,
|
|
90
|
+
initialFocus: PropTypes.bool,
|
|
91
|
+
/** Labels the checkbox */
|
|
92
|
+
label: PropTypes.node,
|
|
93
|
+
/** Name associate with the checkbox. Passed in object as argument to event handlers */
|
|
94
|
+
name: PropTypes.string,
|
|
95
|
+
/** Adds an asterisk to indicate this field is required */
|
|
96
|
+
required: PropTypes.bool,
|
|
97
|
+
tabIndex: PropTypes.string,
|
|
98
|
+
/** Applies 'valid' styling to checkbox and validation text for feedback. Mutually exclusive with `warning` and `error` props */
|
|
99
|
+
valid: sharedPropTypes.statusPropType,
|
|
100
|
+
/** Adds text below the checkbox to provide validation feedback. Acquires styles from `valid`, `warning` and `error` statuses */
|
|
101
|
+
validationText: PropTypes.string,
|
|
102
|
+
/** Value associated with the checkbox. Passed in object as argument to event handlers */
|
|
103
|
+
value: PropTypes.string,
|
|
104
|
+
/** Applies 'warning' styling to checkbox and validation text for feedback. Mutually exclusive with `valid` and `error` props */
|
|
105
|
+
warning: sharedPropTypes.statusPropType,
|
|
106
|
+
/** Called with signature `({ name: string, value: string, checked: bool }, event)` */
|
|
107
|
+
onBlur: PropTypes.func,
|
|
108
|
+
/** Called with signature `({ name: string, value: string, checked: bool }, event)` */
|
|
109
|
+
onChange: PropTypes.func,
|
|
110
|
+
/** Called with signature `({ name: string, value: string, checked: bool }, event)` */
|
|
111
|
+
onFocus: PropTypes.func,
|
|
112
|
+
/** Called with signature `({ name: string, value: string, checked: bool }, event)` */
|
|
113
|
+
onKeyDown: PropTypes.func,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { CheckboxField }
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { CheckboxField } from './index.js'
|
|
4
|
+
|
|
5
|
+
const description = `
|
|
6
|
+
A \`CheckboxField\` is a Checkbox component wrapped with extra form utilities, including the ability to add a label, help text, and validation text. Validation styles like 'error' apply to all of these subcomponents.
|
|
7
|
+
|
|
8
|
+
See the basic Checkbox for usage and design system guidelines.
|
|
9
|
+
|
|
10
|
+
\`\`\`js
|
|
11
|
+
import { CheckboxField } from '@dhis2/ui'
|
|
12
|
+
\`\`\`
|
|
13
|
+
`
|
|
14
|
+
|
|
15
|
+
const logger = ({ name, value, checked }) =>
|
|
16
|
+
console.log(`name: ${name}, value: ${value}, checked: ${checked}`)
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
title: 'Checkbox Field',
|
|
20
|
+
component: CheckboxField,
|
|
21
|
+
parameters: { docs: { description: { component: description } } },
|
|
22
|
+
// Default args for stories
|
|
23
|
+
args: {
|
|
24
|
+
name: 'checkboxName',
|
|
25
|
+
label: 'Checkbox Field',
|
|
26
|
+
value: 'defaultValue',
|
|
27
|
+
onChange: logger,
|
|
28
|
+
},
|
|
29
|
+
argTypes: {
|
|
30
|
+
valid: { ...sharedPropTypes.statusArgType },
|
|
31
|
+
warning: { ...sharedPropTypes.statusArgType },
|
|
32
|
+
error: { ...sharedPropTypes.statusArgType },
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const Template = (args) => <CheckboxField {...args} />
|
|
37
|
+
|
|
38
|
+
const CheckedUncheckedTemplate = (args) => (
|
|
39
|
+
<>
|
|
40
|
+
<CheckboxField {...args} />
|
|
41
|
+
<CheckboxField {...args} checked />
|
|
42
|
+
</>
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
export const Default = Template.bind({})
|
|
46
|
+
|
|
47
|
+
export const FocusedUnchecked = Template.bind({})
|
|
48
|
+
FocusedUnchecked.args = { initialFocus: true }
|
|
49
|
+
// Disable stories on docs page that grab focus
|
|
50
|
+
FocusedUnchecked.parameters = { docs: { disable: true } }
|
|
51
|
+
|
|
52
|
+
export const FocusedChecked = Template.bind({})
|
|
53
|
+
FocusedChecked.args = { ...FocusedUnchecked.args, checked: true }
|
|
54
|
+
FocusedChecked.parameters = { docs: { disable: true } }
|
|
55
|
+
|
|
56
|
+
export const Checked = Template.bind({})
|
|
57
|
+
Checked.args = { checked: true, value: 'checkedValue' }
|
|
58
|
+
|
|
59
|
+
export const Required = Template.bind({})
|
|
60
|
+
Required.args = { required: true }
|
|
61
|
+
|
|
62
|
+
export const Disabled = CheckedUncheckedTemplate.bind({})
|
|
63
|
+
Disabled.args = { disabled: true }
|
|
64
|
+
|
|
65
|
+
export const HelpText = (args) => (
|
|
66
|
+
<>
|
|
67
|
+
<CheckboxField {...args} />
|
|
68
|
+
<CheckboxField
|
|
69
|
+
{...args}
|
|
70
|
+
error
|
|
71
|
+
validationText="Validation text (error state)"
|
|
72
|
+
/>
|
|
73
|
+
</>
|
|
74
|
+
)
|
|
75
|
+
HelpText.args = { helpText: 'Help text' }
|
|
76
|
+
|
|
77
|
+
export const Valid = CheckedUncheckedTemplate.bind({})
|
|
78
|
+
Valid.args = {
|
|
79
|
+
valid: true,
|
|
80
|
+
validationText: 'I am validation text',
|
|
81
|
+
value: 'validValue',
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const Warning = CheckedUncheckedTemplate.bind({})
|
|
85
|
+
Warning.args = {
|
|
86
|
+
warning: true,
|
|
87
|
+
value: 'warningValue',
|
|
88
|
+
validationText: 'I am validation text',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const Error = CheckedUncheckedTemplate.bind({})
|
|
92
|
+
Error.args = {
|
|
93
|
+
error: true,
|
|
94
|
+
value: 'errorValue',
|
|
95
|
+
validationText: 'I am validation text',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const ImageLabel = Template.bind({})
|
|
99
|
+
ImageLabel.args = { label: <img src="https://picsum.photos/id/82/200/100" /> }
|
|
100
|
+
|
|
101
|
+
export const DefaultDense = Template.bind({})
|
|
102
|
+
DefaultDense.storyName = 'Default - Dense'
|
|
103
|
+
DefaultDense.args = { dense: true }
|
|
104
|
+
|
|
105
|
+
export const FocusedUncheckedDense = Template.bind({})
|
|
106
|
+
FocusedUncheckedDense.args = { ...DefaultDense.args, ...FocusedUnchecked.args }
|
|
107
|
+
FocusedUncheckedDense.parameters = { docs: { disable: true } }
|
|
108
|
+
FocusedUncheckedDense.storyName = 'Focused unchecked - Dense'
|
|
109
|
+
|
|
110
|
+
export const FocusedCheckedDense = Template.bind({})
|
|
111
|
+
FocusedCheckedDense.args = { ...DefaultDense.args, ...FocusedChecked.args }
|
|
112
|
+
FocusedCheckedDense.parameters = { docs: { disable: true } }
|
|
113
|
+
FocusedCheckedDense.storyName = 'Focused checked - Dense'
|
|
114
|
+
|
|
115
|
+
export const CheckedDense = Template.bind({})
|
|
116
|
+
CheckedDense.args = { ...DefaultDense.args, ...Checked.args }
|
|
117
|
+
CheckedDense.storyName = 'Checked - Dense'
|
|
118
|
+
|
|
119
|
+
export const RequiredDense = Template.bind({})
|
|
120
|
+
RequiredDense.args = { ...DefaultDense.args, ...Required.args }
|
|
121
|
+
RequiredDense.storyName = 'Required - Dense'
|
|
122
|
+
|
|
123
|
+
export const DisabledDense = CheckedUncheckedTemplate.bind({})
|
|
124
|
+
DisabledDense.args = { ...DefaultDense.args, ...Disabled.args }
|
|
125
|
+
DisabledDense.storyName = 'Disabled - Dense'
|
|
126
|
+
|
|
127
|
+
export const ValidDense = CheckedUncheckedTemplate.bind({})
|
|
128
|
+
ValidDense.args = { ...DefaultDense.args, ...Valid.args }
|
|
129
|
+
ValidDense.storyName = 'Valid - Dense'
|
|
130
|
+
|
|
131
|
+
export const WarningDense = CheckedUncheckedTemplate.bind({})
|
|
132
|
+
WarningDense.args = { ...DefaultDense.args, ...Warning.args }
|
|
133
|
+
WarningDense.storyName = 'Warning - Dense'
|
|
134
|
+
|
|
135
|
+
export const ErrorDense = CheckedUncheckedTemplate.bind({})
|
|
136
|
+
ErrorDense.args = { ...DefaultDense.args, ...Error.args }
|
|
137
|
+
ErrorDense.storyName = 'Error - Dense'
|
|
138
|
+
|
|
139
|
+
export const ImageLabelDense = Template.bind({})
|
|
140
|
+
ImageLabelDense.args = { ...DefaultDense.args, ...ImageLabel.args }
|
|
141
|
+
ImageLabelDense.storyName = 'Image label - Dense'
|
|
142
|
+
|
|
143
|
+
export const RTL = (args) => (
|
|
144
|
+
<div dir="rtl">
|
|
145
|
+
<>
|
|
146
|
+
<CheckboxField {...args} />
|
|
147
|
+
</>
|
|
148
|
+
</div>
|
|
149
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a CheckboxField with help text is rendered', () => {
|
|
4
|
+
cy.visitStory('CheckboxField', 'With help text')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the help text is visible', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uiwidgets-checkboxfield-help"]')
|
|
9
|
+
.contains('Help text')
|
|
10
|
+
.should('be.visible')
|
|
11
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a CheckboxField with a label is rendered', () => {
|
|
4
|
+
cy.visitStory('CheckboxField', 'With label')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the label is visible', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uiwidgets-checkboxfield"]')
|
|
9
|
+
.contains('The label')
|
|
10
|
+
.should('be.visible')
|
|
11
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a CheckboxField with validation text is rendered', () => {
|
|
4
|
+
cy.visitStory('CheckboxField', 'With validation text')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the validation text is visible', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uiwidgets-checkboxfield-validation"]')
|
|
9
|
+
.contains('Validation text')
|
|
10
|
+
.should('be.visible')
|
|
11
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a CheckboxField with label and a required flag is rendered', () => {
|
|
4
|
+
cy.visitStory('CheckboxField', 'With label and required')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the required indicator is visible', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uiwidgets-checkboxfield-required"]').should(
|
|
9
|
+
'be.visible'
|
|
10
|
+
)
|
|
11
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CheckboxField } from './checkbox-field.js'
|
package/src/index.js
ADDED