UNPKG

@intility/bifrost-react

Version:

React library for Intility's design system, Bifrost.

99 lines (98 loc) 4.24 kB
"use client"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { forwardRef, useCallback, useEffect, useRef, useState } from "react"; import classNames from "classnames"; import { faAngleLeft } from "@fortawesome/free-solid-svg-icons/faAngleLeft"; import { faAngleRight } from "@fortawesome/free-solid-svg-icons/faAngleRight"; import Icon from "../Icon/Icon.js"; import useResizeObserver from "../../hooks/useResizeObserver.js"; const WidthOverflow = /*#__PURE__*/ forwardRef(({ children, className, contentProps, ...props }, ref)=>{ const contentRef = useRef(null); const [arrowLeft, setArrowLeft] = useState(false); const [arrowRight, setArrowRight] = useState(false); // todo: consider debounce/throttle. could potentially be a bad idea since // native ResizeObserver has some anti-infinite-loop-prevention built in, // and throttling might thwart that and create a slow infinite loop? const updateArrowState = (element)=>{ if (!element) return; // distance you can scroll to the right const rightOverflow = Math.abs(element.offsetWidth - element.scrollWidth); setArrowLeft(element.scrollLeft > 0); // for right hand side, allow an extra pixel overflow before showing arrow // to account for sub-pixel rendering on certain browsers (like edge on android) setArrowRight(element.scrollLeft - rightOverflow < -1); }; // update arrows based on scroll position useEffect(()=>{ if (!contentRef.current) return; // Initial arrow state updateArrowState(contentRef.current); const handleArrow = ()=>{ updateArrowState(contentRef.current); }; contentRef.current.addEventListener("scroll", handleArrow); return ()=>{ contentRef.current?.removeEventListener("scroll", handleArrow); }; }, [ contentRef ]); // update arrows based on container size useResizeObserver(contentRef, (entry)=>{ updateArrowState(entry.target); }); const handleScroll = useCallback((direction)=>{ if (!contentRef.current) return; // Scroll 1/3 of scrollable area const scrollDistance = contentRef.current.clientWidth / 3; const newScrollPos = direction === "left" ? contentRef.current.scrollLeft - scrollDistance : contentRef.current.scrollLeft + scrollDistance; contentRef.current.scrollTo({ left: newScrollPos, behavior: "smooth" }); }, [ contentRef ]); return /*#__PURE__*/ _jsxs("div", { ...props, className: classNames(className, "bf-width-overflow", { "bf-width-overflow-left": arrowLeft, "bf-width-overflow-right": arrowRight }), ref: ref, children: [ /*#__PURE__*/ _jsx("div", { ...contentProps, className: classNames(contentProps?.className, "bf-width-overflow-content"), ref: contentRef, children: children }), arrowLeft && /*#__PURE__*/ _jsx("button", { type: "button", className: "bf-width-overflow-arrow bf-width-overflow-arrow-left", tabIndex: -1, onClick: ()=>handleScroll("left"), children: /*#__PURE__*/ _jsx("div", { className: "bf-width-overflow-arrow-hover-circle", children: /*#__PURE__*/ _jsx(Icon, { icon: faAngleLeft }) }) }), arrowRight && /*#__PURE__*/ _jsx("button", { type: "button", className: "bf-width-overflow-arrow bf-width-overflow-arrow-right", tabIndex: -1, onClick: ()=>handleScroll("right"), children: /*#__PURE__*/ _jsx("div", { className: "bf-width-overflow-arrow-hover-circle", children: /*#__PURE__*/ _jsx(Icon, { icon: faAngleRight }) }) }) ] }); }); WidthOverflow.displayName = "WidthOverflow"; export default WidthOverflow;