chayns-components
Version:
A set of beautiful React components for developing chayns® applications.
230 lines (225 loc) • 6.97 kB
JavaScript
/**
* @component
*/
import classNames from 'clsx';
import PropTypes from 'prop-types';
import React, { useEffect, useRef, useState } from 'react';
import "./SliderButton.css";
const defaultItems = [{
id: 0,
text: 'Auf'
}, {
id: 1,
text: 'Stopp'
}, {
id: 2,
text: 'Zu'
}];
/**
* A linear set of buttons which are mutually exclusive.
*/
const SliderButton = _ref => {
let {
className,
style,
items = defaultItems,
onChange,
onDragStop,
onDragStart,
selectedItemId = 0,
disabled = false
} = _ref;
const [markerPosX, setMarkerPosX] = useState(0);
const [dragStartPosX, setDragStartPosX] = useState(null);
const [dragStartMarkerPosX, setDragStartMarkerPosX] = useState(null);
const [lastSelectedIndex, setLastSelectedIndex] = useState(selectedItemId);
const sliderButtonRef = useRef();
const sliderButton = sliderButtonRef && sliderButtonRef.current;
const firstItemRef = useRef();
let firstItem = firstItemRef && firstItemRef.current;
const markerRef = useRef();
let marker = markerRef && markerRef.current;
const handleChange = newIndex => {
if (newIndex !== lastSelectedIndex) {
setLastSelectedIndex(newIndex);
if (onChange) {
onChange(items[newIndex]);
}
}
};
const getHoveredItemIndex = function (markerPositionX) {
if (markerPositionX === void 0) {
markerPositionX = markerPosX;
}
if (firstItem && firstItem.clientWidth > 0) {
const markerHalfPosX = markerPositionX + firstItem.clientWidth / 2;
return Math.floor(markerHalfPosX / firstItem.clientWidth);
}
return 0;
};
const setMarkerIndex = index => {
if (firstItem && index > -1 && index < items.length) {
const newMarkerPosX = index * firstItem.clientWidth;
// Element.animate() does not work on iOS, so we need transition
setLastSelectedIndex(index);
markerRef.current.style.left = `${newMarkerPosX}px`;
setMarkerPosX(newMarkerPosX);
if (!marker.style.transition) {
requestAnimationFrame(() => {
marker.style.transition = 'left 0.2s cubic-bezier(0.42, 0, 0.29, 1.36)';
});
}
}
};
const startDrag = posX => {
if (!disabled) {
chayns.disallowRefreshScroll();
// Element.animate() does not work on iOS, so we need transition
// Transition have to be removed if the user drags the marker
marker.style.transition = '';
setDragStartPosX(posX);
setDragStartMarkerPosX(markerPosX);
if (onDragStart) {
onDragStart();
}
}
};
const stopDrag = () => {
if (dragStartPosX) {
chayns.allowRefreshScroll();
setDragStartPosX(null);
setDragStartMarkerPosX(null);
const hoveredItemIndex = getHoveredItemIndex();
setMarkerIndex(hoveredItemIndex);
if (onDragStop) {
onDragStop(items[hoveredItemIndex]);
}
}
};
const handleMovement = posX => {
if (dragStartPosX) {
const maxMarkerPosX = sliderButton && firstItem ? sliderButton.clientWidth - firstItem.clientWidth : 0;
let newMarkerPosX = dragStartMarkerPosX + posX - dragStartPosX;
if (newMarkerPosX < 0) {
newMarkerPosX = 0;
} else if (newMarkerPosX > maxMarkerPosX) {
newMarkerPosX = maxMarkerPosX;
}
const newSelectedIndex = getHoveredItemIndex(newMarkerPosX);
handleChange(newSelectedIndex);
markerRef.current.style.left = `${newMarkerPosX}px`;
setMarkerPosX(newMarkerPosX);
}
};
useEffect(() => {
// eslint-disable-next-line react-hooks/exhaustive-deps
firstItem = firstItemRef.current;
// eslint-disable-next-line react-hooks/exhaustive-deps
marker = markerRef.current;
if (selectedItemId) {
const i = items.findIndex(item => item.id === selectedItemId);
setMarkerIndex(i);
}
}, []);
useEffect(() => {
if (!dragStartPosX) {
const i = items.findIndex(item => item.id === selectedItemId);
setMarkerIndex(i);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedItemId]);
useEffect(() => {
const listener = [{
type: 'mousemove',
cb: ev => handleMovement(ev.clientX)
}, {
type: 'touchmove',
cb: ev => handleMovement(ev.touches[0].clientX)
}, {
type: 'mouseup',
cb: () => stopDrag()
}, {
type: 'touchend',
cb: () => stopDrag()
}];
listener.forEach(lst => window.addEventListener(lst.type, lst.cb));
return () => {
listener.forEach(lst => window.removeEventListener(lst.type, lst.cb));
};
});
const hoveredItemIndex = getHoveredItemIndex();
return /*#__PURE__*/React.createElement("div", {
className: classNames('sliderButton', className, disabled && 'sliderButton--disabled'),
style: style,
ref: sliderButtonRef
}, items.map((item, i) => /*#__PURE__*/React.createElement("div", {
key: item.id,
className: "sliderButton__item button button--disabled" // disabled to remove hover animation
,
ref: ref => {
if (i === 0) {
firstItemRef.current = ref;
}
},
onClick: () => {
if (!disabled) {
setMarkerIndex(i);
handleChange(i);
}
}
}, /*#__PURE__*/React.createElement("div", {
className: "sliderButton__item__content"
}, item.text))), /*#__PURE__*/React.createElement("div", {
className: "sliderButton__item__marker button",
onMouseDown: ev => {
if (!chayns.env.isMobile) {
startDrag(ev.clientX);
}
},
onTouchStart: ev => startDrag(ev.touches[0].clientX),
ref: markerRef
}, /*#__PURE__*/React.createElement("div", {
className: "sliderButton__item__content"
}, items[hoveredItemIndex].text)));
};
SliderButton.propTypes = {
/**
* A classname string that will be applied to the container element.
*/
className: PropTypes.string,
/**
* A React style objec that will be applied to the container element.
*/
style: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
/**
* An array of option items.
*/
items: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
text: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
})),
/**
* A callback that is invoked when the selection changes.
*/
onChange: PropTypes.func,
/**
* A callback that is invoked when the user starts dragging the control.
*/
onDragStop: PropTypes.func,
/**
* A callback that is invoked when the user stops dragging.
*/
onDragStart: PropTypes.func,
/**
* The `id` of the item that should be selected.
*/
selectedItemId: PropTypes.number,
/**
* Wether the `SliderButton` should ignore user interaction and be rendered
* in a disabled style.
*/
disabled: PropTypes.bool
};
SliderButton.displayName = 'SliderButton';
export default SliderButton;
//# sourceMappingURL=SliderButton.js.map