Skip to content
This repository has been archived by the owner on May 14, 2021. It is now read-only.

Commit

Permalink
feat: add Slider component (@boyur #4)
Browse files Browse the repository at this point in the history
* add LegendInterval and LegendCategorical

* update tests

* hot fix

* add Slider

* hot fix

* add new Slider test

* fix test

* update Slider, add Slider tests.

* Edits after the code review

* add new test

* fix Readme

* Edits after the code review

* Edits after the code review
  • Loading branch information
boyur authored and stepankuzmin committed Dec 6, 2017
1 parent a23c0d9 commit c5b20b1
Show file tree
Hide file tree
Showing 12 changed files with 391 additions and 4 deletions.
4 changes: 3 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"import/no-extraneous-dependencies": [
"error",
{ "devDependencies": true, "peerDependencies": true }
]
],
"react/jsx-no-bind": 0,
"no-underscore-dangle": ["error", { "allowAfterThis": true }]
}
}
7 changes: 7 additions & 0 deletions src/components/Slider/Container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from 'react-emotion';

export default styled.div`
position: relative;
margin: auto;
text-align: center;
`;
102 changes: 102 additions & 0 deletions src/components/Slider/Input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import styled from 'react-emotion';

export default styled.input`
width: 100%;
height: 34px;
-webkit-appearance: none;
&::-webkit-slider-runnable-track {
height: 2px;
background: #000000;
border: none;
border-radius: 1px;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
border: none;
height: 28px;
width: 28px;
background-color: #ffffff;
border: solid 2px #000000;
border-radius: 50%;
margin-top: -14px;
&:hover {
cursor: pointer;
}
}
// Firefox
&:focus {
outline: none;
}
&::-moz-range-track {
height: 2px;
background: #000000;
border: none;
border-radius: 1px;
}
&::-moz-range-thumb {
-webkit-appearance: none;
border: none;
height: 28px;
width: 28px;
background-color: #ffffff;
border: solid 2px #000000;
border-radius: 50%;
margin-top: -14px;
&:hover {
cursor: pointer;
}
}
// IE
/*hide the outline behind the border*/
&:-moz-focusring{
outline: 1px solid white;
outline-offset: -1px;
}
&:focus::-moz-range-track {
background: transparent;
}
&::-ms-track {
height: 2px;
background: #000000;
border: none;
border-radius: 1px;
}
&::-ms-fill-lower {
background: #777;
border-radius: 10px;
}
&::-ms-fill-upper {
background: #ddd;
border-radius: 10px;
}
&::-ms-thumb {
height: 28px;
width: 28px;
background-color: #ffffff;
border: solid 2px #000000;
border-radius: 50%;
margin-top: -14px;
&:hover {
cursor: pointer;
}
}
&:focus::-ms-fill-lower {
background: #888;
}
&:focus::-ms-fill-upper {
background: #ccc;
}
`;
31 changes: 31 additions & 0 deletions src/components/Slider/Label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import styled from 'react-emotion';

export default styled.div`
display: block;
border: 0;
width: 100%;
height: 100%;
text-align: center;
outline: none;
cursor: pointer;
&:first-child {
position: relative;
text-align: left;
span {
position: absolute;
transform: translateX(-50%);
}
}
&:last-child {
position: relative;
text-align: right;
span {
position: absolute;
transform: translateX(-50%);
}
}
`;
24 changes: 24 additions & 0 deletions src/components/Slider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Slider

```js
const options = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' }
];

initialState = { value: options[0].value };

const onChange = (value) => {
setState({ value })
};

<div>
Current value: {state.value}
<Slider
onChange={onChange}
options={options}
value={state.value}
/>
</div>
```
9 changes: 9 additions & 0 deletions src/components/Slider/Scale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styled from 'react-emotion';

export default styled.div`
display: flex;
font-size: 14px;
font-weight: 500;
justify-content: space-between;
padding: 0 12px 0 15px;
`;
105 changes: 105 additions & 0 deletions src/components/Slider/Slider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';

// Style
import Container from './Container';
import Scale from './Scale';
import Label from './Label';
import Input from './Input';

/**
* @component
*/
class Slider extends PureComponent {
constructor(props) {
super(props);

this.state = {
index: 0
};

this.onChange = this.onChange.bind(this);
this.onChangeEnd = this.onChangeEnd.bind(this);
this.onScaleClick = this.onScaleClick.bind(this);
this._renderOption = this._renderOption.bind(this);
}

componentWillMount() {
const { options, value } = this.props;

if (!options.length) {
throw new Error('options is empty');
}

const index = options.findIndex(o => o.value === value);
this.setState({ index });
}

onChange(event) {
this.setState({ index: event.target.value });
const index = Math.round(event.target.value);
const { value } = this.props.options[index];

if (value !== this.props.value) {
this.props.onChange(value);
}
}

onChangeEnd(event) {
const index = Math.round(event.target.value);
this.setState({ index });
}

onScaleClick(index) {
this.setState({ index });
const { value } = this.props.options[index];
this.props.onChange(value);
}

_renderOption(option, index) {
return (
<Label
key={option.value}
role="button"
onClick={this.onScaleClick.bind(null, index)}
>
<span>{option.label}</span>
</Label>
);
}

render() {
const { options } = this.props;

return (
<Container>
<Input
type="range"
value={this.state.index}
onChange={this.onChange}
onMouseUp={this.onChangeEnd}
max={options.length - 1}
step={0.01}
/>
<Scale length={options.length}>
{options.map(this._renderOption)}
</Scale>
</Container>
);
}
}

Slider.propTypes = {
options: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.node.isRequired,
label: PropTypes.node
})).isRequired,
value: PropTypes.node,
onChange: PropTypes.func.isRequired
};

Slider.defaultProps = {
value: PropTypes.null
};

export default Slider;
60 changes: 60 additions & 0 deletions src/components/Slider/Slider.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import Slider from '../Slider';
import Input from '../Slider/Input';

const options = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' }
];

const onChange = value => value;

test('Slider does not crash', () => {
const wrapper = shallow(<Slider onChange={onChange} options={options} />);
expect(toJson(wrapper)).toMatchSnapshot();
});

test('Slider with an empty array', () => {
expect(() => shallow(<Slider onChange={onChange} options={[]} />))
.toThrowError('options is empty');
});

test('Slider simulate onChange', () => {
const wrapper = shallow(<Slider
onChange={onChange}
options={options}
value={options[0].value}
/>);
const rangeInput = wrapper.find(Input).first();
expect(wrapper.state('index')).toEqual(0);
rangeInput.simulate('change', { target: { value: 1 } });
expect(wrapper.state('index')).toEqual(1);
});

test('Slider simulate onChangeEnd', () => {
const wrapper = shallow(<Slider
onChange={onChange}
options={options}
value={options[0].value}
/>);
const rangeInput = wrapper.find(Input).first();
expect(wrapper.state('index')).toEqual(0);
rangeInput.simulate('change', { target: { value: 0.8 } });
rangeInput.simulate('mouseUp', { target: { value: 0.8 } });
expect(wrapper.state('index')).toEqual(1);
});

test('Slider simulate onClickScale', () => {
const wrapper = shallow(<Slider
onChange={onChange}
options={options}
value={options[0].value}
/>);
const scaleButton = wrapper.find('[role="button"]').last();
expect(wrapper.state('index')).toEqual(0);
scaleButton.simulate('click');
expect(wrapper.state('index')).toEqual(2);
});
Loading

0 comments on commit c5b20b1

Please sign in to comment.