Gooey Dropdown
nexvyn/goo-dropdownFreeComponent
A dropdown menu with a gooey SVG filter effect that morphs a trigger pill into the panel using spring physics and CSS shape interpolation.
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/goo-dropdown.jsonSource
1'use client'23import React, { useEffect, useId, useMemo, useRef, useState } from 'react'4import { animate, useMotionValue, useMotionValueEvent, useReducedMotion } from 'motion/react'5import { cn } from '@/lib/utils'6import { playHoverSound, playClickSound } from '@/lib/sound'78export type DropdownItem = {9 label: string10 onClick?: () => void11}1213type SpringConfig = {14 type: 'spring'15 stiffness?: number16 damping?: number17 mass?: number18 bounce?: number19 visualDuration?: number20}2122export type GooDropdownProps = {23 trigger?: string24 items?: DropdownItem[]25 width?: number26 align?: 'start' | 'end'27 gap?: number28 itemHeight?: number29 buttonRadius?: number30 panelRadius?: number31 fill?: string32 gooStrength?: number33 spring?: SpringConfig34 className?: string35}3637const BTN_W = 7838const BTN_H = 3439const PANEL_PAD = 640const FILL = 'var(--color-card)'4142const DEFAULT_ITEMS: DropdownItem[] = [43 { label: 'Copy link', onClick: () => {} },44 { label: 'Share on X', onClick: () => {} },45 { label: 'Embed', onClick: () => {} },46]4748const DEFAULT_SPRING: SpringConfig = {49 type: 'spring',50 visualDuration: 0.3,51 bounce: 0.15,52}5354const lerp = (a: number, b: number, t: number) => a + (b - a) * t5556function roundedRectShape(x: number, y: number, w: number, h: number, radius: number) {57 const r = Math.max(0, Math.min(radius, w / 2, h / 2))58 const k = r * 0.552359 const x1 = x60 const y1 = y61 const x2 = x + w62 const y2 = y + h63 const p = (n: number) => `${n.toFixed(3)}px`6465 return (66 `shape(from ${p(x1 + r)} ${p(y1)}, ` +67 `line to ${p(x2 - r)} ${p(y1)}, ` +68 `curve to ${p(x2)} ${p(y1 + r)} with ${p(x2 - r + k)} ${p(y1)} / ${p(x2)} ${p(y1 + r - k)}, ` +69 `line to ${p(x2)} ${p(y2 - r)}, ` +70 `curve to ${p(x2 - r)} ${p(y2)} with ${p(x2)} ${p(y2 - r + k)} / ${p(x2 - r + k)} ${p(y2)}, ` +71 `line to ${p(x1 + r)} ${p(y2)}, ` +72 `curve to ${p(x1)} ${p(y2 - r)} with ${p(x1 + r - k)} ${p(y2)} / ${p(x1)} ${p(y2 - r + k)}, ` +73 `line to ${p(x1)} ${p(y1 + r)}, ` +74 `curve to ${p(x1 + r)} ${p(y1)} with ${p(x1)} ${p(y1 + r - k)} / ${p(x1 + r - k)} ${p(y1)}, ` +75 `close)`76 )77}7879export function GooDropdown({80 trigger = 'Share',81 items = DEFAULT_ITEMS,82 width = 240,83 align = 'end',84 gap = 18,85 itemHeight = 40,86 buttonRadius = 12,87 panelRadius = 20,88 fill = FILL,89 gooStrength = 8,90 spring = DEFAULT_SPRING,91 className,92}: GooDropdownProps) {93 const [open, setOpen] = useState(false)94 const [activeIndex, setActiveIndex] = useState(0)95// … truncated
What it pulls in
npm packages
Files it writes
More from nexvyn
All 40 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | nexvyn | Components | Free | 1 dep | |
| Action Buttonaction-button | nexvyn | Components | Free | 3 deps · 4 files | |
| Adaptive Actionsadaptive-actions | nexvyn | Components | Free | 1 dep | |
| AI Inputai-input | nexvyn | Components | Free | 1 dep · 2 files | |
| 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 |
