Compare Slider
rivelle/compare-sliderFreeComponent
A before-and-after stage with drag, hover and keyboard interaction.
Install it
npx shadcn@latest add https://rivelle.dev/r/compare-slider.jsonSource
1"use client";23import * as React from "react";4import { ChevronLeft, ChevronRight } from "lucide-react";56import { cn } from "@/lib/utils";78type CompareSliderProps = Omit<React.ComponentProps<"div">, "onChange"> & {9 after: React.ReactNode;10 before: React.ReactNode;11 defaultValue?: number;12 mode?: "drag" | "hover";13 onValueChange?: (value: number) => void;14 value?: number;15};1617function CompareSlider({18 after,19 before,20 className,21 defaultValue = 50,22 mode = "drag",23 onPointerLeave,24 onPointerMove,25 onValueChange,26 value,27 ...props28}: CompareSliderProps) {29 const [internalValue, setInternalValue] = React.useState(defaultValue);30 const [dragging, setDragging] = React.useState(false);31 const currentValue = value ?? internalValue;3233 const update = React.useCallback(34 (nextValue: number) => {35 const clamped = Math.min(100, Math.max(0, nextValue));36 if (value === undefined) setInternalValue(clamped);37 onValueChange?.(clamped);38 },39 [onValueChange, value],40 );4142 const updateFromPointer = (event: React.PointerEvent<HTMLDivElement>) => {43 const bounds = event.currentTarget.getBoundingClientRect();44 update(((event.clientX - bounds.left) / bounds.width) * 100);45 };4647 return (48 <div49 className={cn(50 "relative isolate touch-none select-none overflow-hidden rounded-[2rem] border border-foreground/10 bg-card outline-none",51 className,52 )}53 data-slot="compare-slider"54 onPointerDown={(event) => {55 if (mode !== "drag") return;56 setDragging(true);57 event.currentTarget.setPointerCapture(event.pointerId);58 updateFromPointer(event);59 }}60 onPointerLeave={(event) => {61 if (mode === "hover") update(50);62 onPointerLeave?.(event);63 }}64 onPointerMove={(event) => {65 if (mode === "hover" || dragging) updateFromPointer(event);66 onPointerMove?.(event);67 }}68 onPointerUp={(event) => {69 setDragging(false);70 if (event.currentTarget.hasPointerCapture(event.pointerId)) {71 event.currentTarget.releasePointerCapture(event.pointerId);72 }73 }}74 {...props}75 >76 <div className="absolute inset-0">{after}</div>77 <div78 aria-hidden="true"79 className="absolute inset-0 overflow-hidden"80 style={{ clipPath: `inset(0 ${100 - currentValue}% 0 0)` }}81 >82 {before}83 </div>84 <div85 aria-label="Comparison position"86// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from rivelle
All 83 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | rivelle | Components | Free | 2 deps | |
| Alertalert | rivelle | Components | Free | 1 dep | |
| Alert Dialogalert-dialog | rivelle | Components | Free | 1 dep | |
| Aspect Ratioaspect-ratio | rivelle | Components | Free | 1 dep | |
| Avataravatar | rivelle | Components | Free | 1 dep | |
| Badgebadge | rivelle | Components | Free | 2 deps | |
| Breadcrumbbreadcrumb | rivelle | Components | Free | 2 deps | |
| Buttonbutton | rivelle | Components | Free | 2 deps |
