Skip to content

Commit 9dc82d0

Browse files
committed
fix: registry example
1 parent 581a399 commit 9dc82d0

File tree

103 files changed

+1060
-657
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+1060
-657
lines changed

public/c/dialog.json

Lines changed: 5 additions & 0 deletions
Large diffs are not rendered by default.

public/c/morphing-dialog.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
"path": "morphing-dialog.tsx",
1717
"content": "'use client';\n\nimport React, {\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport {\n motion,\n AnimatePresence,\n MotionConfig,\n Transition,\n Variant,\n} from 'motion/react';\nimport { createPortal } from 'react-dom';\nimport { cn } from '@/lib/utils';\nimport { XIcon } from 'lucide-react';\nimport useClickOutside from '@/hooks/useClickOutside';\n\nexport type MorphingDialogContextType = {\n isOpen: boolean;\n setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;\n uniqueId: string;\n triggerRef: React.RefObject<HTMLDivElement>;\n};\n\nconst MorphingDialogContext =\n React.createContext<MorphingDialogContextType | null>(null);\n\nfunction useMorphingDialog() {\n const context = useContext(MorphingDialogContext);\n if (!context) {\n throw new Error(\n 'useMorphingDialog must be used within a MorphingDialogProvider'\n );\n }\n return context;\n}\n\nexport type MorphingDialogProviderProps = {\n children: React.ReactNode;\n transition?: Transition;\n};\n\nfunction MorphingDialogProvider({\n children,\n transition,\n}: MorphingDialogProviderProps) {\n const [isOpen, setIsOpen] = useState(false);\n const uniqueId = useId();\n const triggerRef = useRef<HTMLDivElement>(null!);\n\n const contextValue = useMemo(\n () => ({\n isOpen,\n setIsOpen,\n uniqueId,\n triggerRef,\n }),\n [isOpen, uniqueId]\n );\n\n return (\n <MorphingDialogContext.Provider value={contextValue}>\n <MotionConfig transition={transition}>{children}</MotionConfig>\n </MorphingDialogContext.Provider>\n );\n}\n\nexport type MorphingDialogProps = {\n children: React.ReactNode;\n transition?: Transition;\n};\n\nfunction MorphingDialog({ children, transition }: MorphingDialogProps) {\n return (\n <MorphingDialogProvider>\n <MotionConfig transition={transition}>{children}</MotionConfig>\n </MorphingDialogProvider>\n );\n}\n\nexport type MorphingDialogTriggerProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n triggerRef?: React.RefObject<HTMLDivElement>;\n};\n\nfunction MorphingDialogTrigger({\n children,\n className,\n style,\n triggerRef,\n}: MorphingDialogTriggerProps) {\n const { setIsOpen, isOpen, uniqueId } = useMorphingDialog();\n\n const handleClick = useCallback(() => {\n setIsOpen(!isOpen);\n }, [isOpen, setIsOpen]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n setIsOpen(!isOpen);\n }\n },\n [isOpen, setIsOpen]\n );\n\n return (\n <motion.div\n ref={triggerRef}\n layoutId={`dialog-${uniqueId}`}\n className={cn('relative cursor-pointer', className)}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n style={style}\n role='button'\n aria-haspopup='dialog'\n aria-expanded={isOpen}\n aria-controls={`motion-ui-morphing-dialog-content-${uniqueId}`}\n aria-label={`Open dialog ${uniqueId}`}\n >\n {children}\n </motion.div>\n );\n}\n\nexport type MorphingDialogContentProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction MorphingDialogContent({\n children,\n className,\n style,\n}: MorphingDialogContentProps) {\n const { setIsOpen, isOpen, uniqueId, triggerRef } = useMorphingDialog();\n const containerRef = useRef<HTMLDivElement>(null!);\n const [firstFocusableElement, setFirstFocusableElement] =\n useState<HTMLElement | null>(null);\n const [lastFocusableElement, setLastFocusableElement] =\n useState<HTMLElement | null>(null);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setIsOpen(false);\n }\n if (event.key === 'Tab') {\n if (!firstFocusableElement || !lastFocusableElement) return;\n\n if (event.shiftKey) {\n if (document.activeElement === firstFocusableElement) {\n event.preventDefault();\n lastFocusableElement.focus();\n }\n } else {\n if (document.activeElement === lastFocusableElement) {\n event.preventDefault();\n firstFocusableElement.focus();\n }\n }\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n };\n }, [setIsOpen, firstFocusableElement, lastFocusableElement]);\n\n useEffect(() => {\n if (isOpen) {\n document.body.classList.add('overflow-hidden');\n const focusableElements = containerRef.current?.querySelectorAll(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n if (focusableElements && focusableElements.length > 0) {\n setFirstFocusableElement(focusableElements[0] as HTMLElement);\n setLastFocusableElement(\n focusableElements[focusableElements.length - 1] as HTMLElement\n );\n (focusableElements[0] as HTMLElement).focus();\n }\n } else {\n document.body.classList.remove('overflow-hidden');\n triggerRef.current?.focus();\n }\n }, [isOpen, triggerRef]);\n\n useClickOutside(containerRef, () => {\n if (isOpen) {\n setIsOpen(false);\n }\n });\n\n return (\n <motion.div\n ref={containerRef}\n layoutId={`dialog-${uniqueId}`}\n className={cn('overflow-hidden', className)}\n style={style}\n role='dialog'\n aria-modal='true'\n aria-labelledby={`motion-ui-morphing-dialog-title-${uniqueId}`}\n aria-describedby={`motion-ui-morphing-dialog-description-${uniqueId}`}\n >\n {children}\n </motion.div>\n );\n}\n\nexport type MorphingDialogContainerProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction MorphingDialogContainer({ children }: MorphingDialogContainerProps) {\n const { isOpen, uniqueId } = useMorphingDialog();\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n return () => setMounted(false);\n }, []);\n\n if (!mounted) return null;\n\n return createPortal(\n <AnimatePresence initial={false} mode='sync'>\n {isOpen && (\n <>\n <motion.div\n key={`backdrop-${uniqueId}`}\n className='fixed inset-0 h-full w-full bg-white/40 backdrop-blur-xs dark:bg-black/40'\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n />\n <div className='fixed inset-0 z-50 flex items-center justify-center'>\n {children}\n </div>\n </>\n )}\n </AnimatePresence>,\n document.body\n );\n}\n\nexport type MorphingDialogTitleProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction MorphingDialogTitle({\n children,\n className,\n style,\n}: MorphingDialogTitleProps) {\n const { uniqueId } = useMorphingDialog();\n\n return (\n <motion.div\n layoutId={`dialog-title-container-${uniqueId}`}\n className={className}\n style={style}\n layout\n >\n {children}\n </motion.div>\n );\n}\n\nexport type MorphingDialogSubtitleProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction MorphingDialogSubtitle({\n children,\n className,\n style,\n}: MorphingDialogSubtitleProps) {\n const { uniqueId } = useMorphingDialog();\n\n return (\n <motion.div\n layoutId={`dialog-subtitle-container-${uniqueId}`}\n className={className}\n style={style}\n >\n {children}\n </motion.div>\n );\n}\n\nexport type MorphingDialogDescriptionProps = {\n children: React.ReactNode;\n className?: string;\n disableLayoutAnimation?: boolean;\n variants?: {\n initial: Variant;\n animate: Variant;\n exit: Variant;\n };\n};\n\nfunction MorphingDialogDescription({\n children,\n className,\n variants,\n disableLayoutAnimation,\n}: MorphingDialogDescriptionProps) {\n const { uniqueId } = useMorphingDialog();\n\n return (\n <motion.div\n key={`dialog-description-${uniqueId}`}\n layoutId={\n disableLayoutAnimation\n ? undefined\n : `dialog-description-content-${uniqueId}`\n }\n variants={variants}\n className={className}\n initial='initial'\n animate='animate'\n exit='exit'\n id={`dialog-description-${uniqueId}`}\n >\n {children}\n </motion.div>\n );\n}\n\nexport type MorphingDialogImageProps = {\n src: string;\n alt: string;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction MorphingDialogImage({\n src,\n alt,\n className,\n style,\n}: MorphingDialogImageProps) {\n const { uniqueId } = useMorphingDialog();\n\n return (\n <motion.img\n src={src}\n alt={alt}\n className={cn(className)}\n layoutId={`dialog-img-${uniqueId}`}\n style={style}\n />\n );\n}\n\nexport type MorphingDialogCloseProps = {\n children?: React.ReactNode;\n className?: string;\n variants?: {\n initial: Variant;\n animate: Variant;\n exit: Variant;\n };\n};\n\nfunction MorphingDialogClose({\n children,\n className,\n variants,\n}: MorphingDialogCloseProps) {\n const { setIsOpen, uniqueId } = useMorphingDialog();\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n }, [setIsOpen]);\n\n return (\n <motion.button\n onClick={handleClose}\n type='button'\n aria-label='Close dialog'\n key={`dialog-close-${uniqueId}`}\n className={cn('absolute right-6 top-6', className)}\n initial='initial'\n animate='animate'\n exit='exit'\n variants={variants}\n >\n {children || <XIcon size={24} />}\n </motion.button>\n );\n}\n\nexport {\n MorphingDialog,\n MorphingDialogTrigger,\n MorphingDialogContainer,\n MorphingDialogContent,\n MorphingDialogClose,\n MorphingDialogTitle,\n MorphingDialogSubtitle,\n MorphingDialogDescription,\n MorphingDialogImage,\n};\n",
1818
"type": "registry:ui"
19+
},
20+
{
21+
"path": "hooks/useClickOutside.tsx",
22+
"content": "import { RefObject, useEffect } from 'react';\n\nfunction useClickOutside<T extends HTMLElement>(\n ref: RefObject<T>,\n handler: (event: MouseEvent | TouchEvent) => void\n): void {\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent | TouchEvent) => {\n if (!ref || !ref.current || ref.current.contains(event.target as Node)) {\n return;\n }\n\n handler(event);\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n document.addEventListener('touchstart', handleClickOutside);\n\n return () => {\n document.removeEventListener('mousedown', handleClickOutside);\n document.removeEventListener('touchstart', handleClickOutside);\n };\n }, [ref, handler]);\n}\n\nexport default useClickOutside;\n",
23+
"type": "registry:hook"
1924
}
2025
]
2126
}

public/c/morphing-popover.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
"path": "morphing-popover.tsx",
1717
"content": "'use client';\n\nimport {\n useState,\n useId,\n useRef,\n useEffect,\n createContext,\n useContext,\n isValidElement,\n} from 'react';\nimport {\n AnimatePresence,\n MotionConfig,\n motion,\n Transition,\n Variants,\n} from 'motion/react';\nimport useClickOutside from '@/hooks/useClickOutside';\nimport { cn } from '@/lib/utils';\n\nconst TRANSITION = {\n type: 'spring',\n bounce: 0.1,\n duration: 0.4,\n};\n\ntype MorphingPopoverContextValue = {\n isOpen: boolean;\n open: () => void;\n close: () => void;\n uniqueId: string;\n variants?: Variants;\n};\n\nconst MorphingPopoverContext =\n createContext<MorphingPopoverContextValue | null>(null);\n\nfunction usePopoverLogic({\n defaultOpen = false,\n open: controlledOpen,\n onOpenChange,\n}: {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n} = {}) {\n const uniqueId = useId();\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n\n const isOpen = controlledOpen ?? uncontrolledOpen;\n\n const open = () => {\n if (controlledOpen === undefined) {\n setUncontrolledOpen(true);\n }\n onOpenChange?.(true);\n };\n\n const close = () => {\n if (controlledOpen === undefined) {\n setUncontrolledOpen(false);\n }\n onOpenChange?.(false);\n };\n\n return { isOpen, open, close, uniqueId };\n}\n\nexport type MorphingPopoverProps = {\n children: React.ReactNode;\n transition?: Transition;\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n variants?: Variants;\n className?: string;\n} & React.ComponentProps<'div'>;\n\nfunction MorphingPopover({\n children,\n transition = TRANSITION,\n defaultOpen,\n open,\n onOpenChange,\n variants,\n className,\n ...props\n}: MorphingPopoverProps) {\n const popoverLogic = usePopoverLogic({ defaultOpen, open, onOpenChange });\n\n return (\n <MorphingPopoverContext.Provider value={{ ...popoverLogic, variants }}>\n <MotionConfig transition={transition}>\n <div\n className={cn('relative flex items-center justify-center', className)}\n key={popoverLogic.uniqueId}\n {...props}\n >\n {children}\n </div>\n </MotionConfig>\n </MorphingPopoverContext.Provider>\n );\n}\n\nexport type MorphingPopoverTriggerProps = {\n asChild?: boolean;\n children: React.ReactNode;\n className?: string;\n} & React.ComponentProps<typeof motion.button>;\n\nfunction MorphingPopoverTrigger({\n children,\n className,\n asChild = false,\n ...props\n}: MorphingPopoverTriggerProps) {\n const context = useContext(MorphingPopoverContext);\n if (!context) {\n throw new Error(\n 'MorphingPopoverTrigger must be used within MorphingPopover'\n );\n }\n\n if (asChild && isValidElement(children)) {\n const MotionComponent = motion.create(\n children.type as React.ForwardRefExoticComponent<any>\n );\n const childProps = children.props as Record<string, unknown>;\n\n return (\n <MotionComponent\n {...childProps}\n onClick={context.open}\n layoutId={`popover-trigger-${context.uniqueId}`}\n className={childProps.className}\n key={context.uniqueId}\n aria-expanded={context.isOpen}\n aria-controls={`popover-content-${context.uniqueId}`}\n />\n );\n }\n\n return (\n <motion.div\n key={context.uniqueId}\n layoutId={`popover-trigger-${context.uniqueId}`}\n onClick={context.open}\n >\n <motion.button\n {...props}\n layoutId={`popover-label-${context.uniqueId}`}\n key={context.uniqueId}\n className={className}\n aria-expanded={context.isOpen}\n aria-controls={`popover-content-${context.uniqueId}`}\n >\n {children}\n </motion.button>\n </motion.div>\n );\n}\n\nexport type MorphingPopoverContentProps = {\n children: React.ReactNode;\n className?: string;\n} & React.ComponentProps<typeof motion.div>;\n\nfunction MorphingPopoverContent({\n children,\n className,\n ...props\n}: MorphingPopoverContentProps) {\n const context = useContext(MorphingPopoverContext);\n if (!context)\n throw new Error(\n 'MorphingPopoverContent must be used within MorphingPopover'\n );\n\n const ref = useRef<HTMLDivElement>(null);\n useClickOutside(ref, context.close);\n\n useEffect(() => {\n if (!context.isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') context.close();\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [context.isOpen, context.close]);\n\n return (\n <AnimatePresence>\n {context.isOpen && (\n <>\n <motion.div\n {...props}\n ref={ref}\n layoutId={`popover-trigger-${context.uniqueId}`}\n key={context.uniqueId}\n id={`popover-content-${context.uniqueId}`}\n role='dialog'\n aria-modal='true'\n className={cn(\n 'absolute overflow-hidden rounded-md border border-zinc-950/10 bg-white p-2 text-zinc-950 shadow-md dark:border-zinc-50/10 dark:bg-zinc-700 dark:text-zinc-50',\n className\n )}\n initial='initial'\n animate='animate'\n exit='exit'\n variants={context.variants}\n >\n {children}\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n\nexport { MorphingPopover, MorphingPopoverTrigger, MorphingPopoverContent };\n",
1818
"type": "registry:ui"
19+
},
20+
{
21+
"path": "hooks/useClickOutside.tsx",
22+
"content": "import { RefObject, useEffect } from 'react';\n\nfunction useClickOutside<T extends HTMLElement>(\n ref: RefObject<T>,\n handler: (event: MouseEvent | TouchEvent) => void\n): void {\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent | TouchEvent) => {\n if (!ref || !ref.current || ref.current.contains(event.target as Node)) {\n return;\n }\n\n handler(event);\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n document.addEventListener('touchstart', handleClickOutside);\n\n return () => {\n document.removeEventListener('mousedown', handleClickOutside);\n document.removeEventListener('touchstart', handleClickOutside);\n };\n }, [ref, handler]);\n}\n\nexport default useClickOutside;\n",
23+
"type": "registry:hook"
1924
}
2025
]
2126
}

0 commit comments

Comments
 (0)