123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- 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<HTMLDivElement>;
- /**
- * 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<HTMLInputElement> | React.KeyboardEvent<HTMLInputElement>
- ) => void;
- onChange?: (
- value: SliderProps['value'],
- event: React.ChangeEvent<HTMLInputElement>
- ) => 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<HTMLInputElement>) {
- const newSliderValue = parseFloat(e.target.value);
- setSliderValue(newSliderValue);
- onChange?.(getActualValue(newSliderValue), e);
- }
- function handleCustomInputChange(e: React.ChangeEvent<HTMLInputElement>) {
- setSliderValue(parseFloat(e.target.value) || 0);
- }
- function handleBlur(
- e: React.MouseEvent<HTMLInputElement> | React.KeyboardEvent<HTMLInputElement>
- ) {
- 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 (
- <div className={className} ref={forwardRef}>
- {!showCustomInput && showLabel && <SliderLabel>{labelText}</SliderLabel>}
- <SliderAndInputWrapper showCustomInput={showCustomInput}>
- <Slider
- type="range"
- name={name}
- id={id}
- min={min}
- max={max}
- step={step}
- disabled={disabled}
- onChange={handleInput}
- onInput={handleInput}
- onMouseUp={handleBlur}
- onKeyUp={handleBlur}
- value={sliderValue}
- hasLabel={!showCustomInput}
- aria-valuetext={labelText}
- />
- {showCustomInput && (
- <Input
- placeholder={placeholder}
- value={sliderValue}
- onChange={handleCustomInputChange}
- onBlur={handleInput}
- />
- )}
- </SliderAndInputWrapper>
- </div>
- );
- }
- const RangeSliderContainer = reactForwardRef(function RangeSliderContainer(
- props: SliderProps,
- ref: React.Ref<any>
- ) {
- return <RangeSlider {...props} forwardRef={ref} />;
- });
- export default RangeSliderContainer;
- export type {SliderProps};
|