UNPKG

@chayns-components/core

Version:

A set of beautiful React components for developing your own applications with chayns.

311 lines (310 loc) • 10.6 kB
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