Morphing Popover
motion-primitives/morphing-popoverFreeComponent
A popover component that morphs from its trigger with fluid animations.
Install it
npx shadcn@latest add https://raw.githubusercontent.com/ibelick/motion-primitives/HEAD/public/c/morphing-popover.jsonSource
1'use client';23import {4 useState,5 useId,6 useRef,7 useEffect,8 createContext,9 useContext,10 isValidElement,11} from 'react';12import {13 AnimatePresence,14 MotionConfig,15 motion,16 Transition,17 Variants,18} from 'motion/react';19import useClickOutside from '@/hooks/useClickOutside';20import { cn } from '@/lib/utils';2122const TRANSITION = {23 type: 'spring',24 bounce: 0.1,25 duration: 0.4,26};2728type MorphingPopoverContextValue = {29 isOpen: boolean;30 open: () => void;31 close: () => void;32 uniqueId: string;33 variants?: Variants;34};3536const MorphingPopoverContext =37 createContext<MorphingPopoverContextValue | null>(null);3839function usePopoverLogic({40 defaultOpen = false,41 open: controlledOpen,42 onOpenChange,43}: {44 defaultOpen?: boolean;45 open?: boolean;46 onOpenChange?: (open: boolean) => void;47} = {}) {48 const uniqueId = useId();49 const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);5051 const isOpen = controlledOpen ?? uncontrolledOpen;5253 const open = () => {54 if (controlledOpen === undefined) {55 setUncontrolledOpen(true);56 }57 onOpenChange?.(true);58 };5960 const close = () => {61 if (controlledOpen === undefined) {62 setUncontrolledOpen(false);63 }64 onOpenChange?.(false);65 };6667 return { isOpen, open, close, uniqueId };68}6970export type MorphingPopoverProps = {71 children: React.ReactNode;72 transition?: Transition;73 defaultOpen?: boolean;74 open?: boolean;75 onOpenChange?: (open: boolean) => void;76 variants?: Variants;77 className?: string;78} & React.ComponentProps<'div'>;7980function MorphingPopover({81 children,82 transition = TRANSITION,83 defaultOpen,84 open,85 onOpenChange,86 variants,87 className,88 ...props89}: MorphingPopoverProps) {90 const popoverLogic = usePopoverLogic({ defaultOpen, open, onOpenChange });9192 return (93 <MorphingPopoverContext.Provider value={{ ...popoverLogic, variants }}>94 <MotionConfig transition={transition}>95 <div96 className={cn('relative flex items-center justify-center', className)}97 key={popoverLogic.uniqueId}98 {...props}99 >100 {children}101 </div>102 </MotionConfig>103 </MorphingPopoverContext.Provider>104 );105}106107export type MorphingPopoverTriggerProps = {108 asChild?: boolean;109 children: React.ReactNode;110 className?: string;111} & React.ComponentProps<typeof motion.button>;112113function MorphingPopoverTrigger({114 children,115 className,116 asChild = false,117 ...props118}: MorphingPopoverTriggerProps) {119// … truncated
What it pulls in
npm packages
Files it writes
More from motion-primitives
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | motion-primitives | Components | Free | 1 dep | |
| Animated Backgroundanimated-background | motion-primitives | Components | Free | 1 dep | |
| Animated Groupanimated-group | motion-primitives | Components | Free | 1 dep | |
| Animated Numberanimated-number | motion-primitives | Components | Free | 1 dep | |
| Border Trailborder-trail | motion-primitives | Components | Free | 1 dep | |
| Carouselcarousel | motion-primitives | Components | Free | 1 dep | |
| Cursorcursor | motion-primitives | Components | Free | 1 dep | |
| Dialogdialog | motion-primitives | Components | Free | 1 dep · 2 files |
