DragScroll
Mouse drag-to-scroll functionality for horizontal or vertical scrollable containers.
A wrapper that adds mouse drag-to-scroll functionality to overflowing containers. It restores the natural swipe gesture for desktop users interacting with horizontal galleries, carousels, or data tables.
Installation
npm install @zayne-labs/ui-reactPreview
Loading...
Basic Usage
import { DragScroll } from "@zayne-labs/ui-react/ui/drag-scroll";
function Example() {
return (
<DragScroll.Root>
<DragScroll.Prev>←</DragScroll.Prev>
<DragScroll.List className="flex gap-4 overflow-x-auto">
{items.map((item) => (
<DragScroll.Item key={item.id}>{item.content}</DragScroll.Item>
))}
</DragScroll.List>
<DragScroll.Next>→</DragScroll.Next>
</DragScroll.Root>
);
}Component Source
"use client";import { useCompareSelector } from "@zayne-labs/toolkit-react";import type { PolymorphicPropsStrict } from "@zayne-labs/toolkit-react/utils";import { isFunction, type SelectorFn } from "@zayne-labs/toolkit-type-helpers";import { useMemo } from "react";import { Slot } from "@/components/common/slot";import { DragScrollRootContextProvider, DragScrollStoreContextProvider, useDragScrollRootContext, useDragScrollStoreContext, type DragScrollRootContextType,} from "./drag-scroll-context";import type { DragScrollStore, PartInputProps, UseDragScrollProps } from "./types";import { useDragScroll } from "./use-drag-scroll";/* eslint-disable perfectionist/sort-intersection-types -- I need non-standard props to come first */export type DragScrollRootProps = UseDragScrollProps & { asChild?: boolean; children: React.ReactNode;} & PartInputProps["root"];export function DragScrollRoot(props: DragScrollRootProps) { const { asChild, children, ...restOfProps } = props; const { disableInternalStateSubscription, listRef, propGetters, storeApi } = useDragScroll(restOfProps); const rootContextValue = useMemo<DragScrollRootContextType>( () => ({ disableInternalStateSubscription, listRef, propGetters, }) satisfies DragScrollRootContextType, [listRef, disableInternalStateSubscription, propGetters] ); const Component = asChild ? Slot.Root : "div"; return ( <DragScrollStoreContextProvider store={storeApi}> <DragScrollRootContextProvider value={rootContextValue}> <Component {...propGetters.getRootProps(restOfProps)}>{children}</Component> </DragScrollRootContextProvider> </DragScrollStoreContextProvider> );}export type DragScrollContextProps<TSlice> = { children: React.ReactNode | ((context: TSlice) => React.ReactNode); selector?: SelectorFn<DragScrollStore, TSlice>;};export function DragScrollContext<TSlice = DragScrollStore>(props: DragScrollContextProps<TSlice>) { const { children, selector } = props; const dragScrollCtx = useDragScrollStoreContext(useCompareSelector(selector)); const resolvedChildren = isFunction(children) ? children(dragScrollCtx) : children; return resolvedChildren;}export type DragScrollListProps = { asChild?: boolean;} & PartInputProps["list"];export function DragScrollList<TElement extends React.ElementType = "ul">( props: PolymorphicPropsStrict<TElement, DragScrollListProps>) { const { as: Element = "ul", asChild, ...restOfProps } = props; const { propGetters } = useDragScrollRootContext(); const Component = asChild ? Slot.Root : Element; return <Component {...propGetters.getListProps(restOfProps)} />;}export type DragScrollItemProps = { asChild?: boolean;} & PartInputProps["item"];export function DragScrollItem<TElement extends React.ElementType = "li">( props: PolymorphicPropsStrict<TElement, DragScrollItemProps>) { const { as: Element = "li", asChild, ...restOfProps } = props; const { propGetters } = useDragScrollRootContext(); const Component = asChild ? Slot.Root : Element; return <Component {...propGetters.getItemProps(restOfProps)} />;}export type DragScrollPrevProps = { asChild?: boolean;} & PartInputProps["prevButton"];export function DragScrollPrev(props: DragScrollPrevProps) { const { asChild, ...restOfProps } = props; const { propGetters } = useDragScrollRootContext(); const Component = asChild ? Slot.Root : "button"; return <Component {...propGetters.getPrevButtonProps(restOfProps)} />;}export type DragScrollNextProps = { asChild?: boolean;} & PartInputProps["nextButton"];export function DragScrollNext(props: DragScrollNextProps) { const { asChild, ...restOfProps } = props; const { propGetters } = useDragScrollRootContext(); const Component = asChild ? Slot.Root : "button"; return <Component {...propGetters.getNextButtonProps(restOfProps)} />;}/* eslint-enable perfectionist/sort-intersection-types -- I need non-standard props to come first */Component API
Hook Usage
For advanced use cases, use the useDragScroll hook directly:
import { useDragScroll } from "@zayne-labs/ui-react/ui/drag-scroll";
function CustomScroller() {
const { propGetters } = useDragScroll();
return <div {...propGetters.getListProps()}>{/* items */}</div>;
}Data Attributes
| Component | Attribute | Value | Description |
|---|---|---|---|
List | data-dragging | "true" | "false" | Active when container is being dragged. |
Prev | data-disabled | "true" | "false" | Active when no more content to scroll back. |
Next | data-disabled | "true" | "false" | Active when no more content to scroll forward. |
| All | data-scope | "drag-scroll" | Shared scope identifier. |
| All | data-slot | drag-scroll-* | Combined identifier (e.g. drag-scroll-item). |
Last updated on