import {forwardRef as reactForwardRef, useEffect, useState} from 'react'; import Input from 'sentry/components/input'; import {t} from 'sentry/locale'; import {defined} from 'sentry/utils'; import Slider from './slider'; import SliderAndInputWrapper from './sliderAndInputWrapper'; import SliderLabel from './sliderLabel'; type SliderProps = { name: string; /** * String is a valid type here only for empty string * Otherwise react complains: * "`value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components." * * And we want this to be a controlled input when value is empty */ value: number | ''; /** * Array of allowed values. Make sure `value` is in this list. * THIS NEEDS TO BE SORTED */ allowedValues?: number[]; className?: string; disabled?: boolean; /** * Render prop for slider's label * Is passed the value as an argument */ formatLabel?: (value: number | '') => React.ReactNode; forwardRef?: React.Ref; /** * HTML id of the range input */ id?: string; /** * max allowed value, not needed if using `allowedValues` */ max?: number; /** * min allowed value, not needed if using `allowedValues` */ min?: number; /** * This is called when *any* MouseUp or KeyUp event happens. * Used for "smart" Fields to trigger a "blur" event. `onChange` can * be triggered quite frequently */ onBlur?: ( event: React.MouseEvent | React.KeyboardEvent ) => void; onChange?: ( value: SliderProps['value'], event: React.ChangeEvent ) => void; /** * Placeholder for custom input */ placeholder?: string; /** * Show input control for custom values */ showCustomInput?: boolean; /** * Show label with current value */ showLabel?: boolean; step?: number; }; function RangeSlider({ id, value, allowedValues, showCustomInput, name, disabled, placeholder, formatLabel, className, onBlur, onChange, forwardRef, showLabel = true, ...props }: SliderProps) { const [sliderValue, setSliderValue] = useState( allowedValues ? allowedValues.indexOf(Number(value || 0)) : value ); useEffect(() => { updateSliderValue(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); function updateSliderValue() { if (!defined(value)) { return; } const newSliderValueIndex = allowedValues?.indexOf(Number(value || 0)) ?? -1; // If `allowedValues` is defined, then `sliderValue` represents index to `allowedValues` if (newSliderValueIndex > -1) { setSliderValue(newSliderValueIndex); return; } setSliderValue(value); } function getActualValue(newSliderValue: SliderProps['value']): SliderProps['value'] { if (!allowedValues) { return newSliderValue; } // If `allowedValues` is defined, then `sliderValue` represents index to `allowedValues` return allowedValues[newSliderValue]; } function handleInput(e: React.ChangeEvent) { const newSliderValue = parseFloat(e.target.value); setSliderValue(newSliderValue); onChange?.(getActualValue(newSliderValue), e); } function handleCustomInputChange(e: React.ChangeEvent) { setSliderValue(parseFloat(e.target.value) || 0); } function handleBlur( e: React.MouseEvent | React.KeyboardEvent ) { if (typeof onBlur !== 'function') { return; } onBlur(e); } function getSliderData() { if (!allowedValues) { const {min, max, step} = props; return { min, max, step, actualValue: sliderValue, displayValue: sliderValue, }; } const actualValue = allowedValues[sliderValue]; return { step: 1, min: 0, max: allowedValues.length - 1, actualValue, displayValue: defined(actualValue) ? actualValue : t('Invalid value'), }; } const {min, max, step, actualValue, displayValue} = getSliderData(); const labelText = formatLabel?.(actualValue) ?? displayValue; return (
{!showCustomInput && showLabel && {labelText}} {showCustomInput && ( )}
); } const RangeSliderContainer = reactForwardRef(function RangeSliderContainer( props: SliderProps, ref: React.Ref ) { return ; }); export default RangeSliderContainer; export type {SliderProps};