@intility/bifrost-react
Version:
React library for Intility's design system, Bifrost.
99 lines (98 loc) • 4.24 kB
JavaScript
"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;