{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dynamic-island",
  "title": "Dynamic Island",
  "description": "A functional iPhone-inspired island that morphs between compact and expanded content.",
  "dependencies": [
    "framer-motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/spark-ui/dynamic-island.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport * as React from \"react\";\n\nexport interface DynamicIslandProps {\n  compact?: React.ReactNode;\n  expanded?: React.ReactNode;\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  className?: string;\n  expandLabel?: string;\n  collapseLabel?: string;\n}\n\nexport function DynamicIsland({\n  compact,\n  expanded,\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  className,\n  expandLabel = \"Expand Dynamic Island\",\n  collapseLabel = \"Collapse Dynamic Island\",\n}: DynamicIslandProps) {\n  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);\n  const [isRejecting, setIsRejecting] = React.useState(false);\n  const restoreFocus = React.useRef(false);\n  const reducedMotion = useReducedMotion();\n  const isOpen = open ?? uncontrolledOpen;\n  const isPanelOpen = isOpen && expanded != null;\n  const isEmpty = compact == null && expanded == null;\n  const isPillOpen = isOpen && expanded == null && !isEmpty;\n\n  const triggerRef = React.useCallback((node: HTMLButtonElement | null) => {\n    if (node && restoreFocus.current) {\n      node.focus();\n      restoreFocus.current = false;\n    }\n  }, []);\n\n  const setOpen = (nextOpen: boolean) => {\n    if (open === undefined) setUncontrolledOpen(nextOpen);\n    onOpenChange?.(nextOpen);\n  };\n\n  const collapse = () => {\n    restoreFocus.current = true;\n    setOpen(false);\n  };\n\n  return (\n    <motion.div\n      layout\n      data-state={isOpen ? \"open\" : \"closed\"}\n      animate={\n        isRejecting && !reducedMotion\n          ? { x: [0, -8, 8, -6, 6, 0] }\n          : { x: 0 }\n      }\n      onAnimationComplete={() => {\n        if (isRejecting) setIsRejecting(false);\n      }}\n      onKeyDown={(event) => {\n        if (event.key === \"Escape\" && isOpen) {\n          event.preventDefault();\n          if (isPanelOpen) collapse();\n          else setOpen(false);\n        }\n      }}\n      transition={\n        reducedMotion\n          ? { duration: 0 }\n          : {\n              layout: { type: \"spring\", stiffness: 420, damping: 34 },\n              x: { delay: 0.3, duration: 0.38, ease: \"easeInOut\" },\n            }\n      }\n      className={cn(\n        \"relative isolate overflow-hidden border border-white/15 bg-black text-white shadow-[0_14px_50px_rgba(0,0,0,0.28)]\",\n        isPanelOpen\n          ? \"h-48 w-[min(22rem,calc(100vw-3rem))] rounded-[2rem]\"\n          : isRejecting || isPillOpen\n            ? \"h-16 w-52 rounded-full\"\n            : \"h-11 w-32 rounded-full\",\n        className,\n      )}\n    >\n      <AnimatePresence initial={false} mode=\"wait\">\n        {isPanelOpen ? (\n          <motion.div\n            key=\"expanded\"\n            initial={reducedMotion ? false : { opacity: 0, scale: 0.96 }}\n            animate={{ opacity: 1, scale: 1 }}\n            exit={reducedMotion ? undefined : { opacity: 0, scale: 0.96 }}\n            transition={{ duration: reducedMotion ? 0 : 0.18 }}\n            className=\"absolute inset-x-0 top-0 bottom-10\"\n          >\n            {expanded}\n          </motion.div>\n        ) : (\n          <motion.button\n            ref={triggerRef}\n            key=\"compact\"\n            type=\"button\"\n            aria-expanded={isOpen}\n            aria-label={isOpen ? collapseLabel : expandLabel}\n            onClick={() => {\n              if (isEmpty) {\n                if (!reducedMotion) setIsRejecting(true);\n                return;\n              }\n              setOpen(!isOpen);\n            }}\n            initial={reducedMotion ? false : { opacity: 0, scale: 0.9 }}\n            animate={{ opacity: 1, scale: 1 }}\n            exit={reducedMotion ? undefined : { opacity: 0, scale: 0.9 }}\n            transition={{ duration: reducedMotion ? 0 : 0.15 }}\n            className=\"h-full w-full rounded-full px-2 outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-inset\"\n          >\n            {compact}\n          </motion.button>\n        )}\n      </AnimatePresence>\n\n      {isPanelOpen && (\n        <button\n          type=\"button\"\n          aria-label={collapseLabel}\n          onClick={collapse}\n          className=\"group absolute inset-x-0 bottom-0 z-10 flex h-10 items-center justify-center outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-inset\"\n        >\n          <span className=\"h-1 w-9 rounded-full bg-white/25 transition-colors group-hover:bg-white/45\" />\n        </button>\n      )}\n    </motion.div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}