@chayns-components/core
Version:
A set of beautiful React components for developing your own applications with chayns.
311 lines (310 loc) • 10.6 kB
JavaScript
import { setRefreshScrollEnabled } from 'chayns-api';
import { AnimatePresence, useAnimate } from 'motion/react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useElementSize } from '../../hooks/element';
import { calculateBiggestWidth } from '../../utils/calculate';
import { getNearestPoint, getThumbPosition } from '../../utils/sliderButton';
import Icon from '../icon/Icon';
import Popup from '../popup/Popup';
import { StyledMotionSliderButtonThumb, StyledSliderButton, StyledSliderButtonButtonsWrapper, StyledSliderButtonItem, StyledSliderButtonPopupContent, StyledSliderButtonPopupContentItem, StyledSliderButtonWrapper } from './SliderButton.styles';
const SliderButton = _ref => {
let {
selectedButtonId,
isDisabled,
items,
onChange
} = _ref;
const [dragRange, setDragRange] = useState({
left: 0,
right: 0
});
const [shownItemsCount, setShownItemsCount] = useState(items.length);
const [sliderSize, setSliderSize] = useState({
width: 0
});
const [currentId, setCurrentId] = useState('');
const [currentPopupId, setCurrentPopupId] = useState('');
const [currentIndex, setCurrentIndex] = useState(0);
const sliderButtonRef = useRef(null);
const sliderButtonWrapperRef = useRef(null);
const popupRef = useRef(null);
const [scope, animate] = useAnimate();
const initialItemWidth = useMemo(() => calculateBiggestWidth(items), [items]);
const elementSize = useElementSize(sliderButtonRef);
useEffect(() => {
if (elementSize) setSliderSize(elementSize);
}, [elementSize]);
const setPopupId = useCallback(selectedId => {
const ids = items.slice(shownItemsCount - 1).map(_ref2 => {
let {
id
} = _ref2;
return id;
});
const newId = ids.find(id => id === selectedId);
if (newId) {
setCurrentId('more');
setCurrentPopupId(newId);
return;
}
setCurrentId(selectedId);
}, [items, shownItemsCount]);
const isSliderBigger = useMemo(() => sliderSize && Math.floor(sliderSize.width / initialItemWidth) < items.length - 1, [initialItemWidth, items.length, sliderSize]);
const maxShownItemsCount = useMemo(() => {
let totalWidth = 0;
let count = 0;
while (count < items.length) {
const visibleItems = items.slice(0, count + 1);
const currentMaxWidth = calculateBiggestWidth(visibleItems) + 8;
if (totalWidth + currentMaxWidth > sliderSize.width) break;
totalWidth += currentMaxWidth;
count++;
}
return count;
}, [items, sliderSize.width]);
const itemWidth = useMemo(() => {
const sliderWidth = sliderSize?.width || 0;
const itemCount = items.length || 1;
setShownItemsCount(isSliderBigger ? maxShownItemsCount : itemCount);
return sliderWidth / (isSliderBigger ? maxShownItemsCount : itemCount);
}, [isSliderBigger, items.length, maxShownItemsCount, sliderSize?.width]);
useEffect(() => {
if (sliderSize) {
const sliderWidth = itemWidth * (items.length - 1);
const count = Math.floor(sliderSize.width / itemWidth);
setDragRange({
left: 0,
right: isSliderBigger ? itemWidth * count : sliderWidth
});
}
}, [isSliderBigger, itemWidth, items.length, sliderSize]);
const animation = useCallback(async x => {
await animate(scope.current, {
x
}, {
type: 'tween',
duration: 0.2
});
}, [animate, scope]);
const setItemPosition = useCallback(index => {
setCurrentIndex(index);
void animation(itemWidth * index);
}, [animation, itemWidth]);
useEffect(() => {
if (typeof selectedButtonId === 'string') {
let index = items.findIndex(_ref3 => {
let {
id
} = _ref3;
return id === selectedButtonId;
});
setCurrentId(selectedButtonId);
setPopupId(selectedButtonId);
if (items.length > shownItemsCount && index > shownItemsCount - 1) {
index = shownItemsCount - 1;
}
if (index >= 0) {
setItemPosition(index);
}
}
}, [animation, dragRange.right, isSliderBigger, itemWidth, items, selectedButtonId, setItemPosition, setPopupId, shownItemsCount]);
const handleClick = useCallback((id, index) => {
if (isDisabled) {
return;
}
setPopupId(id);
if (typeof onChange === 'function' && id !== 'more') {
onChange(id);
}
if (popupRef.current) {
if (id === 'more') {
popupRef.current.show();
} else {
popupRef.current.hide();
}
}
setItemPosition(index);
}, [isDisabled, onChange, setItemPosition, setPopupId]);
const buttons = useMemo(() => {
if (items.length > shownItemsCount) {
const newItems = items.slice(0, shownItemsCount - 1);
const otherItems = items.slice(shownItemsCount - 1);
const elements = newItems.map((_ref4, index) => {
let {
id,
text
} = _ref4;
return /*#__PURE__*/React.createElement(StyledSliderButtonItem, {
$width: itemWidth,
key: `slider-button-${id}`,
onClick: () => handleClick(id, index)
}, text);
});
const popupContent = otherItems.map(_ref5 => {
let {
id,
text
} = _ref5;
return /*#__PURE__*/React.createElement(StyledSliderButtonPopupContentItem, {
key: `slider-button-${id}`,
onClick: () => handleClick(id, newItems.length),
$isSelected: id === currentPopupId
}, text);
});
const id = 'more';
elements.push(/*#__PURE__*/React.createElement(StyledSliderButtonItem, {
$width: itemWidth,
key: `slider-button-${id}`
}, /*#__PURE__*/React.createElement(Popup, {
ref: popupRef,
content: /*#__PURE__*/React.createElement(StyledSliderButtonPopupContent, null, popupContent)
}, /*#__PURE__*/React.createElement(Icon, {
icons: ['fa fa-ellipsis'],
color: "white"
}))));
return elements;
}
return items.map(_ref6 => {
let {
id,
text
} = _ref6;
return /*#__PURE__*/React.createElement(StyledSliderButtonItem, {
$width: itemWidth,
key: `slider-button-${id}`
}, text);
});
}, [currentPopupId, handleClick, itemWidth, items, shownItemsCount]);
const pseudoButtons = useMemo(() => {
if (items.length > shownItemsCount) {
const newItems = items.slice(0, shownItemsCount - 1);
const elements = newItems.map((_ref7, index) => {
let {
id,
text
} = _ref7;
return /*#__PURE__*/React.createElement(StyledSliderButtonItem, {
$width: itemWidth,
key: `pseudo-slider-button-${id}`,
onClick: () => handleClick(id, index)
}, text);
});
const id = 'more';
elements.push(/*#__PURE__*/React.createElement(StyledSliderButtonItem, {
$width: itemWidth,
key: `pseudo-slider-button-${id}`,
onClick: () => handleClick(id, newItems.length)
}, /*#__PURE__*/React.createElement(Icon, {
icons: ['fa fa-ellipsis']
})));
return elements;
}
return items.map((_ref8, index) => {
let {
id,
text
} = _ref8;
return /*#__PURE__*/React.createElement(StyledSliderButtonItem, {
$width: itemWidth,
key: `pseudo-slider-button-${id}`,
onClick: () => handleClick(id, index)
}, text);
});
}, [handleClick, itemWidth, items, shownItemsCount]);
/**
* Creates an array with the snap points relative to the width of the items
*/
const snapPoints = useMemo(() => {
const points = [0];
for (let i = 1; i < items.length; i++) {
points.push(itemWidth * i);
}
return points;
}, [itemWidth, items.length]);
const handleDragStart = useCallback(() => {
void setRefreshScrollEnabled(false);
}, []);
const handleDragEnd = useCallback(() => {
void setRefreshScrollEnabled(true);
const position = getThumbPosition({
scope,
itemWidth
});
if (!position) {
return;
}
const {
middle,
left
} = position;
let scrollLeft = 0;
if (sliderButtonWrapperRef.current) {
scrollLeft = sliderButtonWrapperRef.current.scrollLeft;
sliderButtonWrapperRef.current.scrollLeft = getNearestPoint({
snapPoints,
position: middle,
scrollLeft: scrollLeft - left
}).nearestPoint;
}
const {
nearestIndex
} = getNearestPoint({
snapPoints,
position: middle,
scrollLeft
});
const {
nearestPoint
} = getNearestPoint({
snapPoints,
position: middle,
scrollLeft: 0
});
if (nearestPoint >= 0 && nearestIndex >= 0) {
void animation(nearestPoint);
let id;
if (nearestIndex === shownItemsCount - 1) {
id = 'more';
} else {
id = items[nearestIndex]?.id;
}
if (popupRef.current) {
if (id === 'more') {
popupRef.current.show();
} else {
popupRef.current.hide();
}
}
if (typeof onChange === 'function' && id && id !== 'more') {
onChange(id);
}
}
}, [animation, itemWidth, items, onChange, scope, shownItemsCount, snapPoints]);
return useMemo(() => /*#__PURE__*/React.createElement(StyledSliderButton, {
$isDisabled: isDisabled,
ref: sliderButtonRef
}, /*#__PURE__*/React.createElement(StyledSliderButtonButtonsWrapper, {
$isInvisible: true
}, pseudoButtons), /*#__PURE__*/React.createElement(StyledMotionSliderButtonThumb, {
ref: scope,
drag: isDisabled ? false : 'x',
dragElastic: 0,
dragConstraints: isSliderBigger ? {
...dragRange,
right: dragRange.right - itemWidth
} : {
...dragRange
},
$width: itemWidth,
onDragEnd: handleDragEnd,
onDragStart: handleDragStart,
onClick: () => handleClick(currentId, currentIndex)
}), /*#__PURE__*/React.createElement(StyledSliderButtonWrapper, {
$isDisabled: isDisabled,
$width: !isSliderBigger ? dragRange.right + itemWidth : dragRange.right,
ref: sliderButtonWrapperRef
}, /*#__PURE__*/React.createElement(AnimatePresence, null, /*#__PURE__*/React.createElement(StyledSliderButtonButtonsWrapper, null, buttons)))), [buttons, currentId, currentIndex, dragRange, handleClick, handleDragEnd, handleDragStart, isDisabled, isSliderBigger, itemWidth, pseudoButtons, scope]);
};
SliderButton.displayName = 'SliderButton';
export default SliderButton;
//# sourceMappingURL=SliderButton.js.map