elements_Range_index.js

import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { spacerClasses, uniqueId } from '@gravityforms/utils';
import Label from '../Label';
import HelpText from '../HelpText';

const { useState, forwardRef } = React;

/**
 * @module Range
 * @description A multi type input component that supports standard text, email, tel.
 *
 * @since 1.1.15
 *
 * @param {object}                     props                    Component props.
 * @param {object}                     props.customAttributes   Custom attributes for the component.
 * @param {string|Array|object}        props.customClasses      Custom classes for the component.
 * @param {boolean}                    props.disabled           If input is disabled.
 * @param {object}                     props.helpTextAttributes Custom attribute for the help text.
 * @param {string}                     props.helpTextPosition   The position of the help text.
 * @param {string}                     props.id                 Optional id. Auto generated if not passed.
 * @param {object}                     props.labelAttributes    Any custom attributes for the label.
 * @param {number}                     props.max                Maximum value for the range.
 * @param {number}                     props.min                Minimum value for the range.
 * @param {string}                     props.name               The name attribute for the input.
 * @param {Function}                   props.onBlur             On blur function handler.
 * @param {Function}                   props.onChange           On change function handler.
 * @param {Function}                   props.onFocus            On focus function handler.
 * @param {boolean}                    props.showValueInput     Whether to display the value input or not.
 * @param {string|number|Array|object} props.spacing            The spacing for the component, as a string, number, array, or object.
 * @param {number}                     props.step               The step size of the range.
 * @param {string}                     props.theme              The theme of the range.
 * @param {number}                     props.value              The input value.
 * @param {string}                     props.valueInputPosition The position of the value input, one of `before` or `after`.
 * @param {string}                     props.valueInputSuffix   Suffix for the value input.
 * @param {object}                     props.wrapperAttributes  Custom attributes for the wrapper element.
 * @param {string|Array|object}        props.wrapperClasses     Custom classes for the wrapper element.
 * @param {object}                     props.wrapperTagName     Tag to use for the input wrapper. Defaults to `div`.
 * @param {object|null}                ref                      Ref to the component.
 *
 * @return {JSX.Element} The range component.
 *
 * @example
 * import Range from '@gravityforms/components/react/admin/elements/Range';
 *
 * return (
 *     <Range
 *         max={ 100 }
 *         min={ 0 }
 *         name="range-name"
 *         onChange={ () => {} }
 *         step={ 5 }
 *         value={ 50 }
 *     />
 * );
 *
 */
const Range = forwardRef( ( {
	customAttributes = {},
	customClasses = [],
	disabled = false,
	helpTextAttributes = {
		size: 'text-xs',
		weight: 'regular',
	},
	helpTextPosition = 'below',
	id = '',
	labelAttributes = {
		size: 'text-sm',
		weight: 'medium',
	},
	max = 0,
	min = 0,
	name = '',
	onBlur = () => {},
	onChange = () => {},
	onFocus = () => {},
	showValueInput = false,
	spacing = '',
	step = 1,
	theme = 'cosmos',
	value = 0,
	valueInputPosition = 'before',
	valueInputSuffix = '',
	wrapperAttributes = {},
	wrapperClasses = [],
	wrapperTagName = 'div',
}, ref ) => {
	const [ inputValue, setInputValue ] = useState( value );
	const inputId = id || uniqueId( 'input' );
	const helpTextId = `${ inputId }-help-text`;

	const wrapperProps = {
		...wrapperAttributes,
		className: classnames( {
			'gform-input-wrapper': true,
			[ `gform-input-wrapper--theme-${ theme }` ]: true,
			'gform-input-wrapper--input': true,
			'gform-input-wrapper--disabled': disabled,
			...spacerClasses( spacing ),
		}, wrapperClasses ),
		ref,
	};

	const inputProps = {
		...customAttributes,
		className: classnames( {
			'gform-input': true,
			'gform-input--range': true,
			'gform-input--theme-cosmos': true,
		}, customClasses ),
		id: inputId,
		max,
		min,
		step,
		name,
		onBlur,
		onChange: ( e ) => {
			const { value: newInputValue } = e.target;
			setInputValue( newInputValue );
			onChange( newInputValue, e );
		},
		onFocus,
		type: 'range',
		value: inputValue,
	};

	if ( disabled ) {
		inputProps.disabled = true;
	}

	const labelProps = {
		...labelAttributes,
		htmlFor: inputId,
	};

	const helpTextProps = {
		...helpTextAttributes,
		id: helpTextId,
	};

	const inputWrapperProps = {
		className: classnames( {
			'gform-input-range-wrapper': true,
		} ),
	};

	const Container = wrapperTagName;

	/**
	 * @function getValueInput
	 * @description Get the value input for the range component.
	 *
	 * @since 1.1.15
	 *
	 * @return {JSX.Element} The value input react markup.
	 */
	const getValueInput = () => {
		const valueInputProps = {
			className: classnames( {
				'gform-input--range-value-input': true,
			} ),
			disabled,
			max,
			min,
			onChange: ( e ) => {
				const { value: newInputValue } = e.target;
				setInputValue( newInputValue );
			},
			type: 'number',
			value: inputValue,
		};

		const containerProps = {
			className: classnames( {
				'gform-input-range-value-wrapper': true,
			} ),
		};

		return (
			<div { ...containerProps }>
				<input { ...valueInputProps } />
				{ valueInputSuffix && <div className="gform-input--range__suffix">{ valueInputSuffix }</div> }
			</div>
		);
	};

	return (
		<Container { ...wrapperProps }>
			<Label { ...labelProps } />
			{ helpTextPosition === 'above' && <HelpText { ...helpTextProps } /> }
			<div { ...inputWrapperProps } >
				{ showValueInput && valueInputPosition === 'before' && getValueInput() }
				<input { ...inputProps } />
				{ showValueInput && valueInputPosition === 'after' && getValueInput() }
			</div>
			{ helpTextPosition === 'below' && <HelpText { ...helpTextProps } /> }
		</Container>
	);
} );

Range.propTypes = {
	customAttributes: PropTypes.object,
	customClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	disabled: PropTypes.bool,
	helpTextAttributes: PropTypes.object,
	helpTextPosition: PropTypes.string,
	id: PropTypes.string,
	labelAttributes: PropTypes.object,
	max: PropTypes.number,
	min: PropTypes.number,
	name: PropTypes.string,
	onBlur: PropTypes.func,
	onChange: PropTypes.func,
	onFocus: PropTypes.func,
	showValueInput: PropTypes.bool,
	spacing: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.number,
		PropTypes.array,
		PropTypes.object,
	] ),
	step: PropTypes.number,
	theme: PropTypes.string,
	value: PropTypes.number,
	valueInputPosition: PropTypes.oneOf( [ 'before', 'after' ] ),
	valueInputSuffix: PropTypes.string,
	wrapperAttributes: PropTypes.object,
	wrapperClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	wrapperTagName: PropTypes.string,
};

Range.displayName = 'Range';

export default Range;