{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "button-dropdown", "type": "registry:ui", "title": "Button Dropdown", "description": "iOS 26 liquid-glass pill with spring chevron and staggered glass dropdown.", "dependencies": [ "motion" ], "files": [ { "path": "registry/ruixenui/button-dropdown.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion, AnimatePresence } from \"motion/react\";\n\n/* ── sound ── */\nlet _a: AudioContext, _b: AudioBuffer;\nconst tick = () => {\n if (typeof window === \"undefined\") return;\n if (!_a) {\n _a = new AudioContext();\n _b = _a.createBuffer(1, (_a.sampleRate * 0.003) | 0, _a.sampleRate);\n const d = _b.getChannelData(0);\n for (let i = 0; i < d.length; i++)\n d[i] = (Math.random() * 2 - 1) * (1 - i / d.length) ** 4;\n }\n const s = _a.createBufferSource();\n s.buffer = _b;\n const g = _a.createGain();\n g.gain.value = 0.08;\n s.connect(g).connect(_a.destination);\n s.start();\n};\n\n/* ── theme ── */\nconst CSS = `\n.bd{\n --bd-glass:linear-gradient(180deg,rgba(255,255,255,0.78),rgba(255,255,255,0.62));\n --bd-border:rgba(0,0,0,0.06);\n --bd-shadow:0 0 1px rgba(0,0,0,0.04),0 2px 8px rgba(0,0,0,0.04),inset 0 1px 0 rgba(255,255,255,0.8);\n --bd-hi:rgba(0,0,0,0.88);\n --bd-dim:rgba(0,0,0,0.42);\n --bd-hover:rgba(0,0,0,0.03);\n --bd-danger:#FF3B30;\n --bd-menu-shadow:0 8px 32px rgba(0,0,0,0.12),0 0 1px rgba(0,0,0,0.06)\n}\n.dark .bd,[data-theme=\"dark\"] .bd{\n --bd-glass:linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02));\n --bd-border:rgba(255,255,255,0.07);\n --bd-shadow:0 1px 3px rgba(0,0,0,0.08),inset 0 1px 0 rgba(255,255,255,0.04);\n --bd-hi:rgba(255,255,255,0.88);\n --bd-dim:rgba(255,255,255,0.28);\n --bd-hover:rgba(255,255,255,0.04);\n --bd-danger:#FF453A;\n --bd-menu-shadow:0 8px 32px rgba(0,0,0,0.3),0 0 1px rgba(255,255,255,0.06)\n}`;\n\n/* ── types ── */\nexport interface DropdownItem {\n label: string;\n onClick?: () => void;\n destructive?: boolean;\n}\n\nexport interface ButtonDropdownProps {\n label: string;\n items: DropdownItem[];\n sound?: boolean;\n style?: React.CSSProperties;\n}\n\n/* ── component ── */\nexport function ButtonDropdown({\n label,\n items,\n sound = true,\n style,\n}: ButtonDropdownProps) {\n const [open, setOpen] = React.useState(false);\n const ref = React.useRef(null);\n\n React.useEffect(() => {\n if (!open) return;\n const close = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node))\n setOpen(false);\n };\n const esc = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"mousedown\", close);\n document.addEventListener(\"keydown\", esc);\n return () => {\n document.removeEventListener(\"mousedown\", close);\n document.removeEventListener(\"keydown\", esc);\n };\n }, [open]);\n\n const pill: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 6,\n padding: \"8px 14px\",\n borderRadius: 10,\n border: \"1px solid var(--bd-border)\",\n background: \"var(--bd-glass)\",\n boxShadow: \"var(--bd-shadow)\",\n backdropFilter: \"blur(24px)\",\n WebkitBackdropFilter: \"blur(24px)\",\n color: \"var(--bd-hi)\",\n fontSize: 13,\n fontWeight: 500,\n cursor: \"pointer\",\n outline: \"none\",\n userSelect: \"none\",\n };\n\n return (\n <>\n