fk-react-ui-components
Version:
Step 1 : Create a file in [ Seeds / Plants / Trees ] <br> Step 2 : It should export an Object with component name and story Component [Refer other components] <br> Step 3 : Story Component should return a react component <br> Step 3 : Created file should
501 lines (437 loc) • 17.1 kB
JavaScript
import React from 'react';
import propTypes from './propTypes';
import { SliderWrapper, Scale, Line, Mark, Tooltip, Handle, InputWrapper, Separator } from './styles';
import InputComponent from '../FormElements/Input/Input';
import styled from 'styled-components';
const Input = styled(InputComponent)`
width: 50px;
outline: none;
`;
class Slider extends React.PureComponent {
constructor(props) {
super(props);
_initialiseProps.call(this);
this.handleRefs = [];
let value = props.value;
if (typeof value === 'undefined') {
value = typeof props.defaultValue !== 'undefined' ? props.defaultValue : props.range ? [props.min, props.max] : props.min;
}
this.state = {
value,
draggingHandleIndex: null,
isComponentMounted: false
};
this.handleFirstInputValueChange = this.handleInputValueChange.bind(this, 0);
this.handleSecondInputValueChange = this.handleInputValueChange.bind(this, 1);
}
/**
* Set isComponentMounted in state as the marks can only be
* rendered after rendering the main scale.
*/
componentDidMount() {
this.setState({ isComponentMounted: true });
}
/**
* Updates the selected value in the local state of this component
* @param {object} nextProps
*/
componentWillReceiveProps(nextProps) {
let value = nextProps.value;
if (typeof value === 'undefined') {
value = typeof nextProps.defaultValue !== 'undefined' ? nextProps.defaultValue : nextProps.range ? [nextProps.min, nextProps.max] : nextProps.min;
} else if (nextProps.value !== this.state.value) {
value = nextProps.value;
}
if (nextProps.range) {
[0, 1].map(index => {
if (this.state.value[index] < nextProps.min) {
let value = [...this.state.value];
value[index] = nextProps.min;
}
if (this.state.value[index] > nextProps.max) {
let value = [...this.state.value];
value[index] = nextProps.max;
}
});
} else {
if (this.state.value < nextProps.min) {
value = nextProps.min;
}
if (this.state.value > nextProps.max) {
value = nextProps.max;
}
}
if (value !== this.state.value) {
this.setState({ value });
}
}
/**
* Sets the reference for the slider wrapper.
* @param {object} ref
*/
/**
* Sets the reference for the handle(s).
* @param {number} index
* @param {object} ref
*/
/**
* Called on pressing the mouse button.
* Adds global mousemove and mouseup event listeners to listen the drag event.
* In case of single value selection it updates the value in the local state
* which moves the handle to that postion. In case of range selection this cannot
* be done as there are two handles.
* @param {object} event
*/
/**
* Called on releasing the mouse button.
* Removes global mousemove and mouseup event listeners
* @param {object} event
*/
/**
* Called while dragging the mouse.
* Updates the value in the local state of the component.
*/
/**
* Adds global event listeners.
*/
/**
* Removes global event listeners.
*/
/**
* Returns the distance in pixels from the left edge of the slider to a point
* on the slider corresponding to a particular value.
* @param {number} value
* @return {number}
*/
/**
* Helper function to get the nearest marker(step) corresponding to given value
* @param {number} value
* @return {number}
*/
/**
* Returns the corresponding value on the slider of any point on the document where the
* user has clicked / dragged mouse.
* @param {number} clientX The x coordinate of the point
* @return {number}
*/
/**
* Helper function to get the marker data in the required format.
*/
/**
* Renders the scale with the steps / marks
*/
render() {
return React.createElement(
'div',
null,
React.createElement(
SliderWrapper,
{
styles: this.props.styles.slider || {},
innerRef: this.setSliderRef,
onMouseDown: this.handleMouseDown
},
this.renderScale(),
this.props.range ? this.state.value.map((value, index) => {
return React.createElement(Handle, {
key: index,
posX: this.getPositionX(value),
styles: this.props.styles.handle || {},
innerRef: ref => this.setHandleRef(index, ref),
active: this.state.draggingHandleIndex === index
});
}) : React.createElement(Handle, {
posX: this.getPositionX(this.state.value),
styles: this.props.styles.handle || {},
innerRef: ref => this.setHandleRef(0, ref),
active: this.state.draggingHandleIndex === 0
})
),
this.props.showTextField ? this.renderInput() : null
);
}
}
var _initialiseProps = function () {
this.setSliderRef = ref => {
if (ref) {
this.sliderRef = ref;
}
};
this.setHandleRef = (index, ref) => {
if (ref) {
this.handleRefs[index] = ref;
}
};
this.handleInputValueChange = (index, event) => {
let nextValue;
let value = event.target.value;
let minValue = this.props.min;
let maxValue = this.props.max;
if (this.props.range) {
if (index === 0) {
maxValue = this.state.value[1];
} else {
minValue = this.state.value[0];
}
}
value = value < minValue ? minValue : value;
value = value > maxValue ? maxValue : value;
if (this.props.range) {
nextValue = [...this.state.value];
nextValue[index] = value;
} else {
value = value;
nextValue = value;
}
this.setState({ value: nextValue });
};
this.handleMouseDown = event => {
if (this.props.disabled) {
return;
}
if (!this.props.range) {
const value = this.getValue(event.clientX);
this.setState({ value, draggingHandleIndex: 0 });
this.props.onChange(value);
this.addGlobalEventListeners();
} else if (this.handleRefs.indexOf(event.target) > -1) {
this.setState({ draggingHandleIndex: this.handleRefs.indexOf(event.target) });
this.addGlobalEventListeners();
} else {
const Xmin = this.sliderRef.getBoundingClientRect().left;
const clientPosX = event.clientX - Xmin;
const handleDistances = this.state.value.map(val => Math.abs(this.getPositionX(val) - clientPosX));
const handleToMove = handleDistances.indexOf(Math.min(...handleDistances));
const nextValueArray = [...this.state.value];
nextValueArray[handleToMove] = this.getValue(event.clientX);
this.setState({ value: nextValueArray });
this.props.onChange(nextValueArray);
}
};
this.handleGlobalMouseUp = event => {
this.setState({ draggingHandleIndex: null });
this.removeGlobalEventListeners();
};
this.handleGlobalMouseMove = event => {
if (!this.props.range) {
const value = this.getValue(event.clientX);
this.setState({ value });
this.props.onChange(value);
} else if (this.state.draggingHandleIndex !== null) {
let min = this.props.min;
let max = this.props.max;
if (this.state.draggingHandleIndex === 0) {
max = this.state.value[1];
} else if (this.state.draggingHandleIndex === 1) {
min = this.state.value[0];
}
const value = this.getValue(event.clientX, min, max);
let nextValueArray = [...this.state.value];
nextValueArray[this.state.draggingHandleIndex] = value;
this.setState({ value: nextValueArray });
this.props.onChange(nextValueArray);
}
};
this.addGlobalEventListeners = () => {
document.body.addEventListener('mousemove', this.handleGlobalMouseMove);
document.body.addEventListener('mouseup', this.handleGlobalMouseUp);
};
this.removeGlobalEventListeners = () => {
document.body.removeEventListener('mousemove', this.handleGlobalMouseMove);
document.body.removeEventListener('mouseup', this.handleGlobalMouseUp);
};
this.getPositionX = value => {
if (this.sliderRef && this.props.max > this.props.min) {
const { left: Xmin, right: Xmax } = this.sliderRef.getBoundingClientRect();
/** In case of non linear scale simply get posX from the marker data */
if (this.props.nonLinear) {
let posX;
this.getMarkerData().map(data => {
if (data.value === value) {
posX = data.posX;
}
});
return posX;
/** In case of linear scale posX can be calculated via interpolation */
} else {
return parseInt((Xmax - Xmin) / (this.props.max - this.props.min) * (value - this.props.min));
}
} else {
return 0;
}
};
this.getNearestMarkerValue = value => {
let nearestValue;
let minDistance = 0;
this.getMarkerData().map(data => {
const step = data.value;
let distance = Math.abs(value - step);
if (typeof nearestValue === 'undefined' || distance < minDistance) {
nearestValue = step;
minDistance = distance;
}
});
return nearestValue;
};
this.getValue = (clientX, min = this.props.min, max = this.props.max) => {
if (this.sliderRef) {
const { left: Xmin, right: Xmax } = this.sliderRef.getBoundingClientRect();
const posX = clientX - Xmin;
let value;
if (this.props.nonLinear) {
/** In case of non linear scale value should be obtained from marker data */
let nearestValue,
minDistance = 0;
this.getMarkerData().map(data => {
let distance = Math.abs(data.posX - posX);
if (typeof nearestValue === 'undefined' || distance < minDistance) {
nearestValue = data.value;
minDistance = distance;
}
});
value = nearestValue;
/** In case of linear scale value can be calculated using interpolation */
} else {
value = this.props.min + (this.props.max - this.props.min) / (Xmax - Xmin) * posX;
}
/** Sanitize the value */
value = value > max ? max : value;
value = value < min ? min : value;
if (typeof this.props.steps !== 'undefined' && this.props.selectStepsOnly && !this.props.nonLinear) {
value = this.getNearestMarkerValue(value);
}
return value;
} else {
return this.props.min;
}
};
this.getMarkerData = () => {
let marks = [];
/** If steps are provided in props the use it to compute marker data */
if (this.props.steps) {
if (this.props.nonLinear) {
if (!this.sliderRef) {
return;
}
let stepKeys = Object.keys(this.props.steps).sort(function (a, b) {
return parseInt(a) - parseInt(b);
});
if (parseInt(stepKeys[0]) !== this.props.min) {
stepKeys = stepKeys.unshift(this.props.min);
}
if (parseInt(stepKeys[stepKeys.length - 1]) !== this.props.max) {
stepKeys.push(this.props.max);
}
const spaceBetweenKeys = (this.props.max - this.props.min) / (stepKeys.length - 1);
const { left: Xmin, right: Xmax } = this.sliderRef.getBoundingClientRect();
stepKeys.map((step, index) => {
const posX = parseInt((Xmax - Xmin) / (this.props.max - this.props.min) * (spaceBetweenKeys * index - this.props.min));
marks.push({
index: marks.length,
value: parseInt(step),
posX: posX,
tooltip: this.props.getTooltip(this.props.steps[step])
});
});
} else {
Object.keys(this.props.steps).sort().map(step => {
marks.push({
index: marks.length,
value: parseInt(step),
posX: this.getPositionX(parseInt(step)),
tooltip: this.props.getTooltip(this.props.steps[step])
});
});
}
/** If steps is not provided than place marks at equal distance on the linear scale */
} else {
const step = Math.ceil((this.props.max - this.props.min) / 10) || 1;
let val = this.props.min;
while (val <= this.props.max) {
marks.push({
index: marks.length,
value: parseInt(step),
posX: this.getPositionX(val),
tooltip: this.props.getTooltip(val)
});
val = val + step;
}
}
return marks;
};
this.renderScale = () => {
const markerData = this.getMarkerData();
const value = this.state.value;
return React.createElement(
Scale,
{ styles: this.props.styles.scale || {} },
this.state.isComponentMounted ? markerData.map(mark => {
return this.props.showMarkerWithoutTooltip || mark.tooltip ? [React.createElement(Mark, { styles: this.props.styles.mark || {}, posX: mark.posX }), React.createElement(
Tooltip,
{ styles: this.props.styles.tooltip || {}, posX: mark.posX },
mark.tooltip
)] : null;
}) : null,
this.props.range ? React.createElement(Line, {
styles: this.props.styles.line || {},
style: {
left: this.getPositionX(value[0]),
width: this.getPositionX(value[1]) - this.getPositionX(value[0])
}
}) : React.createElement(Line, {
styles: this.props.styles.line || {},
style: {
left: 0,
width: this.getPositionX(value)
}
})
);
};
this.renderInput = () => {
let { value } = this.state;
if (this.props.range) {
value = value.map(v => parseInt(v * 100) / 100);
} else {
value = parseInt(value * 100) / 100;
}
return React.createElement(
InputWrapper,
null,
this.props.range ? [React.createElement(Input, {
value: value[0],
onChange: this.handleFirstInputValueChange,
type: 'number',
disabled: this.props.disabled
}), React.createElement(
Separator,
null,
'-'
), React.createElement(Input, {
value: value[1],
onChange: this.handleSecondInputValueChange,
type: 'number',
disabled: this.props.disabled
})] : React.createElement(Input, {
value: value,
onChange: this.handleFirstInputValueChange,
type: 'number',
disabled: this.props.disabled
})
);
};
};
Slider.propTypes = propTypes;
Slider.defaultProps = {
disabled: false,
selectStepsOnly: false,
showMarkerWithoutTooltip: true,
onChange: val => {},
styles: {},
range: false,
min: 0,
max: 100,
getTooltip: val => val,
showTextField: false
};
export default Slider;
//# sourceMappingURL=index.js.map