"use client" import * as React from "react" import { cn } from "../../lib/utils" import { HorizontalScrollControls, HorizontalScrollViewport, useHorizontalScrollAffordances, useHorizontalScrollAlignEnd, } from "./horizontal-scroll-controls" export type { HorizontalScrollAffordances, HorizontalScrollControlsLayout, HorizontalScrollControlsProps, } from "./horizontal-scroll-controls" export { HORIZONTAL_SCROLL_STEP_PX, HorizontalScrollControls, HorizontalScrollViewport, horizontalScrollViewportClassName, useHorizontalScrollAffordances, useHorizontalScrollAlignEnd, useHorizontalScrollItemIntoView, } from "./horizontal-scroll-controls" export interface HorizontalScrollRegionProps { children: React.ReactNode /** Prefix for prev/next `aria-label`s (e.g. "Views"). */ ariaLabel?: string className?: string scrollClassName?: string /** Scroll to the trailing edge when content overflows (breadcrumb trails). */ alignEnd?: boolean /** * `toolbar` — fixed 32px row (tabs, breadcrumbs). * `content` — grows with tall children (KPI scroll strip). */ fit?: "toolbar" | "content" /** * `split` — prev before content, next after. * `group-end` / `group-start` — shared {@link HorizontalScrollControls} after / before content. */ controlsLayout?: "split" | "group-end" | "group-start" /** * Observe the scroll viewport from outside. Lets a caller measure the row and * shed content *before* falling back to scrolling, e.g. tabs collapsing their * inactive labels to icons. */ viewportRef?: React.Ref /** * Set `false` when the caller offers its own way to reach clipped content, so * the arrows stop competing for the width that reduced the row in the first * place. Tabs do this once they have an overflow menu. */ controls?: boolean } /** * Horizontal scroll container with {@link HorizontalScrollControls} when content overflows. * Use for view-tab toolbars, breadcrumb trails, tab lists, and other clipped rows. */ export function HorizontalScrollRegion({ children, ariaLabel = "Scroll", className, scrollClassName, alignEnd = false, fit = "toolbar", controlsLayout = "group-end", viewportRef, controls = true, }: HorizontalScrollRegionProps) { const scrollRef = React.useRef(null) const { canScrollLeft, canScrollRight, overflowing: clipped, scrollPrev, scrollNext, } = useHorizontalScrollAffordances(scrollRef) const overflowing = controls && clipped const attachViewport = React.useCallback( (node: HTMLDivElement | null) => { scrollRef.current = node if (typeof viewportRef === "function") viewportRef(node) else if (viewportRef) { ;(viewportRef as React.MutableRefObject).current = node } }, [viewportRef], ) useHorizontalScrollAlignEnd(scrollRef, alignEnd, [children]) const isGrouped = controlsLayout === "group-end" || controlsLayout === "group-start" const groupLeading = controlsLayout === "group-start" const controlProps = { ariaLabel, canScrollLeft, canScrollRight, onScrollPrev: scrollPrev, onScrollNext: scrollNext, } return (
{isGrouped && groupLeading && overflowing ? ( ) : null} {!isGrouped && overflowing ? ( ) : null} {children} {isGrouped && !groupLeading && overflowing ? ( ) : null} {!isGrouped && overflowing ? ( ) : null}
) }