my/ui

Command Palette

Search for a command to run...

All components

Floating Panel

modals

Floating panel component with backdrop blur and position-aware animations

responsive · 560px

Install

Same command in any shadcn project — React (Vite/CRA), Next.js, Remix, Astro, and more:

$npx shadcn@latest add https://your-domain/r/floating-panel.json

Usage

"use client"

import {
  FloatingPanelRoot,
  FloatingPanelTrigger,
  FloatingPanelContent,
  FloatingPanelForm,
  FloatingPanelLabel,
  FloatingPanelTextarea,
  FloatingPanelFooter,
  FloatingPanelCloseButton,
  FloatingPanelSubmitButton,
} from "@/registry/cult-ui/floating-panel"

export default function Demo() {
  return (
    <div className="flex items-center justify-center w-full min-h-48 p-8">
      <FloatingPanelRoot>
        <FloatingPanelTrigger title="Add Note">
          Add Note
        </FloatingPanelTrigger>
        <FloatingPanelContent className="w-80 h-48">
          <FloatingPanelForm onSubmit={(note) => console.log("Note:", note)}>
            <FloatingPanelLabel htmlFor="note">Write your note</FloatingPanelLabel>
            <FloatingPanelTextarea id="note" />
            <FloatingPanelFooter>
              <FloatingPanelCloseButton />
              <FloatingPanelSubmitButton />
            </FloatingPanelFooter>
          </FloatingPanelForm>
        </FloatingPanelContent>
      </FloatingPanelRoot>
    </div>
  )
}

Component source

"use client"

import React, {
  createContext,
  useContext,
  useEffect,
  useId,
  useRef,
  useState,
} from "react"
import { ArrowLeftIcon } from "lucide-react"
import { AnimatePresence, motion, MotionConfig, Variants } from "motion/react"

import { cn } from "@/lib/utils"

const TRANSITION = {
  type: "spring" as const,
  bounce: 0.1,
  duration: 0.4,
}

interface FloatingPanelContextType {
  isOpen: boolean
  openFloatingPanel: (rect: DOMRect) => void
  closeFloatingPanel: () => void
  uniqueId: string
  note: string
  setNote: (note: string) => void
  triggerRect: DOMRect | null
  title: string
  setTitle: (title: string) => void
}

const FloatingPanelContext = createContext<
  FloatingPanelContextType | undefined
>(undefined)

function useFloatingPanel() {
  const context = useContext(FloatingPanelContext)
  if (!context) {
    throw new Error(
      "useFloatingPanel must be used within a FloatingPanelProvider"
    )
  }
  return context
}

function useFloatingPanelLogic() {
  const uniqueId = useId()
  const [isOpen, setIsOpen] = useState(false)
  const [note, setNote] = useState("")
  const [triggerRect, setTriggerRect] = useState<DOMRect | null>(null)
  const [title, setTitle] = useState("")

  const openFloatingPanel = (rect: DOMRect) => {
    setTriggerRect(rect)
    setIsOpen(true)
  }
  const closeFloatingPanel = () => {
    setIsOpen(false)
    setNote("")
  }

  return {
    isOpen,
    openFloatingPanel,
    closeFloatingPanel,
    uniqueId,
    note,
    setNote,
    triggerRect,
    title,
    setTitle,
  }
}

interface FloatingPanelRootProps {
  children: React.ReactNode
  className?: string
}

export function FloatingPanelRoot({
  children,
  className,
}: FloatingPanelRootProps) {
  const floatingPanelLogic = useFloatingPanelLogic()

  return (
    <FloatingPanelContext.Provider value={floatingPanelLogic}>
      <MotionConfig transition={TRANSITION}>
        <div className={cn("relative", className)}>{children}</div>
      </MotionConfig>
    </FloatingPanelContext.Provider>
  )
}

interface FloatingPanelTriggerProps {
  children: React.ReactNode
  className?: string
  title: string
}

export function FloatingPanelTrigger({
  children,
  className,
  title,
}: FloatingPanelTriggerProps) {
  const { openFloatingPanel, uniqueId, setTitle } = useFloatingPanel()
  const triggerRef = useRef<HTMLButtonElement>(null)

  const handleClick = () => {
    if (triggerRef.current) {
      openFloatingPanel(triggerRef.current.getBoundingClientRect())
      setTitle(title)
    }
  }

  return (
    <motion.button
      ref={triggerRef}
      layoutId={`floating-panel-trigger-${uniqueId}`}
      className={cn(
        "flex h-9 items-center border border-zinc-950/10 bg-white px-3 text-zinc-950 dark:border-zinc-50/10 dark:bg-zinc-700 dark:text-zinc-50",
        className
      )}
      style={{ borderRadius: 8 }}
      onClick={handleClick}
      whileHover={{ scale: 1.05 }}
      whileTap={{ scale: 0.95 }}
      aria-haspopup="dialog"
      aria-expanded={false}
    >
      <motion.div
        layoutId={`floating-panel-label-container-${uniqueId}`}
        className="flex items-center"
      >
        <motion.span
          layoutId={`floating-panel-label-${uniqueId}`}
          className="text-sm font-semibold"
        >
          {children}
        </motion.span>
      </motion.div>
    </motion.button>
  )
}

interface FloatingPanelContentProps {
  children: React.ReactNode
  className?: string
}

export function FloatingPanelContent({
  children,
  className,
}: FloatingPanelContentProps) {
  const { isOpen, closeFloatingPanel, uniqueId, triggerRect, title } =
    useFloatingPanel()
  const contentRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (
        contentRef.current &&
        !contentRef.current.contains(event.target as Node)
      ) {
        closeFloatingPanel()
      }
    }
    document.addEventListener("mousedown", handleClickOutside)
    return () => document.removeEventListener("mousedown", handleClickOutside)
  }, [closeFloatingPanel])

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") closeFloatingPanel()
    }
    document.addEventListener("keydown", handleKeyDown)
    return () => document.removeEventListener("keydown", handleKeyDown)
  }, [closeFloatingPanel])

  const variants: Variants = {
    hidden: { opacity: 0, scale: 0.9, y: 10 },
    visible: { opacity: 1, scale: 1, y: 0 },
  }

  return (
    <AnimatePresence>
      {isOpen && (
        <>
          <motion.div
            initial={{ backdropFilter: "blur(0px)" }}
            animate={{ backdropFilter: "blur(4px)" }}
            exit={{ backdropFilter: "blur(0px)" }}
            className="fixed inset-0 z-40"
          />
          <motion.div
            ref={contentRef}
            layoutId={`floating-panel-${uniqueId}`}
            className={cn(
              "fixed z-50 overflow-hidden border border-zinc-950/10 bg-white shadow-lg outline-none dark:border-zinc-50/10 dark:bg-zinc-800",
              className
            )}
            style={{
              borderRadius: 12,
              left: triggerRect ? triggerRect.left : "50%",
              top: triggerRect ? triggerRect.bottom + 8 : "50%",
              transformOrigin: "top left",
            }}
            initial="hidden"
            animate="visible"
            exit="hidden"
            variants={variants}
            role="dialog"
            aria-modal="true"
            aria-labelledby={`floating-panel-title-${uniqueId}`}
          >
            <FloatingPanelTitle>{title}</FloatingPanelTitle>
            {children}
          </motion.div>
        </>
      )}
    </AnimatePresence>
  )
}

interface FloatingPanelTitleProps {
  children: React.ReactNode
}

function FloatingPanelTitle({ children }: FloatingPanelTitleProps) {
  const { uniqueId } = useFloatingPanel()

  return (
    <motion.div
      layoutId={`floating-panel-label-container-${uniqueId}`}
      className="px-4 py-2 bg-white dark:bg-zinc-800"
    >
      <motion.div
        layoutId={`floating-panel-label-${uniqueId}`}
        className="text-sm font-semibold text-zinc-900 dark:text-zinc-100"
        id={`floating-panel-title-${uniqueId}`}
      >
        {children}
      </motion.div>
    </motion.div>
  )
}

interface FloatingPanelFormProps {
  children: React.ReactNode
  onSubmit?: (note: string) => void
  className?: string
}

export function FloatingPanelForm({
  children,
  onSubmit,
  className,
}: FloatingPanelFormProps) {
  const { note, closeFloatingPanel } = useFloatingPanel()

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    onSubmit?.(note)
    closeFloatingPanel()
  }

  return (
    <form
      className={cn("flex h-full flex-col", className)}
      onSubmit={handleSubmit}
    >
      {children}
    </form>
  )
}

interface FloatingPanelLabelProps {
  children: React.ReactNode
  htmlFor: string
  className?: string
}

export function FloatingPanelLabel({
  children,
  htmlFor,
  className,
}: FloatingPanelLabelProps) {
  const { note } = useFloatingPanel()

  return (
    <motion.label
      htmlFor={htmlFor}
      style={{ opacity: note ? 0 : 1 }}
      className={cn(
        "block mb-2 text-sm font-medium text-zinc-900 dark:text-zinc-100",
        className
      )}
    >
      {children}
    </motion.label>
  )
}

interface FloatingPanelTextareaProps {
  className?: string
  id?: string
}

export function FloatingPanelTextarea({
  className,
  id,
}: FloatingPanelTextareaProps) {
  const { note, setNote } = useFloatingPanel()

  return (
    <textarea
      id={id}
      className={cn(
        "h-full w-full resize-none rounded-md bg-transparent px-4 py-3 text-sm outline-none",
        className
      )}
      autoFocus
      value={note}
      onChange={(e) => setNote(e.target.value)}
    />
  )
}

interface FloatingPanelHeaderProps {
  children: React.ReactNode
  className?: string
}

export function FloatingPanelHeader({
  children,
  className,
}: FloatingPanelHeaderProps) {
  return (
    <motion.div
      className={cn(
        "px-4 py-2 font-semibold text-zinc-900 dark:text-zinc-100",
        className
      )}
      initial={{ opacity: 0, y: -10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ delay: 0.1 }}
    >
      {children}
    </motion.div>
  )
}

interface FloatingPanelBodyProps {
  children: React.ReactNode
  className?: string
}

export function FloatingPanelBody({
  children,
  className,
}: FloatingPanelBodyProps) {
  return (
    <motion.div
      className={cn("p-4", className)}
      initial={{ opacity: 0, y: 10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ delay: 0.2 }}
    >
      {children}
    </motion.div>
  )
}

interface FloatingPanelFooterProps {
  children: React.ReactNode
  className?: string
}

export function FloatingPanelFooter({
  children,
  className,
}: FloatingPanelFooterProps) {
  return (
    <motion.div
      className={cn("flex justify-between px-4 py-3", className)}
      initial={{ opacity: 0, y: 10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ delay: 0.3 }}
    >
      {children}
    </motion.div>
  )
}

interface FloatingPanelCloseButtonProps {
  className?: string
}

export function FloatingPanelCloseButton({
  className,
}: FloatingPanelCloseButtonProps) {
  const { closeFloatingPanel } = useFloatingPanel()

  return (
    <motion.button
      type="button"
      className={cn("flex items-center", className)}
      onClick={closeFloatingPanel}
      aria-label="Close floating panel"
      whileHover={{ scale: 1.1 }}
      whileTap={{ scale: 0.9 }}
    >
      <ArrowLeftIcon size={16} className="text-zinc-900 dark:text-zinc-100" />
    </motion.button>
  )
}

interface FloatingPanelSubmitButtonProps {
  className?: string
}

export function FloatingPanelSubmitButton({
  className,
}: FloatingPanelSubmitButtonProps) {
  return (
    <motion.button
      className={cn(
        "relative ml-1 flex h-8 shrink-0 scale-100 select-none appearance-none items-center justify-center rounded-lg border border-zinc-950/10 bg-transparent px-2 text-sm text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-800 focus-visible:ring-2 active:scale-[0.98] dark:border-zinc-50/10 dark:text-zinc-50 dark:hover:bg-zinc-800",
        className
      )}
      type="submit"
      aria-label="Submit note"
      whileHover={{ scale: 1.05 }}
      whileTap={{ scale: 0.95 }}
    >
      Submit Note
    </motion.button>
  )
}

interface FloatingPanelButtonProps {
  children: React.ReactNode
  onClick?: () => void
  className?: string
}

export function FloatingPanelButton({
  children,
  onClick,
  className,
}: FloatingPanelButtonProps) {
  return (
    <motion.button
      className={cn(
        "flex w-full items-center gap-2 rounded-md px-4 py-2 text-left text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700",
        className
      )}
      onClick={onClick}
      whileHover={{ backgroundColor: "rgba(0, 0, 0, 0.05)" }}
      whileTap={{ scale: 0.98 }}
    >
      {children}
    </motion.button>
  )
}

export default {
  Root: FloatingPanelRoot,
  Trigger: FloatingPanelTrigger,
  Content: FloatingPanelContent,
  Form: FloatingPanelForm,
  Label: FloatingPanelLabel,
  Textarea: FloatingPanelTextarea,
  Header: FloatingPanelHeader,
  Body: FloatingPanelBody,
  Footer: FloatingPanelFooter,
  CloseButton: FloatingPanelCloseButton,
  SubmitButton: FloatingPanelSubmitButton,
  Button: FloatingPanelButton,
}
// "use client"

// import React, {
//   createContext,
//   useContext,
//   useEffect,
//   useId,
//   useRef,
//   useState,
// } from "react"
// import { AnimatePresence, MotionConfig, motion } from "motion/react"
// import { ArrowLeftIcon } from "lucide-react"

// import { cn } from "@/lib/utils"

// const TRANSITION = {
//   type: "spring",
//   bounce: 0.1,
//   duration: 0.4,
// }

// interface FloatingPanelContextType {
//   isOpen: boolean
//   openFloatingPanel: (rect: DOMRect) => void
//   closeFloatingPanel: () => void
//   uniqueId: string
//   note: string
//   setNote: (note: string) => void
//   triggerRect: DOMRect | null
//   title: string
//   setTitle: (title: string) => void
// }

// const FloatingPanelContext = createContext<
//   FloatingPanelContextType | undefined
// >(undefined)

// function useFloatingPanel() {
//   const context = useContext(FloatingPanelContext)
//   if (!context) {
//     throw new Error(
//       "useFloatingPanel must be used within a FloatingPanelProvider"
//     )
//   }
//   return context
// }

// function useFloatingPanelLogic() {
//   const uniqueId = useId()
//   const [isOpen, setIsOpen] = useState(false)
//   const [note, setNote] = useState("")
//   const [triggerRect, setTriggerRect] = useState<DOMRect | null>(null)
//   const [title, setTitle] = useState("")

//   const openFloatingPanel = (rect: DOMRect) => {
//     setTriggerRect(rect)
//     setIsOpen(true)
//   }
//   const closeFloatingPanel = () => {
//     setIsOpen(false)
//     setNote("")
//   }

//   return {
//     isOpen,
//     openFloatingPanel,
//     closeFloatingPanel,
//     uniqueId,
//     note,
//     setNote,
//     triggerRect,
//     title,
//     setTitle,
//   }
// }

// interface FloatingPanelRootProps {
//   children: React.ReactNode
//   className?: string
// }

// export function FloatingPanelRoot({
//   children,
//   className,
// }: FloatingPanelRootProps) {
//   const floatingPanelLogic = useFloatingPanelLogic()

//   return (
//     <FloatingPanelContext.Provider value={floatingPanelLogic}>
//       <MotionConfig transition={TRANSITION}>
//         <div className={cn("relative", className)}>{children}</div>
//       </MotionConfig>
//     </FloatingPanelContext.Provider>
//   )
// }

// interface FloatingPanelTriggerProps {
//   children: React.ReactNode
//   className?: string
//   title: string
// }

// export function FloatingPanelTrigger({
//   children,
//   className,
//   title,
// }: FloatingPanelTriggerProps) {
//   const { openFloatingPanel, uniqueId, setTitle } = useFloatingPanel()
//   const triggerRef = useRef<HTMLButtonElement>(null)

//   const handleClick = () => {
//     if (triggerRef.current) {
//       openFloatingPanel(triggerRef.current.getBoundingClientRect())
//       setTitle(title)
//     }
//   }

//   return (
//     <motion.button
//       ref={triggerRef}
//       layoutId={`floating-panel-trigger-${uniqueId}`}
//       className={cn(
//         "flex h-9 items-center border border-zinc-950/10 bg-white px-3 text-zinc-950 dark:border-zinc-50/10 dark:bg-zinc-700 dark:text-zinc-50",
//         className
//       )}
//       style={{ borderRadius: 8 }}
//       onClick={handleClick}
//       whileHover={{ scale: 1.05 }}
//       whileTap={{ scale: 0.95 }}
//     >
//       <motion.span
//         layoutId={`floating-panel-label-${uniqueId}`}
//         className="text-sm"
//       >
//         {children}
//       </motion.span>
//     </motion.button>
//   )
// }

// interface FloatingPanelContentProps {
//   children: React.ReactNode
//   className?: string
// }

// export function FloatingPanelContent({
//   children,
//   className,
// }: FloatingPanelContentProps) {
//   const { isOpen, closeFloatingPanel, uniqueId, triggerRect, title } =
//     useFloatingPanel()
//   const contentRef = useRef<HTMLDivElement>(null)

//   useEffect(() => {
//     const handleClickOutside = (event: MouseEvent) => {
//       if (
//         contentRef.current &&
//         !contentRef.current.contains(event.target as Node)
//       ) {
//         closeFloatingPanel()
//       }
//     }
//     document.addEventListener("mousedown", handleClickOutside)
//     return () => document.removeEventListener("mousedown", handleClickOutside)
//   }, [closeFloatingPanel])

//   useEffect(() => {
//     const handleKeyDown = (event: KeyboardEvent) => {
//       if (event.key === "Escape") closeFloatingPanel()
//     }
//     document.addEventListener("keydown", handleKeyDown)
//     return () => document.removeEventListener("keydown", handleKeyDown)
//   }, [closeFloatingPanel])

//   const variants = {
//     hidden: { opacity: 0, scale: 0.9, y: 10 },
//     visible: { opacity: 1, scale: 1, y: 0 },
//   }

//   return (
//     <AnimatePresence>
//       {isOpen && (
//         <>
//           <motion.div
//             initial={{ backdropFilter: "blur(0px)" }}
//             animate={{ backdropFilter: "blur(4px)" }}
//             exit={{ backdropFilter: "blur(0px)" }}
//             className="fixed inset-0 z-40"
//           />
//           <motion.div
//             ref={contentRef}
//             layoutId={`floating-panel-${uniqueId}`}
//             className={cn(
//               "fixed z-50 overflow-hidden border border-zinc-950/10 bg-white shadow-lg outline-none dark:border-zinc-50/10 dark:bg-zinc-800",
//               className
//             )}
//             style={{
//               borderRadius: 12,
//               left: triggerRect ? triggerRect.left : "50%",
//               top: triggerRect ? triggerRect.bottom + 8 : "50%",
//               transformOrigin: "top left",
//             }}
//             initial="hidden"
//             animate="visible"
//             exit="hidden"
//             variants={variants}
//           >
//             <FloatingPanelTitle>{title}</FloatingPanelTitle>
//             {children}
//           </motion.div>
//         </>
//       )}
//     </AnimatePresence>
//   )
// }

// interface FloatingPanelTitleProps {
//   children: React.ReactNode
// }

// function FloatingPanelTitle({ children }: FloatingPanelTitleProps) {
//   const { uniqueId } = useFloatingPanel()

//   return (
//     <motion.div
//       layoutId={`floating-panel-label-${uniqueId}`}
//       className="px-4 py-2 font-semibold text-zinc-900 dark:text-zinc-100"
//     >
//       {children}
//     </motion.div>
//   )
// }

// interface FloatingPanelFormProps {
//   children: React.ReactNode
//   onSubmit?: (note: string) => void
//   className?: string
// }

// export function FloatingPanelForm({
//   children,
//   onSubmit,
//   className,
// }: FloatingPanelFormProps) {
//   const { note, closeFloatingPanel } = useFloatingPanel()

//   const handleSubmit = (e: React.FormEvent) => {
//     e.preventDefault()
//     onSubmit?.(note)
//     closeFloatingPanel()
//   }

//   return (
//     <form
//       className={cn("flex h-full flex-col", className)}
//       onSubmit={handleSubmit}
//     >
//       {children}
//     </form>
//   )
// }

// interface FloatingPanelLabelProps {
//   children: React.ReactNode
//   htmlFor: string
//   className?: string
// }

// export function FloatingPanelLabel({
//   children,
//   htmlFor,
//   className,
// }: FloatingPanelLabelProps) {
//   const { note } = useFloatingPanel()

//   return (
//     <motion.label
//       htmlFor={htmlFor}
//       style={{ opacity: note ? 0 : 1 }}
//       className={cn(
//         "block mb-2 text-sm font-medium text-zinc-900 dark:text-zinc-100",
//         className
//       )}
//     >
//       {children}
//     </motion.label>
//   )
// }

// interface FloatingPanelTextareaProps {
//   className?: string
//   id?: string
// }

// export function FloatingPanelTextarea({
//   className,
//   id,
// }: FloatingPanelTextareaProps) {
//   const { note, setNote } = useFloatingPanel()

//   return (
//     <textarea
//       id={id}
//       className={cn(
//         "h-full w-full resize-none rounded-md bg-transparent px-4 py-3 text-sm outline-none",
//         className
//       )}
//       autoFocus
//       value={note}
//       onChange={(e) => setNote(e.target.value)}
//     />
//   )
// }

// interface FloatingPanelHeaderProps {
//   children: React.ReactNode
//   className?: string
// }

// export function FloatingPanelHeader({
//   children,
//   className,
// }: FloatingPanelHeaderProps) {
//   return (
//     <motion.div
//       className={cn(
//         "px-4 py-2 font-semibold text-zinc-900 dark:text-zinc-100",
//         className
//       )}
//       initial={{ opacity: 0, y: -10 }}
//       animate={{ opacity: 1, y: 0 }}
//       transition={{ delay: 0.1 }}
//     >
//       {children}
//     </motion.div>
//   )
// }

// interface FloatingPanelBodyProps {
//   children: React.ReactNode
//   className?: string
// }

// export function FloatingPanelBody({
//   children,
//   className,
// }: FloatingPanelBodyProps) {
//   return (
//     <motion.div
//       className={cn("p-4", className)}
//       initial={{ opacity: 0, y: 10 }}
//       animate={{ opacity: 1, y: 0 }}
//       transition={{ delay: 0.2 }}
//     >
//       {children}
//     </motion.div>
//   )
// }

// interface FloatingPanelFooterProps {
//   children: React.ReactNode
//   className?: string
// }

// export function FloatingPanelFooter({
//   children,
//   className,
// }: FloatingPanelFooterProps) {
//   return (
//     <motion.div
//       className={cn("flex justify-between px-4 py-3", className)}
//       initial={{ opacity: 0, y: 10 }}
//       animate={{ opacity: 1, y: 0 }}
//       transition={{ delay: 0.3 }}
//     >
//       {children}
//     </motion.div>
//   )
// }

// interface FloatingPanelCloseButtonProps {
//   className?: string
// }

// export function FloatingPanelCloseButton({
//   className,
// }: FloatingPanelCloseButtonProps) {
//   const { closeFloatingPanel } = useFloatingPanel()

//   return (
//     <motion.button
//       type="button"
//       className={cn("flex items-center", className)}
//       onClick={closeFloatingPanel}
//       aria-label="Close floating panel"
//       whileHover={{ scale: 1.1 }}
//       whileTap={{ scale: 0.9 }}
//     >
//       <ArrowLeftIcon size={16} className="text-zinc-900 dark:text-zinc-100" />
//     </motion.button>
//   )
// }

// interface FloatingPanelSubmitButtonProps {
//   className?: string
// }

// export function FloatingPanelSubmitButton({
//   className,
// }: FloatingPanelSubmitButtonProps) {
//   return (
//     <motion.button
//       className={cn(
//         "relative ml-1 flex h-8 shrink-0 scale-100 select-none appearance-none items-center justify-center rounded-lg border border-zinc-950/10 bg-transparent px-2 text-sm text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-800 focus-visible:ring-2 active:scale-[0.98] dark:border-zinc-50/10 dark:text-zinc-50 dark:hover:bg-zinc-800",
//         className
//       )}
//       type="submit"
//       aria-label="Submit note"
//       whileHover={{ scale: 1.05 }}
//       whileTap={{ scale: 0.95 }}
//     >
//       Submit Note
//     </motion.button>
//   )
// }

// interface FloatingPanelButtonProps {
//   children: React.ReactNode
//   onClick?: () => void
//   className?: string
// }

// export function FloatingPanelButton({
//   children,
//   onClick,
//   className,
// }: FloatingPanelButtonProps) {
//   return (
//     <motion.button
//       className={cn(
//         "flex w-full items-center gap-2 rounded-md px-4 py-2 text-left text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700",
//         className
//       )}
//       onClick={onClick}
//       whileHover={{ backgroundColor: "rgba(0, 0, 0, 0.05)" }}
//       whileTap={{ scale: 0.98 }}
//     >
//       {children}
//     </motion.button>
//   )
// }

// export default {
//   Root: FloatingPanelRoot,
//   Trigger: FloatingPanelTrigger,
//   Content: FloatingPanelContent,
//   Form: FloatingPanelForm,
//   Label: FloatingPanelLabel,
//   Textarea: FloatingPanelTextarea,
//   Header: FloatingPanelHeader,
//   Body: FloatingPanelBody,
//   Footer: FloatingPanelFooter,
//   CloseButton: FloatingPanelCloseButton,
//   SubmitButton: FloatingPanelSubmitButton,
//   Button: FloatingPanelButton,
// }

Dependencies

motion

Source: Cult UI