Skip to content

Commit 6c9bda0

Browse files
koskinparkdawehner
authored andcommitted
Added input radio component - #661 (#720)
1 parent 0eb1b0c commit 6c9bda0

File tree

3 files changed

+259
-2
lines changed

3 files changed

+259
-2
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { css } from 'emotion';
4+
import { colors, formColors } from '../../../Utils/colors';
5+
6+
const styles = {
7+
radio: css`
8+
appearance: none;
9+
border: 1px solid ${formColors.colorInputBorder};
10+
width: 18px;
11+
height: 18px;
12+
background-color: ${formColors.colorInputBg};
13+
background-position: center;
14+
background-repeat: no-repeat;
15+
background-size: auto auto;
16+
border-radius: 50%;
17+
box-shadow: 0 0 0 4px transparent;
18+
box-sizing: border-box;
19+
margin: 0 10px 0 0;
20+
cursor: pointer;
21+
display: inline-block;
22+
vertical-align: text-top;
23+
&:hover {
24+
border-color: ${formColors.colorInputFg};
25+
box-shadow: inset 0 0 0 1px ${formColors.colorInputFg};
26+
}
27+
&:focus {
28+
box-shadow: 0 0 0 1px ${formColors.colorInputBg},
29+
0 0 0 4px ${formColors.colorInputFocusShadow};
30+
outline: none;
31+
}
32+
&:focus:hover {
33+
box-shadow: 0 0 0 1px ${formColors.colorInputBg},
34+
0 0 0 4px ${formColors.colorInputFocusShadow},
35+
inset 0 0 0 1px ${formColors.colorInputFg};
36+
}
37+
&:checked {
38+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='10' width='10'%3E%3Ccircle cx='5' cy='5' r='5' fill='%23004ADC' /%3E%3C/svg%3E");
39+
border-color: ${formColors.colorInputBorderFocus};
40+
box-shadow: inset 0 0 0 1px ${formColors.colorInputBorderFocus};
41+
}
42+
&:checked:focus {
43+
box-shadow: 0 0 0 1px ${formColors.colorInputBg},
44+
0 0 0 4px ${formColors.colorInputFocusShadow},
45+
inset 0 0 0 1px ${formColors.colorInputFg};
46+
}
47+
&:checked:hover {
48+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='10' width='10'%3E%3Ccircle cx='5' cy='5' r='5' fill='%23222330' /%3E%3C/svg%3E");
49+
border-color: ${formColors.colorInputFg};
50+
}
51+
`,
52+
radioWithError: css`
53+
border-color: ${formColors.colorInputBorderError};
54+
box-shadow: inset 0 0 0 1px ${formColors.colorInputBorderError};
55+
&:hover {
56+
border-color: ${formColors.colorInputFg};
57+
box-shadow: inset 0 0 0 1px ${formColors.colorInputFg};
58+
}
59+
&:focus {
60+
box-shadow: 0 0 0 1px ${formColors.colorInputBg},
61+
0 0 0 4px ${formColors.colorInputFocusShadow},
62+
inset 0 0 0 1px ${formColors.colorInputBorderError};
63+
}
64+
&:focus:hover {
65+
border-color: ${formColors.colorInputFg};
66+
box-shadow: 0 0 0 1px ${formColors.colorInputBg},
67+
0 0 0 4px ${formColors.colorInputFocusShadow},
68+
inset 0 0 0 1px ${formColors.colorInputFg};
69+
}
70+
&:checked {
71+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='10' width='10'%3E%3Ccircle cx='5' cy='5' r='5' fill='%23D72222' /%3E%3C/svg%3E");
72+
border-color: ${formColors.colorInputBorderError};
73+
box-shadow: inset 0 0 0 1px ${formColors.colorInputBorderError};
74+
}
75+
&:checked:focus {
76+
border-color: ${formColors.colorInputBorderError};
77+
box-shadow: 0 0 0 1px ${formColors.colorInputBg},
78+
0 0 0 4px ${formColors.colorInputFocusShadow},
79+
inset 0 0 0 1px ${formColors.colorInputBorderError};
80+
}
81+
&:checked:hover {
82+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='10' width='10'%3E%3Ccircle cx='5' cy='5' r='5' fill='%23222330' /%3E%3C/svg%3E");
83+
box-shadow: inset 0 0 0 1px ${formColors.colorInputFg};
84+
border-color: ${formColors.colorInputFg};
85+
}
86+
&:checked:focus:hover {
87+
box-shadow: 0 0 0 1px ${formColors.colorInputBg},
88+
0 0 0 4px ${formColors.colorInputFocusShadow},
89+
inset 0 0 0 1px ${formColors.colorInputFg};
90+
border-color: ${formColors.colorInputFg};
91+
}
92+
`,
93+
label: css`
94+
color: ${colors.text};
95+
cursor: pointer;
96+
`,
97+
labelRequired: css`
98+
:after {
99+
content: ' *';
100+
color: ${colors.maximumRed};
101+
}
102+
`,
103+
};
104+
105+
/**
106+
* Renders an Input type radio.
107+
*
108+
* Example:
109+
*
110+
* @code
111+
* <InputRadio required />
112+
* @endcode
113+
*/
114+
const InputRadio = props => {
115+
const {
116+
checked,
117+
children,
118+
error,
119+
required,
120+
htmlAttributes,
121+
onChange,
122+
} = props;
123+
const labelClasses = [styles.label];
124+
const cbClasses = [styles.radio];
125+
if (required) {
126+
labelClasses.push(styles.labelRequired);
127+
}
128+
if (error) {
129+
cbClasses.push(styles.radioWithError);
130+
}
131+
132+
const inputId = `${props.fieldName}-cb`;
133+
134+
return (
135+
<label
136+
id={`${props.fieldName}-label`}
137+
htmlFor={inputId}
138+
className={css`
139+
${labelClasses}
140+
`}
141+
{...htmlAttributes}
142+
>
143+
<input
144+
type="radio"
145+
id={inputId}
146+
defaultChecked={checked}
147+
name={props.fieldName}
148+
className={css`
149+
${cbClasses}
150+
`}
151+
onChange={event => onChange(event.target.checked)}
152+
/>
153+
{children}
154+
</label>
155+
);
156+
};
157+
158+
InputRadio.propTypes = {
159+
/** Content contained within the label element. */
160+
children: PropTypes.oneOfType([
161+
PropTypes.arrayOf(PropTypes.node),
162+
PropTypes.node,
163+
]).isRequired,
164+
165+
/** true if the label relates to an element with an error. */
166+
error: PropTypes.bool,
167+
168+
/** Any additional HTML properties to add to the label element. */
169+
htmlAttributes: PropTypes.objectOf([
170+
PropTypes.string,
171+
PropTypes.number,
172+
PropTypes.bool,
173+
]),
174+
175+
/** Content of the html "for" attribute to use for this label. */
176+
htmlFor: PropTypes.string.isRequired,
177+
178+
/** True if this label relates to an element that is required. */
179+
required: PropTypes.bool,
180+
};
181+
182+
InputRadio.defaultProps = {
183+
error: false,
184+
htmlAttributes: {},
185+
required: false,
186+
};
187+
188+
export default InputRadio;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React from 'react';
2+
/* eslint-disable import/no-extraneous-dependencies */
3+
import { storiesOf } from '@storybook/react';
4+
import { action } from '@storybook/addon-actions';
5+
import { boolean, object, text } from '@storybook/addon-knobs/react';
6+
import figmaDecorator from 'storybook-addon-figma';
7+
/* eslint-enable import/no-extraneous-dependencies */
8+
import InputRadio from './InputRadio';
9+
10+
/**
11+
* There is a known issue with addWithJSX and action() calls.
12+
*
13+
* https://github.com/storybooks/addon-jsx/issues/30
14+
*
15+
* To get both the logger to function while still pretty printing sample code
16+
* in the JSX tab a toSting() method must be provided.
17+
*/
18+
const onChangeAction = action('onChange');
19+
onChangeAction.toString = () => "action('onChange')";
20+
21+
storiesOf('FormElements/InputRadio', module)
22+
.addDecorator(
23+
figmaDecorator({
24+
url:
25+
'https://www.figma.com/file/OqWgzAluHtsOd5uwm1lubFeH/Drupal-Design-system?node-id=2061%3A2254',
26+
}),
27+
)
28+
.addWithJSX('Default', () => (
29+
<InputRadio
30+
checked={false}
31+
fieldName="ControlRadio"
32+
error={boolean('Radio: error', false)}
33+
required={boolean('Radio: required', false)}
34+
htmlAttributes={object('Radio: htmlAttributes', {
35+
'data-test': 'test',
36+
})}
37+
onChange={onChangeAction}
38+
>
39+
{text('Radio: label', 'Input Radio')}
40+
</InputRadio>
41+
))
42+
.addWithJSX('Checked', () => (
43+
<InputRadio
44+
checked
45+
fieldName="ControlRadio"
46+
error={boolean('Radio: error', false)}
47+
required={boolean('Radio: required', false)}
48+
htmlAttributes={object('Radio: htmlAttributes', {
49+
'data-test': 'test',
50+
})}
51+
onChange={onChangeAction}
52+
>
53+
{text('Radio: label', 'Input Radio')}
54+
</InputRadio>
55+
))
56+
.addWithJSX('Error', () => (
57+
<InputRadio
58+
checked={false}
59+
fieldName="ControlRadio"
60+
error={boolean('Radio: error', true)}
61+
required={boolean('Radio: required', true)}
62+
htmlAttributes={object('Radio: htmlAttributes', {
63+
'data-test': 'test',
64+
})}
65+
onChange={onChangeAction}
66+
>
67+
{text('Radio: label', 'Input Radio')}
68+
</InputRadio>
69+
));

packages/components/src/Utils/colors.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const colors = {
1212
maximumRed: '#D72222',
1313
sunglow: '#FFD23F',
1414
celadonGreen: '#228572',
15+
lightningGreen: '#26A769',
1516
// Variations
1617
hover: '#003EBB',
1718
active: '#003B96',
@@ -28,8 +29,7 @@ const formColors = {
2829
colorInputBorderError: colors.maximumRed,
2930
colorInputBorderFocus: colors.absoluteZero,
3031
colorInputFg: colors.text,
31-
colorInputFocusShadow:
32-
'rgba(0, 74, 220, 0.3)' /* Absolute zero with opacity. */,
32+
colorInputFocusShadow: colors.lightningGreen,
3333
};
3434

3535
export default colors;

0 commit comments

Comments
 (0)