UNPKG

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
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