UNPKG

chayns-components

Version:

A set of beautiful React components for developing chayns® applications.

230 lines (225 loc) 6.97 kB
/** * @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