Bounce Sidebar
rare-ui/bounce-sidebarFreeComponent
A vertical navigation list with a bouncy, spring-animated active indicator.
Install it
npx shadcn@latest add https://rareui.com/r/bounce-sidebar.jsonSource
1"use client";23import { useEffect, useLayoutEffect, useRef, useState, type ComponentProps } from "react";4import Link from "next/link";5import { motion, useAnimate } from "motion/react";6import { arc } from "motion";7import { cn } from "@/lib/utils";89const MotionLink = motion.create(Link);1011const useIsomorphicLayoutEffect =12 typeof window !== "undefined" ? useLayoutEffect : useEffect;1314export type BounceSidebarItem = string | { label: string; href?: string };1516export type BounceSidebarProps = Omit<ComponentProps<"ul">, "onChange"> & {17 items: BounceSidebarItem[];18 value?: number;19 defaultValue?: number;20 onChange?: (index: number) => void;21 dotColor?: string;22};2324export function BounceSidebar({25 items,26 value,27 defaultValue = 0,28 onChange,29 dotColor = "#FC4C01",30 className,31 ...props32}: BounceSidebarProps) {33 const [internalValue, setInternalValue] = useState(defaultValue);34 const activeIndex = value ?? internalValue;3536 const [dot, animate] = useAnimate<HTMLSpanElement>();37 const itemRefs = useRef<(HTMLLIElement | null)[]>([]);38 const prevY = useRef<number | null>(null);3940 const [dotSize, setDotSize] = useState(6);41 const [ready, setReady] = useState(false);42 useEffect(() => {43 const dpr = window.devicePixelRatio || 1;44 setDotSize(Math.round(6 * dpr) / dpr);45 }, []);4647 useIsomorphicLayoutEffect(() => {48 let cancelled = false;49 const snap = () => {50 const el = itemRefs.current[activeIndex];51 if (cancelled || !el || !dot.current) return;52 const dpr = window.devicePixelRatio || 1;53 const size = Math.round(6 * dpr) / dpr;54 const toY =55 Math.round((el.offsetTop + el.offsetHeight / 2 - size / 2) * dpr) / dpr;56 animate(dot.current, { x: 0, y: toY }, { duration: 0 });57 prevY.current = toY;58 setReady(true);59 };6061 snap();62 const raf = requestAnimationFrame(snap);63 document.fonts?.ready.then(snap);64 return () => {65 cancelled = true;66 cancelAnimationFrame(raf);67 };68 }, []);6970 useEffect(() => {71 const el = itemRefs.current[activeIndex];72 if (!el || !dot.current) return;7374 const dpr = window.devicePixelRatio || 1;75 const toY =76 Math.round((el.offsetTop + el.offsetHeight / 2 - dotSize / 2) * dpr) /77 dpr;7879 if (prevY.current === null) {80 animate(dot.current, { x: 0, y: toY }, { duration: 0 });81 prevY.current = toY;82 return;83 }8485 const fromY = prevY.current;86 const delta = toY - fromY;87 prevY.current = toY;88// … 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 |
|---|---|---|---|---|---|
| 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 | |
| Gravity Lettersgravity-letters | rare-ui | Components | Free | no deps |
