mdc-react
Version:
Material Components for the web implemented in React
179 lines (151 loc) • 5.19 kB
JSX
import { forwardRef, useRef, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useUpdated } from '../hooks';
import { getEventKey, getPageX } from '../utils';
import { cssClasses } from './constants';
import { getValueForEventKey } from './utils';
import Input from './Input';
import Track from './Track';
import Thumb from './Thumb';
const Slider = forwardRef(({
name,
value = 0,
min = 0,
max = 100,
step,
discrete = false,
disabled = false,
tickMarks = false,
onChange = Function.prototype,
className,
...props
}, ref) => {
const inputRef = useRef();
const trackRef = useRef();
const [active, setActive] = useState(false);
useUpdated(() => {
if (disabled) return;
if (active) {
document.body.addEventListener('mousemove', handleMove);
document.body.addEventListener('touchmove', handleMove);
document.body.addEventListener('mouseup', handleUp);
document.body.addEventListener('touchend', handleUp);
} else {
document.body.removeEventListener('mousemove', handleMove);
document.body.removeEventListener('touchmove', handleMove);
document.body.removeEventListener('mouseup', handleUp);
document.body.removeEventListener('touchend', handleUp);
}
return () => {
document.body.removeEventListener('mousemove', handleMove);
document.body.removeEventListener('touchmove', handleMove);
document.body.removeEventListener('mouseup', handleUp);
document.body.removeEventListener('touchend', handleUp);
};
}, [active]);
const updateValue = useCallback(newValue => {
if (newValue < min) {
newValue = Number(min);
} else if (newValue > max) {
newValue = Number(max);
}
if (step) {
newValue = Math.round(newValue / step) * step;
}
onChange(newValue);
}, [min, max, step, onChange]);
const handleMove = useCallback(event => {
const trackClientRect = trackRef.current.getBoundingClientRect();
const pageX = getPageX(event);
const offsetX = pageX - trackClientRect.left;
const percent = offsetX / trackClientRect.width;
const value = Number(min) + percent * (max - min);
updateValue(value);
}, [min, max, updateValue]);
const handleKeyDown = useCallback(event => {
event.preventDefault();
const value = Number(inputRef.current.value);
const eventKey = getEventKey(event);
const newValue = getValueForEventKey(eventKey, value, min, max, step);
if (isNaN(newValue)) return;
updateValue(newValue);
}, [min, max, step, updateValue]);
const handleRootInteraction = useCallback(event => {
handleMove(event);
}, [handleMove]);
const handleUp = useCallback(() => {
setActive(false);
}, []);
const handleThumbStartInteraction = useCallback(() => {
setActive(true);
}, []);
const handleThumbEndInteraction = useCallback(() => {
setActive(false);
}, []);
const classNames = classnames(cssClasses.ROOT, {
[cssClasses.DISCRETE]: discrete,
[cssClasses.DISABLED]: disabled
}, className);
return (
<div
ref={ref}
className={classNames}
onMouseDown={handleRootInteraction}
onTouchStart={handleRootInteraction}
{...props}
>
<Input
ref={inputRef}
name={name}
value={value}
min={min}
max={max}
step={step}
disabled={disabled}
/>
<Track
ref={trackRef}
value={value}
min={min}
max={max}
step={step}
discrete={discrete}
tickMarks={tickMarks}
/>
<Thumb
value={value}
min={min}
max={max}
discrete={discrete}
onStartInteraction={handleThumbStartInteraction}
onEndInteraction={handleThumbEndInteraction}
onKeyDown={handleKeyDown}
/>
</div>
);
});
Slider.displayName = 'MDCSlider';
Slider.propTypes = {
value: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
min: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
max: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
step: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
discrete: PropTypes.bool,
disabled: PropTypes.bool,
tickMarks: PropTypes.bool,
onChange: PropTypes.func
};
export default Slider;