chayns-components
Version:
A set of beautiful React components for developing chayns® applications.
237 lines (231 loc) • 8.62 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = void 0;
var _clsx = _interopRequireDefault(require("clsx"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _react = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* @component
*/
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] = (0, _react.useState)(0);
const [dragStartPosX, setDragStartPosX] = (0, _react.useState)(null);
const [dragStartMarkerPosX, setDragStartMarkerPosX] = (0, _react.useState)(null);
const [lastSelectedIndex, setLastSelectedIndex] = (0, _react.useState)(selectedItemId);
const sliderButtonRef = (0, _react.useRef)();
const sliderButton = sliderButtonRef && sliderButtonRef.current;
const firstItemRef = (0, _react.useRef)();
let firstItem = firstItemRef && firstItemRef.current;
const markerRef = (0, _react.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);
}
};
(0, _react.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);
}
}, []);
(0, _react.useEffect)(() => {
if (!dragStartPosX) {
const i = items.findIndex(item => item.id === selectedItemId);
setMarkerIndex(i);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedItemId]);
(0, _react.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.default.createElement("div", {
className: (0, _clsx.default)('sliderButton', className, disabled && 'sliderButton--disabled'),
style: style,
ref: sliderButtonRef
}, items.map((item, i) => /*#__PURE__*/_react.default.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.default.createElement("div", {
className: "sliderButton__item__content"
}, item.text))), /*#__PURE__*/_react.default.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.default.createElement("div", {
className: "sliderButton__item__content"
}, items[hoveredItemIndex].text)));
};
SliderButton.propTypes = {
/**
* A classname string that will be applied to the container element.
*/
className: _propTypes.default.string,
/**
* A React style objec that will be applied to the container element.
*/
style: _propTypes.default.objectOf(_propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])),
/**
* An array of option items.
*/
items: _propTypes.default.arrayOf(_propTypes.default.shape({
id: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
text: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.node])
})),
/**
* A callback that is invoked when the selection changes.
*/
onChange: _propTypes.default.func,
/**
* A callback that is invoked when the user starts dragging the control.
*/
onDragStop: _propTypes.default.func,
/**
* A callback that is invoked when the user stops dragging.
*/
onDragStart: _propTypes.default.func,
/**
* The `id` of the item that should be selected.
*/
selectedItemId: _propTypes.default.number,
/**
* Wether the `SliderButton` should ignore user interaction and be rendered
* in a disabled style.
*/
disabled: _propTypes.default.bool
};
SliderButton.displayName = 'SliderButton';
var _default = SliderButton;
exports.default = _default;
//# sourceMappingURL=SliderButton.js.map