{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "author": "DesignX ", "name": "range-slider-bubble", "type": "registry:component", "title": "Bubble Slider", "description": "A slider with a value bubble that pops up on drag.", "dependencies": [ "motion@^14.0.0" ], "registryDependencies": [ "@dx/lib-ease", "@dx/lib-touch", "@dx/utils" ], "files": [ { "path": "components/motion/range-slider-bubble.tsx", "type": "registry:component", "target": "components/motion/range-slider-bubble.tsx", "content": "import {\n AnimatePresence,\n motion,\n useMotionTemplate,\n useMotionValue,\n useReducedMotion,\n useSpring,\n useTransform,\n useVelocity,\n} from \"motion/react\";\nimport { useEffect } from \"react\";\n\nimport { SPRING_GLIDE, SPRING_PANEL, SPRING_PRESS } from \"@/lib/ease\";\nimport { type SliderOptions, useSlider } from \"@/lib/hooks/use-slider\";\nimport { TOUCH_GESTURE_CLASS } from \"@/lib/touch\";\nimport { cn } from \"@/lib/utils\";\n\n// Loose enough that the bubble keeps leaning a beat after the pointer stops.\nconst SPRING_TILT = { stiffness: 260, damping: 22, mass: 0.4 } as const;\n/** Drag speed (px/s of track percent) that maxes out lean and squash. */\nconst FULL_TILT = 320;\n\nexport interface BubbleSliderProps extends SliderOptions {\n /** Formats the value shown in the bubble. */\n format?: (value: number) => string;\n className?: string;\n}\n\n/**\n * Slider with a value bubble that pops out of the thumb on grab and reacts to\n * how fast you drag: it leans into the direction of travel and squashes along\n * the way, then settles upright when you let go.\n */\nexport function BubbleSlider({ format, className, ...options }: BubbleSliderProps) {\n const reduce = useReducedMotion();\n // A bare number needs no valueTextit would only repeat aria-valuenow.\n const { percent, current, dragging, trackProps, sliderProps } = useSlider({\n ...options,\n formatValueText: options.formatValueText ?? format,\n });\n // The value is already snapped to the steprounding here would only make\n // the bubble disagree with aria-valuenow on a fractional scale.\n const readout = format ? format(current) : current;\n\n const target = useMotionValue(percent);\n useEffect(() => {\n target.set(percent);\n }, [percent, target]);\n const smooth = useSpring(target, SPRING_GLIDE);\n const pos = reduce ? target : smooth;\n const left = useMotionTemplate`${pos}%`;\n\n // One spring drives the whole reaction: lean is signed, squash reads its\n // magnitude. Two springs off the same velocity would just run twice.\n const velocity = useVelocity(pos);\n const lean = useSpring(\n useTransform(velocity, [-FULL_TILT, 0, FULL_TILT], [1, 0, -1], { clamp: true }),\n SPRING_TILT,\n );\n const tilt = useTransform(lean, (v) => v * 16);\n const squash = useTransform(lean, (v) => 1 + Math.abs(v) * 0.18);\n const stretch = useTransform(lean, (v) => 1 - Math.abs(v) * 0.12);\n\n return (\n \n \n \n\n {/* thumboverhangs the track by half its width at both ends, which the\n wrapper's padding leaves room for */}\n \n\n {/* bubbleanchored to the thumb, leaning with drag velocity */}\n \n \n {dragging ? (\n \n {readout}\n \n \n ) : null}\n \n \n\n {/* 8px of track is not a touch targetpad the hit area out to 48px */}\n \n \n \n );\n}\n" } ], "categories": [ "motion-kit" ], "meta": { "group": "motion", "docs": "https://dxuireact.com/docs/components/range-slider-bubble" } }