Bounce Sidebar
beui/bounce-sidebarFreeComponent
A vertical sidebar whose active dot jumps between destinations on a curved, spring-loaded path.
Install it
npx shadcn@latest add https://beui.dev/r/bounce-sidebar.jsonSource
1"use client";2// beui.dev/components/motion/bounce-sidebar34import { animate, motion, useMotionValue, useReducedMotion } from "motion/react";5import {6 type ReactNode,7 useCallback,8 useLayoutEffect,9 useRef,10 useState,11} from "react";12import { cn } from "@/lib/utils";1314export interface BounceSidebarItem {15 id: string;16 label: ReactNode;17 href?: string;18 icon?: ReactNode;19 disabled?: boolean;20 target?: "_blank" | "_self" | "_parent" | "_top";21 rel?: string;22}2324export interface BounceSidebarProps {25 items: BounceSidebarItem[];26 value?: string;27 defaultValue?: string;28 onValueChange?: (value: string) => void;29 ariaLabel?: string;30 className?: string;31 listClassName?: string;32 itemClassName?: string;33 indicatorClassName?: string;34}3536const DOT_SIZE = 6;3738// A compact, lightly underdamped spring gives the dot a quick landing without39// turning the sidebar into a playful toy. The sideways arc carries the bounce.40const BOUNCE_SPRING = {41 type: "spring",42 stiffness: 280,43 damping: 18,44 mass: 0.3,45} as const;4647function quadraticBezier(start: number, control: number, end: number, progress: number) {48 const remaining = 1 - progress;49 return (50 remaining * remaining * start +51 2 * remaining * progress * control +52 progress * progress * end53 );54}5556export function BounceSidebar({57 items,58 value,59 defaultValue,60 onValueChange,61 ariaLabel = "Sidebar navigation",62 className,63 listClassName,64 itemClassName,65 indicatorClassName,66}: BounceSidebarProps) {67 const reduce = useReducedMotion();68 const [internalValue, setInternalValue] = useState(69 defaultValue ?? items[0]?.id ?? "",70 );71 const requestedValue = value ?? internalValue;72 const selectedValue = items.some((item) => item.id === requestedValue)73 ? requestedValue74 : (items[0]?.id ?? "");75 const selectedIndex = items.findIndex((item) => item.id === selectedValue);76 const listRef = useRef<HTMLUListElement>(null);77 const itemRefs = useRef(new Map<string, HTMLLIElement>());78 const selectedValueRef = useRef(selectedValue);79 const previousIndexRef = useRef(selectedIndex);80 const hasPositionRef = useRef(false);81 const animationRef = useRef<ReturnType<typeof animate> | null>(null);82 const x = useMotionValue(0);83 const y = useMotionValue(0);84 selectedValueRef.current = selectedValue;8586 const snapIndicator = useCallback(() => {87 const selectedItem = itemRefs.current.get(selectedValueRef.current);88 if (!selectedItem) return;8990// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Action Swap Bluraction-swap-blur | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Cascadeaction-swap-cascade | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Rollaction-swap-roll | beui | Components | Free | 3 deps · 4 files | |
| Agent Activityagent-activity | beui | Components | Free | 4 deps · 9 files | |
| Agent Loading States Agent Progressagent-progress | beui | Components | Free | 3 deps · 3 files | |
| AI Sidebarai-sidebar | beui | Components | Free | 4 deps · 5 files | |
| Animated Badgeanimated-badge | beui | Components | Free | 4 deps · 3 files | |
| Number Animation Animated Numberanimated-number | beui | Components | Free | 3 deps · 3 files |
