Morph Nav
nexvyn/morph-navFreeComponent
A floating action button that morphs through states with SVG path-morphing trigger icon.
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/morph-nav.jsonSource
1'use client'23import {4 createContext,5 forwardRef,6 useCallback,7 useContext,8 useEffect,9 useId,10 useMemo,11 useRef,12 useState,13 type ReactNode,14 type RefObject,15} from 'react'16import { createPortal } from 'react-dom'17import { AnimatePresence, motion, useReducedMotion } from 'motion/react'18import { cn } from '@/lib/utils'19import { springs } from '@/lib/motion-tokens'20import { useProximityHighlight, ProximityHighlight } from '@/lib/hooks/use-proximity-highlight'2122export interface MorphNavItem {23 id: string24 label: string25 icon?: ReactNode26 href?: string27 onSelect?: () => void28 children?: MorphNavItem[]29}3031type MorphNavState = 'closed' | 'main' | 'sub'3233interface MorphNavContextValue {34 state: MorphNavState35 setState: (s: MorphNavState) => void36 items: MorphNavItem[]37 activeSubId: string | null38 setActiveSubId: (id: string | null) => void39 onNavigate?: (href: string, item: MorphNavItem) => void40}4142const MorphNavContext = createContext<MorphNavContextValue | null>(null)4344function useMorphNavCtx(componentName: string) {45 const ctx = useContext(MorphNavContext)46 if (!ctx) throw new Error(`${componentName} must be used within MorphNav`)47 return ctx48}4950const PATH_HAMBURGER = 'M3 5h10M3 8h10M3 11h10'51const PATH_MINUS = 'M3 8h10'52const PATH_BACK = 'M7 5L3 8l4 3'5354function getTriggerPaths(state: MorphNavState) {55 if (state === 'closed') return { top: PATH_HAMBURGER, mid: PATH_HAMBURGER, bot: PATH_HAMBURGER }56 if (state === 'main') return { top: PATH_MINUS, mid: PATH_MINUS, bot: PATH_MINUS }57 return { top: PATH_BACK, mid: PATH_BACK, bot: PATH_BACK }58}5960export interface MorphNavProps {61 children?: ReactNode62 items: MorphNavItem[]63 placement?: 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start'64 onNavigate?: (href: string, item: MorphNavItem) => void65 onOpenChange?: (open: boolean) => void66 triggerLabel?: string67 className?: string68 panelClassName?: string69}7071export const MorphNav = forwardRef<HTMLDivElement, MorphNavProps>(72 (73 {74 items,75 placement = 'bottom-end',76 onNavigate,77 onOpenChange,78 triggerLabel = 'Menu',79 className,80 panelClassName,81 },82 ref,83 ) => {84 const [state, setState] = useState<MorphNavState>('closed')85 const [activeSubId, setActiveSubId] = useState<string | null>(null)86 const triggerRef = useRef<HTMLButtonElement>(null)87 const panelRef = useRef<HTMLDivElement>(null)88 const reduceMotion = useReducedMotion()8990 const reactId = useId()91// … 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 |
