Dropdown Menu
nexvyn/dropdown-menuFreeComponent
A popover action menu with a proximity-driven morphing highlight. The muted hover highlight springs between items as the pointer moves, giving the menu a fluid, alive feel.
Live preview
live · rendered by the registry
Rendered by nexvyn on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.nexvyn.dev/r/dropdown-menu.jsonSource
1'use client'23import {4 createContext,5 forwardRef,6 useCallback,7 useContext,8 useEffect,9 useId,10 useLayoutEffect,11 useMemo,12 useRef,13 useState,14 type HTMLAttributes,15 type ReactNode,16} from 'react'17import { createPortal } from 'react-dom'18import { useReducedMotion, type MotionValue } from 'motion/react'19import { cn } from '@/lib/utils'20import { playHoverSound, playClickSound } from '@/lib/sound'21import { useProximityHighlight, ProximityHighlight } from '@/lib/hooks/use-proximity-highlight'22interface DropdownMenuContextValue {23 open: boolean24 setOpen: (next: boolean) => void25 triggerId: string26 contentId: string27 nextIndex: () => number2829 itemsWidth: number | null30 setItemsWidth: (w: number | null) => void31}3233const DropdownMenuContext = createContext<DropdownMenuContextValue | null>(null)3435function useDropdownMenuCtx(componentName: string) {36 const ctx = useContext(DropdownMenuContext)37 if (!ctx) throw new Error(`${componentName} must be used within DropdownMenu`)38 return ctx39}40interface DropdownMenuContentContextValue {41 activeIndex: number | null42 setActiveIndex: (i: number | null) => void43 highlightX: ReturnType<typeof import('motion/react').useSpring>44 highlightSize: MotionValue<number>45 highlightOpacity: ReturnType<typeof import('motion/react').useSpring>46 axis: 'x' | 'y'47 focusedIndex: number48 setFocusedIndex: (i: number) => void49 setOpen: (next: boolean) => void5051 registerItem: (index: number, el: HTMLElement | null) => void52}5354const DropdownMenuContentContext = createContext<DropdownMenuContentContextValue | null>(null)5556function useDropdownMenuContentCtx(componentName: string) {57 const ctx = useContext(DropdownMenuContentContext)58 if (!ctx) throw new Error(`${componentName} must be used within DropdownMenuContent`)59 return ctx60}61export interface DropdownMenuProps {62 children: ReactNode63 open?: boolean64 defaultOpen?: boolean65 onOpenChange?: (open: boolean) => void66 className?: string67}6869export function DropdownMenu({70 children,71 open: openProp,72 defaultOpen = false,73 onOpenChange,74 className,75}: DropdownMenuProps) {76 const isControlled = openProp !== undefined77 const [internalOpen, setInternalOpen] = useState(defaultOpen)78 const open = isControlled ? openProp : internalOpen79 const [itemsWidth, setItemsWidth] = useState<number | null>(null)8081 const setOpen = useCallback(82 (next: boolean) => {83 if (!isControlled) setInternalOpen(next)84 onOpenChange?.(next)85 },86// … truncated
What it pulls in
npm packages
Files it writes
More from nexvyn
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Badgebadge | nexvyn | Components | Free | 2 deps · 2 files | |
| Bars Themebars-theme | nexvyn | Components | Free | 1 dep · 3 files | |
| Bounce Sidebarbounce-sidebar | nexvyn | Components | Free | 1 dep · 2 files | |
| Breadcrumbsbreadcrumbs | nexvyn | Components | Free | 1 dep · 2 files | |
| Checkboxcheckbox | nexvyn | Components | Free | 1 dep · 3 files | |
| Clipboard Fieldclipboard-field | nexvyn | Components | Free | no deps · 2 files | |
| Color Pickercolor-picker | nexvyn | Components | Free | no deps · 16 files | |
| Comboboxcombobox | nexvyn | Components | Free | 1 dep · 2 files |
