1import React, { useState, useCallback, ChangeEvent, FunctionComponent, FocusEvent } from 'react'; 2import SliderComponent from 'rc-slider'; 3 4import { cx } from '@emotion/css'; 5import { Global } from '@emotion/react'; 6import { useTheme2 } from '../../themes/ThemeContext'; 7import { getStyles } from './styles'; 8import { SliderProps } from './types'; 9import { Input } from '../Input/Input'; 10 11/** 12 * @public 13 */ 14export const Slider: FunctionComponent<SliderProps> = ({ 15 min, 16 max, 17 onChange, 18 onAfterChange, 19 orientation = 'horizontal', 20 reverse, 21 step, 22 value, 23 ariaLabelForHandle, 24 marks, 25 included, 26}) => { 27 const isHorizontal = orientation === 'horizontal'; 28 const theme = useTheme2(); 29 const styles = getStyles(theme, isHorizontal, Boolean(marks)); 30 const SliderWithTooltip = SliderComponent; 31 const [sliderValue, setSliderValue] = useState<number>(value ?? min); 32 33 const onSliderChange = useCallback( 34 (v: number) => { 35 setSliderValue(v); 36 37 if (onChange) { 38 onChange(v); 39 } 40 }, 41 [setSliderValue, onChange] 42 ); 43 44 const onSliderInputChange = useCallback( 45 (e: ChangeEvent<HTMLInputElement>) => { 46 let v = +e.target.value; 47 48 if (Number.isNaN(v)) { 49 v = 0; 50 } 51 52 setSliderValue(v); 53 54 if (onChange) { 55 onChange(v); 56 } 57 58 if (onAfterChange) { 59 onAfterChange(v); 60 } 61 }, 62 [onChange, onAfterChange] 63 ); 64 65 // Check for min/max on input blur so user is able to enter 66 // custom values that might seem above/below min/max on first keystroke 67 const onSliderInputBlur = useCallback( 68 (e: FocusEvent<HTMLInputElement>) => { 69 const v = +e.target.value; 70 71 if (v > max) { 72 setSliderValue(max); 73 } else if (v < min) { 74 setSliderValue(min); 75 } 76 }, 77 [max, min] 78 ); 79 80 const sliderInputClassNames = !isHorizontal ? [styles.sliderInputVertical] : []; 81 const sliderInputFieldClassNames = !isHorizontal ? [styles.sliderInputFieldVertical] : []; 82 83 return ( 84 <div className={cx(styles.container, styles.slider)}> 85 {/** Slider tooltip's parent component is body and therefore we need Global component to do css overrides for it. */} 86 <Global styles={styles.tooltip} /> 87 <label className={cx(styles.sliderInput, ...sliderInputClassNames)}> 88 <SliderWithTooltip 89 min={min} 90 max={max} 91 step={step} 92 defaultValue={value} 93 value={sliderValue} 94 onChange={onSliderChange} 95 onAfterChange={onAfterChange} 96 vertical={!isHorizontal} 97 reverse={reverse} 98 ariaLabelForHandle={ariaLabelForHandle} 99 marks={marks} 100 included={included} 101 /> 102 {/* Uses text input so that the number spinners are not shown */} 103 <Input 104 type="text" 105 className={cx(styles.sliderInputField, ...sliderInputFieldClassNames)} 106 value={`${sliderValue}`} // to fix the react leading zero issue 107 onChange={onSliderInputChange} 108 onBlur={onSliderInputBlur} 109 min={min} 110 max={max} 111 /> 112 </label> 113 </div> 114 ); 115}; 116 117Slider.displayName = 'Slider'; 118