{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"dynamic-island","type":"registry:component","title":"Dynamic Island","description":"A pill that morphs between a compact status line and any number of named live-activity views, the same element reshaping via layout animation each time.","author":"Ryan","dependencies":["clsx","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/dynamic-island.tsx","type":"registry:component","target":"@components/motion/dynamic-island.tsx","content":"\"use client\";\n// easeui.dev/components/motion/dynamic-island\n\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { Children, isValidElement, type ReactElement, type ReactNode } from \"react\";\nimport { EASE_OUT } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface DynamicIslandViewProps {\n  /** Matches the parent's `view` prop when this is the active view. */\n  id: string;\n  className?: string;\n  children?: ReactNode;\n}\n\n/** A named live-activity view. Read by the parent DynamicIsland; never rendered on its own. */\nexport function DynamicIslandView({ children }: DynamicIslandViewProps) {\n  return <>{children}</>;\n}\n\nexport interface DynamicIslandProps {\n  /** Which view is active. `null` shows the compact pill. */\n  view: string | null;\n  /** Compact pill content, shown while no view is active. */\n  compact?: ReactNode;\n  /** One or more DynamicIslandView elements. */\n  children?: ReactNode;\n  className?: string;\n}\n\n// A deliberate exception to this library's usual bounce:0 springs — the\n// island is meant to feel soft and a little elastic, like it's made of\n// liquid, not a stiff panel. Reduced motion drops the bounce entirely below.\nconst MORPH_SPRING = { type: \"spring\", bounce: 0.35, duration: 0.6 } as const;\n\n/**\n * A pill that morphs between a compact status line and any number of named\n * live-activity views — the same element reshaping via layout animation\n * each time, rather than one panel swapping for another beside it.\n */\nexport function DynamicIsland({ view, compact, children, className }: DynamicIslandProps) {\n  const reduce = useReducedMotion();\n  const views = Children.toArray(children).filter(isValidElement) as ReactElement<DynamicIslandViewProps>[];\n  const active = views.find((item) => item.props.id === view);\n\n  return (\n    <motion.div\n      layout={!reduce}\n      transition={reduce ? { duration: 0 } : MORPH_SPRING}\n      style={{ borderRadius: active ? 28 : 9999, willChange: \"transform\" }}\n      className={cn(\n        \"mx-auto flex w-fit max-w-full items-center justify-center overflow-hidden bg-foreground text-background\",\n        className,\n      )}\n    >\n      <AnimatePresence mode=\"popLayout\" initial={false}>\n        {active ? (\n          <motion.div\n            key={active.props.id}\n            layout\n            initial={reduce ? false : { opacity: 0 }}\n            animate={{ opacity: 1, transition: { delay: reduce ? 0 : 0.08, duration: 0.15, ease: EASE_OUT } }}\n            exit={reduce ? undefined : { opacity: 0 }}\n            className={cn(\"flex items-center gap-3 p-4\", active.props.className)}\n          >\n            {active.props.children}\n          </motion.div>\n        ) : (\n          <motion.div\n            key=\"compact\"\n            layout\n            initial={reduce ? false : { opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={reduce ? undefined : { opacity: 0 }}\n            transition={{ duration: 0.15, ease: EASE_OUT }}\n            className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium\"\n          >\n            {compact}\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </motion.div>\n  );\n}\n"},{"path":"lib/ease.ts","type":"registry:lib","target":"@lib/ease.ts","content":"// Shared motion tokens. Micro-interactions run 100 to 150ms, standard UI\n// 150 to 250ms, and panels up to 300ms.\n// Easing curves mirror the CSS custom properties in globals.css.\n\n/** ease-out-quint. Fast start that settles quickly. Entrances, exits, feedback. */\nexport const EASE_OUT = [0.23, 1, 0.32, 1] as const;\n/** ease-in-out-cubic. Elements already on screen moving to a new spot. */\nexport const EASE_IN_OUT = [0.645, 0.045, 0.355, 1] as const;\n/** Sheet and drawer glide. */\nexport const EASE_DRAWER = [0.32, 0.72, 0, 1] as const;\n\n/** CSS string form of EASE_OUT for inline style transitions. */\nexport const EASE_OUT_CSS = \"cubic-bezier(0.23, 1, 0.32, 1)\";\n\n// Springs are described by duration and bounce, which is easier to reason about\n// than stiffness and damping. Bounce stays at zero for product UI.\n\n/** Press feedback on buttons and other tappable surfaces. */\nexport const SPRING_PRESS = {\n  type: \"spring\",\n  duration: 0.15,\n  bounce: 0,\n} as const;\n\n/** Content swaps, label and icon slots trading places inside a control. */\nexport const SPRING_SWAP = {\n  type: \"spring\",\n  duration: 0.2,\n  bounce: 0,\n} as const;\n\n/** Overlay panel entrances, modals and sheets summoned by pointer. */\nexport const SPRING_PANEL = {\n  type: \"spring\",\n  duration: 0.25,\n  bounce: 0,\n} as const;\n\n/** Shared-layout glides, pills and indicators moving between positions. */\nexport const SPRING_LAYOUT = {\n  type: \"spring\",\n  duration: 0.22,\n  bounce: 0,\n} as const;\n\n/** Cursor-follow physics for decorative mouse tracking (magnetic, tilt). */\nexport const SPRING_MOUSE = {\n  stiffness: 320,\n  damping: 26,\n  mass: 0.3,\n} as const;\n\n/** Dragged handles and fills (sliders). Critically damped `useSpring` config,\n * so the value follows the pointer closely and never rebounds off an end. */\nexport const SPRING_GLIDE = {\n  stiffness: 700,\n  damping: 50,\n  mass: 0.5,\n} as const;\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}