@findify/react-components
Version:
Findify react UI components
123 lines (118 loc) • 5.77 kB
JSX
/**
* @module components/RangeFacet
*/
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import cx from 'classnames';
import MapArray from 'components/common/MapArray';
import Button from 'components/Button';
import Text from 'components/Text';
import Grid from 'components/common/Grid';
import Checkbox from 'components/common/Checkbox';
import content from 'components/RangeFacet/content';
import useTranslations from 'helpers/useTranslations';
import { useConfig } from '@findify/react-connect';
import styles from 'components/RangeFacet/styles.css';
import Loadable from 'react-loadable';
import chunks from 'helpers/chunks';
const Slider = Loadable({
loader: chunks.components.rangeSlider,
loading: () => null,
});
const PriceInput = ({ theme, currency, min, max, onBlur, resetOn, precision, reference, label }) => {
const handleWrapperClick = useCallback(() => {
if (reference.current)
reference.current?.focus();
}, [reference]);
const handleKeyPress = useCallback((e) => {
if (e.key === 'Enter')
onBlur(e);
}, []);
useEffect(() => {
if (reference.current)
reference.current.value = null;
}, [resetOn]);
const step = useMemo(() => precision
? `0.${Array.from({ length: precision }, (_, i) => i + 1 === precision ? '1' : '0').join('')}`
: '1', []);
return (<div className={theme.inputWrap} onClick={handleWrapperClick}>
<span className={theme.currency}>{currency}</span>
<input type="number" inputMode="numeric" pattern="[0-9]*" className={theme.input} max={max} min={min} onBlur={onBlur} onKeyPress={handleKeyPress} ref={reference} step={step} aria-label={label}/>
<div className={theme.border}/>
</div>);
};
export default ({ theme = styles, config: facetConfig, facet, hidden, isMobile, }) => {
const { config } = useConfig();
const translate = useTranslations();
const minInput = useRef(null);
const maxInput = useRef(null);
const [[from, to], setState] = useState([]);
const [selectedItems, notSelectedItems] = useMemo(() => [
facetConfig.get('pullSelected')
? facet.get('values').filter((i) => i.get('selected'))
: facet.get('values'),
facetConfig.get('pullSelected')
? facet.get('values').filter((i) => !i.get('selected'))
: facet.get('values'),
], [facet]);
/** Invoked when minimum range is changed */
const onChangeMin = useCallback((e) => {
const _value = parseFloat(e.target.value);
if (isNaN(_value))
return;
const value = Number(_value > (to || facet.get('max'))
? to || facet.get('max')
: _value < facet.get('min')
? facet.get('min')
: _value).toFixed(facetConfig.get('precision', 0));
e.target.value = value;
setState((s) => [value, s[1]]);
}, [from, to]);
/** Invoked when maximum range is changed */
const onChangeMax = useCallback((e) => {
const _value = parseFloat(e.target.value);
if (isNaN(_value))
return;
const value = Number(_value < (from || facet.get('min'))
? from || facet.get('min')
: _value > facet.get('max')
? facet.get('max')
: _value).toFixed(facetConfig.get('precision', 0));
e.target.value = value;
setState((s) => [s[0], value]);
}, [from, to]);
const onSubmit = useCallback((e) => {
e.preventDefault();
if (!from && !to)
return;
facet.setValue({ from, to });
setState([]);
if (minInput.current && maxInput.current) {
minInput.current.value = maxInput.current.value = '';
}
}, [from, to]);
const onSliderChange = useCallback(([min, max]) => {
const range = facet.get('max') - facet.get('min');
const minValue = ((range / 100) * min || facet.get('min')).toFixed(facetConfig.get('precision', 0));
const maxValue = ((facet.get('max') / 100) * max).toFixed(facetConfig.get('precision', 0));
if (minInput.current && maxInput.current) {
minInput.current.value = minValue;
maxInput.current.value = maxValue;
}
setState([minValue, maxValue]);
}, []);
return (<div className={theme.root} id={`facet-${facet.get('name')}`} role="region" hidden={hidden}>
<MapArray display-if={facetConfig.get('pullSelected')} array={selectedItems} factory={Checkbox} content={content} config={config} isMobile={isMobile}/>
<MapArray array={notSelectedItems} factory={Checkbox} content={content} config={config} isMobile={isMobile}/>
<Grid wrapperComponent="form" onSubmit={onSubmit} columns="3|fit|3|auto" className={cx(theme.range, theme.inputBlock)}>
<PriceInput theme={theme} currency={config.getIn(['currency', 'symbol'])} max={to} min={facet.get('min')} resetOn={facet} onBlur={onChangeMin} reference={minInput} precision={facetConfig.get('precision', 0)} label="fromprice"/>
<div className={theme.divider}>-</div>
<PriceInput theme={theme} currency={config.getIn(['currency', 'symbol'])} min={from} max={facet.get('max')} resetOn={facet} onBlur={onChangeMax} reference={maxInput} precision={facetConfig.get('precision', 0)} label="toprice"/>
<Button className={theme.submit} type="submit" onClick={onSubmit}>
<Text primary uppercase>
{translate('facets.submit')}
</Text>
</Button>
</Grid>
<Slider display-if={config.get('slider')} className={theme.slider} thumbClassName={theme.thumb} trackClassName={theme.track} defaultValue={[0, 100]} onChange={onSliderChange}/>
</div>);
};