UNPKG

@kiwicom/orbit-components

Version:

Orbit-components is a React component library which provides developers with the easiest possible way of building Kiwi.com's products.

177 lines (176 loc) 5.63 kB
"use client"; import * as React from "react"; import cx from "clsx"; import Stack from "../Stack"; import mergeRefs from "../utils/mergeRefs"; import useTheme from "../hooks/useTheme"; import useScrollBox from "./useScroll"; import ChevronBackward from "../icons/ChevronBackward"; import ChevronForward from "../icons/ChevronForward"; const getSnap = scrollSnap => { if (scrollSnap === "mandatory") return "x mandatory"; if (scrollSnap === "proximity") return "x proximity"; return scrollSnap; }; const ArrowButton = ({ children, className, isHidden, onClick, ariaLabel }) => { return /*#__PURE__*/React.createElement("button", { className: cx("z-default absolute flex h-full items-center", isHidden && "invisible", className), onClick: onClick, type: "button", "aria-label": ariaLabel }, children); }; const ElevationHaze = ({ className, style }) => { return /*#__PURE__*/React.createElement("div", { className: cx("z-default absolute top-0 h-full", className), style: style }); }; const HorizontalScroll = ({ children, spacing = "300", arrows, arrowColor, arrowLeftAriaLabel, arrowRightAriaLabel, scrollSnap = "none", onOverflow, elevationColor = "paletteCloudDark", overflowElevation, scrollPadding, dataTest, id, minHeight, ref }) => { const scrollWrapperRef = React.useRef(null); const [isOverflowing, setOverflowing] = React.useState(false); const [reachedStart, setReachedStart] = React.useState(true); const [reachedEnd, setReachedEnd] = React.useState(false); const containerRef = React.useRef(null); const { isDragging } = useScrollBox(scrollWrapperRef); const theme = useTheme(); const scrollEl = scrollWrapperRef.current; const handleOverflow = React.useCallback(() => { if (scrollWrapperRef.current?.scrollWidth && containerRef.current?.offsetWidth) { const { scrollWidth: containerScrollWidth } = scrollWrapperRef.current; const { offsetWidth } = containerRef.current; if (containerScrollWidth > offsetWidth) { setOverflowing(true); if (onOverflow) onOverflow(); } else { setOverflowing(false); } } }, [onOverflow]); const handleClick = direction => { if (scrollEl) { const { scrollLeft, offsetWidth } = scrollEl; const scrollAmount = scrollLeft + (direction === "left" ? -offsetWidth / 2 : offsetWidth / 2); scrollEl.scrollTo({ left: scrollAmount, behavior: "smooth" }); } }; const handleScroll = React.useCallback(() => { if (scrollEl) { const scrollWidth = scrollEl.scrollWidth - scrollEl.clientWidth; const { scrollLeft } = scrollEl; if (scrollLeft === 0) { setReachedStart(true); } else { setReachedStart(false); } if (scrollLeft >= scrollWidth) { setReachedEnd(true); } else { setReachedEnd(false); } } }, [scrollEl]); const handleResize = React.useCallback(() => { handleOverflow(); handleScroll(); }, [handleOverflow, handleScroll]); React.useEffect(() => { handleOverflow(); window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, [handleOverflow, handleResize, scrollWrapperRef.current?.scrollWidth]); return /*#__PURE__*/React.createElement("div", { className: cx("orbit-horizontal-scroll relative inline-flex w-full items-center overflow-hidden", isOverflowing && (isDragging ? "cursor-grabbing" : "cursor-grab")), "data-test": dataTest, "data-overflowing": isOverflowing || undefined, id: id, ref: mergeRefs([ref, containerRef]), style: { minHeight } }, overflowElevation && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ElevationHaze, { className: cx("left-0", (!isOverflowing || reachedStart) && "invisible"), style: { boxShadow: `5px 0px 20px 20px ${theme.orbit[elevationColor]}` } }), /*#__PURE__*/React.createElement(ElevationHaze, { className: cx("right-0", (!isOverflowing || reachedEnd) && "invisible"), style: { boxShadow: `-5px 0px 20px 20px ${theme.orbit[elevationColor]}` } })), arrows && /*#__PURE__*/React.createElement(ArrowButton, { className: "left-100", isHidden: reachedStart || !isOverflowing, onClick: () => handleClick("left"), ariaLabel: arrowLeftAriaLabel }, /*#__PURE__*/React.createElement(ChevronBackward, { ariaHidden: true, customColor: arrowColor })), /*#__PURE__*/React.createElement("div", { className: "scrollbar-none size-full overflow-x-auto overflow-y-hidden", ref: scrollWrapperRef, onScroll: handleScroll, style: { scrollPadding, scrollSnapType: isDragging ? "none" : getSnap(scrollSnap) } }, /*#__PURE__*/React.createElement("div", { className: cx("relative inline-flex size-full", isDragging && "pointer-events-none") // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex , tabIndex: isOverflowing ? 0 : undefined }, /*#__PURE__*/React.createElement(Stack, { inline: true, spacing: spacing }, children))), arrows && /*#__PURE__*/React.createElement(ArrowButton, { className: "right-100", isHidden: reachedEnd || !isOverflowing, onClick: () => handleClick("right"), ariaLabel: arrowRightAriaLabel }, /*#__PURE__*/React.createElement(ChevronForward, { ariaHidden: true, customColor: arrowColor }))); }; export default HorizontalScroll;