Scroll Progress
rare-ui/scroll-progressFreeComponent
A scroll progress pill that tracks reading position and expands into a squircle menu of sections you can jump to.
Install it
npx shadcn@latest add https://rareui.com/r/scroll-progress.jsonSource
1"use client"23import * as React from "react"4import {5 AnimatePresence,6 motion,7 useReducedMotion,8 useScroll,9 useSpring,10} from "motion/react"1112import { cn } from "@/lib/utils"1314export type ScrollProgressSection = { id: string; label: string }1516const EASE_IN_OUT = [0.65, 0, 0.35, 1] as const17const EASE_OUT = [0.22, 1, 0.36, 1] as const18const SIZE_SPRING = { type: "spring", bounce: 0.16, duration: 0.5 } as const19const LABEL_CROSSFADE = { duration: 0.22, ease: EASE_OUT } as const20const LAYER_FADE = { duration: 0.24, ease: EASE_IN_OUT } as const2122const useIsoLayoutEffect =23 typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect2425type Size = { width: number; height: number }2627export type ScrollProgressProps = React.ComponentProps<"div"> & {28 sections?: ScrollProgressSection[]29 containerRef?: React.RefObject<HTMLElement | null>30 offset?: number31}3233const ScrollProgress = ({34 className,35 sections = [],36 containerRef,37 offset = 120,38 ...props39}: ScrollProgressProps) => {40 const layoutId = React.useId()41 const reduceMotion = useReducedMotion()4243 const { scrollYProgress } = useScroll(44 containerRef ? { container: containerRef } : undefined45 )46 const progress = useSpring(scrollYProgress, {47 stiffness: 120,48 damping: 30,49 mass: 0.3,50 })5152 const [activeId, setActiveId] = React.useState(sections[0]?.id)53 const [open, setOpen] = React.useState(false)5455 const scrollLock = React.useRef(false)56 const scrollLockTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)5758 React.useEffect(() => {59 const scroller = containerRef?.current ?? window6061 const update = () => {62 if (scrollLock.current) return63 const anchor =64 (containerRef?.current?.getBoundingClientRect().top ?? 0) + offset65 const active = sections.findLast(({ id }) => {66 const top = document.getElementById(id)?.getBoundingClientRect().top67 return top !== undefined && top <= anchor68 })69 setActiveId(active?.id ?? sections[0]?.id)70 }7172 update()73 scroller.addEventListener("scroll", update, { passive: true })74 window.addEventListener("resize", update)75 return () => {76 scroller.removeEventListener("scroll", update)77 window.removeEventListener("resize", update)78 }79 }, [sections, containerRef, offset])8081 const label = sections.find((s) => s.id === activeId)?.label8283 const labelVersion = React.useRef(0)84 const prevLabel = React.useRef(label)85 if (label !== prevLabel.current) {86// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from rare-ui
All 13 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Bounce Sidebarbounce-sidebar | rare-ui | Components | Free | 1 dep | |
| Code Blockcode-block | rare-ui | Components | Free | 3 deps | |
| Duration Pickerduration-picker | rare-ui | Components | Free | 5 deps | |
| Emoji Reactionemoji-reaction | rare-ui | Components | Free | 4 deps | |
| Family Drawerfamily drawer | rare-ui | Components | Free | 2 deps | |
| Fluid Orbfluid-orb | rare-ui | Components | Free | no deps | |
| Folder Componentfolder-component | rare-ui | Components | Free | 1 dep | |
| GitHub Activitygithub-activity | rare-ui | Components | Free | 1 dep |
