Horizontal Scroll Row

Horizontal scroll container with snap scrolling, chevron navigation, and hidden scrollbar.

Signaler un bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/horizontal-scroll-row.json
bash

Storybook

Explorez les variantes, controles et verifications d'accessibilite dans le playground Storybook interactif.

Voir dans Storybook

Code

"use client";

import { memo, type ReactNode } from "react";

import { ChevronLeft, ChevronRight } from "lucide-react";

import { useHorizontalScroll } from "../../lib/use-horizontal-scroll";
import { cn } from "../../lib/utils";
import { Button } from "../button/button";

type HorizontalScrollRowProps = {
  children: ReactNode;
  className?: string;
  description?: string;
  title: string;
};

const HorizontalScrollRow = memo(function HorizontalScrollRow({
  children,
  className,
  description,
  title,
}: HorizontalScrollRowProps) {
  const { canScrollLeft, canScrollRight, containerRef, scroll } =
    useHorizontalScroll();

  const showControls = canScrollLeft || canScrollRight;

  return (
    <section className={cn("space-y-4", className)}>
      <div className="flex items-center justify-between">
        <div>
          <h3 className="text-lg font-semibold">{title}</h3>
          {description ? (
            <p className="text-sm text-muted-foreground">{description}</p>
          ) : null}
        </div>
        {showControls ? (
          <div className="flex gap-1">
            <Button
              aria-label="Scroll left"
              className="size-8"
              disabled={!canScrollLeft}
              onClick={() => {
                scroll("left");
              }}
              size="icon"
              variant="outline"
            >
              <ChevronLeft className="size-4" />
            </Button>
            <Button
              aria-label="Scroll right"
              className="size-8"
              disabled={!canScrollRight}
              onClick={() => {
                scroll("right");
              }}
              size="icon"
              variant="outline"
            >
              <ChevronRight className="size-4" />
            </Button>
          </div>
        ) : null}
      </div>
      <div
        className="flex gap-4 overflow-x-auto snap-x snap-mandatory [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
        ref={containerRef}
      >
        {children}
      </div>
    </section>
  );
});

HorizontalScrollRow.displayName = "HorizontalScrollRow";

export { HorizontalScrollRow };
export type { HorizontalScrollRowProps };
typescript

Dependances

  • @vllnt/ui@^0.2.1
  • lucide-react