// // Copyright 2026 DXOS.org // import { createContext } from '@radix-ui/react-context'; import { Primitive } from '@radix-ui/react-primitive'; import { Slot } from '@radix-ui/react-slot'; import React, { CSSProperties, useMemo } from 'react'; import { type AllowedAxis, type SlottableProps } from '@dxos/ui-types'; import { useThemeContext } from '../../hooks'; import { composableProps, slottable } from '../../util'; import { scrollbar } from './scrollbar'; // // Context // const SCROLLAREA_NAME = 'ScrollArea'; type ScrollAreaContextType = { /** Orientation of scrollbars. */ orientation: AllowedAxis; /** Hide scrollbars when not scrolling. */ autoHide: boolean; /** Show scrollbars. */ scrollbars?: boolean; /** Apply padding to opposite side of scrollbar. */ centered?: boolean; /** Apply padding. */ padding: boolean; /** Use thin scrollbars. */ thin: boolean; /** Enable snap scrolling. */ snap: boolean; }; const [ScrollAreaProvider, useScrollAreaContext] = createContext(SCROLLAREA_NAME); // // Root // const SCROLLAREA_ROOT_NAME = 'ScrollArea.Root'; type ScrollAreaRootProps = Partial; /** * ScrollArea provides native scrollbars with custom styling. */ const ScrollAreaRoot = slottable( ( { children, asChild, orientation = 'vertical', autoHide = true, scrollbars = true, centered = false, padding = false, thin = false, snap = false, ...props }, forwardedRef, ) => { const { tx } = useThemeContext(); const { className, ...rest } = composableProps(props); const Comp = asChild ? Slot : Primitive.div; const options = useMemo( () => ({ orientation, autoHide, scrollbars, centered, padding, thin, snap }), [orientation, autoHide, scrollbars, centered, padding, thin, snap], ); return ( {children} ); }, ); ScrollAreaRoot.displayName = SCROLLAREA_ROOT_NAME; // // Viewport // const SCROLLAREA_VIEWPORT_NAME = 'ScrollArea.Viewport'; type ScrollAreaViewportProps = SlottableProps; const ScrollAreaViewport = slottable(({ children, asChild, ...props }, forwardedRef) => { const { tx } = useThemeContext(); const options = useScrollAreaContext(SCROLLAREA_VIEWPORT_NAME); const density = options.thin ? scrollbar.md : scrollbar.lg; const { className, ...rest } = composableProps(props); const { style, ...restWithoutStyle } = rest as { style?: CSSProperties; [key: string]: any }; const Comp = asChild ? Slot : Primitive.div; return ( {children} ); }); ScrollAreaViewport.displayName = SCROLLAREA_VIEWPORT_NAME; // // ScrollArea // export const ScrollArea = { Root: ScrollAreaRoot, Viewport: ScrollAreaViewport, }; export type { ScrollAreaRootProps, ScrollAreaViewportProps };