wix-style-react
Version:
252 lines (213 loc) • 6.76 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Slide from 'rc-slider';
import { dataHooks } from './constants';
import { generateID } from '../utils/generateId';
import SliderHandle from './SliderHandle';
import Text from '../Text';
import { st, classes } from './Slider.st.css';
const range = ({ min, max, step }) => {
const arr = [];
for (let i = min; i <= max; i += step) {
arr.push(i);
}
return arr;
};
/**
* A slider component with multi-range support
*/
export default class Slider extends Component {
_getMarks() {
const marksLabels = {};
if (this._isCustomMarks()) {
const { marks } = this.props;
Object.entries(marks).forEach(([key, value]) => {
marksLabels[key] = {
label: this._createMarkNode(value, true),
};
});
} else {
const { min, max, step, startPoint } = this.props;
range({ min, max, step }).forEach(entry => {
const shouldRenderMarkLabel =
entry === min || entry === max || entry === startPoint;
marksLabels[entry] = {
label: this._createMarkNode(entry, shouldRenderMarkLabel),
};
});
}
return marksLabels;
}
_isCustomMarks() {
const { marks } = this.props;
return Object.entries(marks).length > 0;
}
_createMarkNode(value, shouldRenderMarkLabel) {
return (
<div className={st(classes.mark, { direction: this.props.direction })}>
<div className={classes.markLine} />
<div className={classes.markValue}>
{shouldRenderMarkLabel && (
<Text
className={classes.markLabel}
dataHook={dataHooks.sliderMarkLabel}
weight="thin"
size="small"
>
{value}
</Text>
)}
</div>
</div>
);
}
_renderHandle = handleProps => {
const { displayTooltip, disabled, marks, dataHook, direction } = this.props;
const { index, value, min, max, offset, ref, ariaLabel, reverse } =
handleProps;
let tooltipValue;
if (this._isCustomMarks()) {
if (marks.hasOwnProperty(value)) {
tooltipValue = marks[value].toString();
}
} else {
tooltipValue = value.toString();
}
return (
<SliderHandle
key={index}
disabled={disabled}
displayTooltip={displayTooltip}
tooltipValue={tooltipValue}
handleProps={{ ref, offset, min, max, value, reverse, ariaLabel }}
index={index}
dataHook={dataHook}
direction={direction}
/>
);
};
_renderSlider = () => {
const {
pushable,
allowCross,
value,
displayMarks,
direction,
startPoint,
dataHook,
id,
ariaLabelForHandle,
rtl,
className,
...rest
} = this.props;
return Array.isArray(value) && value.length > 1 ? (
<Slide.Range
{...rest}
vertical={direction === 'vertical'}
prefixCls="wsr-slider"
handle={this._renderHandle}
marks={displayMarks ? this._getMarks() : {}}
value={value}
pushable={pushable}
allowCros={allowCross}
reverse={rtl}
ariaLabelGroupForHandles={ariaLabelForHandle}
/>
) : (
<Slide
{...rest}
vertical={direction === 'vertical'}
prefixCls="wsr-slider"
startPoint={startPoint}
handle={this._renderHandle}
marks={displayMarks ? this._getMarks() : {}}
value={Array.isArray(value) ? value[0] : value}
reverse={rtl}
ariaLabelForHandle={
Array.isArray(ariaLabelForHandle)
? ariaLabelForHandle[0]
: ariaLabelForHandle
}
/>
);
};
render() {
const { dataHook, id, direction, className } = this.props;
return (
<div
className={st(classes.root, className)}
id={id}
data-hook={dataHook}
data-direction={direction}
>
{this._renderSlider()}
</div>
);
}
}
Slider.displayName = 'Slider';
Slider.propTypes = {
/** Allows sliders handles to cross. */
allowCross: PropTypes.bool,
/** Applies a data-hook HTML attribute that can be used in the tests. */
dataHook: PropTypes.string,
/** Specifies a CSS class name to be appended to the component’s root element. */
className: PropTypes.string,
/** Controls the visibility of the marks. */
displayMarks: PropTypes.bool,
/** Controls visibility of slide handle tooltip */
displayTooltip: PropTypes.bool,
/** Assigns an unique identifier for the root element. */
id: PropTypes.string,
/** Sets the absolute maximum value of sliders range. */
max: PropTypes.number,
/** Sets the absolute minimum value of the sliders range. */
min: PropTypes.number,
/** Specifies slider marks. The key determines the position, and the value determines what will show. The object structure should be either
* ```{ number : number}``` / ```{ number : string }```
* */
marks: PropTypes.object,
/** Defines a callback function which will be called when ontouchend or onmouseup is triggered. */
onAfterChange: PropTypes.func,
/** Defines a callback function which will be called when ontouchstart or onmousedown is triggered. */
onBeforeChange: PropTypes.func,
/** Defines a callback function which is called upon every value change. */
onChange: PropTypes.func.isRequired,
/** Adjust component for RTL. */
rtl: PropTypes.bool,
/** Specifies the interval between range values. */
step: PropTypes.number,
/** Push surrounding handles when moving a handle (relevant for multi value sliders only). Number specifies a minimum distance between handles. */
pushable: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
/** Specifies the selected value. */
value: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.number),
PropTypes.number,
]),
/** Specifies whether interactions are disabled. */
disabled: PropTypes.bool,
/** Specifies the starting value of a track. If undefined, the minimum value of a range is used as a starting point. */
startPoint: PropTypes.number,
/** Sets slider direction in either horizontal way or vertical */
direction: PropTypes.oneOf(['vertical', 'horizontal']),
/** Set the aria-label attributes for slider handles. */
ariaLabelForHandle: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.string,
]),
};
Slider.defaultProps = {
min: 1,
max: 20,
step: 1,
value: 3,
allowCross: true,
id: generateID(),
displayTooltip: true,
displayMarks: true,
marks: {},
rtl: false,
disabled: false,
startPoint: undefined,
};