@dhis2-ui/radio 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 +4 -3
- package/src/__tests__/radio.test.js +31 -0
- package/src/features/accepts_initial_focus/index.js +9 -0
- package/src/features/accepts_initial_focus.feature +5 -0
- package/src/features/accepts_label/index.js +11 -0
- package/src/features/accepts_label.feature +5 -0
- package/src/features/can_be_blurred/index.js +19 -0
- package/src/features/can_be_blurred.feature +6 -0
- package/src/features/can_be_changed/index.js +19 -0
- package/src/features/can_be_changed.feature +6 -0
- package/src/features/can_be_disabled/index.js +13 -0
- package/src/features/can_be_disabled.feature +6 -0
- package/src/features/can_be_focused/index.js +19 -0
- package/src/features/can_be_focused.feature +6 -0
- package/src/index.js +1 -0
- package/src/radio-icons.js +106 -0
- package/src/radio.e2e.stories.js +38 -0
- package/src/radio.js +199 -0
- package/src/radio.prod.stories.js +159 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2-ui/radio",
|
|
3
|
-
"version": "10.16.
|
|
3
|
+
"version": "10.16.3",
|
|
4
4
|
"description": "UI Radio",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,13 +33,14 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@dhis2/prop-types": "^3.1.2",
|
|
36
|
-
"@dhis2/ui-constants": "10.16.
|
|
36
|
+
"@dhis2/ui-constants": "10.16.3",
|
|
37
37
|
"classnames": "^2.3.1",
|
|
38
38
|
"prop-types": "^15.7.2"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
|
41
41
|
"build",
|
|
42
|
-
"types"
|
|
42
|
+
"types",
|
|
43
|
+
"src"
|
|
43
44
|
],
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"react": "^18.3.1",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { render, fireEvent, screen } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Radio } from '../radio.js'
|
|
4
|
+
|
|
5
|
+
describe('<Radio />', () => {
|
|
6
|
+
it('should call the onKeyDown callback when provided', () => {
|
|
7
|
+
const onKeyDown = jest.fn()
|
|
8
|
+
|
|
9
|
+
render(
|
|
10
|
+
<Radio
|
|
11
|
+
name="foo"
|
|
12
|
+
value="bar"
|
|
13
|
+
checked={false}
|
|
14
|
+
onKeyDown={onKeyDown}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
fireEvent.keyDown(screen.getByRole('radio'), {
|
|
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,9 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Radio with initialFocus is rendered', () => {
|
|
4
|
+
cy.visitStory('Radio', 'With initial focus')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the Radio is focused', () => {
|
|
8
|
+
cy.focused().parent('[data-test="dhis2-uicore-radio"]').should('exist')
|
|
9
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Radio with a label is rendered', () => {
|
|
4
|
+
cy.visitStory('Radio', 'With label')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the label is shown', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-radio"]')
|
|
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 Radio with initialFocus and onBlur handler is rendered', () => {
|
|
4
|
+
cy.visitStory('Radio', 'With initial focus and on blur')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the Radio is blurred', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-radio"] 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 Radio with onChange handler is rendered', () => {
|
|
4
|
+
cy.visitStory('Radio', 'With on change')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the Radio is checked', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-radio"]').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,13 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a disabled Radio is rendered', () => {
|
|
4
|
+
cy.visitStory('Radio', 'With disabled')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the user clicks the Radio', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-radio"] input').click({ force: true })
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
Then('the Radio is not focused', () => {
|
|
12
|
+
cy.focused().should('not.exist')
|
|
13
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a Radio with onFocus handler is rendered', () => {
|
|
4
|
+
cy.visitStory('Radio', 'With on focus')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the Radio is focused', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-radio"] 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
|
+
})
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Radio } from './radio.js'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { colors, theme } 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 styles = css`
|
|
7
|
+
svg {
|
|
8
|
+
display: block;
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
svg {
|
|
13
|
+
fill: ${colors.grey800};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
circle.background {
|
|
17
|
+
fill: ${colors.white};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
svg.checked {
|
|
21
|
+
fill: ${colors.teal700};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
svg.disabled {
|
|
25
|
+
fill: ${colors.grey400};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
svg.error {
|
|
29
|
+
fill: ${theme.error};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
svg.valid {
|
|
33
|
+
fill: ${theme.valid};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
svg.warning {
|
|
37
|
+
fill: ${theme.warning};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
svg:not(.checked) .inner,
|
|
41
|
+
svg:not(.checked) .outer.checked {
|
|
42
|
+
fill: none;
|
|
43
|
+
}
|
|
44
|
+
`
|
|
45
|
+
|
|
46
|
+
export function RadioRegular({ className }) {
|
|
47
|
+
return (
|
|
48
|
+
<svg
|
|
49
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
50
|
+
viewBox="0 0 18 18"
|
|
51
|
+
className={className}
|
|
52
|
+
>
|
|
53
|
+
<circle className="background" cx="9" cy="9" r="9"></circle>
|
|
54
|
+
<path
|
|
55
|
+
d="M9,0 C13.9705627,0 18,4.02943725 18,9 C18,13.9705627 13.9705627,18 9,18 C4.02943725,18 0,13.9705627 0,9 C0,4.02943725 4.02943725,0 9,0 Z M9,1 C4.581722,1 1,4.581722 1,9 C1,13.418278 4.581722,17 9,17 C13.418278,17 17,13.418278 17,9 C17,4.581722 13.418278,1 9,1 Z"
|
|
56
|
+
className="outer unchecked"
|
|
57
|
+
></path>
|
|
58
|
+
<path
|
|
59
|
+
d="M9,18 C13.9705627,18 18,13.9705627 18,9 C18,4.02943725 13.9705627,0 9,0 C4.02943725,0 0,4.02943725 0,9 C0,13.9705627 4.02943725,18 9,18 Z M9,16 C5.13400675,16 2,12.8659932 2,9 C2,5.13400675 5.13400675,2 9,2 C12.8659932,2 16,5.13400675 16,9 C16,12.8659932 12.8659932,16 9,16 Z"
|
|
60
|
+
className="outer checked"
|
|
61
|
+
></path>
|
|
62
|
+
<circle className="inner" cx="9" cy="9" r="5"></circle>
|
|
63
|
+
<style jsx>{`
|
|
64
|
+
svg {
|
|
65
|
+
height: 18px;
|
|
66
|
+
width: 18px;
|
|
67
|
+
}
|
|
68
|
+
`}</style>
|
|
69
|
+
<style jsx>{styles}</style>
|
|
70
|
+
</svg>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
RadioRegular.propTypes = {
|
|
74
|
+
className: PropTypes.string,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function RadioDense({ className }) {
|
|
78
|
+
return (
|
|
79
|
+
<svg
|
|
80
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
81
|
+
viewBox="0 0 14 14"
|
|
82
|
+
className={className}
|
|
83
|
+
>
|
|
84
|
+
<circle className="background" cx="7" cy="7" r="7"></circle>
|
|
85
|
+
<path
|
|
86
|
+
d="M7,14 C10.8659932,14 14,10.8659932 14,7 C14,3.13400675 10.8659932,0 7,0 C3.13400675,0 0,3.13400675 0,7 C0,10.8659932 3.13400675,14 7,14 Z M7,13 C3.6862915,13 1,10.3137085 1,7 C1,3.6862915 3.6862915,1 7,1 C10.3137085,1 13,3.6862915 13,7 C13,10.3137085 10.3137085,13 7,13 Z"
|
|
87
|
+
className="outer unchecked"
|
|
88
|
+
></path>
|
|
89
|
+
<path
|
|
90
|
+
d="M7,14 C10.8659932,14 14,10.8659932 14,7 C14,3.13400675 10.8659932,0 7,0 C3.13400675,0 0,3.13400675 0,7 C0,10.8659932 3.13400675,14 7,14 Z M7,12 C4.23857625,12 2,9.76142375 2,7 C2,4.23857625 4.23857625,2 7,2 C9.76142375,2 12,4.23857625 12,7 C12,9.76142375 9.76142375,12 7,12 Z"
|
|
91
|
+
className="outer checked"
|
|
92
|
+
></path>
|
|
93
|
+
<circle className="inner" cx="7" cy="7" r="3"></circle>
|
|
94
|
+
<style jsx>{`
|
|
95
|
+
svg {
|
|
96
|
+
height: 14px;
|
|
97
|
+
width: 14px;
|
|
98
|
+
}
|
|
99
|
+
`}</style>
|
|
100
|
+
<style jsx>{styles}</style>
|
|
101
|
+
</svg>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
RadioDense.propTypes = {
|
|
105
|
+
className: PropTypes.string,
|
|
106
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Radio } from './radio.js'
|
|
3
|
+
|
|
4
|
+
window.onChange = window.Cypress && window.Cypress.cy.stub()
|
|
5
|
+
window.onBlur = window.Cypress && window.Cypress.cy.stub()
|
|
6
|
+
window.onFocus = window.Cypress && window.Cypress.cy.stub()
|
|
7
|
+
|
|
8
|
+
export default { title: 'Radio' }
|
|
9
|
+
export const WithOnChange = () => (
|
|
10
|
+
<Radio name="Ex" label="Radio" value="default" onChange={window.onChange} />
|
|
11
|
+
)
|
|
12
|
+
export const WithInitialFocusAndOnBlur = () => (
|
|
13
|
+
<Radio
|
|
14
|
+
name="Ex"
|
|
15
|
+
label="Radio"
|
|
16
|
+
value="default"
|
|
17
|
+
initialFocus
|
|
18
|
+
onBlur={window.onBlur}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
export const WithOnFocus = () => (
|
|
22
|
+
<Radio
|
|
23
|
+
initialFocus
|
|
24
|
+
name="Ex"
|
|
25
|
+
label="Radio"
|
|
26
|
+
value="default"
|
|
27
|
+
onFocus={window.onFocus}
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
export const WithDisabled = () => (
|
|
31
|
+
<Radio name="Ex" label="Radio" value="default" disabled />
|
|
32
|
+
)
|
|
33
|
+
export const WithLabel = () => (
|
|
34
|
+
<Radio name="Ex" label="The label" value="default" />
|
|
35
|
+
)
|
|
36
|
+
export const WithInitialFocus = () => (
|
|
37
|
+
<Radio name="Ex" label="The label" value="default" initialFocus />
|
|
38
|
+
)
|
package/src/radio.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { colors, spacers, theme, sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import cx from 'classnames'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
import React, { Component, createRef } from 'react'
|
|
5
|
+
import { RadioRegular, RadioDense } from './radio-icons.js'
|
|
6
|
+
|
|
7
|
+
class Radio extends Component {
|
|
8
|
+
ref = createRef()
|
|
9
|
+
|
|
10
|
+
componentDidMount() {
|
|
11
|
+
if (this.props.initialFocus) {
|
|
12
|
+
this.ref.current.focus()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handleChange = (e) => {
|
|
17
|
+
if (this.props.onChange) {
|
|
18
|
+
this.props.onChange(this.createHandlerPayload(), e)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
handleBlur = (e) => {
|
|
23
|
+
if (this.props.onBlur) {
|
|
24
|
+
this.props.onBlur(this.createHandlerPayload(), e)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
handleFocus = (e) => {
|
|
29
|
+
if (this.props.onFocus) {
|
|
30
|
+
this.props.onFocus(this.createHandlerPayload(), e)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
handleKeyDown = (e) => {
|
|
35
|
+
if (this.props.onKeyDown) {
|
|
36
|
+
this.props.onKeyDown(this.createHandlerPayload(), e)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
createHandlerPayload() {
|
|
41
|
+
return {
|
|
42
|
+
value: this.props.value,
|
|
43
|
+
name: this.props.name,
|
|
44
|
+
checked: !this.props.checked,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static defaultProps = {
|
|
49
|
+
dataTest: 'dhis2-uicore-radio',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
render() {
|
|
53
|
+
const {
|
|
54
|
+
checked = false,
|
|
55
|
+
className,
|
|
56
|
+
disabled,
|
|
57
|
+
error,
|
|
58
|
+
label,
|
|
59
|
+
name,
|
|
60
|
+
tabIndex,
|
|
61
|
+
valid,
|
|
62
|
+
value,
|
|
63
|
+
warning,
|
|
64
|
+
dense,
|
|
65
|
+
dataTest = 'dhis2-uicore-radio',
|
|
66
|
+
} = this.props
|
|
67
|
+
|
|
68
|
+
const classes = cx({
|
|
69
|
+
checked,
|
|
70
|
+
disabled,
|
|
71
|
+
valid,
|
|
72
|
+
error,
|
|
73
|
+
warning,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<label
|
|
78
|
+
className={cx(className, {
|
|
79
|
+
disabled,
|
|
80
|
+
dense,
|
|
81
|
+
})}
|
|
82
|
+
data-test={dataTest}
|
|
83
|
+
>
|
|
84
|
+
<input
|
|
85
|
+
type="radio"
|
|
86
|
+
ref={this.ref}
|
|
87
|
+
name={name}
|
|
88
|
+
value={value}
|
|
89
|
+
checked={checked}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
tabIndex={tabIndex}
|
|
92
|
+
onChange={this.handleChange}
|
|
93
|
+
onFocus={this.handleFocus}
|
|
94
|
+
onKeyDown={this.handleKeyDown}
|
|
95
|
+
onBlur={this.handleBlur}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
<div className={cx('icon', { dense })}>
|
|
99
|
+
{dense ? (
|
|
100
|
+
<RadioDense className={classes} />
|
|
101
|
+
) : (
|
|
102
|
+
<RadioRegular className={classes} />
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{label}
|
|
107
|
+
|
|
108
|
+
<style jsx>{`
|
|
109
|
+
label {
|
|
110
|
+
display: flex;
|
|
111
|
+
position: relative;
|
|
112
|
+
flex-direction: row;
|
|
113
|
+
align-items: center;
|
|
114
|
+
justify-content: flex-start;
|
|
115
|
+
color: ${colors.grey900};
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
line-height: 19px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
label.dense {
|
|
121
|
+
font-size: 14px;
|
|
122
|
+
line-height: 16px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
label.disabled {
|
|
126
|
+
cursor: not-allowed;
|
|
127
|
+
color: ${theme.disabled};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
input {
|
|
131
|
+
opacity: 0;
|
|
132
|
+
position: absolute;
|
|
133
|
+
|
|
134
|
+
/* The same size as the icon */
|
|
135
|
+
height: 18px;
|
|
136
|
+
width: 18px;
|
|
137
|
+
|
|
138
|
+
/* The same offset as the icon, 2px border, 1px padding */
|
|
139
|
+
inset-inline-start: 3px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
label.dense input {
|
|
143
|
+
/* The same size as the dense icon */
|
|
144
|
+
height: 14px;
|
|
145
|
+
width: 14px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.icon {
|
|
149
|
+
user-select: none;
|
|
150
|
+
margin-inline-end: ${label ? spacers.dp4 : 0};
|
|
151
|
+
border: 2px solid transparent;
|
|
152
|
+
padding: 1px;
|
|
153
|
+
border-radius: 50%;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
label.dense .icon {
|
|
157
|
+
margin-inline-end: 3px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
input:focus + .icon {
|
|
161
|
+
outline: 3px solid ${theme.focus};
|
|
162
|
+
outline-offset: -3px;
|
|
163
|
+
}
|
|
164
|
+
`}</style>
|
|
165
|
+
</label>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
Radio.propTypes = {
|
|
171
|
+
checked: PropTypes.bool,
|
|
172
|
+
className: PropTypes.string,
|
|
173
|
+
dataTest: PropTypes.string,
|
|
174
|
+
dense: PropTypes.bool,
|
|
175
|
+
disabled: PropTypes.bool,
|
|
176
|
+
/** Adds 'error' styling for feedback. Mutually exclusive with `valid` and `warning` props */
|
|
177
|
+
error: sharedPropTypes.statusPropType,
|
|
178
|
+
initialFocus: PropTypes.bool,
|
|
179
|
+
label: PropTypes.node,
|
|
180
|
+
/** Name associated with this element. Passed in object to event handler functions */
|
|
181
|
+
name: PropTypes.string,
|
|
182
|
+
tabIndex: PropTypes.string,
|
|
183
|
+
/** Adds 'valid' styling for feedback. Mutually exclusive with `error` and `warning` props */
|
|
184
|
+
valid: sharedPropTypes.statusPropType,
|
|
185
|
+
/** Value associated with this element. Passed in object to event handler functions */
|
|
186
|
+
value: PropTypes.string,
|
|
187
|
+
/** Adds 'warning' styling for feedback. Mutually exclusive with `valid` and `error` props */
|
|
188
|
+
warning: sharedPropTypes.statusPropType,
|
|
189
|
+
/** Called with the signature `({ name: string, value: string, checked: bool }, event)` */
|
|
190
|
+
onBlur: PropTypes.func,
|
|
191
|
+
/** Called with the signature `({ name: string, value: string, checked: bool }, event)` */
|
|
192
|
+
onChange: PropTypes.func,
|
|
193
|
+
/** Called with the signature `({ name: string, value: string, checked: bool }, event)` */
|
|
194
|
+
onFocus: PropTypes.func,
|
|
195
|
+
/** Called with the signature `({ name: string, value: string, checked: bool }, event)` */
|
|
196
|
+
onKeyDown: PropTypes.func,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { Radio }
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Radio } from './radio.js'
|
|
4
|
+
|
|
5
|
+
const subtitle = `A control that allows a user to select a single option from a choice of several`
|
|
6
|
+
|
|
7
|
+
const description = `
|
|
8
|
+
Radio buttons are used where a user has the choice of several options but must select only one. Radio buttons should be used where the user has to make a choice, there is no 'off' or 'none' state unless explicitly defined. Radio buttons should be used when there are 5 or less options available. With more than five, a dropdown/Select menu should be used instead.
|
|
9
|
+
|
|
10
|
+
Do not use a radio button if only a single option is available; use a Checkbox here instead.
|
|
11
|
+
|
|
12
|
+
If there are many options that need to select from, consider using a Select instead.
|
|
13
|
+
|
|
14
|
+
#### Size
|
|
15
|
+
|
|
16
|
+
Radio buttons are available in Regular and Dense sizes. Regular size is usually used in forms and whenever radio buttons are used standalone. Dense size radio buttons are used inside other complex components, not as main elements of a UI.
|
|
17
|
+
|
|
18
|
+
#### See more
|
|
19
|
+
|
|
20
|
+
Learn more about Radio buttons at [Design System: Radio](https://github.com/dhis2/design-system/blob/master/atoms/radio.md).
|
|
21
|
+
|
|
22
|
+
\`\`\`js
|
|
23
|
+
import { Radio } from '@dhis2/ui'
|
|
24
|
+
\`\`\`
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
window.onChange = (payload, event) => {
|
|
28
|
+
console.log('onChange payload', payload)
|
|
29
|
+
console.log('onChange event', event)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
window.onFocus = (payload, event) => {
|
|
33
|
+
console.log('onFocus payload', payload)
|
|
34
|
+
console.log('onFocus event', event)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
window.onBlur = (payload, event) => {
|
|
38
|
+
console.log('onBlur payload', payload)
|
|
39
|
+
console.log('onBlur event', event)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const onChange = (...args) => window.onChange(...args)
|
|
43
|
+
const onFocus = (...args) => window.onFocus(...args)
|
|
44
|
+
const onBlur = (...args) => window.onBlur(...args)
|
|
45
|
+
|
|
46
|
+
export default {
|
|
47
|
+
title: 'Radio',
|
|
48
|
+
component: Radio,
|
|
49
|
+
parameters: {
|
|
50
|
+
componentSubtitle: subtitle,
|
|
51
|
+
docs: { description: { component: description } },
|
|
52
|
+
},
|
|
53
|
+
// Default args for all stories
|
|
54
|
+
args: {
|
|
55
|
+
name: 'Ex',
|
|
56
|
+
label: 'Radio',
|
|
57
|
+
value: 'default',
|
|
58
|
+
onChange: onChange,
|
|
59
|
+
onFocus: onFocus,
|
|
60
|
+
onBlur: onBlur,
|
|
61
|
+
},
|
|
62
|
+
argTypes: {
|
|
63
|
+
valid: { ...sharedPropTypes.statusArgType },
|
|
64
|
+
error: { ...sharedPropTypes.statusArgType },
|
|
65
|
+
warning: { ...sharedPropTypes.statusArgType },
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const Template = (args) => <Radio {...args} />
|
|
70
|
+
|
|
71
|
+
const CheckedUncheckedTemplate = (args) => (
|
|
72
|
+
<>
|
|
73
|
+
<Radio {...args} />
|
|
74
|
+
<Radio {...args} checked />
|
|
75
|
+
</>
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
export const Default = Template.bind({})
|
|
79
|
+
|
|
80
|
+
export const FocusedUnchecked = (args) => (
|
|
81
|
+
<>
|
|
82
|
+
<Radio {...args} initialFocus className="initially-focused" />
|
|
83
|
+
<Radio {...args} className="initially-unfocused" />
|
|
84
|
+
</>
|
|
85
|
+
)
|
|
86
|
+
// Stories with initial focus are distracting on docs page
|
|
87
|
+
FocusedUnchecked.parameters = { docs: { disable: true } }
|
|
88
|
+
|
|
89
|
+
export const FocusedChecked = FocusedUnchecked.bind({})
|
|
90
|
+
FocusedChecked.args = { checked: true }
|
|
91
|
+
FocusedChecked.parameters = { docs: { disable: true } }
|
|
92
|
+
|
|
93
|
+
export const Checked = Template.bind({})
|
|
94
|
+
Checked.args = { checked: true, value: 'checked' }
|
|
95
|
+
|
|
96
|
+
export const Disabled = CheckedUncheckedTemplate.bind({})
|
|
97
|
+
Disabled.args = { disabled: true, value: 'disabled' }
|
|
98
|
+
|
|
99
|
+
export const Valid = CheckedUncheckedTemplate.bind({})
|
|
100
|
+
Valid.args = { valid: true, value: 'valid' }
|
|
101
|
+
|
|
102
|
+
export const Warning = CheckedUncheckedTemplate.bind({})
|
|
103
|
+
Warning.args = { warning: true, value: 'warning' }
|
|
104
|
+
|
|
105
|
+
export const Error = CheckedUncheckedTemplate.bind({})
|
|
106
|
+
Error.args = { error: true, value: 'error' }
|
|
107
|
+
|
|
108
|
+
export const ImageLabel = Template.bind({})
|
|
109
|
+
ImageLabel.args = {
|
|
110
|
+
label: <img src="https://picsum.photos/id/82/200/100" />,
|
|
111
|
+
value: 'with-help',
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const DefaultDense = Template.bind({})
|
|
115
|
+
DefaultDense.args = { dense: true }
|
|
116
|
+
DefaultDense.storyName = 'Default - Dense'
|
|
117
|
+
|
|
118
|
+
export const FocusedUncheckedDense = FocusedUnchecked.bind({})
|
|
119
|
+
FocusedUncheckedDense.args = { ...DefaultDense.args }
|
|
120
|
+
FocusedUncheckedDense.storyName = 'Focused unchecked - Dense'
|
|
121
|
+
FocusedUncheckedDense.parameters = { docs: { disable: true } }
|
|
122
|
+
|
|
123
|
+
export const FocusedCheckedDense = FocusedUnchecked.bind({})
|
|
124
|
+
FocusedCheckedDense.args = { ...DefaultDense.args, checked: true }
|
|
125
|
+
FocusedCheckedDense.storyName = 'Focused checked - Dense'
|
|
126
|
+
FocusedCheckedDense.parameters = { docs: { disable: true } }
|
|
127
|
+
|
|
128
|
+
export const CheckedDense = Template.bind({})
|
|
129
|
+
CheckedDense.args = { ...Checked.args, ...DefaultDense.args }
|
|
130
|
+
CheckedDense.storyName = 'Checked - Dense'
|
|
131
|
+
|
|
132
|
+
export const DisabledDense = CheckedUncheckedTemplate.bind({})
|
|
133
|
+
DisabledDense.args = { ...Disabled.args, ...DefaultDense.args }
|
|
134
|
+
DisabledDense.storyName = 'Disabled - Dense'
|
|
135
|
+
|
|
136
|
+
export const ValidDense = CheckedUncheckedTemplate.bind({})
|
|
137
|
+
ValidDense.args = { ...Valid.args, ...DefaultDense.args }
|
|
138
|
+
ValidDense.storyName = 'Valid - Dense'
|
|
139
|
+
|
|
140
|
+
export const WarningDense = CheckedUncheckedTemplate.bind({})
|
|
141
|
+
WarningDense.args = { ...Warning.args, ...DefaultDense.args }
|
|
142
|
+
WarningDense.storyName = 'Warning - Dense'
|
|
143
|
+
|
|
144
|
+
export const ErrorDense = CheckedUncheckedTemplate.bind({})
|
|
145
|
+
ErrorDense.args = { ...Error.args, ...DefaultDense.args }
|
|
146
|
+
ErrorDense.storyName = 'Error - Dense'
|
|
147
|
+
|
|
148
|
+
export const ImageLabelDense = Template.bind({})
|
|
149
|
+
ImageLabelDense.args = { ...ImageLabel.args, ...DefaultDense.args }
|
|
150
|
+
ImageLabelDense.storyName = 'Image label - Dense'
|
|
151
|
+
|
|
152
|
+
export const NoLabel = Template.bind({})
|
|
153
|
+
NoLabel.args = { label: null, className: 'some-name' }
|
|
154
|
+
|
|
155
|
+
export const RTL = (args) => (
|
|
156
|
+
<div dir="rtl">
|
|
157
|
+
<Template {...args} />
|
|
158
|
+
</div>
|
|
159
|
+
)
|