Skip to content

Commit

Permalink
feat(Checkbox Group): Checkbox group children management
Browse files Browse the repository at this point in the history
  • Loading branch information
ddsilva committed Apr 8, 2019
1 parent d799516 commit bdad3c0
Show file tree
Hide file tree
Showing 7 changed files with 856 additions and 86 deletions.
13 changes: 8 additions & 5 deletions components/Checkbox/Checkbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,33 +108,36 @@ const HiddenCheckbox = styled.input.attrs({

HiddenCheckbox.displayName = 'HiddenCheckbox';

const Checkbox = ({ label, error, id, ...rest }) => (
const Checkbox = ({ children, error, id, label, value, ...rest }) => (
<Wrapper>
<CheckboxWrapper>
<HiddenCheckbox id={id} error={error} {...rest} />
<HiddenCheckbox id={id} error={error} value={value} {...rest} />
<CheckIcon />
<CheckboxLabel htmlFor={id}>{label}</CheckboxLabel>
<CheckboxLabel htmlFor={id}>{children || label || value}</CheckboxLabel>
</CheckboxWrapper>
{error && typeof error === 'string' && <ErrorMessage>{error}</ErrorMessage>}
</Wrapper>
);

Checkbox.defaultProps = {
children: '',
checked: undefined,
disabled: false,
error: '',
id: '',
label: '',
value: '',
};

Checkbox.propTypes = {
children: PropTypes.string,
checked: PropTypes.bool,
disabled: PropTypes.bool,
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
id: PropTypes.string,
label: PropTypes.string,
value: PropTypes.string,
value: PropTypes.string.isRequired,
};

Checkbox.displayName = 'Checkbox';

export default Checkbox;
30 changes: 19 additions & 11 deletions components/Checkbox/Checkbox.unit.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,45 @@ describe('<Checkbox />', () => {
beforeEach(mockFn.mockClear);

it('should match the snapshot', () => {
expect(renderer.create(<Checkbox />).toJSON()).toMatchSnapshot();
expect(
renderer.create(<Checkbox error="error message" />).toJSON(),
renderer.create(<Checkbox value="foo" />).toJSON(),
).toMatchSnapshot();
expect(
renderer.create(<Checkbox error="error message" checked />).toJSON(),
renderer.create(<Checkbox value="foo" error="error message" />).toJSON(),
).toMatchSnapshot();
expect(renderer.create(<Checkbox checked />).toJSON()).toMatchSnapshot();
expect(renderer.create(<Checkbox disabled />).toJSON()).toMatchSnapshot();
expect(
renderer.create(<Checkbox label="Some text" />).toJSON(),
renderer
.create(<Checkbox value="foo" error="error message" checked />)
.toJSON(),
).toMatchSnapshot();
expect(
renderer.create(<Checkbox checked disabled />).toJSON(),
renderer.create(<Checkbox value="foo" checked />).toJSON(),
).toMatchSnapshot();
expect(
renderer.create(<Checkbox value="foo" disabled />).toJSON(),
).toMatchSnapshot();
expect(
renderer.create(<Checkbox value="foo" label="Some text" />).toJSON(),
).toMatchSnapshot();
expect(
renderer.create(<Checkbox value="foo" checked disabled />).toJSON(),
).toMatchSnapshot();
});

it('should pass onChange prop to checkbox component', () => {
const wrapper = mount(<Checkbox onChange={mockFn} />);
const wrapper = mount(<Checkbox value="foo" onChange={mockFn} />);
const checkbox = wrapper.find('HiddenCheckbox');
expect(wrapper.prop('onChange')).toEqual(checkbox.prop('onChange'));
});

it('should pass checked prop to checkbox component', () => {
const wrapper = shallow(<Checkbox checked />);
const wrapper = shallow(<Checkbox value="foo" checked />);
const checkbox = wrapper.find('HiddenCheckbox');
expect(checkbox.prop('checked')).toEqual(true);
});

it('should apply id prop', () => {
const wrapper = shallow(<Checkbox id="test" />);
const wrapper = shallow(<Checkbox value="foo" id="test" />);

const label = wrapper.find('CheckboxLabel');
const checkbox = wrapper.find('HiddenCheckbox');
Expand All @@ -51,7 +59,7 @@ describe('<Checkbox />', () => {
});

it('should apply label prop', () => {
const wrapper = mount(<Checkbox label="test" />);
const wrapper = mount(<Checkbox value="foo" label="test" />);
const labelText = wrapper.find('CheckboxLabel').text();

expect(labelText).not.toBeUndefined();
Expand Down
111 changes: 67 additions & 44 deletions components/Checkbox/CheckboxGroup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { FieldGroup, ErrorMessage } from '../shared';

import Checkbox from './Checkbox';

const Group = styled(FieldGroup)`
Expand All @@ -15,41 +14,67 @@ const ErrorLabel = styled(ErrorMessage)`

ErrorLabel.displayName = 'ErrorLabel';

const CheckboxGroup = ({
children,
error,
name,
onChange,
options,
value,
...rest
}) => {
const _onChange = () => {
const checkedValues = React.Children.map(children, child => child.value);

console.log(checkedValues);
class CheckboxGroup extends React.Component {
constructor(props) {
super(props);

const { children, options } = this.props;
const values = {};
const childrenProps = React.Children.count(children)
? React.Children.toArray(children).map(
({ props: childProps }) => childProps,
)
: options;

childrenProps.forEach(({ value, checked }) => {
values[value] = Boolean(checked);
});

this.state = { values };
}

_onChange = ({ target: { checked, value } }) => {
const { onChange } = this.props;
const { values: stateValues } = this.state;

const values = {
...stateValues,
[value]: checked,
};

onChange(values);

this.setState({ values });
};

const commonProps = { name, error: Boolean(error), onChange: _onChange };
const checkboxOptions = options.map(option =>
Object.assign({}, option, {
key: option.value,
...commonProps,
}),
);

const items =
React.Children.map(children, child =>
React.cloneElement(child, { ...commonProps }),
) || checkboxOptions.map(props => <Checkbox {...props} />);

return (
<Group {...rest}>
{items}
{error && <ErrorLabel>{error}</ErrorLabel>}
</Group>
);
};
render() {
const { children, error, name, options } = this.props;

const commonProps = {
name,
error: Boolean(error),
onChange: this._onChange,
};
const checkboxOptions = options.map(option =>
Object.assign({}, option, {
key: option.value,
...commonProps,
}),
);

const checkboxes =
React.Children.map(children, child =>
React.cloneElement(child, commonProps),
) || checkboxOptions.map(childProps => <Checkbox {...childProps} />);

return (
<Group>
{checkboxes}
{error && <ErrorLabel>{error}</ErrorLabel>}
</Group>
);
}
}

CheckboxGroup.Checkbox = Checkbox;

Expand All @@ -61,26 +86,24 @@ CheckboxGroup.defaultProps = {
error: undefined,
onChange: () => {},
options: [],
value: undefined,
};

CheckboxGroup.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.element),
PropTypes.element,
]),
error: PropTypes.string,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
options: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
value: PropTypes.string.isRequired,
disabled: PropTypes.bool,
checked: PropTypes.bool,
}),
),
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.element),
PropTypes.element,
]),
onChange: PropTypes.func,
/** Initialize CheckboxGroup with a value */
value: PropTypes.string,
name: PropTypes.string.isRequired,
error: PropTypes.string,
};

export default CheckboxGroup;
Loading

0 comments on commit bdad3c0

Please sign in to comment.